swiftでできない2つのことを利用
associatedtype
- genericなprotocolを作れる
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を用意する
- 何に依存しているかを明確にする
protocol Repository {
var remoteStore: StoreRemoteStore! { get }
var memoryStore: StoreMemoryStore! { get }
}
- 何を公開しているかを明確にする
protocol StoreRepository {
func find(id: Int, completion: (Store) -> Void)
}
- 実装
struct StoreRepositoryImpl: Repository, StoreRepository {
let remoteStore: StoreRemoteStore!
let memoryStore: StoreMemoryStore!
func find(id: Int, completion: (Store) -> Void) {
fatalError("need to implement")
}
}
- 使い所
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)
}
}
}
- 問題点
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)
protocol Repository {
associatedtype RemoteStore
associatedtype MemoryStore
var remoteStore: RemoteStore! { get }
var memoryStore: MemoryStore! { get }
}
- 公開用protocol (interface)
protocol StoreRepository {
func find(id: Int, completion: (Store) -> Void)
}
- 実装
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")
}
}
- 使い所
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)
protocol Repository {
associatedtype RemoteStore
associatedtype MemoryStore
var remoteStore: RemoteStore! { get }
var memoryStore: MemoryStore! { get }
}
- 公開protocol (interface)
protocol StoreRepository {
func find(id: Int, completion: (Store) -> Void)
}
protocol UserRepository {
func findAll(completion: ([User]) -> Void)
}
- 依存protocolの実装
struct RepositoryImpl<R, M>: Repository {
let remoteStore: R
let memoryStore: M
}
- 公開protocolの実装
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")
}
}
- 使い所
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の実装クラスを利用するクラスから参照できないようにするといったアプローチが必要になります。