[Swift]堅牢で崩れにくいコードを実現する

# swiftでできない2つのことを利用

## associatedtype

- genericなprotocolを作れる

```swift
protocol Sequence {
    associatedtype Element
    associatedtype Iterator: IteratorProtocol where Iterator.Element == Element
    func makeIterator() -> Iterator
 }
```

- contrete type(class, struct, enum)に準拠させる際に実際の型を指定して使える
- generic functionで型を指定して使える

**associatedtypeを持つprotocolをpropertyの型として利用することができない**

## extension

- protocolやcontrete type(class, struct, enum)の機能を拡張できる

**extensionブロックの中でstored propertyを持つことができない（黒魔術を使わない限り）**

# 実装例

## 1. 依存用と公開用のprotocolを分ける

### 二つのprotocolを用意する

- 何に依存しているかを明確にする

```swift
protocol Repository {
    var remoteStore: StoreRemoteStore! { get }
    var memoryStore: StoreMemoryStore! { get }
}
```

- 何を公開しているかを明確にする

```swift
protocol StoreRepository {
    func find(id: Int, completion: (Store) -> Void)
}
```

- 実装

```swift
struct StoreRepositoryImpl: Repository, StoreRepository {
    let remoteStore: StoreRemoteStore!
    let memoryStore: StoreMemoryStore!
        
    func find(id: Int, completion: (Store) -> Void) {
        fatalError("need to implement")
    }
}
```

- 使い所

```swift
import UIKit

final class ViewController: UIViewController {
    
    private let storeRepo: StoreRepository = StoreRepositoryImpl()

    override func viewDidLoad() {
        super.viewDidLoad()
        
        // StoreRepositoryの持つfindしかアクセスできない
        storeRepo.find(id: 1) { (store) in
            print(store)
        }
    }
}
```

- 問題点

```swift
import UIKit

final class ViewController: UIViewController {
    
    private let storeRepo: Repository = StoreRepositoryImpl() // 依存用プロトコルがpropertyの型として利用されてしまう

    override func viewDidLoad() {
        super.viewDidLoad()
        
        storeRepo.remoteStore.find(id: 1) { (store) in
            print(store)
        }
    }
}
```

## 2. 依存用のprotocolをpropertyとして持てなくする

### associatedtypeを使って依存protocolを作る

- 依存用protocol (blueprint)

```swift
protocol Repository {
    associatedtype RemoteStore
    associatedtype MemoryStore
    
    var remoteStore: RemoteStore! { get }
    var memoryStore: MemoryStore! { get }
}
```

- 公開用protocol (interface)

```swift
protocol StoreRepository {
    func find(id: Int, completion: (Store) -> Void)
}
```

- 実装

```swift
struct StoreRepositoryImpl: Repository, StoreRepository {
    typealias RemoteStore = StoreRemoteStore
    typealias MemoryStore = StoreMemoryStore

    let remoteStore: RemoteStore!
    let memoryStore: MemoryStore!
        
    func find(id: Int, completion: (Store) -> Void) {
        fatalError("need to implement")
    }
}
```

- 使い所

```swift
final class ViewController: UIViewController {
    
    private let storeRepo: StoreRepository = StoreRepositoryImpl()

    private let storeRepo2: Repository = StoreRepositoryImpl() // これができなくなる

    override func viewDidLoad() {
        super.viewDidLoad()
        
        storeRepo.find(id: 1) { (store) in
            print(store)
        }
    }
}
```

## 3.余計なpropertyを生やさせない

### extensionを利用する

- 依存protocol (blueprint)

```swift
protocol Repository {
    associatedtype RemoteStore
    associatedtype MemoryStore
    
    var remoteStore: RemoteStore! { get }
    var memoryStore: MemoryStore! { get }
}
```

- 公開protocol (interface)

```swift
protocol StoreRepository {
    func find(id: Int, completion: (Store) -> Void)
}
```

```swift
protocol UserRepository {
    func findAll(completion: ([User]) -> Void)
}
```

- 依存protocolの実装

```swift
struct RepositoryImpl<R, M>: Repository {
    let remoteStore: R
    let memoryStore: M
}
```

- 公開protocolの実装

```swift
extension RepositoryImpl: UserRepository where R: UserRemoteStore, M: UserMemoryStore {
  func findAll(completion: ([User]) -> Void) {
    fatalError("need to implement")
  }
}

extension RepositoryImpl: StoreRepository where R: StoreRemoteStore, M: StoreMemoryStore {
  func find(id: Int, completion: (Store) -> Void) {
    fatalError("need to implement")
  }
}
```

- 使い所

```swift
final class ViewController: UIViewController {
    
    private let userRepo: UserRepository = RepositoryImpl<UserRemoteStoreImpl, UserMemoryStoreImpl>()
    private let storeRepo: StoreRepository = RepositoryImpl<StoreRemoteStoreImpl, StoreMemoryStoreImpl>()

    override func viewDidLoad() {
        super.viewDidLoad()
                
        userRepo.fetchAll { (users) in
            print(users)
        }
        
        storeRepo.find(id: 1) { (store) in
            print(store)
        }
    }
}
```

# まとめ
インスタンスをpropertyとして持つことが前提となりますが、
使う側からは依存用protocolのpropertyにアクセスすることができず、
また実装クラスが依存するproperty以外の余計なpropertyを増やすことができなくなります。(大勢で作業する上で好き勝手されない)

※簡易的な実装例のため省略していますが、↑の例だと依存protocolの実装をpropertyとして利用できるため、Default Initializerを利用せず、依存protocolの実装クラスを利用するクラスから参照できないようにするといったアプローチが必要になります。