美文网首页Des's iOS iOS开发心得架构师之路
Xcode8调试黑科技:Memory Graph实战解决闭包引用

Xcode8调试黑科技:Memory Graph实战解决闭包引用

作者: 没故事的卓同学 | 来源:发表于2016-10-16 00:56 被阅读13833次

Xcode8的调试技能又增加了一个黑科技:Memory Graph。简单的说就是可以在运行时将内存中的对象生成一张图。在现场的开发者听到了这个消息时响起了雷鸣般的掌声!我们来看看前方记者发回的现场照片:



妈妈说再也不用担心引用循环啦!除非你是个瞎子。



那么通过一个实际项目来练习一下吧。
首先我们写了一个自定义UIView:MyView。初始化的时候接收一个没有参数也没有返回值的闭包作为参数,并存为自己的属性:
typealias Action = () -> Void

class MyView: UIView {
    
    var action: Action?
    
    init(action: @escaping Action) {
        self.action = action
        super.init(frame: CGRect.zero)
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

接着我们在一个ViewController中初始化MyView,并且也保存为属性:

class ViewController: UIViewController {
    
    @IBOutlet weak var label: UILabel!

    var myView: MyView?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        myView = MyView(action: testMethod)
    }

    func testMethod() {
        label.text = "haha"
    }

}

这vc的view上有一个label控件,在viewDidLoad时初始化myView,并且将自身的一个testMethod方法当做参数传给了myView。
testMethod中设置了自身label的text。
注意,划重点了!


这里体现了swift函数式的特性:函数可以自由的当做一个变量传递。

这个例子影射里开发中一个常见的场景:一个tableViewCell中有一个删除按钮,通过闭包将方法传进去,cell保存这个闭包;另一方面这个闭包被调起后,删除某条数据后刷新数据源。

那么这么写会产生引用循环吗?

    func testMethod() {
        label.text = "haha"
    }

核心在这段代码上,一个类的方法里设置自身的属性,会捕捉这个属性吗?这个地方可以写self,但是捕捉策略是unowned还是strong呢?
这个闭包的实现是不能自己声明捕捉策略的:


于是就来验证一下。运行起来后,push这个ViewController后pop出去(记得要进行两次,好像只有一次Xcode有时不会启动分析)。
接着点击这个按钮:



这个时候就进入了断点模式,可以查看issue面板,注意选择右边Runtime:



有很多叹号说明就有问题了。看内存中object的名字,有一条是Closure captures leaked。展开后点击就可以看到这个issue对应的内存图形展示在中间的面板中。
当然了,我们更多的时候是在debug页面下查看:

注意到我们刚才的对象名:一个叫MyView,一个叫ViewController。我们pop了两次,按理说内存里不应该有这个两个对象,然而还是有两份实例。所以,这里面引用循环了。点击紫色的叹号会出现Xcode分析出来的内存引用图形:

有了这个图就很容易看出来了:myView保持了action,action保持了testMethod,testMethod中因为设置了vc的label所以也保持了VC。所以我们可以确定:方法中隐式的self的捕捉策略是strong。这样直接把方法传入子view中会引起引用循环。

解决方案

1.将逻辑实现在一个匿名闭包里,不实现在类的方法上

这样就可以自己声明捕捉策略。这样的方式使用就和OC的block类似了:

        myView = MyView(){ [unowned self] in
            self.label.text = "haha"
        }

2.在匿名闭包中调用方法

不是直接传入testMethod方法,而是在传入的闭包中调用自身的方法:

        myView = MyView(){ [unowned self] in
            self.testMethod()
        }

欢迎关注我的微博:@没故事的卓同学

相关链接:
WWDC 2016 Session 410 Visual Debugging with Xcode

相关文章

网友评论

  • 超_iOS:能在补充点么?没太看懂:joy:
  • 荔枝lizhi_iOS程序猿:还不知道的功能
  • b430595f0525:对于项目简单的用起来还可以,项目复杂这个东西真的不好用
  • ChoiKarl:很多时候内存泄露抓不到啊,不知道是以什么样的机制去抓的
  • 小_夭:楼主,按照你文章写的demo并没有出现后面的循环引用等情况,有demo分享一下不?
  • 言溪Lee:博主这种情况好像没用过 一般是在在cell.swift里声明一个闭包作为参数(weak var tipClickColsure:((id:Int, count:Int, isSelected:Bool)->Void))!进行数值传递,类似代理的作用 初始化cell调用这个参数在闭包中处理代码
    cell.tipClickColsure = {[unowned self]
    (id, count, isSelected) in
    ......
    } 这种情况该如何处理呢
  • 言溪Lee:解决方案里MyView(){}是什么写法?😓第一次见这种写法
    没故事的卓同学:@xiaoheziQH 尾闭包,一种语法糖
  • angelababa:请问 使用 [unowned self] 和[weak self] 有什么区别呢
    我把今生当成了来世:@angelababa weak会回收吧,unowned会成为野指针
  • 逆行风:好文章就要赞,不赞不地道
  • SHY圆圆圈圈圆圆:测试了一下,发现会存在该问题,但是这个界面下没有紫色感叹号是什么情况。
  • 一条渴死的鱼:OC源代码的工程貌似没有能看到闭环啊
    一条渴死的鱼:@一条渴死的鱼 而且确定有循环引用
  • 了了此心:我这边发现有一个字符串存在循环引用,但是不知道是哪个类?不知道如何排查,能否指点一下
  • 612b500d03e5:swift3 一个网络请求通过逃逸闭包返回,如果成功再调用一个网络请求继续通过闭包返回,debug模式没有问题,release下崩溃:smile:
  • 只有NO1:我怎么觉得不对啊,在匿名闭包里调用方法这里,swift官方文档说,在闭包里调用方法不会循环引用,因此不加unown self
    zhouwude:@只有NO1 这里使用的是全局的所以会有这种问题
  • c50765594d45:好厉害!!!
  • GJCode:好厉害学习了
  • 没梦想的咸鱼2:oc 不能用吗 :disappointed_relieved:
  • ChenJZ:学到新东西了
  • GJCode:666
  • butterflyer:为啥test method 里的方法不走啊。。
    Origheart:@butterflyer 你需要调用这个闭包,才会走的~
  • MYS_iOS_8801:太感谢了
  • KCatherine:刚好最近遇到控制器无法释放的问题,作者真是雪中送炭:+1:
  • JanzTam:配图太6!:joy:
  • sim_cai:有东西
  • 王小宾:函数是值类型吗?
    六阿哥:明显不是吧
  • Sheepy:666
  • 一缕殇流化隐半边冰霜:这个好啊!!!
    4023b5cb645c:saber喊你回家吃饭了
    一缕殇流化隐半边冰霜:@冰熊熊熊 ???:stuck_out_tongue_winking_eye:
    a7642f69975b:熟悉的面孔

本文标题:Xcode8调试黑科技:Memory Graph实战解决闭包引用

本文链接:https://www.haomeiwen.com/subject/nksoyttx.html