美文网首页
转载一篇文章,debug的精神非常受用

转载一篇文章,debug的精神非常受用

作者: 乡村武装青年 | 来源:发表于2015-12-30 23:49 被阅读58次

    文章出自:http://gracelancy.com/blog/2012/11/27/debug-objc-code/

    作者简介:蓝晨钰(Lancy)目前在猿题库(yuantiku.com)任 iOS 研发工程师。

    从一段奇葩的objc代码看代码规范的重要性

    背景介绍

    昨天,在和我一个朋友讨论,到底是用self.propertyName还是_propertyName来访问property,我认为应该使用self.propertyName,因为我在听Stanford Open Course的时候,苹果的工程师告诫要使用self.propertyName,不要使用_propertyName。而朋友认为应该使用_propertyName,因为google objc code style认为最好不要用self.propertyName。

    我没看过google objc code style,我只看过objective c programming guide。在我的理解里property的作用在于根据参数生成相应的getter和setter。self.propertyName本质上既是调用getter函数的,而_propertyName直接访问成员函数,因为相应参数生成的getter和setter是不会被调用的。

    再说,我还是决定相信apple,而不是google,毕竟Objc还是apple在支持和维护。

    上代码


    问题

    这段代码是无效的,按下按钮之后,setupData被调用了,已经log确定dataArray已经改变,tableview的delegate和datasource都设置正确,确定numberOfRowsInSection被调用,奇葩的是cellForRowAtIndexPath没有调用,故而tableview没有改变。

    奇葩的来了

    朋友跟我说,你只要把[self.tableview reloadData]改成[_tableview reloadData],他就生效了。是的,他就生效了。你设一个断点在这个地方,然后把self.tableview和_tableviewpo出来,发现他们的指针是一样的。朋友说写这个代码的那货折腾了一天,百思不得其解,最后得出结论self.propertyName就是坑爹。

    生效的修改方法

    朋友提供的:

    前面说的讲把[self.tableview reloadData]改成[_tableview reloadData]

    把tableview的getter函数的init里面的self.view.bounds改成CGReckMake(0,0, 320, 480)

    朋友试图用这个两个方法来说明,self.property是坑爹的。

    我在初步debug的时候,由于我是property的拥护者,property自动生成setter和getter函数,我是不支持重写getter函数的,所以我将getter函数删掉,把初始化代码移到viewdidload里面。然后代码就生效了。

    但是即使代码生效了,还是没有找到问题的关键,仍然没办法解释为什么[self.tableview reloadData]改成[_tableview reloadData]就能运行了,因为po出来的指针是完全一样的,这不科学。

    真正的问题所在

    在各种Stackoverflow,google无果之后,我还是着手准备深入debug。

    通过各种断点和gdb,最后打印函数调用栈才让我发现了真正的问题所在。

    整个程序的执行顺序是这样的:

    initWithNibName(执行到[self setupData],没执行完) –>

    第一次setupData(执行到[self.tableView reloadData],没执行完) –>

    第一次执行tableview getter(到init,调用self.view,没执行完)->

    viewDidLoad(到addSubview:self.tableView, 没执行完) –>

    第二次执行tableview getter(问题在这里!第一次执行的时候没有init玩,所以又会执行一次!)->

    回到4.viewDidLoad,这是add的subview是第二次的init而先init完的tableview –>

    回到3.第一次执行getter,(又alloc了一次tableView,这是self.property指向的是第一次init而后init完成的tableview))

    所以,显示在界面上的tableview根本不是self.tableview指向的tableview,故而根本没法刷新(cellForRowAtIndexPath,是当需要显示的时候才会调用的)。

    那为什么把[self.tableview reloadData]改成[_tableview reloadData]就能生效了呢?因为这样在initWithNibName的第一次调用setupData,就不会在reload的时候调用tableview getter,也就不会有后面一连串的连锁反应。之后顺利在viewdidload的时候只调用一次,完成init。

    知道了问题的关键,还能有各种各样让他生效的方法,就不吐槽了。

    正确的写法

    这段奇葩代码带给我最大的感触就是,不好好写规范的代码,各种问题都会坑死你。我认为规范的写法应该是

    不要重写getter和setter函数,使用property生成的getter和setter

    不要在vc的init的函数里面初始化,尤其是初始化视图。而应该在viewdidload里面初始化,保证self.view已经生成。(非ARC环境下还需要注意memory warning导致的viewdidload多次加载而多次初始化所带来的内存泄露问题。最安全的做法是lazy instantiation)

    应该使用自顶向下的程序设计方法,保证程序的顺序执行和层次关系。不应该出现如上程序的跳来跳去的调用。

    后记

    帮人debug还是有好处的,让我结识了这位bug兄。也让我更加深入的了解了cocoa的变量访问机制,debug的时候顺带还测试了KVO。

    Edit

    我又重新去看了property和getter,setter的资料,也看了苹果对property的解释。最后我修正关于不要重写getter和setter函数的观点,更正为可以重写getter和setter,目的可以为lazy instantiation, UI updating, consistency checking,等。但需要注意如上程序的连锁反应。代码的灵活性和安全性

    关于@property,经过和大家的讨论也有了一个结论:

    Why property?

    Most importantly, it provides safety and subclassablility for instance variables. Also provides “value” for lazy instantiation, UI updating, consistency checking, etc.

    Lancy

    11.27.2012

    相关文章

      网友评论

          本文标题:转载一篇文章,debug的精神非常受用

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