iT邦幫忙

2023 iThome 鐵人賽

DAY 2
0

其他 Retain Cycle 範例


Closure

我們先從一個範例開始‧
下面這段 code 雖然沒有 leak‧
實際上 closure 已經造成 strong capture self

再不修改 closure 的情況下,我們只需要一些改動就會造成 retain cycle

class C {
    func test() {
        let handler = {
            print(self)
        }
    }
}

範例一

handler -> self -> handler

class C {
+    var handler: () -> ()
    func test() {
-        let handler = {
+        self.handler = {
            print(self)
        }
    }
}

範例二

handler -> self -> handler

class C {
+    var handler: () -> ()
    func test() {
        let handler = {
            print(self)
        }
+        self.handler = handler
    }
}

範例三

handler -> self -> sub -> handler

class C {
+    class Sub {
+        var handler: () -> ()
+    }
+    var sub: Sub?
    func test() {
-        let handler = {
-            print(self)
-        }
+        sub = Sub {
+            print(self)
+        }
    }
}

解法(在 capture list 加入 weak self)

其實這個案例也是大家最常見遇到的 case‧

let handler = { [weak self] in
    print(self)
}

每個 closure 都必須要 weak?

Non-escaping closure 只會造成一次性使用,所以不用

[1, 2, 3].map { value in
    return value + self.value
}

Implict Capture

我們再拉回看剛剛的 handler
這三段 code 可以視為等價
也可以說第一種寫法造成 Implict Capture

let handler = {
    print(self)
}
let handler = { [self] in
    print(self)
}
let handler = { [self = self] in
    print(self)
}

因為 Implict Capture 導致的誤用

在內層使用 weak 就不會 strong capture?

let handler = {
    let handler = { [weak self] in
        print(self)
    }
}

因為 closure 並不會平白無故就抓取變數
當 closure 在該層找不到變數的話
就抓取(Implict Capture)前一層 closure/function 的變數
如果前一層也沒有變數
前一層 會向 前前一層 抓取
依此類推

我們把上面的 code 加上編號的等價 code
就會發現 handler1 capture self

let handler1 = { [self1 = self] in
    let handler2 = { [weak self2 = self1] in
        print(self2)
    }
}

解法一

每一層皆 capture self

主要是避免忘記需要 capture 這件事

let handler = { [weak self] in
    let handler = { [weak self] in
        print(self)
    }
}

解法一之二

應該說第一層 capture self 即可

我們先展開成最完整的等價 code,如下

let handler1 = { [weak self1 = self] in
    let handler2 = { [weak self2 = self1] in
        let handler3 = { [weak self3 = self2] in
            print(self3)
        }
    }
}

接著取消 handler2 的 capture

let handler1 = { [weak wself1 = self] in
    let handler2 = { // [wself1]
        let handler3 = { [weak wself3 = wself1] in
            print(wself3)
        }
    }
}

最後的等價 code

let handler1 = { [weak self] in
    let handler2 = {
        let handler3 = { [weak self] in
            print(self)
        }
    }
}

解法二

直接 capture weak self

weak var ws = self
let handler = {
    let handler = {
        print(ws)
    }
}

Implict Capture(Instance function)

另外一個會 capture self 的東西可比較少人注意到,也就是 instance function

instance function 其實是所謂的 Curry Function

用以下的 code 做展示

class Foo {
    func hello() {
        print("bar")
    }
}
// Foo.hello
func Foo_Hello(_ `self`: Foo) -> () -> () {
    return { [self] in
        print("bar")
    }
}
let foo = Foo()

foo.hello() // bar

let hello1 = Foo.hello(foo)
hello1() // bar

let hello2 = Foo_Hello(foo)
hello2() // bar

Instance function 的 leak 案例

RX

rx
  .subscribe(onNext: self.hello)
  .disposed(by: bag)

Combime

cancellable = publisher
    .sink(receiveCompletion: self.hello)

解法

cancellable = publisher
    .sink { [weak self] in
        self?.hello()
    }

上一篇
ARC
下一篇
定義潛在 Leak
系列文
自己的 Leak, 自己抓(swift)30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言