Effective Objective-c 2.0:编写高质量iOS与OS X代码的52个有效的方法,这本书非常好,看了很多遍,今天把自己看的,感觉很常用的知识记录下来,就当做是一个简单的读书笔记好了。

多用字面量语法,少用与之等价的方法

要点

  • 应该使用字面量语法来创建字符串、数值、数组、字典。与创建此类对象的常规方法相比,这么做更加简明扼要。
  • 应该通过取下标操作来访问数组下标或字典中的键所对应的元素。
  • 用字面语法创建数据和字典时,若值中有nil,则会抛出异常。因此务必确保值里不含nil。
  • 使用字面量语法创建出来的字符串,数组,字典对象都是不可变得,若想要可变版本的对象,则需要复制一份。
NSMutableArray *mutable *mutable = [@[@1,@2,@3,@4,@5] mutableCopy];

使用方式

NSNumber

有时需要把整数、浮点数、布尔值封装入objective-c 对象中,这种情况下可以用NSNumber类,该类可处理多种类型的数值。若不用字面量,那么就需要按下面的方式进行创建:

NSNumber *someNumber = [NSNumber numberWithInt:1];
然而使用字面量能令代码更为整洁:
NSNumber *someNumber =@1;
能够以NSNumber实例表示的所有的数据类型都可以使用该语法。例如:
NSNumber *intNumber =@1;
NSNumber *floatNumber =@2.5f;
NSNumber *doubleNumber =@3.1415926;
NSNumber *boolNumber =@YES;
NSNumber *charNumber =@'a';
字面量语法也使用于下述表达式
int x = 5;
float y = 6.32f;
NSNumber *expresssionNumber = @{x * y};

NSArray

数组是常用的数据结构,常规的创建数据的方法如下:

  NSArray *animals = [NSArray arrayWithObjects:@"cat",@"dog",@"mouse",nil];

若使用字面量,则是:

	NSArray *animals = @[@"cat",@"dog",@"mouse"];
	   

上面这种做法不仅简单,而且还有利于操作数组,数据的常见操作就是取某个下标所对应的对象。不用字面量的做法为:

   NSString *dog = [animals objectAtIndex:1];

若使用字面量的做法为:

   NSString *dog = [animals objectAtIndex:1];

大家想想:如果object1与object3都指向了有效的Objective-c对象,而object2是nil,那么会出现什么情况呢?按字面量语法创建的arrayB时会抛出异常。arrayA虽然能创建出来,大师其中却只含有object1一个对象。原因在于arrayWithObjects: 方法会依次处理各个参数,知道发现nil为止,由于object2是nil,所以该方法会提前结束。

  • 这个微妙的差别表明,使用字面量语法更安全。抛出异常令应用程序终止结束,这比创建好数组之后才发现元素个数少了要好。*

NSDictionary

字典的一般创建方法为:

 NSDictionary *personData =[ NSDictionary  dictionaryWithObjectsAndKeys:@"Matt",@"firstName",@"Galloway",@"lastName",[NSNumber numberWithInt:28],@"age", nil];

而使用字面量的写法为:

 NSDictionary *personData = @{@"firstName" : @"Matt",@"lastName" : @"Galloway", @"age" : @28};

通过取下标操作,可以访问数组中的某个小标或是字典中否个键所对应的元素。如果数组与字典是可变的,那么也可以通过下标修改其中的元素值。

多用类型常量,少用#define预处理指令

要点

  1. 不要用预处理指令定义常量。这样定义出来的常量不含任何类型的信息,编译器只是会在编译前据此执行查找与替代操作,即使有人重新定义了常量值,编译器也不会产生警告信息,这将导致应用程序中的常量值不一致。
  2. 在显示文件中使用 static const 来定义“只在编译单元内可见的常量”。由于此类常量不在全局符号表中,所以无需为其名称加前缀。
  3. 在头文件中使用extern来声明全局常量,并在相关实现文件中定义此值,这种常量要出现在全局符号中,所以此名称赢加以区隔,通常用与之相关的类名作为前缀。

用枚举表示状态、选项、状态码

要点

  1. 应该用枚举来表示状态机的状态、传递给方法的选项以及状态码等值,给这些值起个易懂的名字。
  2. 如果把传递给某个方法的选项表示为枚举类型,而多个选项又可同时使用,那么就将各选项定义为2的幂,以便通过按位或操作将其组合起来。
  3. 用NS_ENUM 与NS_OPTIONS 宏来定义枚举类型,并指明起底层数据类型,这样做可以确保枚举是用开发者所选底层数据类型实现出来的,而不是采用编译器所选的类型。
  4. 在处理枚举类型的switch语句中不要实现default分支,这样的话,加入新枚举之后,编译器就会提示开发者:switch语句并未处理所有的枚举。

用前缀避免命名空间冲突

容易产生命名冲突的情况

  • 类名
  • 为既有类新增的“分类”
  • 类的实现文件中所用的纯C函数及全局变量

解决办法

  1. 选择与你公司、应用程序或二者皆有关联之名作为类名的前缀(最少三个字符),并在所有代码中均使用这一前缀。
  2. 若自己所开发的程序库中用到了第三方库,则应为其中的名称加上前缀。

提供“全能初始化方法”

我们把可为对象提供碧霄信息以便其能完成公司的初始化方法叫做“全能初始化方法”。

要点

  • 在类中提供一个全能初始化方法,并于文档中指明。其他初始化方法均应调用此方法。
  • 若全能初始化方法与超类的不同,则需要复写超类中对应的方法。
  • 如果超类的初始化方法不适用于子类,那么应该覆写这个超类的方法,并在其中抛出异常。

实现description方法

要点

  • 实现description方法返回一个有意义的字符串,用以描述该实例。最常用的技巧就是用字典封装成key-value方式。
  • 若想在调试时打印出更详尽的对象描述信息,则应实现debugDescription。


尽量使用不可变对象

要点

  • 尽量创建不可变对象
  • 若某属性仅可于对象内部修改,则在“class-continuation分类”中将其由readonly属性扩展为readwrite属性
  • 不要把可变的collection作为属性公开,而应提供相关的方法,以此修改对象中的可变collection。


为私有方法名加前缀

要点

  • 给私有方法加上一个前缀,这样可以很容易地将其公共方法区分开
  • 不要单用一个下划线做私有方法的前缀,因为这种做法是预留给苹果公司用的。


理解NSCopying协议

要点

  • 若想令自己所写的对象具有拷贝功能,则需实现NSCopying协议
  • 如果自定义的对象分为可变版本和不可变版本,那么就要同时实现NSCopying与NSMutableCopying协议
  • 复制对象时需确定采用浅拷贝还是深拷贝,一般情况下应该尽量执行浅拷贝。
  • 如果你所写的对象需要深拷贝,那么不可考虑新增一个专门执行深拷贝的方法。

将类的实现代码分散到便于管理的数个分类之中

要点

  • 使用分类机制把类的实现代码划分成易于管理的小块。
  • 将应该视为“私有”的方法归入名叫Private的分类中,以隐藏实现细节。

用“僵尸对象”调试内存管理问题

大家都知道,向业已回收的对象发送消息是不安全的,那么做有时可以,有时不行。具体可行与否,完全取决于对象所占内存有没有为其他内容所覆写。而这块内存有没有移做他用,又无法确定,因此,应用程序只是偶尔崩溃。在没有崩溃的情况下,那块内存可能只复用了其中一部分,所以对象中的某些二进制数据依然有效。还有一种可能,就会说那块内存恰好为另外一个有效且存活的对象所占据。在这种情况下,运行期系统会把消息发送到新对象那里,而此对象也许能应答,也许不能。如果能,那程序就不崩溃,可你会觉得奇怪:为什么收到消息的对象不是预想的那个呢?若新对象无法响应选择子,则程序依然会崩溃。 所幸Cocoa提供了“僵尸进程”这个非常方便的功能,启动这项功能之后,运行期系统会把所有已经回收的实例转化成特殊的“僵尸对象”,而不会真正的回收它们。这种对象所在的核心内存无法复用,因此不可能遭到覆写。僵尸对象收到消息后,会抛出异常,其中准确说明了发送过来的消息,并描述了回收之前的那个对象。僵尸对象是调试内存管理问题的最佳方式

理解iOS中“块”这一概念

定义块的时候,其所占得内存区域是分配在栈中的。这就是说,块只在定义它的那个范围内有效。

要点

  • 在创建对象时,可以使用内联的handler块将相关业务逻辑一并声明
  • 在有多个实例需要监控时,如果采用委托模式,那么经常需要根据传入的对象来切换,而若改用handler块来实现,则可直接姜块与之相关的对象放在一起。
  • 在设计API时如果用到了handler快,那么可以增加一个参数,使调用者可通过此参数来决定应该把块安排在哪个队列上执行。


多用派发队列,少用同步锁

要点

  • 派发队列可用来表述同步意义,这种做法要比使用@synchronized块或NSLock对象更简单
  • 将同步与异步派发结合起来,可以实现与普通加锁机制一样的同步行为,而这么做却不会阻塞执行异步派发的线程
  • 使用同步队列及栅栏块,可以令同步行为更加高效

多用块枚举,少用for循环

遍历collection 有四种方法,分别是for循环,nsenumberator遍历法,快速遍历法,块枚举法

for循环:最普遍使用的一种方法


NSEnumerator 遍历: NSEnumerator是一个抽象类,其中只定义了两个方法,供其具体的子类来实现

	- NSArray *allObjects
	- idnextObject
	

其中关键的方法是nextObject,他返回枚举里的下一个对象,每次调用改方法时,其内部数据结构都会跟新,使得下次调用方法时能返回下个对象。等到枚举中的全部对象都已返回之后,在调用就将返回nil,这表达到枚举末端了。

NSArray *anArray =
NSEnumerator *enumerator = [anArray objectEnumerator]
id object;
while ((object = [enumerator nextObject]) != nil){

}

这种写法的功能与标准的for循环相似,但是代码却多了一些,其真正的优势在于:不论遍历那种collection,都可以采用这套相似的语法。比如说:遍历字典及set时也可以按照这种写法来做:

NSDictionary *aDictionary = 
NSEnumerator *enumerator = [aDictionary keyEnumerator];
id key ;
while ((key = [enumerator nextObject]) !=nil){
	id value = aDictionary[key];
}


快速遍历:实现方法为:

NSArray *anArray = 
for id object in anArray{

}

NSDictionary *aDictionary = 
for (id key in aDictionary ){
	id value = aDictionary[key];
}


基于块的遍历方式

实现方式

NSArray *anArray = 

[anArray enumerateObjectsUsingBlock:^(id object ,NSUinteger idx, BOOL *stop){

}];

NSDictionary *aDictionary = 
[aDictionary enumerateKeyAndObjectsUsingBlock:^(id key ,id object, BOOL *stop){

}];

别忘了NSTimer会保留其目标对象

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!


hetaodie

Mobile development

简单,深入的研究移动客户端开发技术"