簡體   English   中英

我是否在這個嵌套的 function 中捕獲自我? 編譯器不會發出警告

[英]Am I capturing self in this nested function? The compiler does not fire a warning

我找不到任何有關此的官方文檔,並且那里有不同的意見。

在以下情況下,一切都很好。

final class MyVC: UIViewController {
    
    var space: Space!
    
    private let tableView = MenuCategoriesTableView()
    
    private let tableViewHandler = MenuCategoriesTableViewHandler()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        tableView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(tableView)
        NSLayoutConstraint.activate([
            tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            tableView.topAnchor.constraint(equalTo: view.topAnchor),
            tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
        ])
        
        tableView.dataSource = tableViewHandler
        tableView.delegate = tableViewHandler
        
        tableViewHandler.didSelectRow = { [unowned self] option in
            let category = option.makeCategory()
            if category.items.count > 0 {
                let controller = MenuItemsViewController()
                controller.title = option.rawValue
                controller.space = self.space
                self.show(controller, sender: self)
            } else {
                // whatever
            }
        }
    }
}

但是,如果我進行以下更改,我不再需要使用unowned self ,但我仍然擔心捕獲自我。 我應該擔心嗎? 如果不是,為什么?

final class MyVC: UIViewController {
    
    ...etc...
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        ...etc...
        
        func categorySelected(_ option: MenuOption, _ category: MenuCategory) {
            let controller = MenuItemsViewController()
            controller.title = option.rawValue
            controller.space = space
            show(controller, sender: self)
        }
        
        tableViewHandler.didSelectRow = { option in
            let category = option.makeCategory()
            if category.items.count > 0 {
                categorySelected(option, category)
            } else {
                // whatever
            }
        }
    }
}

當您將閉包分配給tableViewHandler.didSelectRow時,您分配給它並保留該閉包捕獲的任何內容。

self保留tableViewHandler

因此,危險在於您將在閉包內引用self 如果你這樣做,那就是一個保留周期。

這可能不是因為明確地提到了self 任何提及self的屬性或方法都是對self的隱式引用。


好的,因此,讓我們檢查一下閉包。

您沒有在閉包的主體中隱式或顯式地提及self 但是,您確實調用了本地方法categorySelected 因此,您捕獲此方法。

並且categorySelected確實提到了self 因此,捕獲self (因為每個 function 都是一個閉包)。

因此存在一個潛在的保留周期,您應該繼續說unowned self以防止保留周期。

(我認為編譯器在這里無法通過警告保留周期來幫助您;這太復雜了。這是您必須通過人為原因解決的問題。)

我做了一些調查,如果你使用引用self的內部 function ,你確實會得到一個保留周期。

這是一個例子:

typealias ClosureType = () -> ()
class Test {
    var closure:ClosureType?
    let value = 42
    
    func setClosure1() {
        self.closure = {
            print ("from setClosure1")
        }
    }
    
    func setClosure2() {
        self.closure = {
            [unowned self] in
            let v = self.value
            print ("from setClosure2 - value: \(v)")
        }
    }
    
    func setClosure3() {
        func innerFunc() {
            // [unowned self] in  // not allowed, compile error (sometimes even crashes)
            let v = value
            print ("value: \(v)")
        }
        self.closure = {
            [unowned self] in   // compiler warning: "Capture [self] was never used"
            print ("from setClosure3")
            innerFunc()
        }
    }

    deinit {
        print ("Deinit")
    }
}

如果您使用setClosure1 (trivial) 或setClosure2 (capture 子句),則不會發生保留循環:

if (1==1) {
    let t = Test()
    t.setClosure1()
    t.closure?()
}  // deinit called here

但是如果你調用setClosure3deinit將不會被調用:

if (1==1) {
    let t = Test()
    t.setClosure3()
    t.closure?()
}  // deinit NOT called here

沒有直接的方法可以解決這個問題; 如您所見,在內部 function 中使用[unowned self]會導致編譯器錯誤,在setClosure3中使用它會導致警告。

不過,有一種方法可以解決這個問題 - 您可以使用第二個閉包,而不是使用內部 function,您可以在其中指定[unowned self]捕獲子句:

    func setClosure4() {
        let innerClosure:ClosureType = {
            [unowned self] in
            let v = self.value
            print ("value: \(v)")
        }
        self.closure = {
            print ("from setClosure4")
            innerClosure()
        }
    }

// ...

if (1==1) {
    let t = Test()
    t.setClosure4()
    t.closure?()
}  // deinit called here

結論:Swift 應該允許內部函數中的捕獲子句。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM