目標
init(_ otherInstance: Self) {
self = otherInstance
}
というようなことをclass
でもしてみたいけど、どうしたらできる…?
(想定: Swift 5現在)
はじめに: self
への代入ができるのはどんなとき?
構造体(struct)
次のように、イニシャライザやmutating func
の中でself
に代入をすることができます:
struct S {
init() {}
init(_ otherInstance: S) {
self = otherInstance
}
mutating func replace(with otherInstance: S) {
self = otherInstance
}
}
let s1 = S()
let s2 = S(s1)
var s3 = S()
s3.replace(with: s2)
列挙型(enum)
列挙型も構造体と同様です:
enum E {
case a, b
init() {
self = .a
}
init(_ otherInstance: E) {
self = otherInstance
}
mutating func replace(with otherInstance: E) {
self = otherInstance
}
}
let e1 = E() // .a
let e2 = E.b
var e3 = E(e2) // .b
e3.replace(with: e1) // .a
クラス(class)では…?
クラスで同様のコードを書いてみると…
class C {
init() {}
init(_ otherInstance: C) {
self = otherInstance // ⛔️ error: cannot assign to value: 'self' is immutable
}
func replace(with otherInstance: C) {
self = otherInstance // ⛔️ error: cannot assign to value: 'self' is immutable
}
}
self
がimmutableだから代入できないと怒られてしまいました…。
Swiftのclass
におけるself
は(struct
やenum
のそれと違って)、実体としてのインスタンスを指し示すものではなく、実体を参照する(C言語的にいえば)ポインタを指し示すものなので、self
自体は常にimmutableなのです1。
どうしてもclass
でもself
に代入したい
前述のように単純なコードでは無理ですが、実は、コードをこねくりまわせばclass
においてもself
に代入ができるのです。…正確にはself
に代入したかのような動作をするコードを書くことができるのです。
どうするのかというと、前項には登場しなかったアレ…そう、プロトコルを使うのです。
まずは、プロトコルのコードをみてみましょう:
protocol P {}
extension P {
init(otherInstance: Self) {
self = otherInstance
}
}
このように、プロトコルにおいてもself
への代入が許容されているのです。
あとは、クラスをこのプロトコルに準拠させて、このプロトコルで定義されているイニシャライザを呼び出せば…
class CP: P {
convenience init(_ otherInstance: CP) {
self.init(otherInstance: otherInstance as! Self)
}
}
できました!
注意点
上記のコードではas! Self
という強制型変換が必要になります。要するにCP
のサブクラスでこのイニシャライザを呼んだ時の安全性が確保されません。なので、CP
をfinal class
として宣言したり、プロトコルP
もprivate
で宣言したりすることが望まれます。
使用例
1. クラスクラスタ
ちょうどいい例がGistにありました:
https://gist.github.com/JadenGeller/1f4acd5cd07d4642c246d87ca25bd3e0
2. 同一入力に単一インスタンス
// 生成されたインスタンスを格納
private var _instances: [Int: MyClass] = [:]
// selfへの代入を可能にするためのプロトコル
private protocol _MyClass {}
extension _MyClass where Self: MyClass {
fileprivate init(_integer integer: Int) {
if let instance = _instances[integer] {
self = instance as! Self
} else {
self.init(__integer: integer)
_instances[integer] = self
}
}
}
// 今回メインのクラス
// "designated initializer"は外部から呼べないようにし、
// "convenience initializer"の内部でプロトコルのイニシャライザを呼び出す
public final class MyClass: _MyClass {
public let integer: Int
fileprivate init(__integer integer: Int) {
self.integer = integer
}
public convenience init(_ integer: Int) {
self.init(_integer: integer)
}
}
print(ObjectIdentifier(MyClass(0)) == ObjectIdentifier(MyClass(0))) // -> true
print(ObjectIdentifier(MyClass(0)) == ObjectIdentifier(MyClass(1))) // -> false
print(ObjectIdentifier(MyClass(1)) == ObjectIdentifier(MyClass(1))) // -> true
おわりに
いかがだったでしょうか。
class
でもself
に代入したい場面というのはそこまで多くはないですが、ゼロではないはずです。方法が存在するということを知っておくだけでも、今後どこかで役に立つかもしれません。…というか役に立つといいなと思っています。
-
逆にプロパティの書き換えは、インスタンスを参照するポインタの値が書き変わるわけではないので、メソッドの宣言などで
mutating
というキーワードが不要ということになります。 ↩