Swiftのprotocolおぼえがき

connpass.com

「Swift実践入門」刊行記念 Tech Talks #swiftjn を聞きに行って、知らないこといっぱいありそうだったので、この本で学んだことをおぼえがきとして残しておきます。

本の裏にも書いてあることを引用しますが、「Swiftは簡潔な言語ですが、その言語仕様を理解し、正しく使うことはけっして容易ではありません。」という言葉、まさしくその通りって感じてます。

『Swift実践入門』の著者の石川さんも言ってましたが、

大事なことです。

せっかくSwiftで書くならSwiftらしいコードで書けるようになりたい。そのためのインプットとして「Swift実践入門」を教科書代わりに使ってみようと思います。

Swift実践入門 ── 直感的な文法と安全性を兼ね備えた言語 (WEB+DB PRESS plus)

Swift実践入門 ── 直感的な文法と安全性を兼ね備えた言語 (WEB+DB PRESS plus)

めんどくさがりやなので、14章から読み始めればOK問題ない、と思ってたけど、いざ読んでみるとしっかり理解していない箇所が結構ありそうなので、とりあえず第7章のprotocolから読みつつ、playgroundで書いたりして遊んでみます。

protocolの基本

ぼんやり知ってたことの復習。ストアドプロパティとかコンピューテッドプロパティという呼び方知らなかった。

protocol SomeProtocol{
    var title:String { get }
    func someMethod(string: String) -> Void
    nonmutating func nonmutatingSomeMethod(string: String) -> Void
    mutating func mutatingSomeMethod(string: String) -> Void
}

struct SomeStruct1 : SomeProtocol {
    //ストアドプロパティ
    internal var title: String
    func someMethod(string: String) {
//        self.title = string //=> エラー
    }
    nonmutating func nonmutatingSomeMethod(string: String) -> Void{
//        self.title = string //=> エラー
    }
    mutating func mutatingSomeMethod(string: String) -> Void{
        self.title = string
    }
}

struct SomeStruct2 : SomeProtocol {
    //コンピューテッドプロパティ
    internal var title: String {
        return "default"
    }
    func someMethod(string: String) {
//        self.title = string //=>エラー
    }
    mutating func mutatingSomeMethod(string: String) -> Void{
//        self.title = string //=>エラー
        print("nasi")
    }
    nonmutating func nonmutatingSomeMethod(string: String) -> Void{
//        self.title = string //=>エラー
    }
}

class SomeClass : SomeProtocol {
    var title:String = ""
    func someMethod(string: String) -> Void{
        self.title = string
    }
    func nonmutatingSomeMethod(string: String) -> Void{
        self.title = string
    }
    func mutatingSomeMethod(string: String) -> Void{
        self.title = string
    }
}

let struct1 = SomeStruct1.init(title: "aaa")
struct1.title
//struct1.title = "hoge" //#=>エラー
//struct1.mutatingSomeMethod(string: "hoge") //#=> struct1をletで生成しているのでエラー
struct1.title


let struct2 = SomeStruct2.init()
struct2.title
//struct2.title = "hoge" //#=> コンピューテッドプロパティなのでエラー

let someClass = SomeClass.init()
someClass.title = "hoge"

associatedtype を使った抽象化

適当なサンプル出したくて消費税とかで書いてみたけど、実装が同じで意味なかった😢

protocol TaxProtocol {
    associatedtype AssociatedType
    var tax: AssociatedType { get }
    func calculate(price: Int) -> AssociatedType
}

// 実装からAssociatedTypeを自動で定義する方法
struct JapaneseTax: TaxProtocol {
    var tax: Float
    func calculate(price: Int) -> Float{
        return floor(Float(price) + Float(price) * self.tax)
    }
}

let japaneseTax = JapaneseTax(tax: 0.08)
japaneseTax.calculate(price: 20100)


// typealiasを使ってAssociatedTypeを定義する方法
struct KoreanTax: TaxProtocol {
    typealias AssociatedType = Float
    var tax: AssociatedType
    func calculate(price: Int) -> AssociatedType{
        return floor(Float(price) + Float(price) * self.tax)
    }
}

let koreanTax = KoreanTax(tax: 10.0)
let taxPrice = koreanTax.calculate(price: 20100)


// ネスト型AssociatedTypeを定義する方法(使いみちどんな時...?)
struct ItarianTax: TaxProtocol {
    struct AssociatedType {}
    
    var tax: AssociatedType
    func calculate(price: Int) -> AssociatedType{
        return AssociatedType()
    }
}

protocol extension によるデフォルト実装 / extension where Self : を使った制約

Tackleって🎣の装備品?をまとめてタックルと言います。

KamipはFishingTackleとBloggerというprotocolを持っているけど、HogeFisherはFishingTackleだけ持っている例。これも適当な思いつきで読みながら進めたので、FishingTackleがwriteDiaryを持っている謎仕様ですが気にしない。HogeとかFooとかで書いたほうがよかったな..。

protocol FishingTackle {
    var rod: String { get }
    var reel: String { get }
    var line: String { get }
    var lure: String { get }
}

protocol Blogger {
    var blogUrl: String { get }
}

extension FishingTackle {
    var list: String {
        return [
            "rod: \(self.rod)",
            "reel: \(self.reel)",
            "line: \(self.line)",
            "lure: \(self.lure)",
        ].joined(separator: "\n")
    }
    
    func printList() -> Void {
        print(self.list)
    }
}

extension FishingTackle where Self : Blogger {
    func writeDiary() -> Void{
        // 日記を書く処理
    }
}

struct Kamip : FishingTackle, Blogger {
    var rod: String
    var reel: String
    var line: String
    var lure: String
    var blogUrl: String
}

let kamip = Kamip(
    rod: "Daiwa presso-ltd ags ml",
    reel: "Daiwa 15luvias 2004",
    line: "SanyoNylon GT-R PINK-SELECTION 2lb",
    lure: "Jackall tearo 1.6g",
    blogUrl: "http://kamip.net/"
)

print(kamip.list)
kamip.printList()
print(kamip.blogUrl)
kamip.writeDiary()


struct HogeFisher: FishingTackle {
    var rod: String
    var reel: String
    var line: String
    var lure: String
}

let hogeFisher = HogeFisher(rod: "", reel: "", line: "", lure: "")
print(hogeFisher.list)
hogeFisher.printList()
//hogeFisher.writeDiary() //#=> Bloggerじゃないのでエラーになる

自分で書くと色々試せるのでとてもよい。

次は

標準ライブラリのプロトコルを使う設計が自然にできたら素敵そうだけど、そもそも、標準ライブラリのプロトコル何があるかInputしないと。 classstruct の違いも理解が足りなそうなので、次は第5章を読もう。ジェネリクスとかは怖いなー怖いなー。

参考

Swift実践入門 ── 直感的な文法と安全性を兼ね備えた言語 (WEB+DB PRESS plus)

Swift実践入門 ── 直感的な文法と安全性を兼ね備えた言語 (WEB+DB PRESS plus)

2017/2/24 #swiftjn 「Swift実践入門」刊行記念 Tech Talks - Togetterまとめ