- Effective Objective-C 2.0笔记(二)
- Effective Objective-C 2.0笔记(一)
- Effective Objective-C 2.0笔记(三)
- Effective Objective-C 2.0笔记(五)
- Effective Objective-C 2.0 读后总结
- 《Effective Objective-C 2.0 》 阅读笔
- 编写高质量iOS与OS X代码的52个有效方法
- Effective Objective-C 2.0 Tips 总
- Effective Objective-C 2.0 无废话精简篇
- Effective Objective-C 2.0 脑图- [O
最近有时间,所以计划读几本书。这篇文章是对Effective Objective-C 2.0读后的一些总结。这本书总共介绍了52条建议,一下是我对每一条读后的总结。
- 了解Objective-C起源
OC是C的超集,在C的基础上增加了面向对象特性,OC是一门动态语言,其真正的类型是在运行期间决定的。runtime 是OC具有了面向对象的特性。
2.在类的头文件中尽量少引用其他头文件,因为过多引入头文件会增加编译时间。在.h中最好使用向前声明"@class"方式。这样一方面可以降低类之间的耦合,另一方面可以防止2个类的互相引用。
3.尽量使用字面量语法来创建字符串、数值、数组、字典。这种方式看起来更加简明扼要,而且创建数组、字典时可以保证不会出现nil。使用字面量创建的字符串,数组,字典都是不可变得,如果需要可变字符串,则需要mutableCopy一份。
4.多用类型常量(static const),少用#define预处理指令。不要用预处理指令定义常量。这样定义出来的常量不含类型信息。而且在别处可以随意修改。编译器只是会在编译前据此执行查找与替换操作。即使有人重新定义了常量值,编译器也不会产生警告信息,这将导致应用程序中的常量值不一样。另外使用类型常量时也要注意命名,一般需要加类名作为前缀,防止重复。
5.使用枚举来表示状态机的状态、传递给方法的选项以及状态码等值。在处理枚举类型的switch语句中不要实现default分支。这样的话,加入新枚举之后,编译器就会指示开发者:switch语句并处理所有枚举。
6.iOS开发中几乎所有属性都声明为nonatomic,这是因为iOS中使用同步锁开销很大,这会带来性能问题,但是这不能保证“线程安全”,所以在特定的时候要学会修改为atomic,但是大部分时候还是用nonatomic。
7.在对象内部尽量直接访问实例变量。最直白的区别是在对象内使用:self.name是属性访问。_name是直接访问。推荐设置属性的时候使用self.name,获取属性的时候使用_name。
两种写法的区别
由于不经过OC的方法派发步骤,所以直接访问实例变量的速度比较快。
直接访问实例变量时,不会调用其“设置”方法,这就绕过了为相关属性所定义的“内存管理语义”。比方说,如果在ARC下直接访问一个声明为copy的属性,那么并不会拷贝该属性,只会保留新值并释放旧值。
如果直接访问实例变量,那么不会触发“键值观测KVO”通知。这样做是否会产生问题,还取决于具体的对象行为。
通过属性来访问有助于排查与之相关的错误,因为可以给“获取方法”、“设置方法”中新增“断点”,监控该属性的调用者及访问时机。
**在写入实例变量时,通过其“设置方法”来做,而在读取实例变量时,则直接访问之。
8.判断对象是否相同时,除了要重写isEqual方法之外还要重写hash方法,保证hash密码也要相同。
isEqula判断步骤为:
1.判断两个指针是否相等,如果相等则二者相同。
2.判断是否为同一类,不同则不为同一对象。
3.判断逐个属性是否相同,都相同则为同一对象。
hash方法内部实现为:
一般返回每个属性的hash值之和或相与之后的值。
9.类簇模式(工厂模式)可以把实现细节隐藏在公共接口后面。eg:[UIButton buttonWithType:UIButtonType],根据不同的类型返回不同的对对象。判断类对象时,“isMemberOfClass”能够判断出对象是否为某个特定类的实例。而“isKindOfClass”能够判断出对象是否为某类或其派生类的实例。这里尽量使用“isKindOfClass”,因为某些类可能实现了消息转发功能。
10.在既有类中使用关联对象使用自定义数据。例如在一个分类中加属性就要用到关联方法。
使用以下两个方法
objc_setAssociatedObject(<#id _Nonnull object#>, <#const void * _Nonnull key#>, <#id _Nullable value#>, <#objc_AssociationPolicy policy#>)
objc_getAssociatedObject(<#id _Nonnull object#>, <#const void * _Nonnull key#>)
11.objc_msgSend().
OC是通过消息转发来调用消息的。我们写的方法调用,在运行时都会转化为objc_msgSend()方法。
eg:
[test doSomeThing];
在运行时会被转化为
objc_msgSend(test,@selector(doSomeThing));
类对象中有一个isa指针,指向对象所属的类,类中存放着方法列表,这样就能找到对应的方法,如果这个类中没有,向父类中查找,若果找到则对这个方法进行缓存,下次先调用方法缓存列表,若找不到,则执行消息转发。
12.消息转发。如果对象调用了一个没有实现的方法,就会发生消息转发。一般我们会在一步动态解析的时候做处理。
+(BOOL) resolveInstanceMethod:(SEL)selector
流程如下图:

书中有一个利用消息传递来做一个存储类的例子eg:
.h
#import <Foundation/Foundation.h>
@interface EOCDictionary : NSObject
@property (nonatomic,strong)NSString *string;
@property (nonatomic,strong)NSNumber *number;
@property (nonatomic,strong)NSDate *date;
@property (nonatomic,strong)id opaqueObject;
@end
.m
#import "EOCDictionary.h"
#import <objc/runtime.h>
@interface EOCDictionary()
@property (nonatomic,strong)NSMutableDictionary *backingStore;
@end
@implementation EOCDictionary
@dynamic string,number,date,opaqueObject;
- (id)init
{
if ((self = [super init]))
{
_backingStore = [NSMutableDictionary new];
}
return self;
}
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
NSString *selectorString = NSStringFromSelector(sel);
if ([selectorString hasPrefix:@"set"])
{
class_addMethod(self, sel, (IMP)autoDictionarySetter, "v@:@");
}
else
{
class_addMethod(self, sel, (IMP)autoDictionaryGetter, "@@:");
}
return YES;
}
id autoDictionaryGetter(id self,SEL _cmd)
{
EOCDictionary *typedSelf = (EOCDictionary *)self;
NSMutableDictionary *backingStore = typedSelf.backingStore;
//key即为属性名
NSString *key = NSStringFromSelector(_cmd);
return [backingStore objectForKey:key];
}
id autoDictionarySetter(id self,SEL _cmd,id value)
{
EOCDictionary *typedSelf = (EOCDictionary *)self;
NSMutableDictionary *backingStore = typedSelf.backingStore;
NSString *selectorString = NSStringFromSelector(_cmd);
NSMutableString *key = [selectorString mutableCopy];
//去掉:
[key deleteCharactersInRange:NSMakeRange(key.length-1, 1)];
//去掉set
[key deleteCharactersInRange:NSMakeRange(0, 3)];
//变小写
NSString *lowercaseFirstChar = [[key substringToIndex:1] lowercaseString];
[key replaceCharactersInRange:NSMakeRange(0, 1) withString:lowercaseFirstChar];
if (value)
{
[backingStore setObject:value forKey:key];
}
else
{
[backingStore removeObjectForKey:key];
}
return nil;
}
@end
这样就可以使用了,若果需要增加,定义新的属性,并且声明为@dynamic即可。
EOCDictionary *tesg = [[EOCDictionary alloc] init];
tesg.string = @"ni hao";
NSLog(@"%@",tesg.string);
13.方法调配,也就是方法交换,在运行期间,可以通过交换方法的IMP,来达到交换方法实现的目的,达到调用A但实际上调动的是B,一般是在调试时会用到,实际开发中慎用。
14.类。
类以及实例对象的本质都是结构体。在runtime.h文件中可以看到对类的定义。
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE;
const char * _Nonnull name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
实例对象的isa指针指向对象所属的类,在类中存放着实例方法。这样就可以找到对应的方法实现。类方法的isa指针指向元类,元类中存放着类方法。元类也是一个结构体,它的isa指针指向元类,最终会指向根元类,根元类的isa指针指向自己,最终形成一个闭环。
15.编写自己的类库时,最好加上自己的前缀。公司的项目则看项目组要求。这样能相对较好的防止命名冲突。
16.提供全能初始化方法,写完一个全能初始化方法供其它初始化方法调用,这样以后修改初始化方法的只需要修改那一个就可以了。
17.实现description方法,可以更方便的打印出对象的信息。
18.属性声明生命时尽量使用尽量使用不可变对象,例如把属性声明为只读属性,如需需要修改,.h文件里声明修改方法。再.m里将属性修改为读写即可,这样可以保证数据不会被随意的修改。
19.使用清晰而协调的命名方式,方法名要言简意赅,从左到右读起来要像个日常用语中的句子。给类起名字时要加前缀,见15.
20.给私有方法加前缀。例如:p_someMethod。这样做的好处是区分了私有方法与公共方法。公共方法对外提供接口调用,一般不能随意修改。私有方法在内部使用。可以修改。
21.理解OC的错去。OC中发生了必须处理的错误才会发生崩溃。不严重的情况下把错误信息经由NSError传递给开发者。
22.如果想令你写的类具有copy功能,需要实现NSCopy协议。
23.善于使用代理模式。
24.使用分类机制把一个类分为易于管理的小块。例如在AppDelegatea里有很多方法,比如推送,初始化参数,等等,可以把这些功能分散的不同的分类里,这行更加便于管理。
25.实现分类时,最好在类名以及方法名前加一下自己的专有标识。这样做事为了防止名称重复。
26.尽量不要在分类中定义属性,如果在分类中定义属性,系统只是帮我们简单声明了一下而已,并不会实现set和get方法。可以用runtime实现分类定义属性,但尽量不要使用。
27.可以使用分类来隐藏某些实现细节接。使用分类的的方法。这样就不用再.h中暴露方法。可以实现细节隐藏。
28.通过协议提供匿名对象。
29.理解引用计数。引用计数是OC用来管理内存的方式,不过现在大多使用的是ARC。系统在编译的时候回自动帮我们加上release和autoRelease。
- ARC。自动引用计数,系统在编译的时候回自动帮我们加上release和autoRelease,不用我们自己再添加。coreFoundation对象不归arc管理,所以使用时要加上CFReleas或CFRetain.
31.在delloc 方法里释放对象,移除监听。
32.编写异常安全代码时,留意内存管理问题,例如要使用try-catch 注意要把try块内的对象释放干净。
33.使用弱引用避免循环引用。例如我们在声明代理的时候,一般会使用weak,weak是弱引用,并且释放后会自动置空。
34.使用自动释放池避免内存峰值。
@autoReleasePool{}
某些代码在大量创建某些对象的时候可以把这段代码放入自动释放池中国,避免了短时间内内存暴涨问题。
35.使用僵尸对象来调试内存问题。
36.不要使用retainCount.因为retainCount没有考虑到自动释放,ARC下也不能retainCount了。
37.理解block块。block使用copy,从栈区copy到堆区。
38.为常用的块创建typedef,eg:
typedef(^TESBlock)(int a,int b);
这样就创建了一个TESBlock的类型。
39.使用block作为参数。例如封装数据请求时,使用block作为参数回调。尽量使用一个。
40.使用块时要防止循环引用。
41.多用派发队列(GCD),少用同步锁。将同步和异步派发结合起来,可用实现与普通加锁机制一样的同步行为,而这么做却不会阻塞执行异步派发的线程。使用同步队列及栅栏块,可以令同步行为更加高效。
42.如果要使用分线程,多使用GCD,少使用performSelector.
43.GCD更适合大体的多线程操作,如果要更细致的操作多线程则使用NSOperationQueue操作队列,如需要取消线程操作、指定操作的优先级、监听键值变化、指定操作间的依赖关系等。
44.通过Dispatch Group机制,根据系统资源状况来执行任务。一般要确定很多请求完毕后再进行某些操作会用到这个。
45.使用dispatch_once来执行只需运行一次的线程安全代码。(编写单例对象时,推荐使用)。
46.不要使用dispatch_get_current_queue()。这个函数已经废弃。因为它可能一起循环引用问题。
47.熟悉系统框架,例如UIKit,UIFoundation。
48.多使用块语句,少使用for循环。
49.通过桥接技术,可以是先Foundation和coreFoundation之间的数据转换。
50.自定义缓存时使用NSCache而不是NSDictionary。
51.在加载阶段,会调用类的load方法。类的load方法优先于分类。load方法不能覆写。首次调用某个类。系统会向该类发送initialize消息,此方法遵循覆写规则。
52.使用NSTimer时注意不要发生循环引用。
网友评论