Help us understand the problem. What is going on with this article?

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

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の実装クラスを利用するクラスから参照できないようにするといったアプローチが必要になります。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした