熟悉系统框架
框架:将一系列代码封装为动态库,并在其中放入描述其接口的头文件,这样做出来的东西就叫做框架。 有时为iOS平台而构造的第三方框架所使用的是静态库,但所有iOS平台的系统框架仍然使用动态库。
要点:
- 许多系统框架都可以直接使用。其中最重要的是
Foundation 与CoreFoundation ,这个框架提供了构建应用程序所需的许多核心功能。 - 很多常见任务都能用框架来做,例如音频与视频处理、网络通信、数据管理等。
- 请记住:用纯C写成的框架与用
Objective-C 写成的一样重要,若想成为优秀的 Objective-C 开发者,应该掌握C语言的核心概念
多用块枚举,少用for循环
使用Objective-C 1.0的NSEnumerator来遍历
NSEnumerator 是个抽象基类,其中只定义了两个方法,供其具体子类实现:
- (NSArray*)allObjects
- (id)nextObject
其中关键的方法是nextObject,它返回枚举里的下个对象。等到枚举中的全部对象都已返回之后,再调用就将返回nil。 Foundation框架中内建的collection类都实现了这种遍历方法。例如遍历数组: 虽然代码较for循环多了一点,但其真正的优势在:不论遍历哪种collection,都可以采用这套相似的语法。比如遍历字典和set:
快速遍历
自Objective-C 2.0引入了快速遍历这一功能。它为for循环开设了in关键字,比如要遍历数组: 如果某个类的对象支持快速遍历,则可以宣称自己遵从名为NSFastEnumeration的协议,从而令开发者可以采用此语法来迭代该对象。此协议只定义了一个方法:
该方法允许类实例返回多个对象,这就使得循环遍历操作更为高效。 由于NSEnumerator对象也实现了NSFastEnumeration协议,所以也可以用来执行反向遍历。例如:反向遍历数组:
基于块的遍历方式
在当前的Objective-C语言中,最新引入的一种做法就是基于块来遍历。NSArray中定义了下面这个方法,它可以实现最基本的遍历功能: 这个块有三个参数:分别是当前迭代所针对的对象,所针对的下标,以及指向布尔值的指针。通过最好一个参数提供的机制,开发者可以终止遍历操作。 例如:遍历数组: 此方法不仅可以用来遍历数组,还可以用来遍历NSSet或者NSDictionary: 块的方法签名可以修改,以免进行类型转换操作。比方说,要用“快速遍历法”来遍历字典。若已知字典中的对象必为字符串,则可以这样编码: 但是改为基于块的方式来遍历,那么就可以在块方法签名中直接转换:
要点:
- 遍历collection有四种方式。最基本的办法是for循环,其次是NSEnumerator遍历法及快速遍历法,最新、最先进的方式则是“块枚举法”。
- “块枚举法”本身就能通过GCD来并发执行遍历操作,无须另行编写代码。而采用其他遍历方式则无法轻易实现这一点。
- 若提前知道待遍历的collection含有何种对象,则应修改块签名,指出对象的具体类型。
对自定义其内存管理语义的collection使用无缝桥接
要点:
- 通过无缝桥接技术,可以在
Foundation 框架中的 Objective-C 对象与CoreFoundation 框架中的C语言数据结构之间来回转换。 - 在
CoreFoundation 层面创建 collection 时,可以指定许多回调函数,这些函数表示此 collection 应如何处理其元素。然后,可运用无缝桥接技术,将其转换成具备特殊内存管理语义的 Objective-C collection 。
构建缓冲时选用NSCache而非NSDictionary
开发时,经常会遇到一个问题,那就是从英特网下载的图片应如何缓冲。大部分程序员都是将其保存到字典里,这样的话稍后使用时就无需再次下载了。但是NSCache这个类更好,它是Foundation框架专为处理这种任务而设计的。
当系统资源将要耗尽时,它可以自动删减缓冲,这是NSDictionary 所不具备的,此外,NSCache 还会先行删减“最久未使用的”对象。NSCache 不会“拷贝”键,而是会“保留”它。且NSCache 是线程安全的,在开发者自己不编写加锁代码的前提下,多个线程可以同时访问NSCache 。
在编写缓冲代码时,可以借助NSPurgeableData ,二者搭配使用效果更佳。此类是NSMutableData 的子类,而且实现了NSDiscardableContent 协议。如果某个对象所占的内存能够根据需要随时丢弃,那么就可以实现该协议所定义的接口。这就是说,当系统资源紧张时,可以把保存NSPurgeableData 对象的那块内存释放掉。NSDiscardableContent 协议里定义了名为isContentDiscarded 的方法,可用来查询相关内存是否已释放。
在需要访问某个NSPurgeableData 对象时,可以调用beginContentAccess 方法,用完之后可以调用endContentAccess 方法。这些调用可以嵌套,所以就像引用计数机制一样。
如果将NSPurgeableData 对象加入NSCache ,那么当该对象为系统所丢弃时,也会自动从缓冲中移除。通过NSCache 的evictsObjectsWithDiscardedContent 属性,可以开启或者关闭此功能。
创建好NSPurgeableData对象之后,其“purge 引用计数”会多一,所以无须在调用beginContentAccess 了,然而其后必须调用endContentAccess ,将多出来的“1”取消掉。
要点:
- 实现缓存时应选用
NSCache 而非NSDictionary 对象。因为NSCache 可以提供优雅的自动删减功能,而且是“线程安全的”,此外,它与字典不同,并不会拷贝键。 - 可以给
NSCache 对象设置上限,用以限制缓存中的对象总个数及“总成本”,而这些尺度则定义了缓存删减其中对象的时机。但是绝对不要把这些尺度当成可靠的“硬限制”(hard limit),它们仅对NSCache 起指导作用。 - 将
NSPurgeableData 与NSCache 搭配使用,可实现自动清除数据的功能,也就是说,当NSPurgeableData 对象所占内存为系统所丢弃时,该对象自身也会从缓存中移除。 - 如果缓存使用得当,那么应用程序的响应速度就能提高。只有那种“重新计算起来很费事的”数据,才值得放入缓存,比如那些需要从网络获取或从磁盘读取的数据。
精简initialize与load的实现代码
有时候,有一些类必须执行初始化操作,然后才能正常使用。在Objective-C中,绝大多数类都继承至NSObject这个根类,而该类有两个方法,可用来实现这种初始化操作。 首先说一下load方法,对于加入运行期系统的每个类及分类来说,必定会调用此方法,且仅调用一次。如果分类和其所属的类都定义了load方法,则先调用类里的,再调用分类里的。
在load 方法中使用其他类是是不安全的,因为无法确认其他类是否已经加载好了。
load 方法不像普通的方法那样,它并不遵从继承。如果某个类本身没实现load方法,那么不管其各级超类是否实现了此方法,系统都不会调用。
load 尽量精简一些,能不写的代码就不要写,避免引起程序无响应。
除了load 方法,就是initialize 方法。对于每个类来说,该方法会在程序首次用该类之前调用,且只调用一次。它是由运行期系统来调用的,绝不是通过代码来直接调用。它与load不同,它是“惰性调用”,只有在用到相关类的时候才会调用;在运行期系统执行initialize 方法时,是处于正常状态的;initialize 方法与其他消息一样,如果某个类没实现它,而其超类实现了,那么就会运行其超类的实现代码,所以initialize是遵循继承规则的。
load和initialize都是尽量使这两个方法的实现代码精简,不要执行那种耗时或加锁的任务。
要点:
- 在加载阶段,如果类实现了
load 方法,那么系统就会调用它。分类里也可以定义此方法,类的load 方法要比分类中的先调用。与其他方法不同,load 方法不参与覆写机制。 - 首次使用某个类之前,系统会向其发送
initialize 消息。由于此方法遵从普通的覆写规则,所以通常应该在里面判断当前要初始化的是哪个类。 load 与initialize 方法都应该实现得精简一些,这有助于保持应用程序的响应能力,也能减少引人“依赖环”(interdependencycycle )的几率。- 无法在编译期设定的全局常量,可以放在
initialize 方法里初始化。
别忘了NSTimer会保留其目标对象
计时器要和“运行循环(run loop)”相关联,运行循环到时候会触发任务。只有把NSTimer放到运行循环里,才能正常触发任务。
要点 :
NSTimer 对象会保留其目标,直到计时器本身失效为止,调用invalidate方法可令计时器失效,另外,一次性的计时器在触发完任务之后也会失效。- 反复执行任务的计时器(repeatingtimer),很容易引入保留环,如果这种计时器的目标对象又保留了计时器本身,那肯定会导致保留环。这种环状保留关系,可能是直接发生的,也可能是通过对象图里的其他对象间接发生的。
- 可以扩充
NSTimer 的功能,用“块”来打破保留环。不过,除非NSTimer 将来在公共接口里提供此功能,否则必须创建分类,将相关实现代码加入其中。
|