提高iOS代码质量 2018-01-07

转发自
http://blog.csdn.net/skylin19840101/article/details/51500900

谢谢大佬

质量问题不仅仅是商品应该注重的,在移动互联占据人们各个生活领域的前提下,产品质量更显重要,以最具人气和潜力的iOS为例,iOS从系统研发和客户端软件开发环节对质量的要求异常高,在注重用户体验的同时提升产品质量,这也是很多用户非iOS不用的原因,iOS系统已经让移动互联网的品质得到升级。那么我们在开发iOS产品时,如何提高它的质量呢?

涉及到质量问题,这就是一个很大的话题,包括很多方面,比如代码书写的质量,开发流程的规范,项目管理的到位,测试的最后把关等各个环节。编码需要规范,命名需要有意义;接口低耦合、高内聚、易扩展,代码能重用、避免重复代码;提交代码后需要做CodeReview;Release前,自测需要充分,包括单元测试、和其他模块(服务器)的联调测试,网络性能测试,不同机型、不同系统版本、越狱与否等各个方面的测试;通过冒烟测试后才交于QA测试等等。在这里,我们主要分享从开发人员的角度如何提高产品质量,包含的内容如下:

<a name="t0" style="box-sizing: border-box; background-color: transparent; color: rgb(79, 161, 219); text-decoration: none; margin: 0px; padding: 0px; font-weight: 400; outline: 0px; background-position: initial initial; background-repeat: initial initial;"></a>避免异常:

image

从上面可以看到,iOS开发中常见的异常包括以下几种:
NSInvalidArgumentException
NSRangeException
NSGenericException
NSInternalInconsistencyException
NSFileHandleOperationException

<a name="t1" style="box-sizing: border-box; background-color: transparent; color: rgb(79, 161, 219); text-decoration: none; margin: 0px; padding: 0px; font-weight: 400; outline: 0px; background-position: initial initial; background-repeat: initial initial;"></a>NSInvalidArgumentException

非法参数异常(NSInvalidArgumentException)是 Objective - C 代码最常出现的错误,所以平时在写代码的时候,需要多加注意,加强对参数的检查,避免传入非法参数导致异常,其中尤以nil参数为甚。

<a name="t2" style="box-sizing: border-box; background-color: transparent; color: rgb(79, 161, 219); text-decoration: none; margin: 0px; padding: 0px; font-weight: 400; outline: 0px; background-position: initial initial; background-repeat: initial initial;"></a>集合数据的参数传递

比如NSMutableArray, NSMutableDictionary的数据操作

(1) 不能删除nil的key
image

(2) NSDictionary不能添加nil的对象


image

(3) 不能插入nil的对象
[图片上传失败...(image-ca0fe2-1515303094173)]

(4) 其他一些nil参数


image

<a name="t3" style="box-sizing: border-box; background-color: transparent; color: rgb(79, 161, 219); text-decoration: none; margin: 0px; padding: 0px; font-weight: 400; outline: 0px; background-position: initial initial; background-repeat: initial initial;"></a>其他一些API的使用

<a name="t4" style="box-sizing: border-box; background-color: transparent; color: rgb(79, 161, 219); text-decoration: none; margin: 0px; padding: 0px; font-weight: 400; outline: 0px; background-position: initial initial; background-repeat: initial initial;"></a>

(1) NSDictionary不能删除nil的key

<a name="t5" style="box-sizing: border-box; background-color: transparent; color: rgb(79, 161, 219); text-decoration: none; margin: 0px; padding: 0px; font-weight: 400; outline: 0px; background-position: initial initial; background-repeat: initial initial;"></a>

APP一般都会有网络操作,免不了使用网络相关接口,比如NSURL的初始化,不能传入nil的http地址:

image

<a name="t6" style="box-sizing: border-box; background-color: transparent; color: rgb(79, 161, 219); text-decoration: none; margin: 0px; padding: 0px; font-weight: 400; outline: 0px; background-position: initial initial; background-repeat: initial initial;"></a>未实现的方法

(1) .h文件里修改了函数名,却忘了修改.m文件里对应的函数名;或者头文件里定义了函数,.m文件里没有实现

(2) 使用第三方库时,没有添加”-ObjC” flag
(3) MRC时,大部分情况下是因为对象被提前release了,在你心里不希望他release的情况下,指针还在,对象已经不在了。 比如:


image
image

<a name="t7" style="box-sizing: border-box; background-color: transparent; color: rgb(79, 161, 219); text-decoration: none; margin: 0px; padding: 0px; font-weight: 400; outline: 0px; background-position: initial initial; background-repeat: initial initial;"></a>

(4) 对象类型使用不当

[图片上传失败...(image-af0fee-1515303094173)]

image

因为imagenS本来是NSDictionary的对象,被当做NSString来处理了

image

类似的,NSDictionary的对象被当做NSArrary来处理了

<a name="t8" style="box-sizing: border-box; background-color: transparent; color: rgb(79, 161, 219); text-decoration: none; margin: 0px; padding: 0px; font-weight: 400; outline: 0px; background-position: initial initial; background-repeat: initial initial;"></a> NSRangeException

越界异常(NSRangeException)也是比较常出现的异常,有如下几种类型:

<a name="t9" style="box-sizing: border-box; background-color: transparent; color: rgb(79, 161, 219); text-decoration: none; margin: 0px; padding: 0px; font-weight: 400; outline: 0px; background-position: initial initial; background-repeat: initial initial;"></a>

<a name="t10" style="box-sizing: border-box; background-color: transparent; color: rgb(79, 161, 219); text-decoration: none; margin: 0px; padding: 0px; font-weight: 400; outline: 0px; background-position: initial initial; background-repeat: initial initial;"></a>数组最大下标处理错误

比如数组长度count, index的下标范围[0, count -1], 在开发时,可能index的最大值超过数组的范围;


image

<a name="t11" style="box-sizing: border-box; background-color: transparent; color: rgb(79, 161, 219); text-decoration: none; margin: 0px; padding: 0px; font-weight: 400; outline: 0px; background-position: initial initial; background-repeat: initial initial;"></a>

<a name="t12" style="box-sizing: border-box; background-color: transparent; color: rgb(79, 161, 219); text-decoration: none; margin: 0px; padding: 0px; font-weight: 400; outline: 0px; background-position: initial initial; background-repeat: initial initial;"></a>下标的值是其他变量赋值

这样会有很大的不确定性, 可能是一个很大的整数值

image
image

这里的值达到32位和64位整数的最大值,肯定是一个不正常的参数,比如:

image

如果找不到str ,则返回NSNotFound,32位下它就是2147483647,64位下18446744073709551615 ,将它作为参数传递则会导致NSRangeException。

<a name="t13" style="box-sizing: border-box; background-color: transparent; color: rgb(79, 161, 219); text-decoration: none; margin: 0px; padding: 0px; font-weight: 400; outline: 0px; background-position: initial initial; background-repeat: initial initial;"></a>

<a name="t14" style="box-sizing: border-box; background-color: transparent; color: rgb(79, 161, 219); text-decoration: none; margin: 0px; padding: 0px; font-weight: 400; outline: 0px; background-position: initial initial; background-repeat: initial initial;"></a>使用空数组

如果一个数组刚刚初始化,还是空的,就对它进行相关操作

[图片上传失败...(image-4a5ea9-1515303094176)]

所以,为了避免NSRangeException的发生,必须对传入的index参数进行合法性检查,是否在集合数据的个数范围内。

<a name="t15" style="box-sizing: border-box; background-color: transparent; color: rgb(79, 161, 219); text-decoration: none; margin: 0px; padding: 0px; font-weight: 400; outline: 0px; background-position: initial initial; background-repeat: initial initial;"></a>

NSGenericException

NSGenericException这个异常最容易出现在foreach操作中,在for in循环中如果修改所遍历的数组,无论你是add或remove,都会出错,比如:

image

执行上面的代码会出现以下的错误:

image

原因就在这 "for in",它的内部遍历使用了类似 Iterator进行迭代遍历,一旦元素变动,之前的元素全部被失效,所以在foreach的循环当中,最好不要去进行元素的修改动作,若需要修改,循环改为for遍历,由于内部机制不同,不会产生修改后结果失效的问题。
[图片上传失败...(image-c8a4bb-1515303094173)]

<a name="t16" style="box-sizing: border-box; background-color: transparent; color: rgb(79, 161, 219); text-decoration: none; margin: 0px; padding: 0px; font-weight: 400; outline: 0px; background-position: initial initial; background-repeat: initial initial;"></a>NSMallocException

这也是内存不足的问题,无法分配足够的内存空间

image

<a name="t17" style="box-sizing: border-box; background-color: transparent; color: rgb(79, 161, 219); text-decoration: none; margin: 0px; padding: 0px; font-weight: 400; outline: 0px; background-position: initial initial; background-repeat: initial initial;"></a>NSFileHandleOperationException

处理文件时的一些异常,最常见的还是存储空间不足的问题,比如应用频繁的保存文档,缓存资料或者处理比较大的数据:[图片上传失败...(image-ec2fa3-1515303094176)]

所以在文件处理里,需要考虑到手机存储空间的问题。

<a name="t18" style="box-sizing: border-box; background-color: transparent; color: rgb(79, 161, 219); text-decoration: none; margin: 0px; padding: 0px; font-weight: 400; outline: 0px; background-position: initial initial; background-repeat: initial initial;"></a>二、错误处理

在进行函数调用的时候,如果有NSError的参数传递,最好都处理以下,特别是一些网络和文件的处理,比如:

文件操作错误

比如在删除文件时,有可能发生错误,我们需要做一些处理
[图片上传失败...(image-60990c-1515303094172)]

网络处理错误

在使用NSURLConnection进行网络请求时,发生错误是难免的,必须做相应处理


image

<a name="t19" style="box-sizing: border-box; background-color: transparent; color: rgb(79, 161, 219); text-decoration: none; margin: 0px; padding: 0px; font-weight: 400; outline: 0px; background-position: initial initial; background-repeat: initial initial;"></a>三、本地缓存的兼容处理

在APP的运行过程中,难免会有I/O的操作,一般的情况,从技术的角度来看也不会出什么问题,但是业务逻辑上可能就需要特别注意了,比如APP升级版本后,可能保存缓存数据的格式发生变化了,读取数据后的处理逻辑也就相应发生变化,这时就需要考虑兼容旧版本数据的处理,否则对升级用户了说,很可能由于异常发生崩溃。

<a name="t20" style="box-sizing: border-box; background-color: transparent; color: rgb(79, 161, 219); text-decoration: none; margin: 0px; padding: 0px; font-weight: 400; outline: 0px; background-position: initial initial; background-repeat: initial initial;"></a>四、解析网络数据

客户端开发时,对接收到的服务器数据,需要特别小心,因为经过网络传输回来的数据都是不可信的,什么问题都可能发生,比如协议中该有的字段没有,是整数型的字段结果传了一个子串,Json格式的内容变成xml了…,所以我们在解析服务器数据时,需要有充分的异常处理机制。

<a name="t21" style="box-sizing: border-box; background-color: transparent; color: rgb(79, 161, 219); text-decoration: none; margin: 0px; padding: 0px; font-weight: 400; outline: 0px; background-position: initial initial; background-repeat: initial initial;"></a>五、代码静态检查

在开发完成后,需要对代码进行静态检查,这样能发现一些内存泄露以及一些warning。

<a name="t22" style="box-sizing: border-box; background-color: transparent; color: rgb(79, 161, 219); text-decoration: none; margin: 0px; padding: 0px; font-weight: 400; outline: 0px; background-position: initial initial; background-repeat: initial initial;"></a>内存泄露

<a name="t23" style="box-sizing: border-box; background-color: transparent; color: rgb(79, 161, 219); text-decoration: none; margin: 0px; padding: 0px; font-weight: 400; outline: 0px; background-position: initial initial; background-repeat: initial initial;"></a> MRC

在MRC的时候,内存泄露是个大问题,一不小心就会中招

[图片上传失败...(image-699c34-1515303094175)]

注意上面的代码并不是L63行存在泄漏,我们点击“Potential leak of an object”前面的箭头,指示会出现一些变化,如下图。
image

alloc一个对象的时候,其内存计数内存计数(retain count)+1


image
image

因为content的setter方发会将object的内存计数+1,如下代码,content是retain属性。执行完L62代码后,self.content的内存计数就为 2


image

建议修改方案:


image

<a name="t24" style="box-sizing: border-box; background-color: transparent; color: rgb(79, 161, 219); text-decoration: none; margin: 0px; padding: 0px; font-weight: 400; outline: 0px; background-position: initial initial; background-repeat: initial initial;"></a>ARC

在ARC下,这方面的问题就少很多了,但也还是会出现的,比如:

(1)使用CF CG有关的函数,内存的释放还是需要手动调用的,不调用则会造成内存泄漏


image

CFUUIDRef和CFStringRef都需要手动释放


image

CGImageRef类似也需要手动释放

(2) Runtime方法中的class_copyIvarListclass_copyMethodList这些方法返回的对象,也需要手动释放(free)

image

(3) 如果iOS中使用C/C++编程

 比如使用Openssl的RSA接口,用到一些内存的分配操作,使用结束需要释放

[图片上传失败...(image-ff2626-1515303094173)]

在iOS中,怎么避免内存泄漏的产生呢?除了开发经验的积累,我们也可以通过两个方法来发现内存泄漏,以便及时修复,一个就是上面讲到的静态分析(Analyze),比较简单;对于静态没有检测出来的内存泄露问题,需要使用动态的方法,下一节来分享。

<a name="t25" style="box-sizing: border-box; background-color: transparent; color: rgb(79, 161, 219); text-decoration: none; margin: 0px; padding: 0px; font-weight: 400; outline: 0px; background-position: initial initial; background-repeat: initial initial;"></a>无效数据监测(Dead store)

Unused、Never read....这个比较简单,修改即可。

<a name="t26" style="box-sizing: border-box; background-color: transparent; color: rgb(79, 161, 219); text-decoration: none; margin: 0px; padding: 0px; font-weight: 400; outline: 0px; background-position: initial initial; background-repeat: initial initial;"></a>逻辑错误监测(Logic error)

[图片上传失败...(image-5b1cb5-1515303094174)]

Tag不等于1、2和3的时候,就会出现很问题了。len is a garbage value。建议在声明变量时,同时进行初始化。

<a name="t27" style="box-sizing: border-box; background-color: transparent; color: rgb(79, 161, 219); text-decoration: none; margin: 0px; padding: 0px; font-weight: 400; outline: 0px; background-position: initial initial; background-repeat: initial initial;"></a>其他一些warning

对完美主义者来说,warning在Xcode里始终都比较碍眼,直接消除。

<a name="t28" style="box-sizing: border-box; background-color: transparent; color: rgb(79, 161, 219); text-decoration: none; margin: 0px; padding: 0px; font-weight: 400; outline: 0px; background-position: initial initial; background-repeat: initial initial;"></a>Instruments分析

Analyze分析内存泄露不能把所有的内存泄露查出来,有的内存泄露是在运行时,用户操作时才产生的。在 C、C++混编时,对于C++的内存分配,也是需要手动释放,比如iOS中使用Openssl来进行RSA的加解密操作,其中就有很多的内存操作,如果不进行手动释放,Analyze是检查不出来的,这个时候就需要用到Instruments了。

初始化


image

释放的代码注释掉
[图片上传失败...(image-c34e8f-1515303094170)]
这个时候使用Analyze分析,一点内存泄漏也没有,我们使用Instruments来分析

“Product” -> “Profile”:


image

按上面操作,build成功后跳出Instruments工具,选择Leaks工具,这时候Instruments工具就运行起来了,显示效果如下:


image

点击上面的”红色的圆圈”,才会启动对应的APP,这时”红色的圆圈”变成”黑色的正方形”:


image

这时用户就可以在APP上任意操作,查看内存使用情况,选择”Leak Checks”项,右边的图中,如果是”绿色的勾”表示内存使用正常,”红色的叉”则表示有内存泄漏,这时点击”黑色的正方形”,让APP暂停下来。


image

这时你只要在”Reponsible Frame”中双击对应的方法,就会跳转到具体的代码,比如双击”MS_RSA_Public_E”:
image

代表malloc后没有使用free释放OutBuf的内存。
修改如下:


image
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容