最近感觉之看草草看过的《Effective Objective-C 2.0》,有必要再重读一遍,以打牢自己并不靠谱的基础知识。
PS:为了实践,以后坚持一本书读完再读其他的原则。同一时期,只读一本书。
第一章
Tip1:
Objective C与C++最大的不同,在于函数的调用方式上。C++使用的是虚函数表,在运行时查找函数来调用方法。而OC是使用消息机制,在运行时,通过发送消息来实现函数的调用。所以不存在方法,而都是消息。
Objective C是基于C的扩展。所以,OC对象,带*的,都是在Stack上的指针,指向Heap中的实际分配内容。
CGRect是C语言的结构体,与Int等对象一样,直接在Stack上。不需要分配和释放内存。
Tip2:
过多引入.h头文件中,会增加编译时间。
Tip3:
字面量语法,除字符串外,所创建的对象,必须是Foundation框架中。
Tip4:
用static const NSTimeInterval kAnmationDuration = 0.3;
这种变量定义,替代#define
这种预处理方法。
static const变量定义,有类型、有适用范围,更易于后期维护。同时,在编译时,与Define一样,会将所有调用此变量的直接用值替换,而不会在运行时修改。
而如下常量定义,会将该常量放入global symbol table(全局符号表)中,以供编译单元之外的全局调用。
//Define in header file
extern NSString *const EOCStringConstant;
//In the implementation file
NSString *const EOCStringConstant = @"VALUE";
Tip5:
enum枚举时,使用<< i
位移方法,可以实现|
按位或,来实现多种枚举值的并列判断。
对于enum枚举类型使用switch时,最好不要加default分支,这样编译器可以提醒是否遍历所有可能的状态。
第二章
Tip6: 理解(property)声明属性
@property
变量需要按指定命名格式设置存取方法(getset),Property声明,让编译器
自动完成设置get和set方法,以及定义实例变量名的设置:
.h 实现文件中
@interface 类名:父类{
变量类型 变量名;
}
-(self *)变量名;
-(void)变量名:(id)值;
.m 中
@synthesize 变量名 = _变量名
以上定义相当于
@property 变量类型 变量名;
OC采用将实例变量定义为一种专门存储变量实际内存地址指针的运行期变量,交给类对象(class object)在运行期管理,而编译前,变量定义的变化,不会导致内存地址变动,而拉长编译时间(想想C++工程的编译时间吧)
使用@dynamic
声明,可以要求不实现自动创建存取方法和实例变量,而是由程序在运行期动态创建存取方法。(一些极特殊场景下会用到,比如NSManagedObject类及子类,其数据来自后端数据库)
属性声明attribute
在@property后,可以使用一些特殊的声明,来决定变量的一些性质。
原子性(atomicity) atomic | nonatomic
- atomic : 原子性,系统其他部分无法观察其中间步骤生成的临时结果,只能看到操作前和操作后的结果。Mac开发中,多数使用atomic。
- nonatomic :非原子性, iOS基本上全部使用这一属性。因为使用原子性对系统性能开销较大,同时它也无法完全保证线程安全,仍然必须加锁才行。
内存管理 assign | strong | weak | unsafe_unretained | copy
- assign: 简单类型,Int等。存放在栈中,无需内存管理的变量。
- strong: 强类型对象,有retain 和release 的内存计数加减动作。
- weak: 弱引用对象,不会引用计数加1. 当指向的对象计数归零时,weak对象会被指向nil
- unsafe_unretained: 非安全弱引用对象,弱引用,但当指向的对象计数归零时,unsafe_unretained对象不会自动指向nil
- copy: 创建copy:适合NSString之类拥有NSMutable对象的可变子类型的属性,防止指向的内存地址在变量不知情的情况下,被其他同样指向这一地址的可变对象修改。
自定义存取方法:
一般用来指定BOOL类型的is前缀等情况的指定读取方法名:
@property (nonatomic, getter=isOn) BOOL on;
Tip7 访问实例变量
- 大多数情况下,使用
_变量名
直接读取实例变量,而通过属性(点语法,或者[类名 变量sett方法名])来设置/修改变量。
直接访问和通过属性访问的区别:
– 直接读取变量,不经过OC的方法派发机制(method dispatch),速度更快。
– 直接读取变量,绕开了设置方法,不会引起copy等属性的变量拷贝新值等损耗。但在修改时,失去了内存管理的意义。
– 直接读取变量,不会触发KVO通知。此特质需要注意。
– 直接读取变量,不会走set/get方法,所以也没有办法打断点,来调试程序。
- 在初始化方法中(init等),总应使用直接访问实例变量,而不是使用属性来访问。防止子类覆写(voerride)父类set/get方法,导致初始化失败。
-
如果使用了惰性初始化(lazy initalization)时,必须使用属性来存取变量。
-(id)变量名{
if(_变量名){
_变量名 = [变量类型 new];
}
return _变量名;
}
Tip8 对象相等比较
-
==
是比较指针本身,不比较其所指对象。 -
NSObject中,有两种比较方法:
-(BOOL)isEqual:(id)object;
-(NSUInteger)hash;
以上方法,当且仅当其指针值完全相等,这两个对象才完全相等。
同时,当isEqual方法相等时,hash值肯定相等,但当hash值相等,isEqual未必相等。
-(NSUInteger)hash{
return 1337;//这种返回固定Hash值是合法的,但它会引起在集合类里(NSArray等)的性能问题,因为这些集合类会将不同hash值分散保存,以优化存取性能,如多个对象hash值完全一样,则会大幅降低存取性能。
}
//标准hash实现方法,性能与碰撞率折中方案,即能保证较高性能,又避免了hash碰撞的机率。
-(NSUInteger)hash{
NSUInteger id1hash = [_id1 hash];
NSUInteger id2hash = [_id2 hash];
NSUInteger int1hash = _int1;
return id1hash ^ id2hash ^ int1hash;
}
PS: 对于集合对象中的可变元素,在放入集合后,改变其值时,不能再改变其hash值,否则会导致【错误的装箱】问题。
Tip9 类族(class cluster)/类簇
- 工厂方法的实现方式,超类定义接口,子类实现,调用者不用关心具体由那个子类实现。
- 类族的
[id class]
方法,返回的是子类方法,所以不能用[id class] == [类族 class]
来判断,而要用[id isKindOfClass:[类族 class]]
来判断 - 在没有源代码,为类族新建子类时(类似NSArray),需要认真阅读文档:实现自己的数据存储方法,覆写需要覆写的方法。
Tip10 关联对象(Associated Object)
一种非继承的方法,将两个对象绑定在一起。
- 按指定policy内存管理策略,绑定Object和Value: void objc_setAssocaiatedObject(id object, void *key, id value, objc_AssocaiationPolicy policy)
- 获取和删除绑定对象:
- id objc_getAssocaiatedObjects(id Object)
- void objc_removeAssociatedOjbects(id Object)
- 需要导入runtime.h
- 只有在其它方案不行时,才采用这种方案。
Demo:
#import <objc/runtime.h>
static void *EOCMyAlertViewKey = "EOCMyAlertViewKey";
-(void)setAlertView{
创建Alert对象
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Test"
message:@"This is Test Alert"
delegate:self
cancelButtonTitle:@"cancel"
othersButtonTitle:[@"test1",@"test2",@"test3",nil]];
创建一个Block来处理
void (^block)(NSInteger) = ^(NSInteger buttonIndex){
switch (buttonIndex){
case 0:{
[self doCancel];
}
break;
case 1:{
[self doTest1];
}
break;
case 2:{
[self doTest2];
}
break;
case 3:{
[self doTest3];
}
break;
default:
break;
}
}
魔法开始
objc_AccociatedObject(alert, EOCMyAlertViewKey , block , BJC_ACCOCIATION_COPY);
show alert
[alert show];
}
-(void)alertView:(UIAlertView*)alertView
clickedButtonAtIndex:(NSInteger)buttonIndex{
获取绑定对象
void (^runBlock)(NSInteger) = objc_getAccociatedObject(alertView, EOCMyAlertViewKey);
runBlock(buttonIndex);
}
- 优点:将逻辑与实现放在一起,同时不需要子类继承等耦合的情况。更易读和维护。
- 缺点:只有在关联的时候才能定义内存管理方法,与类定义无关,只能预先设定,极易造成内存强引用。
Tip11 理解objc_msgSend
消息有name和selector两种
向对象发送消息并接收返回
id returnValue = [someObject messageName:parameter]; /*<** 其中,someObject是receiver, messageName是 Selector
*/
等价于C语言的
id returnValu = objc_msgSend(someObject,@select(messageName),parameter);
- 消息派发的路径: ojbc_msgSend 首先寻找接收者(receiver)的list of methods(方法列表),找到即跳转到方法代码实现。如果没有找到,就沿继承体系向上查找父类list of methods。如最终找不到,就执行message forwarding消息转发操作。
-
objc_msgSend会将方法列表匹配结果缓存在fast map(快速映射表)中,以提高查找效率。其带来的性能损失并不明显,但还是会比静态绑定函数调用(statically bound function call)操作要耗时。
-
在某些edge case情况下,会用特殊其他的objc_msgaSend函数来处理,如结构体用objc_msgSend_stret(在cpu寄存器能容纳返回的结构体时)、浮点数用objc_msgSend_fqret、 给超类发消息为objc_msgSendSuper等等
Tip12 消息转发机制
?对象在接收到无法解读的消息之后会发什么情况?
- 首先:询问receiver,是否有动态添加方法,来处理未知消息(unknown selector),称之为动态方法解析 (dynamic method resolution)
- 之后,进入full forwarding mechanism(完整消息转发机制),请求接收者以其他手段来处理消息相关方法调用。
- 查找有没有备用接收者(replacement receiver)能够接收此消息,如有,正常转发。
- 最后,运行期的所消息相关细节都被封装到NSInvocation对象中,令receiver处理这个异常消息。
- 再不处理,则崩溃。
动态方法解析
实例方法,转发给:
+(BOOL)resolveInstanceMethod:(SEL)selector;
类方法(+号方法),转发给
+(BOOL)resolveClassMethod:(SEL)selector;
- 常用来实现@dynamic属性,在运行期时确定变量的存取方法;
Tip13 Method swizzling 方法调配
- OC的动态运行机制,可以实现让指定方法名对应的函数指针,在运行期被替换、改变。
-
主要实现方法:
获取映射表中的方法
Method m1 = class_getInstanceMethod([className],@selector(MethodName));
交换映射表中两个已经存在的方法
void method_exchangeImplementations(Method m1, Method m2)
- 一般来说,只有调试程序的时候才需要在运行期修改方法实现,不宜滥用。
Tip14 理解类对象
- 所有Objective对象实例都是指针,声明时体现为*
- id对象本身就是指针,所以声明时不需要加*
- 声明时指定类型,是帮助编译器识别类型,并给出警告。
//id类型的定义
typedef struct objc_object {
Class isa;
} *id;
//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_iva_list *ivars;
struct objc_method_list **methodLists;
struct objc_cache *cache;
struct objc_protocol_list *protocols;
};
- isMemberOfClass: 查询是否指定类的实例, isKindOfClass:查询是否是指定类及其派生类的实例。
第三章: 接口与API设计
Tip15: 前缀命名避免命名空间冲突
- Apple本身保留了2个字母的前缀命名权,所以应以最少3个字母的前缀命名
- 对C语言函数加上前缀,防止全局命名冲突。
- SDK再引入的第三方库,加上自己的命名,以防止版本冲突和其他SDK引用同名库导致的冲突。
Tip16: 使用designated initializer 基础初始化
- 各种类型的初始化指向一个基础初始化方法
- 初始化的调用链要完整
- 子类提供了新的初始化方法后,在要子类中覆写超类的初始化方法
Tip17:实现description方法便于调试
-
NSLog对象时,会调用类的description方法,要求对象输出描述文字
-
另外有debugDescription方法,可以输入只在调试模式下才输出的日志(NSLog默认不输出,控制台打印输出)
Tip18: 尽量使用不可变对象
- 可变对象放入Set等collection后,又改变其内容,很容易破坏set的内部结构
- 不实现setter方法的只读属性,尽量也声明内存管理属性,方便扩展。
- 只读声明,在对象外部,依然能使用KVC设置属性值。
- 对于可变set,可以在对象内部copy一份可变set变量供修改,对外部则提供一个readonly的只读不可变set接口。
Tip19:使用清晰协调的全名方式
- 尽量清晰简洁的完整描述
- 适应OC的语言风格,并乐于使用它
- 带返回值的方法,首词最好是返回值类型,除非前面还有类型修饰词。取方法一般不需要,因为其属性命名应该足以反映其类型。
- 参数前面体现出参数的类型
- BOOL类型变量的get读方法,最好加
is
或者has
之类的前缀。 - get前缀的读方法,应该是返回值被接收者保存的方法。
- 方法起名风格与当前框架、代码相适应。
Tip20:私有方法加前缀
- 公开方法修改名称,所有调用者都需要修改
- 私有方法只要修改本类内部代码即可。
- 尽量不要使用_前缀当私有方法,因为这是苹果默认系统级私有方法前缀,可能会导致覆写父类的私有方法。
Tip21: OC错误模型
- OC异常很难做到内存安全,所以通常做法是只有在极小可能下抛出异常,一旦异常,程序应该退出,而不恢复现场。
- 只有致命错误(fatal error),才抛出异常。nonfatal error时,返回nil/0,或者使用NSError
- NSError对象包含:
- Error domain :错误范围,通常用一个特有全局变量定义。
- Error Code: 采用enum来定义的错误代码。
- User info: 额外的错误信息。
- 非致命错误,通过代理或其他方法,返回给调用者,由调用者来决定是否处理错误。
Tip22: NSCopying
- NSZone是历史开发时有内存分区的概念,目前不再需要。
首先声明类遵守NSCopying协议
@interface AELClassName : NSObject <NSCopying>
@end
@implementation AELClassName
实现Copy方法
-(id)copyWithZone:(NSZone *)zone{
AELClassName *copy = [[[self class] allocWithZone:zone] initwWith...];
return copy;
}
- NSCopy时,对于可变set对象,需要copy一份。
-
不可变set,可以指向同一对象,不需要copy。
-
NSMutableCopying协议供有可变类、不可变类区分的父类实现可变子类时实现。此时:
- 可变对像调用[mutableClass copy]方法时,返回的是不可变对象。
- 不可变对象调用[class mutableCopy]方法时,返回的是可变对象。
- 大多数情况下,拷贝执行的都是浅拷贝,只有在必要的情况下,才需要实现深拷贝方法:
-(id)initWithSet:(NSArray *)array copyItems:(BOOL)copyItem
第四章 delegate代理与Category扩展
Tip23:通过代理与数据源(data sourse)进行通信
- Delegate代理模式即是定义标准接口,要求实现这一代理的对象,实现这组接口,从而在运行期向代理对象发送代理消息。
-
Delegate定义的接口,即为@protocol协议
-
Delegate对象一定要是weak,防止循环引用
Tip24: 尽量将类子类化
- 将复杂的数据类,尽量抽象成为基本属性类,可以尽可能的子类化
Tip25: Category使用时记得加前缀
- 加前缀的目的,就是为了防止命名冲突。
- 多个Category如果同时修改某一方法,就会产生多次覆写。
Tip26: Category不要加属性(变量)
- Category扩展要实现变量,很多时候需要声明@dynamic变量,并在运行期提供存取方法。
- category中添加的变量,很容易出现内存管理问题,并破坏了类的完整性,不易于维护。
Tip27 在.m文件中实现私有类和变量
- 将私有类和私有变量的定义,放在.m文件中,不要在.h文件中公开。
- 这种定义只是防止别人偷窥,但无法防止别人调用。因为消息机制和运行期的问题。
- 在OC和C++混写时,将C++实现在.m文件中,可以保持对外公开的接口均一致为OC状态。
- 可以将public中的只读变量,在.m文件中重新声明为可写变量,可以方便管理数据。但要注意的是,外部观察者读取的同时,内部写入数据,可能引起竞争条件(rece condition),需要加锁或其他同步方案来解决。
Tip28: id匿名实现方法
- 不关心id的具体类型,只要求对象实现
协议 - 不需要关心具体实现是什么类,只知道匿名对象可以实现指定的协议方法,封装了实现细节,又避开了OC只能单继承的缺点。
第五章 内存管理
Tip29: 内存引用计数
- retain +1 alloc +1 copy +1 release -1
- 对象引用计数归零后,deallocated后,会进入avaiable pool(可用内存池),只有在覆写内存后,原对象才真正消失。
- release后,将对象=nil,可以清空指针,防止调用无效对象。
-(void)setVaild:(id)vaild{
[vaild retain];
[_vaild release];
_vaild = vaild;
}
- autorelease会下一event loop里对retinaCount -1,可能会更高。
- autorelease适合需要返回对象时,将返回对象放到循环池内,
- 引用计数解决不了循环引用的问题(retain cycle),需要weak或强制置空的方式来处理。
- 使用newcopyallocmutableCopy开头命名的方法或对象,会自动导致强引用。
- ARC只管理OC对象,coreFoundation等对象仍然要手动调用CFRetain/CFRelease来释放。
Tip31: dealloc方法
- 不要自己调用dealloc方法
- 大内容,在内存吃紧时,手动清理,不要依赖dealloc方法释放。
Tip32: 异常处理的内存安全
- try如果在没有release时抛出异常,catch里无法得知强引用对象。
- 在MRC时,可以将release语句,放到@finally块中释放内存,但ARC无法这么处理。
- 使用
-fojbc-arc-exceptions
标签,可以在ARC时打开MRC的@finally处理异常的方式,但应该只在C++兼容模式时打开这一功能,减少性能损耗代价。
Tip33:避免循环引用
- 垃圾回收机制可以处理循环引用,但Mac在10.8之后不支持,iOS从来不支持。
- 弱引用可以解决这一问题,但弱引用只是不持有对象,但对象被强引用对象释放后,弱引用者再发消息,会崩溃。
- weak可以在强引用对象被释放后,将弱引用对象=nil,但如果不处理,依然会有逻辑问题。
Tip34: 自动释放池改进内存管理
- @autoreleasepool可以控制内存释放的范围,减少循环等批量代码块产生的变量堆积
- @autoreleasepool类似于stack结构,入池相当于入栈,释放相当于出栈。
Tip35:僵尸对象帮助调试
- 打开Zombie Object调试功能后,在调试运行时,会将内存回收对象放入特殊的Zombie对象池中,当zombie对象收到消息,会崩溃并提示zombie具体对象。
- Zombie对象不会被覆写,确保它与内存回收时一致
- @try-@catch的捕获异常的方法,最大的问题在于,捕获异常时,无法处理内存引用计数问题释放。
Tip36:不要管retainCount计数
-无论是ARC还是MRC,retianCount都无用
第六章 block和GCD
- block:闭包,可以将代码块当做对象传递,并保留代码访问作用域内的变量功能。
- GCD:grand Central Dispatch,提供对于线程的基于’派发队列’的抽象。
Tip37: 理解block
- 技术上说,block基于C语言。所以C、OC、C++都可以调用
int addtional = 5;
__block NSInteger changeAddtional = 10;
int (^blcokName) (int a, int b) = ^(int a, int b){
// addtional = 15; << error,不能修改非__block修饰的变量。
changeAddtional = 15;
return a+b+addtional+changeAddtional;
}
int add = blockName(3,5);
- block可以捕获定义block时的有效变量,并读取。
- 只有使用__block修饰的变量,才允许可以在blcok内部修改。
- 如果self对象中包含的对象持有block,而在此block里,又引用了self,那就会导致循环引用
- block捕获的对象,会拷贝一份指针到block对象域中
- block定义时,会保存在栈中,只有执行[^{} copy]时,才会移到堆中。移到堆中的block,具有引用计数功能,需要进行内存管理。
- block如果没有需要捕获变量,会被定义为gloal block,可以声明在全局内存中,也不会有copy操作和内存管理的必要。
Tip38: 为block创建typedef
//定义
typedef 返回值(^BlockName) (参数);
//使用时
BlockName block = ^(){
};
//普通函数使用block参数
-(void)startWithCompletionHandler:(void(^)(NSData *date, NSError *error))completion;
//更佳使用方法:
typedef void(^EOCCompletionHandler)(NSDate *date, NSError *error);
-(void)startWithCompletionHandler:(EOCCompletionHandler)completion;
- 给block命名,使代码更易读,对于将block当做参数传递的函数,更容易维护和变更。
Tip39: 在Handler使用block替代Delegate
- block让回调更直观,代码逻辑更清晰
- block回调将错误和正确返回同时处理的优点是更容易处理特殊情况(正确下载一半错误、正确的时候也会出现错误的情况(下载数据过短等)),缺点是返回处理代码会过长,逻辑复杂。
Tip40: 注意block产生的循环引用
- 注意在block块引用持有block的对象(一般为self对象)
- 使用完后,可以手动在block块里释放持有block的对象
- 更好的方式,是AFNetworing采用的__weak + __strong方案
Tip41: 少用锁,多用派发队列
- 同步锁的两种形式: @synchronied(self){} //同步块, [[NSLock all]init]//同步锁
- 锁的系统开销比较大,还可能会导致死锁问题出现。
- 使用GCD的串行同步队列替代锁机制
_syncQueue = dispatch_queue_create("com.aeieli.syncQueue",NULL);
-(NSString *)someString{
__block NSString *localSomeString;
dispatch_sync(_syncQueue,^{
localsSomeString = _someString;
});
}
-(void)setSomeString:(NSString*)someString{
dispatch_sync(_syncQueue,^{
_someString = someString;
});
}
- 对于写入异步方法,可以使用barrier来实现单独执行写操作。
Tips 42: 用GCD替换performSelector方法
- 使用performSelector来调用方法,最大的问题是ARC无法判断如何进行内存管理。
-
performSelector无法使用整型、结构体等参数,也无法返回这些变量,只能返回指针类型
-
使用dispatch_after来替代延后执行
Tip43: GCD、NSOpeationQueue的使用场景
- GCD是纯C的API,NSOperation是OC对象,底层实现现在基于GCD
- NSOperation的优势:
- 可以取消:NSOpertaion队列被执行前,是可以cancel的,但执行后,与GCD一样无法取消
- 操作队列可以有依赖
- 含有适合KVO观察的属性
- 可以指定具体任务的优先级
- 基于OC的NSOperation,可以重载和继承
Tip44: Dispatch_group的使用
-
dispatch_group采用类似引用计数的方式,使用
dispatch_group_enter(dispatch_group_t group);
将任务派发进组,使用dispatch_group_leave(dispatch_group_t group);
离开组 -
dispatch_group有一系列的通知机制:dispatch_group_wait、dispatch_group_notify等。
-
GCD会自动创建新线程或复用旧线程,所以使用异步并发队列,可以提高系统性能利用率。
Tip45: dispatch_once 保证任务的一次运行
- dispatch_once 依赖于dispatch_once_t标识,此标识必须对于只需执行一次的块来说,每次调用的标识必须完全相同,并与其他标识不同。
Tip46: 不要使用dispatch_get_current_queue
-
使用dispatch_get_current_queue时,返回的是当前队列,无法反映出父队列、队列嵌套的关系,所以不能保证检测出的队列与实际执行的队列标签一致
-
使用dispatch_queue_set_specific的队列持有数据模式,来解决队列间的数据持有问题。
第7章 系统框架
Tip47: 熟悉系统框架
-
系统框架都为动态库(dynamic library),iOS第三方框架为静态库(static library)
-
熟悉系统框架,特别是纯C写出的框架:CoreGraphics、CoreText、CFNetwork等
Tip48: 多用块枚举
-
几种遍历方式: for 循环、 NSEnumerator遍历、for… in枚举、 enumerateObjectsUserBlock:块枚举
-
块枚举的优势:
- 在枚举时直接可以获得下标、键值等信息。
- 枚举时可以指定获取的对方,避免类型转换操作
- 可以使用NSEnumberationOptions指定遍历模式是按位。
- 可以指定开启并发提高速度
Tip49: 使用too-free bridging转换Foundation与CoreFoundation类
- __bridge ARC仍然具备OC对象所有权, __bridge_retained 将会把所有权交给CF等C语言对象,由C语言对象负责管理内存。
- __bridge_transfer是反向将C语言对象转换成OC对象
- 使用C语言创建的一些Collection对象,可以实现一些非标准的OC对象功能,特别是在内存管理和操作上,具有相对ARC更大的自主权。
Tip50: 用NSCache来实现缓存
- NSCache具有低内存自动删减机制、自动删减最久未使用的对象等系统底层的缓存机制。
- NSCache不拷贝对象,而是保留(持有)对象
- NSCache是线程安全的
- 只有重新计算很费事的数据,才值得放入缓存
Tip51: 精简initialize与load代码
- load方法执行时,使用其他类是不安全的
- load方法不遵守继承规则,当类没有实现load方法时,并不从超类寻找load方法。
-
类和category里的load方法都会被执行,首先执行类的方法,再执行category里的load方法。
-
initialize方法遵守继承规则,当父类实现了initialize方法,那么在类初始化的时候,先调用父类的initialize方法,再调用子类的initialize方法(即使没有实现,也会直接从父类继承这一方法并调用)
- initialize只有在类被调用前才会加载,而load在应用初始化时会调用
Tip52: NSTimer会保留目标对象
- NSTimer的target对象是Self,如果此时NSTimer又同时是Self的变量(为了方便管理,这几乎是必然的),那么就形成了循环引用
- 可以使用GCD、扩展使用Block方法的NSTimer对象,来适当解决循环引用的问题。