LoginSignup
27
21

More than 5 years have passed since last update.

【Kotlin】と【Swift】のCallback実装をまとめてみる

Last updated at Posted at 2018-10-14

KotlinとSwiftを両方書いていて、Callbackの書き方に迷うことがちょくちょくあるので、備忘録としてまとめます。

KotlinとSwift両方の言葉が混在しており、自分も消化しきれてないところもあるので、文法的に違うとか、ツッコミあったら是非お願いします!

環境

  • Swift: 4.2
  • Kotlin: 1.2.60

KotlinとSwiftのCallback早見表

Swiftを始めて一番思ったのが、protocol(interface)の無名クラス実装ができないことでした。
extensionを使った継承ができるから不要という考え方なのだろうか :thinking:

ということで、何ができるか一覧にしました。
まとめてみると、ほとんど同じことができることがわかりました。

実装方法 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の循環参照については、こちらを参考にさせていただきました。

27
21
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
27
21