はじめに

この記事はSwiftのObjectIdentifierについての記事です

ObjectIdentifierとは

ObjectIdentifierとはクラスまたはメタタイプの一意のidを表す型です。 structやenum, 関数、タプルに対してはObjectIdentifierを生成できなく、classやactorに対してObjectIdentifierを生成できます。


// --- Structに対してはObjectIdentifierを生成できない ---

struct S {}

let s = S()
let num: Int = 0

ObjectIdentifier(s) // Error: Argument type 'S' expected to be an instance of a class or class-constrained type
ObjectIdentifier(num) // Error: Argument type 'Int' expected to be an instance of a class or class-constrained type

// --- Classに対してはObjectIdentifierを生成できる ---

class B {
    var value: Int

    init(value: Int) {
        self.value = value
    }
}

actor A {}

let b = B(value: 0)
let a = A()
ObjectIdentifier(b)
ObjectIdentifier(a)
structに対してObjectIdentifierを生成しようとするとエラーになる
structに対してObjectIdentifierを生成しようとするとエラーになる

ObjectIdentifierによる比較は参照が同じという意味になるので===と同じ意味になる認識です。

let a = B()
let b = B()

a === b // false
ObjectIdentifier(a) == ObjectIdentifier(b) // false

// 参照が同じならtrue
a === a // true
ObjectIdentifier(a) == ObjectIdentifier(a) // true

IdentifiableとObjectIdentifier

個人的にObjectIdentifierの存在を知ったきっかけがIdentifiableというプロトコルでした。 structに対してidentifiableを適合させるためにidというプロパティを持たせる必要がありますが、コード補完を使うとid: ObjectIdentifierというスニペットが出てきます。

Identifiableを適合させるためにidというプロパティを持たせる必要がある
Identifiableを適合させるために<code>id</code>というプロパティを持たせる必要がある

しかし、実際はid: StringのようにObjectIdentifierではなくString型をidの型とすることもできます。

インターフェースを見ると、idの型はHashableでありclass-constrained typeである場合(AnyObjectを継承している場合)についてprotocol extensionが提供され、ObjectIdentifierでidのデフォルト実装が提供されていました。(参照

/// `Identifiable` provides a default implementation for class types (using
/// `ObjectIdentifier`), which is only guaranteed to remain unique for the
/// lifetime of an object. If an object has a stronger notion of identity, it
/// may be appropriate to provide a custom implementation.
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
public protocol Identifiable<ID> {

    /// A type representing the stable identity of the entity associated with
    /// an instance.
    associatedtype ID : Hashable

    /// The stable identity of the entity associated with this instance.
    var id: Self.ID { get }
}

@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
extension Identifiable where Self : AnyObject {

    /// The stable identity of the entity associated with this instance.
    public var id: ObjectIdentifier { get }
}

自分の疑問は、Identifiableのidの型はObjectIdentifierじゃないといけないという認識が間違っていて、実際はHashableであれば良く、class-constrained typeである場合についてprotocol extensionがIdentifiableに準拠する実装が自動で提供されているということでした。 なのでclass/actorの場合はIdentifiableに準拠する際にid型を指定する必要はないということでした。(当然と言えば当然ですが)

struct S: Identifiable {
    var id: String
}

class C: Identifiable {} // 追加実装不要で準拠できる

まとめ

  • ObjectIdentifierはclass/actorに対してのみ生成できる
  • Identifiableのidの型はObjectIdentifierではなくHashableであれば良い
  • class/actorの場合はIdentifiableに準拠する際にid型を指定する必要はない

個人的にはIdentifiableのコード補完はid: ObjectIdentifierではなくid: some Hashableでも良いと感じたのでどういう仕組みでコード補完が動いているのか覗いてみようと思ったのですが、swiftリポジトリに実装がないかもしれないので、そこから調べてみることになりそうです。