在 Swift 闭包中调用 Objective-C 方法会导致内存泄漏

Calling Objective-C methods in Swift closure causes memory leaks

提问人:Jinjiang You 提问时间:8/26/2022 更新时间:8/26/2022 访问量:128

问:

我正在开发一个需要 Swift 和 Objective-C 的 IOS 应用程序。我发现在 Swift 闭包中调用 Objective-C 方法可能会导致内存泄漏 - 即使没有其他人引用,在 Objective-C 方法中访问的实例仍将保留在内存中。

假设我们有两个类和:AB

@objc class A : NSObject {
    @objc var name : String
    init(name : String) {
        self.name = name
        print("Class A \"\(self.name)\" is constructed.")
        super.init()
    }
    deinit {
        print("Class A \"\(self.name)\" is destructed.")
    }
}

@objc class B : NSObject {
    @objc var nestedClass : A
    init(nestedClass : A) {
        self.nestedClass = nestedClass
        print("Class B \"\(self.nestedClass.name)\" is constructed.")
        super.init()
    }
    deinit {
        print("Class B \"\(self. nestedClass.name)\" is destructed.")
    }
}

在我们的类和方法中,我们不断实例化类在闭包中:ContentViewModelstartWorkingB

class ContentViewModel : ObservableObject {
    
    private var _isWorking = ManagedAtomic<Bool>(false)
    private(set) var isWorking: Bool {
        set {
            self.objectWillChange.send()
            self._isWorking.store(newValue, ordering: .relaxed)
        }
        get {
            return self._isWorking.load(ordering: .relaxed)
        }
    }
    
    //To perform async work
    let dispatchQueue = DispatchQueue(label: "ContentViewModel", qos: .userInteractive, attributes: .concurrent)
    
    //Objective-C interface, used to call objc methods
    let objcInterface = ObjcInterface()
    
    //Start
    func startWorking() {
        self.isWorking = true
        var count = 0
        self.dispatchQueue.async {
            while self.isWorking {
                //Instantiate class A and B
                let b = B(nestedClass: A(name: String(count)))
                //In "process" method, we do nothing but access "b.nestedClass". We do not retain it.
                self.objcInterface.process(b)
                count += 1
                //Now, "b" is no longer referenced. So, it should be destructed.
                //Also, its nested instance "b.nestedClass" should also be destructed.
            }
        }
    }
    
    //Stop
    func stopWorking() {
        self.isWorking = false
    }
}

在类的方法中,我们什么都不做,只是访问:processObjcInterfaceb.nestedClass

@implementation ObjcInterface
- (void)process:(B* _Nonnull)b {
    b.nestedClass;
}
@end

如果我们调用类的方法(例如,通过UI中的按钮),我们期望该类并保持构造和析构。但是,如果我们看一下控制台:startWorkingContentViewModelAB

Class A "0" is constructed.
Class B "0" is constructed.
Class B "0" is destructed.
Class A "1" is constructed.
Class B "1" is constructed.
Class B "1" is destructed.
Class A "2" is constructed.
Class B "2" is constructed.
Class B "2" is destructed.
Class A "3" is constructed.
Class B "3" is constructed.
Class B "3" is destructed.
Class A "4" is constructed.
Class B "4" is constructed.
Class B "4" is destructed.
Class A "5" is constructed.
Class B "5" is constructed.
Class B "5" is destructed.
Class A "6" is constructed.
Class B "6" is constructed.
Class B "6" is destructed.
......

我们会发现这个类永远不会被破坏。A

如果我们调用类的方法,我们通知调度队列完成执行闭包,那么所有这些类实例都将被破坏:stopWorkingContentViewModelA

......
Class A "7" is destructed.
Class A "6" is destructed.
Class A "5" is destructed.
Class A "4" is destructed.
Class A "3" is destructed.
Class A "2" is destructed.
Class A "1" is destructed.
Class A "0" is destructed.

这不是我想看到的。我希望这些类实例被破坏,只要没有其他人引用它们。这是 Clang 的 ARC 实现的错误吗?有什么优雅的方法可以解决这个问题吗?A

swift objective-c memory-leaks 闭包 automatic-ref-counting

评论

0赞 Shadowrun 8/26/2022
如果用 autoreleasepool { ... } 包装 while 循环的主体会发生什么
0赞 The Dreams Wind 8/26/2022
单独运行此代码(没有任何框架,但已导入)不会导致任何泄漏(实例在循环结束时被释放)。我相信使用自动释放池@Shadowrun是正确的,因为实例可能已经使用自动释放机制进行了实例化,并且只要 GCD 一个永无止境的线程池继续运行,实例就永远不会离开它们被实例化的自动释放池,所以你可能需要制作自己的FoundationAtomicsAwhileAA

答: 暂无答案