账号密码登录
微信安全登录
微信扫描二维码登录

登录后绑定QQ、微信即可实现信息互通

手机验证码登录
找回密码返回
邮箱找回 手机找回
注册账号返回
其他登录方式
分享
  • 收藏
    X
    【十分诡异】swift 4.0.3 版本 closure 非预期的内存不泄露问题
    44
    0

    疑问: 为什么函数定义外的 closure 不会引起作用域内其他变量引用计数的变化?

    问题描述:

    仔细观察以下不同代码片段的不同输出:

    片段A:

    class Book{
        let name: String
        
        lazy var whoami:(()->String)? = {
            return self.name
        }
        
        init(name:String) {
            self.name = name
        }
        
        deinit {
            print("\(name) is being deinitialized")
        }
    }
    
    var aBook:Book? = Book(name: "风之影")
    print(aBook!.whoami!())
    
    aBook = nil
    
    /*
    输出:
    
    风之影
    */

    片段B:

    class Book{
        let name: String
        
        lazy var whoami:(()->String)? = {
            return self.name
        }
        
        init(name:String) {
            self.name = name
        }
        
        deinit {
            print("\(name) is being deinitialized")
        }
    }
    
    var aBook:Book? = Book(name: "风之影")
    print(aBook!.whoami!())
    
    aBook?.whoami = nil
    aBook = nil
    
    /*
    输出:
    
    风之影
    风之影 is being deinitialized
    */

    片段C:

    class Book{
        let name: String
        
        lazy var whoami:(()->String)? = {
            return self.name
        }
        
        init(name:String) {
            self.name = name
        }
        
        deinit {
            print("\(name) is being deinitialized")
        }
    }
    
    var aBook:Book? = Book(name: "风之影")
    
    aBook?.whoami = {
        return aBook!.name + " new"
    }
            
    print(aBook!.whoami!())
            
    aBook = nil
    
    /*
    输出:
    
    风之影 new
    风之影 is being deinitialized
    */

    片段A, aBook 内存泄露,经典的 closure self 循环引用问题.

    片段B,是 closure self 循环引用的一个可选解决方案,即 self 主动切断对 closure 的引用.

    片段C,比较诡异. aBook 引用了一个新的 closure,新的 closure 内又引用了 aBook 一次,但是 aBook 竟然还是可以正确释放,并没有预期中的内存泄露问题.令人费解!?

    解决方案:

    片段 D:

    class Book{
        let name: String
        
        lazy var whoami:(()->String)? = {
            return self.name
        }
        
        init(name:String) {
            self.name = name
        }
        
        deinit {
            print("\(name) is being deinitialized")
        }
    }
    
    var aBook:Book? = Book(name: "风之影")
    
    aBook?.whoami = {
        [aBook] in
        return aBook!.name + " new"
    }
            
    print(aBook!.whoami!())
            
    aBook = nil
    
    /*
    输出:
    
    风之影 new
    */

    可以看到,这样 aBook 就会泄露了.片段 D 与 片段 C 的区别在于 closure 中的那句 [aBook] in .这个语法,是我"杜撰"的,语义上近似于以强引用方式捕捉 aBook 对应的真实对象.[官方文档](https://developer.apple.com/l...
    )中并没有提到有这种语法.

    另外,参考 objc 中block 的行为,我尝试搜索相关 swift 中 栈(stack) block 的相关信息.如果 closure 也区分栈和堆,倒是还可以勉强解释.不过,并没有相关的信息,而且 closure 本身也是不支持 copy 操作的.

    注意: 当前复现此问题用的是 swift 4.0.3 版本,不同版本中的 closure 的行为可能不一致.

    猜想:

    或许 swift 中,只有内部有可能直接使用 self 的 closure,才需要特别考虑closure引起的内存泄露问题.

    个人猜测,可能是因为 self 比较特殊, closure 只能直接捕捉其真实对象.

    0
    打赏
    收藏
    点击回答
        全部回答
    • 0
    • 林间有新绿 普通会员 1楼

      在 Swift 4.0.3 版本中,使用 closure 创建的匿名函数并没有进行垃圾回收。这意味着即使在使用完 closure 之后,它的内存仍然存在于内存中。这可能会导致内存泄漏,因为 closure 可能会一直运行,占用内存。

      为了避免这种情况,你可以在使用完 closure 后使用 delete 关键字来释放它的内存。例如:

      ```swift let closure = { return 42; } let memoryUsage = memory usage of closure { return 42; }

      // 使用完 closure 后,释放其内存 memoryUsage.deallocate() ```

      这样,即使 closure 仍在内存中,也不会导致内存泄漏。

      另外,你也可以使用 Swift 的垃圾回收特性来自动管理 closure 的内存。Swift 的垃圾回收特性允许你为 closure 添加一个 deallocate 方法,当这个方法被调用时,它会释放 closure 的内存。例如:

      ```swift let closure = { return 42; } let memoryUsage = memory usage of closure { return 42; }

      // 使用完 closure 后,删除并释放其内存 memoryUsage.deallocate() ```

      这样,即使 closure 仍在内存中,也不会导致内存泄漏。

    更多回答
    扫一扫访问手机版
    • 回到顶部
    • 回到顶部