KotlinとSwiftを両方書いていて、Callbackの書き方に迷うことがちょくちょくあるので、備忘録としてまとめます。
KotlinとSwift両方の言葉が混在しており、自分も消化しきれてないところもあるので、文法的に違うとか、ツッコミあったら是非お願いします!
環境
- Swift: 4.2
- Kotlin: 1.2.60
KotlinとSwiftのCallback早見表
Swiftを始めて一番思ったのが、protocol(interface)の無名クラス実装ができないことでした。
extensionを使った継承ができるから不要という考え方なのだろうか
ということで、何ができるか一覧にしました。
まとめてみると、ほとんど同じことができることがわかりました。
実装方法 | Kotlin | Swift |
---|---|---|
委譲(Delegateパターン)の継承実装 | ○ interface |
○ protocol |
委譲(Delegateパターン)の無名クラス実装 | ○ interface |
× |
関数型の変数 | ○ lambda |
○ closure |
typealias(名前付き関数)の変数 | ○ lambda |
○ closure |
委譲(Delegateパターン)の継承実装
kotlinでもswiftでもよく使うやり方だと思います。
interfaceやprotocolを継承して、委譲元に自分自身のinstanceを渡すのが特徴です。
Kotlin
kotlinだとOnClickListenerとかでよく使う印象があります。
interface SomeCallback {
fun doSomething()
}
class Child {
var callback: SomeCallback? = null
fun doSomething(){
callback?.doSomething()
}
}
class Parent: SomeCallback {
var child = Child()
init {
child.callback = this
}
override fun doSomething() {
// do something
}
}
Swift
swiftだと、〇〇Deligateというクラスがよく出てくるので、馴染みが深いですね。
callbackがweakになっているのと、protocolがClass-Only Protocolになっているのに注意して下さい。忘れると、メモリリークします。
protocol SomeCallback: class {
func doSomething()
}
class Child {
weak var callback: SomeCallback? = nil
func doSomething(){
callback?.doSomething()
}
}
class Parent: SomeCallback {
var child = Child()
init() {
child.callback = self
}
func doSomething() {
// do something
}
}
委譲(Delegateパターン)の無名クラス実装
委譲先のクラスのインスタンスを委譲元のクラスに渡すのが、なんとなく大げさな気がして、kotlinではよく使っていましたが、swiftではできないようです。
Kotlin
// ChildクラスとSomeCallbackクラスは同上
class Parent {
var child = Child()
child.callback = object: SomeCallback {
override fun doSomething() {
// do something
}
}
}
Swift
できません。
できない代わりに、extension毎にprotocolを実装することはできます。これで、コードの見通しはよくなります。
ただ、自分自身のinstanceを渡していることには変わりないので、Child側のcallbackがweakでないと、メモリリークします。
// ChildクラスとSomeCallbackクラスは同上
class Parent {
var child = Child()
init() {
child.callback = self
}
}
extension Parent: SomeCallback {
func doSomething() {
// do something
}
}
関数型の変数
関数型の変数を用いることで、interface, protocolを用いるより、ライトな感じでコールバックを実装できます。
Kotlin
class Child {
var function: (() -> Unit)? = null
fun doSomething(){
function?.invoke()
}
}
class Parent {
var child = Child()
init {
child.function = {
// do something
}
}
}
Swift
Swiftでは、代入しているClosureが[weak self]になっているのに、注意して下さい。
これも、ないとメモリリークの原因になります。
class Child {
var function: (() -> ())? = nil
func doSomething(){
function?()
}
}
class Parent {
var child = Child()
init() {
child.function = { [weak self] in
// do something
}
}
}
typealias(名前付き関数)の変数
実装内容的にはほぼ関数型の変数と同じです。
関数に名前をつけることで、少しわかりやすくなる印象です。
Kotlin
typealias Alias = () -> Unit
class Child {
var alias: Alias? = null
fun doSomething(){
alias?.invoke()
}
}
class Parent {
var child = Child()
init {
child.alias = {
// do something
}
}
}
Swift
typealias Alias = () -> ()
class Child {
var alias: Alias? = nil
func doSomething(){
alias?()
}
}
class Parent {
var child = Child()
init() {
child.alias = { [weak self] in
// do something
}
}
}
まとめ
今回はKotlinとSwiftでコールバック実装を書いてみました。
一通り両方で書いてみてKotlinでは循環参照とかそんなに意識しないで書いているなー。ということに気づきました。
swiftはかなり意識して書かないと循環参照が起きてメモリリークを起こすという文献がたくさん出てきたけど、Kotlinは調べて出てこなかったので、ガベージコレクタが優秀なのだと思います。
(デバッグ等できちんと確認はしてないので、間違っていたらごめんなさい。ツッコミお待ちしております!
参考
Swiftの循環参照については、こちらを参考にさせていただきました。