编写高质量iOS的有效方法

发表于:2016-5-20 11:44

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:小小小科夫斯基    来源:51Testing软件测试网采编

分享:
  接口与API设计
  1)使用前缀避免命名空间冲突
  A)给类名加上适当的前缀,并且在所有代码中使用这一前缀。
  B)若开发的程序中用到了第三方库,则应为其中的名称加上前缀。
  2)提供全能的初始化方法
  A)在类中提供一个全能初始化方法,并于文档里指明。其它初始化方法均应调用此方法。
  B)如果全能初始化方法和超类不同,则需要覆写超类中的对应方法。
  C)如果超类的初始化方法不适合用于子类,那么应该覆写这个超类方法,并在其中抛出异常。
  3)尽量使用不可变对象
  A)若属性仅可内部修改,则在分类中将readonly改成readwrite属性。
  B)不要把可变的集合作为属性公开,而应该提供相关方法,以此修复对象中的可变集合属性。
  4)为私有方法加前缀名
  A)不要单用下划线作为前缀。苹果公司框架内部使用的就是下划线,避免继承框架的类时覆盖超类的方法。
  5)OC的错误处理
  A)只有发生可使整个应用程序崩溃的严重错误时,才应使用异常。
  B)在错误不严重的情况下,可以指派代理来处理错误,也可以把错误信息放在NSError对象中,作为输出参数返回给调用者。
  6)理解NSCopying协议
  A)实现copyWithZone:方法, 系统方法以应用程序唯一的“默认区”为参数(以前的程序内存有多个分区)
  B)[NSMutableArray copy] => NSArray; [NSArray mutableCopy] => NSMutableArray
  C)如果自定义的对象分为可变和不可变版本,需要同时实现NSCopying和NSMutableCopying协议
  D)复制对象时需要决定使用深拷贝还是浅拷贝,一般情况下应该尽量执行浅拷贝。
  E)如果所写的对象需要深拷贝,可以考虑新增一个专门执行深拷贝的方法。
  协议与分类
  1)通过委托与数据源进行通信
  A)若有必要,可实现含有位段的结构体,将委托对象是否能响应相关协议方法这一信息缓存至其中(如网络请求中通过代理方法传递数据进度)。
  2)将类的实现代码分散到各个分类中,便于管理
  A)根据回溯信息的分类名称,便于调试。
  B)应该将“私有”方法归入名叫Private的分类,以隐藏实现细节。
  3)向第三方类中添加分类时,应给其名称和其中 的方法名加上专用前缀。
  A)为了避免覆盖,出现难以排查的bug。
  4)分类中的属性
  A)把封装数据所用的全部属性都定义在主接口里。
  B)分类中不推荐定义属性,因为分类无法合成与属性相关的实例变量。即使可以使用关联对象来解决问题,也不推荐。
  5)用分类隐藏实现细节
  A)新增实例变量/只读扩展为读写/声明私有方法原型/遵守不为外部知道的协议
  6)使用匿名对象
  内存管理
  1)ARC
  A)以alloc/new/copy/mutableCopy开头的方法,其返回的对像归调用者所有,返回创建对象时不会执行[object release]。
  B)CoreFOundation对象不归ARC管理,所以应适当使用CFRetain/CFRelease
  2)dealloc用法
  A)这个方法只用来释放其它对象的应用、取消KVO、通知,不要做其它事。
  B)如果对象持有文件描述符等系统资源,则应该专门编写一个方法释放该资源。如和使用者约定,用完资源后必须调用close方法。
  C)异步任务不要放在dealloc,正常状态下执行的方法也不要放在dealloc
  D)对于开销较大的如套接字、文件描述符、大块内存等,应该单独实现另一个方法,当程序用完这些资源时就调用此方法。因为不能指望dealloc会在某个特定时期调用甚至不会调用,且在等到dealloc执行时,这些资源就会保留过长。
  3)异常
  A)捕获对象时要将try中创立的对象清理干净。
  B)默认情况下,ARC不生成处理异常所需要的代码,样板代码多、影响性能等副作用。开启编译标志-fobjc-arc-exceptions后,可以生成这种代码。
  4)保留环
  A)A、B相互作为属性,其中之一应置weak。不建议使用unsafe_unretained,否则继续使用会崩溃。
  5)自动释放池
  A)自动释放池排布在栈中,对象收到autorelease消息后,系统将其放入最顶端。
  B)合理运用释放池可以降低应用程序的内存峰值。
  C)@autoreleasepool这种新式写法能创建出更为轻便的自动释放池。
  6)僵尸对象
  A)系统回收对象时,可以不将其真的收回,而是转化为僵尸对象。通过环境变量NSZombieEnabled可开启此功能。
  B)系统会修改对象的isa指针,令其指向僵尸类(从而变为了僵尸对象)。僵尸类能够响应所有选择子,方式为打印一条包含消息内容及其接受者的消息,并终止程序。
  块与大中枢派发
  1)块
  A)块也是一个对象,块可以分配在栈、堆、全局的。栈上的离开创建区域之后会被销毁。
  2)用handler块降低代码分散程度
  A)创建对象时,可以使用内联的handler块把相关业务逻辑一起声明。特别是有多个实例需要监控时,用handler可以直接将块与相关对象放在一起。
  B)设计API时如果用到了handler块,那么可以增加一个参数,使调用者可通过此参数来决定应该把块安排在哪个队列上执行。
  3)多用派发队列,少用同步锁
  A)派发队列可用来表述同步语意,这种做法比@synchronized块或NSLOck块对象更简单。例如:
  - (NSString *)someString{
   __block NSString *tempStr;
   dispatch_syns(_syncQueue, ^{ tempStr = _someString});
  return tempStr;
  }
  B)将同步和异步派发结合起来,可实现与普通加锁机制一样的同步行为,而这么做却不会阻塞执行异步派发的线程。
  C)使用同步队列及栅栏块,可以令同步行为更加高效
  void dispatch_barrier_async(queue, block); void dispatch_barrier_sync(queue, block);
  备注:barrier块必须单独执行,不能与其他block并行。这只对并发队列有意义。并发队列如果发现接下来要执行的block是个barrier block,那么就一直要等到当前所有并发的block都执行完毕,才会单独执行这个barrier block代码块,等到这个barrier block执行完毕,再继续正常处理其他并发block。
  4)多用GCD,少用performSelector系列方法
  A) 在内存管理方面容易疏失。无法确定将要执行的选择子具体是什么,所以ARC无法插入适当的内存管理方法。
  B)能处理的选择子过于局限,返回值类型以及参数都收到限制。
  C)如果想把任务放在另一个线程上,最好不要用 performSelector,应该封装到块里。
  5)适当选择GCD和操作队列的使用时机
  A)操作队列实现了高层的OC API,能实现一些复杂的功能和操作,如
  取消操作、指定操作间的依赖关系、通过键值观测监控NSOperation对象的属性、指定优先级、重用NSOperation对象等。GCD主要针对整个队列间,操作队列主要针对队列中的每个块。
  6)通过dispatch Group机制,根据系统资源状况来执行任务
  A)一系列任务可归入一个dispatch group中,开发者可在这组任务执行完毕获得通知。
  相关API:
  dispatch_group_create();? ? ? ? // 创建组
  dispatch_group_async(group, queue, block);? // 在组中异步执行队列中的block
  dispatch_group_enter(group);? // 加入组
  dispatch_group_leave(group);? //和enter成对出现,否则改group的任务永远执行不完
  dispatch_group_wait(group, timeout);? ? // 等待组。DISPATCH_TIME_FOREVER 表示永久等待
  dispatch_group_notify(group, queue, block);? // 组中任务执行完毕,会执行block
  dispatch_apply(count, queue, block(size));? // 重复执行block共count次,是一个持续阻塞函数,直到任务执行完(可在异步dispatch_async中调用该方法)
  7)使用dispatch_once来执行只需要运行一次的线程安全代码
  dispatch_once(token, block);
  A)注意标记token应该声明在static或global中。
  8)不要使用dispatch_get_current_queue
  A)该函数的行为常常与开发者所预期不同,因此iOS6.0已废弃,只能做调试使用。
  B)该函数用于解决由不可重入的代码所引发的死锁。可以用标记队列dispatch_queue_set_specific、递归锁NSRecursiveLock来解决。
  熟悉系统框架
  1)多用块枚举,少用for循环
  A)遍历collection有四种方式。最基本的办法是for循环,其次是NSEnumerator遍历法及快速遍历法,最新最先进的是“块枚举法”。
  B)“块枚举”本身通过GCD来并发执行遍历操作。
  2)对自定义内存管理语义的collection使用无缝桥接
  A)在CoreFoundation层面创建collection时,可以指定许多回调函数,这些函数表示此collection应该如何处理其函数。运用无缝桥接,可实现特殊内存管理语义。例如可以使OC中的字典实现键值均保留。
  3)构建缓存时选用NSCache,而不是NSDictionary
  A)好处之一是系统资源将要耗尽时,NSCache可以自动删减缓存。且线程安全,保留键值。
  B)可以给NSCache对象设置上线,用以限制缓存中的对象个数以及总成本。但这些限制并不可靠,NSCache只是”参考”。
  C)将NSPurgeableData(NSMutableData的子类)与NSCache搭配使用,可实现自动清除数据的功能。即当NSPurgeableData对象所占内存为系统丢弃时,该对象自身也可以从缓存中移除。
  D)只有那种“重新计算起来很费事”的数据,才值得放入缓存
  4)精简initialize与load的实现代码
  A)运行期的类和分类,必定会调用+load方法,且只调用一次。
  B)给定的程序库,无法判断各个类的载入顺序,因此,在+load方法中使用其它类是不安全的,因为不确定这个其它类是否已经载入了。程序执行+load方法时都会阻塞。
  C)+load方法不像普通方法,它不遵循继承规则。如果某个类未实现+load方法,那么它的超类不管是否实现了该方法,都不会被调用。类的+load会比分类的+load先执行。
  D)+load的真正用途在于调试程序。时下编写OC代码,不需要使用它。
  E)+initialize 属于懒加载,首次使用该类之前调用,且只调用一次。在+initialize中可以放心调用任意类的任意方法。如果某个类未实现它,但其超类实现了,那么就会运行超类的实现代码。
  F )无法在编译期设定的全局常量,可以放在initialize方法里初始化。如全局可变数组,单例使用前需要执行某些操作。(PS:使用时,用if(self == [MyClass class])做一下判断,否则可能会执行多变)
  5)别忘了 NSTimer会保留其目标对象
  A)可以扩充NSTimer的功能,用”块”打破保留环。
22/2<12
重磅发布,2022软件测试行业现状调查报告~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2023
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号