第七章:系统框架

最近发现自己每天早晨醒来后,首先想到的是拿起手机刷刷科技新闻,关注下各个互联网公司的发展动态,反而越来越脱离代码逻辑本身了。希望自己扎扎实实地读完吃透一本书,还是倒着看每一章节记读书笔记吧,给自己一点动力。顺便再闲话一句,CSDN的markdown编辑器貌似很意思,现在就试着拿来写博客 :-)

大家通常会用Objective-C来开发Mac OS X或iOS程序。在这两种情况下都有一套完整的系统框架可供使用,前者名为Cocoa,后者名为Cocoa Touch。本章将总览这些框架,并深入研究其中某些类。

第47条:熟悉系统框架

何为框架?就是一系列代码被打包成一个动态库,并提供头文件来描述它的接口,以供调用。

当你在开发Mac OS X/iOS图形应用程序的时候,会用到的Cocoa/Cocoa Touch框架。还会用到的主要框架是Foundation,它包含NSObject,NSArray,NSDictionary等类。Foundation顾名思义,是所有Objective-C应用的基础框架。CoreFoundation框架也很重要,它是一个C语言的API,包含了与Foundation相似的大多数功能。可以在CoreFoundation的C语言数据结构和Foundation的Objective-C语言对象之间无缝地相互桥接(Toll-Free Bridging)。

用C语言写的API在速度性能上超过Objective-C语言写的API,因为它能越过Objective-C的运行时。但是,在内存管理上,需要更多的注意,因为ARC只对Objective-C对象有效。

尽量使用Objective-C的系统框架。

第48条:少使用for循环,多使用块枚举(Block Enumeration)

Objective-C中有多种枚举集合(NSArray,NSDictionary,NSSet等)的方法:

  • 标准C语言式的for循环

NSArray的枚举如下:

NSArray *anArray = /* ... */;
for (int i = 0; i < anArray.count; i++) {
  id object = anArray[i];
  // Do something with 'object'
}

NSDictionary和NSSet的枚举更复杂一些:

// Dictionary
NSDictionary *aDictionary = /* ... */;
NSArray *keys = [aDictionary allKeys];
for (int i = 0; i < keys.count; i++) {
  id key = keys[i];
  id value = aDictionary[key];
  // Do something with 'key' and 'value'
}

// Set
NSSet *aSet = /* ... */;
NSArray *objects = [aSet allObjects];
for (int i = 0; i < objects.count; i++) {
  id object = objects[i];
  // Do something with 'object'
}

按照定义,NSDictionary和NSSet都是无序的,因此不能直接按照索引来访问其中的每个元素,必须先额外生成一个有序的中间数组对象,才能进行按索引枚举。由于创建这个数组对象来retain集合中的元素是额外工作,并且还需要release集合中的元素和这个数组对象,导致不必要的函数调用,这样就会造成更多的系统开销。所有其他的枚举方法都避免了这种创建额外的中间数组的需要。

  • Objective-C 1.0中的NSEnumerator枚举

NSEnumerator是一个抽象的基类,子类必须实现它定义的两个方法:

 - (NSArray*)allObjects
 - (id)nextObject

关键的方法是nextObject,在每次枚举后,中间的数据结构会更新,以返回下一个枚举对象。所有的值都枚举过后,它返回nil,指示枚举终止。

在Foundation Framework中所有内置的集合类都实现了这种枚举方法。例如,枚举一个数组:

NSArray *anArray = /* ... */;
NSEnumerator *enumerator = [anArray objectEnumerator];
id object;
while ((object = [enumerator nextObject]) != nil) {
  // Do something with 'object'
}

NSDictionary和NSSet的枚举更复杂一些:

// Dictionary
NSDictionary *aDictionary = /* ... */;
NSEnumerator *enumerator = [aDictionary keyEnumerator];
id key;
while ((key = [enumerator nextObject]) != nil) {
id value = aDictionary[key];
  // Do something with 'key' and 'value'
}

// Set
NSSet *aSet = /* ... */;
NSEnumerator *enumerator = [aSet objectEnumerator];
id object;
while ((object = [enumerator nextObject]) != nil) {
  // Do something with 'object'
}

使用NSEnumerator额外的益处是它支持多种枚举类型,例如可以倒序枚举,比for循环方式更易读一些:

NSArray *anArray = /* ... */;
NSEnumerator *enumerator = [anArray reverseObjectEnumerator];
id object;
while ((object = [enumerator nextObject]) != nil) {
  // Do something with 'object'
}
  • Objective-C 2.0中的快速枚举

快速枚举的工作原理是使用NSFastEnumeration protocal,它只定义了一个方法:

- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState*)state
  objects:(id*)stackbuffer
  count:(NSUInteger)length

在具体使用中,增加了in关键字,以NSArray为例:

NSArray *anArray = /* ... */;
for (id object in anArray) {
  // Do something with 'object'
}

NSDictionary和NSSet的枚举也很简单:

// Dictionary
NSDictionary *aDictionary = /* ... */;
for (id key in aDictionary) {
  id value = aDictionary[key];
  // Do something with 'key' and 'value'
}
// Set
NSSet *aSet = /* ... */;
for (id object in aSet) {
  // Do something with 'object'
}

这种方法兼顾语义和效率,但相比for循环不容易获得枚举时的索引。

  • 块枚举

枚举一个数组(其他有序的集合如NSOrderedSet类似):

NSArray *anArray = /* ... */;
[anArray enumerateObjectsUsingBlock:
^(id object, NSUInteger idx, BOOL *stop){
  // Do something with 'object'
  if (shouldStop) {
    *stop = YES;
  }
}];

NSDictionary和NSSet的枚举类似,需要注意的是NSDictionary枚举需要处理key和value;NSDictionary和NSSet是无序的,所以不能获取枚举时的索引:

// Dictionary
NSDictionary *aDictionary = /* ... */;
[aDictionary enumerateKeysAndObjectsUsingBlock:
^(id key, id object, BOOL *stop){
  // Do something with 'key' and 'object'
  if (shouldStop) {
    *stop = YES;
  }
}];

// Set
NSSet *aSet = /* ... */;
[aSet enumerateObjectsUsingBlock:
^(id object, BOOL *stop){
  // Do something with 'object'
  if (shouldStop) {
    *stop = YES;
  }
}];

块枚举中一个有益的地方是可以修改block方法签名来转换对象的类型。例如,在快速枚举中,如果你知道了一个NSDictionary中键值的类型都是string,枚举代码如下:

for (NSString *key in aDictionary) {
NSString *object = (NSString*)aDictionary[key];
  // Do something with 'key' and 'object'
}

在块枚举中,就可以这样写:

NSDictionary *aDictionary = /* ... */;
[aDictionary enumerateKeysAndObjectsUsingBlock:
^(NSString *key, NSString *obj, BOOL *stop){
  // Do something with 'key' and 'obj'
}];

块枚举还允许倒序枚举或并行枚举的方式,只需指定NSEnumerationOptions参数为NSEnumerationReverse或NSEnumerationConcurrent,还可以执行按位OR运算:

- (void)enumerateObjectsWithOptions:
(NSEnumerationOptions)options
usingBlock:(void(^)(id obj, NSUInteger idx, BOOL *stop))block
- (void)enumerateKeysAndObjectsWithOptions:(NSEnumerationOptions)options
usingBlock:
(void(^)(id key, id obj, BOOL *stop))block

有四种方式来枚举集合。for循环是最基本的,其次是NSEnumerator和快速枚举,块枚举是最时髦和先进的方式。

块枚举允许以并行的方式来执行枚举,而不需要任何额外的代码,它利用了GCD的方式。通过其他枚举方式不容易达到这样的效果。

如果知道block签名中的每个对象的准确类型,可以更改block签名中对象的类型。

第49条:对需要自定义其内存管理语义的集合使用无缝桥接(Toll-Free Bridging)

“桥式转换”(bridged cast)包括三种转换方式:__bridge,__bridge_retained,__bridge_transfer。

  • __bridge支持Objective-C对象与C数据结构的相互转换,它是这三者中最常用的修饰符,不改变对象的所有权,表示ARC仍然具有Obective-C对象的所有权,C数据结构也仍然需要调用CFRelease(cfStruct)方法以释放内存。
  • __bridge_retained表示将Objective-C对象转换成C数据结构,ARC交出此Objective-C对象的所有权,需要调用CFRelease(cfStruct)方法以释放内存。
  • 与__bridge_retained相似,反向转换可通过__bridge_transfer来实现,表示将C数据结构转换成Objective-C对象,ARC获得转换后的Objective-C对象的所有权,从而不再需要调用CFRelease(cfStruct)方法来释放内存。

在使用Foundation框架中的NSDictionary对象时,会遇到一个大问题,那就是key的内存管理语义为“copy”,而value会被retain。除非使用强大的无缝桥接技术,否则无法改变其语义。

通过无缝桥接技术,可以在Foundation框架中的Objective-C对象与CoreFoundation框架中的C数据结构之间来回转换。

在CoreFoundation层面创建集合,允许你指定很多回调函数来指示此集合如何处理其中的元素。然后,可以运用无缝桥接技术将它转换成具备特殊内存管理语义的Objective-C集合对象。

第50条:构建缓存时选用NSCache而非NSDictionary

当开发一个Mac OS X或iOS应用程序过程中需要从网上下载一些图片时,常遇到的一个问题是决定如何缓存这些图片。一般首先想到的方法是使用一个字典来存储这些图片到内存中。新手开发者常倾向于选择NSDictionary(更准确地说是使用NSMutableDictionary)。然而,更好的方式是使用NSCache类,它也是Foundation框架中的类,被专门设计于处理这类情况。

NSCache优于NSDictionary的地方是,当系统资源将要耗尽的时候,它可以自动删减缓存。当使用一个普通dictionary的时候,你常需要手动书写代码来监听内存耗尽时的系统通知。然而,NSCache可以自动删减缓存,由于它是Foundation框架中的一部分,所以与开发者相比能够在更深层次上处理这一事件。NSCache会首先删减那些最久没被使用的缓存对象。

其次,NSCache并不copy key,而是retain key。此行为NSDictionary也能做到,但需要编写相对复杂的代码。很多时候,key都是由不支持copy操作的对象来担当的,而NSCache处于这个原因,默认不copy key。另外,NSCache是线程安全的,这意味着你可以通过多个线程同时访问NSCache对象,而不需要再手动编写加锁的代码。

开发者可以操控NSCache删减其内容的时机。有两个用户可控的与系统资源相关的指标:限制缓存中对象总数,所有对象的“总开销”(Overall Cost)。每个对象在被加进缓存时,可以为其指定一个开销值(cost)。当缓存中对象总数或所有对象的总开销超过上限时,cache会删减对象,正如可用的系统内存将要耗尽时一样。但是需要记住的是,开发者并不能确定这时缓存一定会删减某个对象,只是拥有这个可能性。删减对象的顺序由具体实现决定。

向缓存中添加对象时,只有在能够很快计算出“开销值”的情况下,才考虑设定这个指标。毕竟NSCache的本意是为了使应用程序更快地响应用户的操作。比如,如果计算“开销值”,需要访问磁盘才能确定文件大小,或者访问数据库才能确定“开销值”,那就会是个坏主意。然而,如果要加入缓存的是一个NSData对象,就不妨指定“开销值”,将数据大小(NSData.length属性)当做“开销值”,因为NSData对象的数据大小是已知的,所以计算“开销值”的过程只不过是读取一项属性而已。

NSPurgeableData是另一个能够与NSCache有效地结合使用的类,它是实现了NSDiscardableContent协议的NSMutableData子类。这个协议为那些内存可以被销毁的对象定义了接口isContentDisabled,指示内存是否应该被销毁。可以调用NSPurgeableData的beginContentAccess与endContentAccess,指示对象处于访问和结束访问状态,以控制对象是否可以被销毁,正如引用技术一样。

当缓存时考虑使用NSCache来代替NSDictionary。NSCache可以提供优化的自动删减行为,线程安全,也不像NSDictionary一样会copy key。

使用对象总数和总开销值来控制缓存何时删减缓存中的对象,但这仅对NSCache起指导作用。

将NSPurgeableData与NSCache一起使用,可实现自动清除数据的功能。即当NSPurgeableData对象被purged之后,它会自动从NSCache中移除。

NSCache当使用得当时,会提高应用程序的响应速度。只缓存那些需要耗费很多资源来重新得到的数据,例如从网络获取的或从磁盘中读取的数据。

第51条:精简initialize和load方法的实现代码

有时候类需要先执行某些初始化操作,才能正常使用。在Objective-C中,绝大多数类都继承自NSObject,它有两个公有方法,可用来实现初始化操作:

+ (void)load;
+(void)initialize;

load方法在运行时仅且只被调用一次,发生在包含load方法的class或category的库被加载的时候,对于iOS代码来说一般是程序启动时;Mac OS X程序相对自由些,因为它可以使用动态加载的特性。

在执行子类load方法之前,必须先执行所有父类的load方法;而且如果还依赖了其他程序库,必须先执行依赖库里所有类的load方法。根据某个给定的程序库,无法判断其中各个类的加载顺序。

在加载阶段,如果类实现了load方法,那么系统就会调用它。category里也可以实现load方法,并且class里的load方法要比category里的先调用。与其他方法不同,load方法不参与类的覆盖机制。

首次使用某类前,系统会向其发送initialize消息。由于此方法遵从普通的覆盖机制,故通常在此方法里面判断当前要初始化的是哪个类(self==[objClass Class])。

load与initialize方法都应尽量精简,有助于保持应用程序的响应能力,也能减少引入”依赖环“(interdependency cycles)的几率。

无法在编译期间设定的全局常量(例如除数字类型和NSString类型的其他类对象),可以放在initialize方法中初始化。

第52条:谨记NSTimer会retain它的目标对象

如何写一个安全的NSTimer对象?解决办法就是:实现NSTimer的分类(category),并将self写成weak。block中是否适合使用weak self,就看是否有循环引用。

在MRC(manual reference count)中, __block 可以让一个变量在 block 中进行修改而不会被这个 block retain。在ARC(automatic reference count)中, __block则会引起retain,而ARC中应该使用__weak或__unsafe_unretained弱引用。weak只能在iOS5以后使用。1

NSTimer对象会retain它的target对象,直到它被激活后或通过显示调用invalidate方法来使该timer对象失效。

重复使用NSTimer,并且该timer对象的target对象也retain了该timer对象,这样就容易导致“环式retain”(retain cycle)。

将NSTimer类扩展(category),配合使用block,可以打破这种“环式retain”现象。


  1. http://tanqisen.github.io/blog/2013/04/19/gcd-block-cycle-retain/ “正确使用Block避免Cycle Retain和Crash” 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
《软考系统集成项目管理第二版》是一本介绍系统集成项目管理的教材。该书主要包括以下内容: 第一章:系统集成项目管理基础 介绍了软件工程的定义、软件开发的过程、项目管理的概念、项目管理的过程和项目管理的生命周期等内容。 第二章:项目启动阶段 介绍了项目启动阶段的目标、项目启动阶段的活动、项目启动阶段的文档和项目启动阶段的输出等内容。 第三章:项目规划阶段 介绍了项目规划阶段的目标、项目规划阶段的活动、项目规划阶段的文档和项目规划阶段的输出等内容。 第四章:项目执行阶段 介绍了项目执行阶段的目标、项目执行阶段的活动、项目执行阶段的文档和项目执行阶段的输出等内容。 第五章:项目监控与控制阶段 介绍了项目监控与控制阶段的目标、项目监控与控制阶段的活动、项目监控与控制阶段的文档和项目监控与控制阶段的输出等内容。 第六章:项目收尾阶段 介绍了项目收尾阶段的目标、项目收尾阶段的活动、项目收尾阶段的文档和项目收尾阶段的输出等内容。 第七章:项目管理工具与技术 介绍了项目管理工具与技术的种类和用途,包括网络图、甘特图、资源分配图、风险分析、成本估算、质量管理和变更管理等内容。 第八章:软件项目管理实践 介绍了软件项目管理实践的原则和方法,包括项目管理框架、项目管理过程、项目管理工具和技术以及项目管理的成功因素等内容。 该书适用于软件工程师、项目经理、项目管理人员、软件开发团队、软件开发公司等人员进行系统集成项目管理的学习和实践。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值