熟悉 Objective-C
一、OC的起源
OC的方法(本质上讲是消息发送)在运行时决定。使用函数调用的语言,由编译器决定。如果涉及多态,则用到虚函数表。
二、降低类之间的耦合
在类的头文件里尽量少的的引入其他头文件,或者将引入其他头文件的时机延后(使用@class),@class 也被称为向前声明。这样可以减少编译时间。
如果在各自头文件引入对方的头文件,还对导致循环引用(chicken-and-egg situation)。使用#import而非#include指令虽然不会导致死循环,但是也会导致其中一个类无法正确编译。
有时无法使用向前声明的时候,比如要声明某各类遵循一项协议,尽量把该类遵循某协议的这条声明移动至分类中,或者是把协议单独放在一个头文件中,然后将其引入。
三、多用字面量语法,少用与之等价的方法。
如:使用 NSString *str = @"HHHH" 代替 NSString *str = [[NSString alloc] initWithString: @"str"] 等等。
注意:向数组或者字典中插入 nil 时,如果使用字面量会直接报错,如果是使用初始化方法,则 nil 以后的对象被忽略。
四、多用类型常量,少用#define预处理指令
#define ANIMATION_DURATION 0.3
static const NSTimerInterval kAnimationDuration = 0.3
- 不在头文件里面声明预处理命令,防止被别的文件引用,
static const也不能用 - 在
.m文件里面声明的变量,需要加上 static,不然会产生外部符号,重复的外部符号会导致编译错误 - const保证了所声明的变量不能被修改。static则可以用来保证只在编译单元内可见。
- 用
static const定义只在类内部可见的常量 - 用
extern定义全局常量,并使用类名作为前缀,但是尽量少使用extern - 使用
FOUNDATION_EXPORT比extern更好
五、用枚举值表示状态、选项、状态码
- 指定枚举变量的类型
1 << 0: 按位左移,即1的二进制向左移动0位,结果还是1
M << N: 将M的二进制左移N位,即M乘以2的N次方
M >> N: 是M除以2的N次方
&: 按位与
|: 按位或
typedef NS_ENUM(NSInteger, Direction) {
left = 1
right = 2
up = 3
down = 4
};
对象、消息、运行期
六、属性相关
属性:是OC的一项特性,用于封装对象中的数据;OC对象通常会把其所需要的数据保存为各种实例变量。
@property 的作用是合成存取方法
@synthesize 可以给属性生成的实例变量改名
@dynamic 不会自动创建实现属性的实例变量,也不会为其创建存取方法
nonatomic 原子性
readwrite/readonly 读写权限
assign/strong/weak/copy/ 内存管理语义
getter=<name> / setter=<name> 方法名
七、实例变量和属性
- 在对象内部尽量直接访问实例变量,在写入数据时则应该通过属性写入;
- 在初始化方法和dealloc方法中,总是应该通过实例变量的方式来读写数据;
- 懒加载的时候,需要通过属性来读写数据(
getter)
Tips:直接访问实例变量会比较快,绕过了OC的method dispatch,也不会触发KVO
八、理解"对象等同性"这一概念 --- isEqual
一般认为:当且仅当NSObject类其"指针值"完全相等时,这两个对象才相等。
- (BOOL)isEqual:(id)object;
- (NSUInteger)hash;
对象相等时,这两个等式都成立
如果把一个对象放入到set之后又修该对象的内容,那么可能破坏set的一些特性。如:
NSMutableSet *set = [NSMutableSet new];
NSMutableArray *arr01 = [@[@1, @2] mutableCopy];
[set addObject: arr01]; // set {((1, 2))}
NSMutableArray *arr02 = [@[@1] mutableCopy];
[set addObject: arr02]; // set {((1), (1, 2))}
[arr02 addObject: @2]; // set {((1, 2), (1, 2))} set元素的不重复性则被破坏了
NSSet *setB = [set set copy]; // setB {((1, 2))} copy之后setB正常了
所以,要么确保对象的哈希值不依赖内部可变的状态,要么确保依赖的状态不会改变。
九、以"类族模式"隐藏实现细节 类族(class cluster)
也就是工厂模式(Factory pattern)实现方式如下:
typedef NS_ENUM(NSUInteger, EmployeeType) {
EmployeeTypeDeveloper,
EmployeeTypeDesigner,
EmployeeTypeFinance,
};
@interface Employee: NSObject
@property (copy) NSStirng *name;
@property (assign) NSUInteger salary;
+ (Employee *)employeeWithType:(EmployeeType)type;
- (void)doWork;
@end
@implementation: Employee
+ (Employee *)employeeWithType:(EmployeeType)type {
switch (type) {
case EmployeeTypeDeveloper:
return [EmployeeTypeDeveloper new];
break;
case EmployeeTypeDesigner:
return [EmployeeTypeDesigner new];
break;
case EmployeeTypeFinance:
return [EmployeeTypeFinance new];
break
}
}
- (void)doWork {
// subclass implement
}
@end
@interface EmployeeTypeDeveloper:: Employee
@end
@implementation: EmployeeTypeDeveloper
- (void)doWork {
[self writeCode];
}
@end
- 子类应该继承自类族中的抽象基类
- 子类应该定义自己的数据存储方法
- 子类应该重写
(overwrite)父类文档中指明要重写的方法 - 类族模式可以把实现细节隐藏在一套简单的公共接口后面
- 系统框架中经常使用类族
十、在既有类中使用关联对象存放自定义数据Associated Object管理关联对象
1、以给定的键和策略为某对象设置关联对象值
void objc_setAssociatedObject(id object, void *key, id value, objc_AssociationPolicy policy)
2、根据给定的键从某对象中获取相应的关联对象值
id objc_getAssociatedObject(id object, void *key)
3、移除指定对象的全部关联对象
void objc_removeAssociatedObjects(id object)
eg: 如果在一个类中处理多个警告信息视图:
UIAlertView *alert = [[UIAlertView alloc] initWithTitle: @"title" message: @"message" delegate: self cancelButtonTitle: @"cancel" otherButtonTitles: @"continue", nil];
void (^block)(NSInteger) = ^(NSInteger buttonIndex) {
if (buttonIndex == 0) {
} else {
}
};
objc_setAssociatedObject(alert, AlertViewKey, block, OBJC_ASSOCIATION_COPY);
[alert show];
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
void (^block)(NSInteger) = objc_getAssociatedObject(alertView, AlertViewKey);
block(buttonIndex);
}
- 可以通过关联对象将两个对象连起来
- 定义关联对象时可以指定内存管理语义,用以模仿定义属性时所采用的"拥有关系"与"非拥有关系"
- 尽量少使用关联对象
十一、理解objc_msgSend的作用
消息包括 "name"、"selector"可以接受参数,而且还可以有返回值。
给对象发消息可以这样写:
id returnValue = [someObject messageName:parameter]
someObject叫做"接收者(receiver)"; messageName是selector,selector和参数合起来称为message。编译器看到此消息后,将其转换为一个标准的C语言函数调用,所调用的函数是消息传递机制中的核心函数,叫做
objc_msgSend:void objc_msgSend(id self, SEL cmd, ...)
第一个参数代表接收者;第二个指的是selector;然后编译器会把刚才那个例子找那个的消息转换为如下函数:
id returnValue = objc_msgSend(someObject, @selector(messageName:), parameter);
objc_msgSend函数会依据接收者与selector的类型来调整适当的方法。为了完成此操作,该方法需要在接收者所属的类中搜寻其方法列表(list of methods),如果能找到与selector名称相符的方法,就跳转至其实现代码。如果找不到就会沿着继承继续向上查找。如果最终还是找不到相符的方法,那就执行消息转发(message forwarding)操作。
- 消息由接收者、
selector及参数构成 - 给对象发消息就是在该对象上调用方法
- 发给某对象的全部消息都要由"动态消息派发系统"
(dynamic message dispatch system)来处理,该系统会查出对应的方法,并执行其代码
十二、理解消息转发机制
在编译期向类发送了无法解读的消息并不会报错,因为在运行时我们可以继续向类中添加方法,当对象接收到无法解读的消息之后,就会启动消息转发机制(message forwarding),我们可以在此时告诉对象应该如何处理未知消息。
消息转发分为三个阶段:
- 第一个阶段,先征询接收者所属的类,看其能否动态添加方法,以处理当前这个"未知的
selector",这叫做动态方法解析(dynamic method resolution) - 第二阶段,当接收者无法增加方法来响应该消息,那么先让接收者看看有没有其他对象能处理这条消息;若有,系统会将消息转给那个对象,消息转发过程结束
- 若没有,则启动完整的消息转发机制,运行系统会把与消息有关的全部细节都封装到
NSInvocation对象中,在给接收者最后一次机会,另其设法解决当前还未处理的消息。
动态方法解析:
对象在收到无法解读的消息之后,先会调用下面的方法(使用该方法的前提是:相关的方法的实现代码已经写好,只等着运行的时候动态的插在类里面就可以了,此方法常用来@dynamic属性)
+ (BOOL)resolveInstanceMethod:(SEL)selector
当前接收者还有第二次机会来处理该selector,系统会让接收者看看有没有其他对象能处理这条消息,下面方法返回的是接收者找到的能处理该消息的对象,有就返回对象,没有就返回nil
- (id)forwardingTargetForSelector:(SEL)selector
若没有能够处理的对象,则创建一个NSInvocation对象把该消息有关的全部细节都封存。
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
- (void)forwardInvocation:(NSInvocation *)invocation;
- 若对象无法响应某个
selector,则进入消息转发流程 - 通过运行时额动态方法解析功能,我们可以在需要用到某个方法时再将其加入类中
- 对象可以把无法响应的
selector转交给其他对象来处理 - 如果上述之后,还没办法处理
selector,那就启动完整的消息转发机制
十三、用"方法调配技术"调试"黑盒方法"
方法调配技术:method swizzling类的方法列表会把selector的名称映射到相关的方法实现之上,使得"动态消息派发系统"能够据此找到应该调用的方法。这些方法均以函数指针的形式来表示,这种指针叫做
IMP:id(* IMP)(id, SEL, ...)
交换两个方法的实现:
void method_exchangeImplementations(Method m1, Method m2)
上述函数中的方法可以通过以下函数获得:
Method class_getInstanceMethod(Class aClass, SEL aSelector)
- 在运行时,可以向类中新增或者替换selector所对应的方法实现
- 使用另一份实现来替换原有的方法实现,叫做方法调配,我们可以使用此技术向原有实现中添加新功能。
- 一般来说,只有调试程序的时候才需要在运行时修改方法实现
十四、理解类对象的用意
Class对象定义在运行期程序库的头文件:
typedef struct objc_class *Class;
struct objc_class {
Class isa;
Class super_class;
const char *name;
long version;
long info;
long instance_size;
struct objc_ivar_list *ivars;
struct objc_method_list **methodLists;
struct objc_cache *cache;
struct objc_protocol_list *protocols;
};
此结构体存放类的元数据"metadata"、例如类的实例实现了几个方法,具备多少个实例变量等信息。结构体的第一个变量也是isa指针,说明了Class本身就是Object-C对象。类对象所属的类型是另外一个类,叫做元类metaclass。用来表述类对象本身所具备的元数据。每一个类就只有一个类对象,每个类对象只有一个元类。
-isMemberOfClass能够判断出对象是否为某个特定类的实例-isKindOfClass能够判断出对象是否为某类或其派生类的实例
isMemberOfClass/isKindOfClass 具体的分析可见另一篇文章 isKindOf vs isMemberOf
NSMutableDictionary *dict = [NSMutableDictionary new];
[dict isMemberOfClass:[NSDictionary class]]; // NO
[dict isMemberOfClass:[NSMutableDictionary class]]; // YES
[dict isKindOfClass:[NSDictionary class]]; // YES
[dict isKindOfClass:[NSArray class]]; // NO
- 类型信息查询方法:
isMemberOfClass、isKindOfClass - 每个实例都有一个指向
Class对象的指针,用以表明其类型,而这些Class对象则构成了类的继承体系 - 如果对象类型无法在编译期确定,那么就应该使用类型信息查询方法来探知
- 不要直接比较类对象
接口与 API 设计
十五、用前缀避免命名空间冲突
- 全局函数和变量需要注意避免冲突
- 在自己开发的库中,为用到的第三方库添加前缀
十六、提供"全能初始化方法"
一般创建一个类的时候,我们尽可能的会提供比较多的初始化方法,但是最好提供一个全能的初始化方法,让其他初始化方法都调用此方法实现 eg:NSDate
- (id)init;
- (id)initWithString:(NSString *)string;
- (id)initWithTimeIntervalSinceNow:(NSTimeInterval)seconds;
- (id)initWithTimeIntervalSinceReferenceDate:(NSTimeInterval)seconds;
initWithTimeIntervalSinceReferenceDate就是一个全能的初始化方法,这些初始化方法都会调用这个方法。
十七、实现description方法
实现模型的description方法,在控制台打印log的时候才会显示出打印的属性
- 正常
NSLog需要实现description - 在控制台里
po,需要实现debugDescription
十八、尽量使用不可变对象
十九、使用清晰而协调的命名方式
- 如果返回值是新建的,首个词是返回值类型
- 不要使用
str这种简称,使用string get前缀仅在由“输出参数”保存返回值的方法中使用
二十、为私有方法名添加前缀
为私有方法名添加前缀,但是不要以一个下划线作为前缀,这是苹果自己预留的方法,将私有方法和共有方法区别用过命名方式开来。
二十一、理解OC的错误模型
- 只有在发生了可以使整个应用程序崩溃的严重错误时,才应该使用异常
- 一般情况下使用
NSError,比如封装在delegate方法里,返回给调用者 NSError对象封装了三条信息:
1、Error domain(错误范围,其类型为字符串);
2、Error code(错误码,其类型为整数);
3、userInfo(用户信息,其类型为字典)
二十二、理解NSCopying协议
想要使自己的类支持copy操作,就要实现NSCopying协议:----返回一个不可变的copy版本
- (id)copyWithZone:(NSZone *)zone
OC中使用->语法,是因为修饰的是实例变量而不是属性
NSMutableCopying协议----返回一个可变的copy版本
- (id)mutableCopyWithZone:(NSZone *)zone
可变copy和不可变copy:
[NSMutableArray copy] => NSArray
[NSArray copy] => NSMutableArray
deep copy: 在拷贝对象自身时,将其底层数据也一并复制过去;深copy之后内容所指向的对象是原始内容中相关对象的一份copyshallow copy: 浅copy之后的内容与原始内容均指向相同的对象
一般情况下,大部分执行了NSCopying协议的对象的copy都是执行的浅copy,如果需要执行深copy需要自己来编写相关的方法。
对象的copy:
- 深
copy: 直接copy整个对象到另一块内存中(内容拷贝) - 浅
copy: 不复制对象本身,仅仅是copy指向对象的指针(指针拷贝)
集合的浅复制有非常多种方法。当你进行浅复制时,会向原始的集合发送retain消息,引用计数加1,同时指针被拷贝到新的集合。
NSArray *shallowCopyArray = [someArray copyWithZone:nil];
NSSet *shallowCopySet = [NSSet mutableCopyWithZone:nil];
NSDictionary *shallowCopyDict = [[NSDictionary alloc] initWithDictionary:someDictionary copyItems:NO];
集合的深复制:
NSDictionary shallowCopyDict = [[NSDictionary alloc] initWithDictionary:someDictionary copyItems:YES];
- 如果你用这种方法深复制,集合里的每个对象都会收到
copyWithZone: 消息。 - 如果集合里的对象遵循
NSCopying协议,那么对象就会被深复制到新的集合。 - 如果对象没有遵循
NSCopying协议,而尝试用这种方法进行深复制,会在运行时出错。
copyWithZone: 这种拷贝方式只能够提供一层内存拷贝(one-level-deep copy),而非真正的深复制。
第二个方法是将集合进行归档(archive),然后解档(unarchive),如:
NSArray *trueDeepCopyArray = [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:oldArray]];
系统对象的copy与mutableCopy:
copy返回imutable对象;所以,如果对copy返回值使用mutable对象接口就会crash;mutableCopy返回mutable对象;
在非集合类对象中:
[immutableObject copy] // 浅复制
[immutableObject mutableCopy] // 深复制
[mutableObject copy] // 深复制
[mutableObject mutableCopy] // 深复制
集合对象中:
[immutableObject copy] // 浅复制
[immutableObject mutableCopy] // 单层深复制
[mutableObject copy] // 单层深复制
[mutableObject mutableCopy] // 单层深复制
注意点:
NSString *str = @"string";
str = @"newString";
上面这段代码,在执行第二行代码后,内存地址发生了变化。乍一看,有点意外。按照 C 语言的经验,初始化一个字符串之后,字符串的首地址就被确定下来,不管之后如何修改字符串内容,这个地址都不会改变。但此处第二行并不是对 str 指向的内存地址重新赋值,因为赋值操作符左边的 str 是一个指针,也就是说此处修改的是内存地址。
赋值之后地址就会发生变化。
NSString *name = @"name";
NSLog(@"===name====%p==", name);// ===name====0x10f803188==
name = @"HHHH";
NSLog(@"===name====%p==", name);// ===name====0x10f8031c8==
用@property声明NSString、NSArray、NSDictionary 经常使用copy关键字,是因为他们有对应的可变类型:NSMutableString、NSMutableArray、NSMutableDictionary,他们之间可能进行赋值操作,为确保对象中的字符串值不会无意间变动,应该在设置新属性值时拷贝一份。
strong修饰的对象发生变化时也发生了变化copy修饰的对象则不受外界的影响
协议与分类
二十三、通过委托与数据源协议进行对象间的通信
Objective-c分类(Category)和扩展(Extension):
Category:
typedef struct objc_category *Category;
struct objc_category {
char *category_name // 分类名
char *class_name // 分类所属的类名
struct objc_method_list *instance_methods // 实例方法列表
struct objc_method_list *class_methods // 类方法列表
struct objc_protocol_list *protocols //分类所实现的协议列表
}
这个结构体里面是没有实例变量列表的。分类中可以写@property,但是不会生成setter/getter方法,也不会实现合成成员变量。
分类中也可以通过其他方式添加属性,但是一般不推荐这么做:
(objc_getAssociatedObject/objc_setAssociatedObject)
Extension类扩展:
Tips: class-continuation中文也翻译扩展
实现方式如下:
@interface Class_Name ()
@end
一般的类扩展是写到.m文件中,一般的私有属性都是写到.m文件的类扩展中。
作用:为一个类添加额外的变量、方法和属性。
分类和扩展的区别:
- 分类原则上只能增加方法(添加属性是通过
runtime操作) - 类扩展不仅可以增加方法,还可以增加实例变量(或者属性),只是该实例变量默认是
@private类型的(范围只能是当前类) - 类扩展中声明的方法没被实现,编译器会报警,但是分类中的方法没被实现,编译器不会报警。因为类扩展是在编译阶段添加到类的,分类是在运行时添加到类中的。
- 如果分类和其所属类有相同的方法时,优先调用分类的方法,然后
super。 - 扩展没有自己单独的实现部分
(implementation),依托所属类的实现来实现
二十四、将类的实现代码分散到便于管理的数个分类中
按照不同的分类和属性进行代码分块,私有方法放入私有的类中。
二十五、总是为第三方类的分类名添加前缀
向第三方类中添加分类时,为分类名称和方法名称添加前缀,防止出现了重写系统方法或者是被其他分类重写了当前分类的方法。
二十六、不要在分类中声明属性
- 分类中无法创建实例变量
- 可以定义存取方法,但是不要定义属性
尽管在分类里也可以声明属性,但是这种做法还是要尽量避免。原因是除了class-continuation分类之外,其他分类都无法向类中新增实例变量,因此它们无法把实现属性所需的实例变量合成出来。此时需要使用@dynamic,在分类中为该属性实现存取方法。
二十七、使用class-continuation隐藏实现实现细节
- 比如我们将不愿意暴露的属性或者方法实现在扩展中
- 比如当一个类需要遵从某一个协议的时候,我们可以在扩展中遵从,在类的实现中实现协议方法
二十八、通过协议提供匿名对象
- 具体的对象类型可以淡化成遵从某协议的
id类型,协议里规定了对象所应该实现的方法 - 使用匿名对象来隐藏类型名称(或者类名)
内存管理
二十九、理解引用计数
引用计数工作原理:对象有个计数器,用以表示当前有多少个对象想令此对象继续存活下去。在Objective-C中叫做保留计数(retain count)也叫引用计数(reference count)。
NSObject协议声明了下面三个方法用于操作计数器:
retain: 递增保留计数release: 递减保留计数autorelease: 在清理自动释放池时,再递减保留计数
- 先保留新值然后释放旧值,然后更新实例变量
- 引用计数机制通过可以递增递减的计数器来管理内存。对象创建好之后,其保留计数至少为1。若保留计数为正,则对象继续存活,当保留计数降为0的时候,对象就被销毁了。
- 在对象生命周期中,其余对象通过引用来保留或释放此对象。保留与释放操作分别会递增及递减保留计数。
三十、以ARC简化引用计数
__strong: 默认语义,保留此值__weak: 不保留此值,但是变量可以安全使用,因为如果系统把这个对象回收了,那么变量也会自动清空__autoreleasing: 把对象"按引用传递"给方法时,使用这个特殊的修饰符,此值在方法返回时自动释放。__weak常常用来防止block的循环引用
ARC管理对象生命周期的方法基本上就是:在合适的地方插入"retain"和"release"操作。在ARC环境下,变量的内存管理语义可以通过属性的修饰符来致命。
ARC只负责管理Objective-C对象的内存。CoreFoundation对象不归ARC管理,开发者必须手动调用CFRealse、CFRetain。
三十一、在dealloc方法中只释放引用并解除监听
在dealloc中只用来移除KVO或NSNotificationCenter的通知,不要做其他事情
三十二、编写"异常安全代码"时留意内存管理问题
try {
} catch {
}
若使用ARC且必须捕获异常时,则需要打开编译器的-fobjc-arc-exceptions标志。如果是手动管理引用计数且需要捕获异常时,则需要设法把对象正确清理干净。
三十三、以弱引用来避免循环引用
weakunsafe_unretained
三十四、以"自动释放池"降低内存峰值
使用
@autoreleasepool {
}
将会占用大量内存的代码包裹起来,系统就会在代码块的结尾把某些对象回收掉,从而降低内存峰值。
三十五、用僵尸对象(Zombie Object)调试内存管理问题
退出程序调用abort()方法
TODO 此处需要扩充
三十六、不要使用retainCount方法
NSObject协议定义了下列方法,用来查询当前对象的保留计数:
- (NSUInteger)retainCount;
在ARC此方法已经废弃了。
Block和GCD
三十七、理解block
block基础知识:
^{};
void (^someBlock)() = ^{
}
定义block的时候,其所占的内存区域是分配在栈中的。当对一个block执行copy操作时,就会把block从栈复制到堆上。
三十八、为block创建typedef
typedef int(^someBlock)(BOOL flag, int value);
该block的名称为someBlock;返回值为int;传入的参数有两个,一个是BOOL,一个是int。
三十九、用handler降低代码分散程度
将方法里的闭包用typedef抽取出来
typedef void(^NetworkErrorHandler)(NSError *error);
- (void)finishWithCompletionHandler:(NetworkErrorHandler)failure;
- 在创建对象时,可以使用内联的
handler块将相关的业务逻辑一并声明 - 在设计接口的时候,根据情形判断是需要时候用
handler、block还是delegate。
四十、用块引用所属对象时注意循环引用
当block捕捉的对象直接或间接的保留了块本身,那就需要注意循环引用。
如:在block里持有了self,self又持有了block;或者block里持有了self,self持有了一个对象A,A又持有block。
四十一、多用派发队列,少用同步锁
在GCD出来之前,如果有多个线程要执行同一份代码,通常使用锁来实现某种同步机制。
- 是使用内置的同步块
(synchronization block):
@synchronized(self) {
}
此方法会根据给定的对象,自动创建一个锁,并等待块中的代码执行完毕。这种写法会降低代码效率。
2. NSLock、NSRecursiveLock对象:
NSLock *lock = [[NSLock alloc] init];
[lock lock];
// 将代码写在此处
[lock unlock];
或者使用NSRecursiveLock这种递归锁(recursive lock)。线程能够多次持有该锁,而且不会出现死锁deadlock现象。
缺陷:效率也不是很高。
使用串行同步队列(serial synchronization queue)可以代替同步块或锁对象。将读取、写入操作都安排在同一个队列里,即可保证数据同步。
_syncQueue = dispatch_queue_create("queueName", NULL);
dispatch_sync(_syncQueue, ^{
});
并发队列(concurrent queue):
_syncQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
栅栏(barrier):
dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);
- 派发队列可用来表述同步
(synchronization semantic),这种做法要比使用@synchronized或者NSLock对象更简单,更有效率 - 将同步与异步派发结合起来,可以实现与普通枷锁机制一样的同步行为,而这么做却不会阻塞执行异步派发的线程
- 使用同步队列及栅栏,可以令同步行为更加高效
四十二、多用GCD,少用performSelector系列方法
如果想要把任务放在另一个线程上执行,最好不要用performSelectorOnMainThread,而应该用GCD的方法实现。因为performSelector系列方法在内存管理方面容易有疏失。
四十三、掌握GCD及操作队列的使用时机
- 同步机制、只需执行一次的代码(比如单例)使用
GCD是最好的选择。 - 但是执行后台任务时,
GCD并不一定是最好的方法。NSOperationQueue。开发者可以把操作以NSOperation子类的形式放在队列中,这些操作也可以并发执行。 GCD是纯C的API,操作队列是OC的对象。
NSOperationQueue类的addOperationWithBlock
使用NSOperation及NSOperationQueue的好处如下:
- 取消某个操作。如果使用操作队列,那么想要取消操作是很容易的。运行任务之前,可以在
NSOperation对象上调用cancel方法。不过已经启动的任务是无法取消的。 - 指定操作间的依赖关系。一个操作可以依赖其他多个操作。使一个操作必须在另一个操作顺利执行完毕之后方可执行。
- 通过键值观察机制监控
NSOperation对象的属性。如isCancelled属性来判断任务是否已经取消,isFinished属性来判断任务是否已经完成。 - 指定操作的优先级。
GCD的优先级是针对队列而言,而不是针对每个块。NSOperation对象也有线程优先级(thread priority)这决定了运行此操作的线程处在何种优先级上。这个功能要比GCD方便。 - 重用
NSOperation对象。系统内置了很多NSOperation的子类,我们也可以自己创建。 NSOperationQueue: 操作队列GCD:派发队列
四十四、通过Dispatch Group机制,根据系统资源状况来执行任务
dispatch group是GCD的一项特性,能够把任务分组。调用者可以等待这组任务执行完毕,也可以在提供回调函数之后继续往下执行,这组任务完成时,调用者会得到通知。
四十五、用dispatch_once来执行只需运行一次的线程安全代码
单例模式singleton:
+ (id)sharedInstance {
static SomeClass *sharedInstance = nil;
@synchronized(self) {
if (!sharedInstance) {
sharedInstance = [[self alloc] init];
}
}
return sharedInstance;
}
Tips: 上述方法可能会出现线程安全问题
只执行一次的GCD的API:
dispatch_once(dispatch_once_t *token, dispatch_block_t block)
该函数里的block必定会执行,且只执行一次。
+ (id)sharedInstance {
static SomeClass *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
dispatch_one可以彻底的保证线程安全。而且更高效。static表示把该变量定义在static作用域中,可以保证编译器每次在每次执行sharedInstance方法时都会复用这个变量,而不会创建新的变量。
四十六、不要使用diapatch_get_current_queue
系统框架
四十七、熟悉系统框架
四十八、多用块枚举,少用for循环
NSEnumerator:
- (NSArray *)allObjects
- (id)nextObject // 返回nil就表示到达枚举末端了
遍历数组:
NSArray *anArray = /*....*/;
NSEnumerator *enumerator = [anArray objectEnumerator];
id object;
while ((object = [enumerator nextObject]) != nil) {
}
这种写法和For循环相似,但是代码多了一些,不过是通用的,可以用在所有的collection中
NSDictionary *aDictionary = /*...*/;
NSEnumerator *enumerator = [aDictionary keyEnumerator];
id key;
while ((key = [enumerator nextObject]) != nil) {
id value = aDictionary[key];
}
for in快速遍历;
enumerateObjectsUsingBlock 块枚举法具备比较多的优势,提供的信息比较丰富
四十九、对自定义其内存管理语义的collection使用__bridge
当使用CoreFoundation框架的时候,(带CF前缀的),注意使用桥式转换(bridgedcast)
__bridge:ARC仍然具备这个OC对象的所有权。__bridge_retained:ARC将交出对象的所有权,需要调用CFRelease__bridge_transfer: 让ARC获得所有权,把CFArray转换为NSArray
五十、构建缓存时选用NSCache而非NSDictionary
NSCache优点:
- 当系统资源将要耗尽时,可以自动删减缓存
NSCache并不会拷贝键,而是会保留它。NSCache是线程安全的
五十一、精简initialize和load的实现代码
- 尽量不要使用
load方法 initialize是懒加载的,只有在调用的时候才会执行- 在加载阶段,如果类实现了
load方法,那么系统就会调用它。load方法不支持overwrite。