《Effective Objective-C 2.0》读书分享

读书总结《effective objectivec 2.0》编写高质量IOS与OS X代码的52个有效方法

下面简单分享下在平时开发中会常接触到的15条建议。

第二条:在类的头文件中尽量少引用其他头文件

1.除非必要情况,否则不要引入头文件。一般情况,应在某个类的头文件中使用向前声明来提及别的类(@class),并在实现文件中引入那些类的头文件。这样做可以降低类之间的耦合,并减少编译时间;

2.使用向前声明也解决了两个类互相引用的问题,使用#import时会出现无法被正确编译的问题;

3.有时无法使用向前声明,比如要声明某个类遵循一项协议。这种情况下,尽量把“该类遵循某协议”的这条声明移至“class-continuation分类中”。如果不行的话,就把协议单独放在一个头文件中,然后将其引入;

第五条:用枚举表示状态、选项、状态码

1.应该用枚举来表示状态机的状态、传递给方法的选项以及状态码等值,给这些值起个易懂的名字;

2.如果把传递给某个方法的选项表示为枚举类型,而多个选项又可同时使用,那么就将个选项值定义为2的幂,以便通过按位或操作将其组合起来(参见UIView的UIViewAutoresizing枚举定义);

3.用NS_ENUM和NS_OPTIONS宏来定义枚举类型,并指明其底层数据类型。这样做可以确保枚举是用开发者所选的底层数据类型出来的,而不采用编译器所选的类型;

4.在处理枚举类型的switch语句中不要实现default分支。这样的话,加入新枚举之后,编译器就会提示:switch语句并未处理所有枚举。使用default就会漏掉某种状态;

第七条:在对象内部尽量直接访问实例变量

1.对象内部读取数据时,应该直接通过实例变量来读,而写入数据时,则应通过属性来写;

2.在初始化方法及dealloc方法中,总是应该直接通过实例变量来读写数据;

3.有时会使用惰性初始化方法配置数据,这种情况下,需要通过属性来读取数据;

4.直接访问实例变量和属性的区别:

直接访问实例变量,由于不经过OBJ-C的方法派发,所以速度会比较快,编译器会直接访问保存对象实例的内存;

直接访问实例变量,不会调用设置方法,绕过了内存管理语义;

直接访问实例变量,不会触发KVO;

通过属性访问,有助于排查问题;

第八条:理解“对象等同性”这一概念

1.使用set集合的注意事项,等同性判定的执行深度

创建一个可变数组arr1=[@[@1,@2] mutbleCopy];将其放入NSMutableSet中,再创建一个可变数组arr2=[@[@1,@2] mutbleCopy];将其放入NSMutableSet中。这个时候我们打印set,set={((1,2))},因为arr1和arr2数组对象相 同,所以set不会改变;

再次创建一个可变数组arr3=[@[@1] mutbleCopy];将其放入NSMutableSet中,set={((1),(1,2))},这个时候我们修改[arr3 addObject:@2];发现set={((1,2),(1,2))},更神奇的地方是,如果我们set2=[set copy],set2={((1,2))}。这个地方一定要注意!

如果把某对象放入set之后又修改其内容,后面的行为将很难预料。

第九条:以“类簇模式”隐藏实现细节

1.类簇模式可以把实现细节隐藏在一套简单的公告接口后面;

类簇是Foundation框架中广泛使用的设计模式。类簇将一些私有的、具体的子类组合在一个公共的、抽象的超类下面,以这种方法来组织类可以简化一个面向对象框架的公开架构,而又不减少功能的丰富性。比如:

UIButton的buttonWithType:(UiButton)type;

另外还有很多系统框架都使用类簇;

这种模式和工厂模式有很多相似之处;

2.从类簇的公共抽象基类中继承子类时要当心,若有开发文档,则应首先阅读;

因为Obj-C没法标明某个基类是抽象的,所以需要开发者自己在文档中写明类的用法

第十四条:理解“类对象”的用意

1.如果对象类型无法在编译期确定,那么就应该使用类型信息查询方法来探知;

isMemberOfClass:能够判断出对象是否为某个特定类的实例;

isKindOfClass:能够判断出对象是否为某类或其派生类的实例;

2.尽量使用类型信息查询方法来确定对象类型,而不要直接比较对象类型,因为某些对象可能实现了消息转发功能;

第十五条:用前缀避免命名空间的冲突

1.选择与你的公司、app或者二者皆有关联的名称作为类前缀,并在所有代码中均使用这一前缀;

2.若自己所开发的的程序库中用到了第三方库,则应为其中的名称加上前缀;

第十八条:尽量使用不可变对象

1.尽量创建不可变的对象;

尽量把对外公布出来的属性设为只读(readonly),而且只在的确有必要时才将属性公布出来。

2.如果对象内部要进行修改,可以在对象内部设置成(readwrite).

不过即使设置成readonly,仍然能通过KVC的方式设置这些属性,通过setValue:forKey来修改。不过这样做苹果官方是不建议的

3.不要把可变的集合作为属性公开,而应提供相关方法,以此修改对象中的集合;

第二十二条:理解NSCopying协议

1.若想令自己所写的对象具有copy功能,则需要实现NSCopying协议;

要实现copyWithZone:方法,举个例子,定义一个类:EOCPerson

@interfaceEOCPerson :NSObject

@property(nonatomic,strong)NSString*firstName;

@property(nonatomic,strong)NSString*lastName;

@property(nonatomic,strong)NSString*friends;

-(id)initWithFirstName:(NSString*)firstName andLastName:(NSString*)lastName;

@end

实现协议方法:

-(id)copyWithZone:(NSZone*)zone

{

EOCPerson*copy = [[[selfclass]allocWithZone:zone]initWithFirstName:_firstName

andLastName:_lastName];

returncopy;

}

2.如果自定义的对象分为可变版本与不可变版本,那么就要同时实现NSCopying与NSMutableCopying协议;

-(id)mutableCopyWithZone:(NSZone *)zone;

对于不可变的NSArray和可变的NSMutableArray来说:

[NSMutableArray copy]     =>NSArray

[NSArray mutableCopy]    =>NSMutableArray

copy所返回的拷贝对象与当前对象的类型一致,而mutableCopy和immutableCopy反别返回可变和不可变的拷贝。

3.复制对象时需要决定采用浅拷贝还是深拷贝,一般情况下应该尽量执行浅拷贝;

Foundation框架中的所有集合类,默认情况下都执行浅拷贝;

在NSArray 类中,提供了一个初始化方法用以执行深拷贝:

NSArray *arr=[[NSArray alloc]initWithArray:_arrComment copyItems:YES];     当copyItems:YES时,会向数组中的每个元素发送copy消息,用拷贝好的元素创建新的数组。同样NSDictionary、NSSet等类也 存在该方法;

如果集合里要包含其他类的对象,需要在该类中实现相应的copyWithZone:方法

4.如果你所写的对象需要深拷贝,那么可以考虑新增一个专门执行深拷贝的方法;

第二十四条:将类的实现代码分散到便于管理的数个分类之中

1.使用分类机制把类的实现代码划分成容易管理的小块;

如果一个类较为复杂的话,可以按照一定的规则把他划分为多个小类,比如:

@interfaceEOCPerson :NSObject

@property(nonatomic,strong)NSString*firstName;

@property(nonatomic,strong)NSString*lastName;

@property(nonatomic,strong)NSString*friends;

-(void)addFirend:(EOCPerson*)person;

-(void)removeFirend:(EOCPerson*)person;

-(void)performDaysWork;

-(void)takeVacationFromwork;

-(void)gotoTheCinema;

-(void)gotoSportGame;

@end

改写为:

@interfaceEOCPerson :NSObject

@property(nonatomic,strong)NSString*firstName;

@property(nonatomic,strong)NSString*lastName;

@property(nonatomic,strong)NSString*friends;

@end

@interfaceEOCPerson(Friendship)

-(void)addFirend:(EOCPerson*)person;

-(void)removeFirend:(EOCPerson*)person;

@end

@interfaceEOCPerson(Work)

-(void)performDaysWork;

-(void)takeVacationFromwork;

@end

@interfaceEOCPerson(Play)

-(void)gotoTheCinema;

-(void)gotoSportGame;

@end

2.将应该视为“私有”的方法归入名为Private的分类中,以隐藏实现细节。

第三十七条:理解“块”这一概念

1.块是C、C++、Obj-C中的词法闭包;

借由此机制,开发者可将代码像对象一样传递,令其在不同环境下运行。

2.块可以接受参数、也可以返回值;

其语法结构:return_type (^block_name) (parameters)     举例:

int (^addBlock) (int a,int b)=^(int a,int b){

return a+b;

}

int add=addBlock(1,2);

3.块的强大之处是:在声明它的范围里,所有变量都可以为其所使用。不过默认情况下这些变量是不能修改的,除非在声明变量时加上__block;

如果块捕获的变量是对象类型,那么就会自动保留它,也包括:self,所以要定义一个weak形式的self来解决循环引用的问题;

4.块可以分配在栈或堆上,也可以是全局的。分配在栈上的块可以拷贝到堆里,这样的话,就和标准的Obj-C对象一样,具备引用计数了;

void(^block)();

if (self) {

block=^{

DLog(@"[block]-a");

};

}

else{

block=^{

DLog(@"[block]-b");

};

}

block();

由于定义块的时候,其所占的内存区域是分配在栈中得,也就是说,块只在定义它的那个范围内有效。上面的例子中得块只能保证在if或else语句范围内有效,虽然可以编译,但是运行时有时会正确有时会错误,甚至导致crush。

为解决此问题,可以给块对象发送copy消息,这样的话,块从栈复制到堆了。并且可以在定义的范围之外使用。

void(^block)();

if (self) {

block=[^{

DLog(@"[block]-a");

} copy];

}

else{

block=[^{

DLog(@"[block]-b");

} copy];

}

block();

第三十八条:为常用的块类型创建typedef

1.以typedef重新定义块类型,可令块变量用起来更简单;

typedef int (^FBlock) (int var,BOOL flag);

FBlock block=^(int var, BOOL flag){};

注意:定义方法参数所用的块类型语法,和定义变量时不同:

typedef int (^FRequestHandler) (int var,BOOL flag);

-(void)requestHttp:(FRequestHandler)handler;

2.定义新类型时应遵从现有的命名习惯,务使其名称与别的类型相冲突;

3.不妨为同一个块签名多个类型别名。如果要重构的代码使用了块类型的某个别名,那么只需修改相应typedef中的签名即可,无须改动其他typedef;

如果好几个类都要执行相似但各有区别的异步任务,而这几个类又不能放入同一个继承体系,那么,每个类就应该有自己的处理方法,并在每个类中定义各自的别名;反之若这些类能纳入同一个继承体系中,则应该将类型定义语句放在超类,供各个子类使用。

第三十九条:用handler块降低代码分散程度

1.在创建对象时,可以使用内联的handler块将相关业务逻辑一并声明;

2.在有多个实例需要监控时,如果采用委托模式,那么经常需要根据传入的对象来切换,而若改用handler块来实现,则可直接将块与相关对象放在一起;

委托模式和handler块经常可以实现同样的功能,但是在有些情况handler会比委托模式更灵活:如果一个类中有多个方法实现同一个代理(处理不 同业务数据),那么代理方法中需要根据不同业务数据做判断,使得delegate回调方法变得很长。而使用块的话,针对每一个业务数据处理,执行各自的业 务逻辑。

3.在基于块的API都是用块来处理错误,分别处理成功和失败情况(目前我们工程大部分代码都是这么做的),这种写法令代码更易读懂。

不过也有些情况会把两者放在一起,虽说放在一起会令代码变长并且复杂。但是有些特殊情况使用此种方法会更好,比如:成功响应的过程中,发现错误。数据不完整,这种情况可能也是错误的一种,要按照失败去处理。分开处理的话就没法共享同一份错误处理代码。

总的来说作者建议用同一个块处理成功与失败,苹果公司的API也是这么设计的。

4.设计API时如果用到了handler块,那么可以增加一个参数,使调用者可通过此参数来决定应该把块安排在哪个队列上执行;

比如NSNotification就属于这种API:

[[NSNotificationCenter defaultCenter] addObserverForName:@"notification"

object:self

queue:[NSOperationQueue currentQueue]

usingBlock:^(NSNotification *note) {

}];

第四十二条:多用GCD,少用performSelector系列方法

1.performSelector系列方法在内存管理方面容易有疏失。它无法确定将要执行的选取器具体是什么,因而ARC编译器也就无法插入适当的内存管理方法;

如果根据判断得到选取器,我们只有在运行时才知道真正的选取器,如下:

SEL selector;

if (expression) {

selector=@selector(@"new");

}

else if (expression){

selector=@selector(@"copy");

}

else{

selector=@selector(@"action");

}

id ret=[self performSelector:selector];

如果用非ARC前两个方法需要由变量ret释放,第三个方法不需要释放;如果用ARC系统很难判断添加内存管理方法;

2.performSelector系列方法所能处理选取器过于局限,返回类型及发送给方法的参数个数都有限制;

3.如果想把任务放在另一个线程执行,那么最好通过GCD来去替代performSelector系列方法;

比如延迟调用可以使用:

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.3 * NSEC_PER_SEC)),    dispatch_get_main_queue(), ^{

});

其他替代方法完全可以使用dispatch_async等实现;

第四十八条:多用块枚举,少用for循环

1.遍历collection有四种方式。最基本的就是for循环,其次是NSEnumerator遍历法及快速遍历法,最新、最先进的方式则是“块枚举法”;

NSArray *arr=@[@"a",@"b"];

[arr enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {}];

NSDictionary *dic=@{@"a": @"1"};

[dic enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {}];

obj就是当前遍历的对象;idx当前索引;stop类似于循环中使用break,用于终止遍历操作。

2.快枚举法本身就能通过GCD来并发执行遍历操作,无须另行编写代码。而采用其他遍历方式则无法轻易实现这一点;

3.若提前知道待遍历的集合含有何种对象,则应修改块签名,指出对象的具体类型;

NSArray *arr=@[@"a",@"b"];

[arr enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {}];

如果已知数组里的对象类型,可以将id obj改为NSString *string,否则还需要自己做类型装换。

以上就是本次分享的15条开发建议。全书总共52条,需要重复阅读来强化记忆和加强理解,同时结合实际应用来理解推荐的这些方法的好处。有感兴趣的欢迎借阅~~

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

推荐阅读更多精彩内容