LoginSignup
1
2

More than 1 year has passed since last update.

最小実装でモデルのIDをタイプセーフに表現する

Posted at

実際のアプリを作るとき、多数のモデルが存在することになると思いますが、
APIから取得したデータはIDというプロパティを持つことが多いと思います。
それらはStringやIntなどの型で表現されると思いますが、
メソッドの引数名によっては誤ったモデルのIDを入れてしまうヒューマンエラーが起こる可能性があります。
それをSwiftの型を用いて解決したいと思います。

ヒューマンエラーの例

fetchLikesというメソッドはStringのidを引数に取るため、LikeでもUserでもidがStringで宣言されているので、引数にLikeのIDを入れることができてしまいます。

final class LikeRepository {
  /// UserのIDをもとにUserのお気に入り一覧を取得する
  func fetchLikes(id: String) -> [Like] {
    ...
  }
}

struct User {
  var id: String
}

struct Like {
  var id: String
}

let user = User(id: "id")
let repository = LikeRepository()
// 正しい使い方
repository.fetchLikes(id: user.id)

let like = Like(id: "id")
// 誤った使い方でもコンパイルできてしまうので、問題に気づけない
repository.fetchLikes(id: like.id)

解決策

ユーティリティを実装する

protocol Identifiable {
  typealias ID = Identifier<Self>
  var id: Identifier<Self> { get }
}

struct Identifier<Object>: Hashable {
  var rawValue: String
}

/// User.IDなどのIdentifierをStringで初期化可能にする
extension Identifier: ExpressibleByStringLiteral {
  init(stringLiteral value: StringLiteralType) {
    self = Identifier(rawValue: value)
  }
}

使い方

struct User: Identifiable, Equatable {
  var id: ID
}

struct Like: Identifiable, Equatable {
  var id: ID
}

final class LikeRepository {
  func fetchLikes(id: User.ID) -> [Like] {
    // id.rawValueでStringにアクセスできる
    ...
  }
}

let user = User(id: "id")
let repository = LikeRepository()
 // 正しい使い方
repository.fetchLikes(id: user.id)

let like = Like(id: "id")

// エラーになりコンパイルできないので、ミスに気づける
// Cannot convert value of type 'Like.ID' (aka 'Identifier<Like>') to expected argument type 'User.ID' (aka 'Identifier<User>')
repository.fetchLikes(id: like.id) 

参考

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2