《Effective Objective-C 2.0》笔记

这里仅记录对我有帮助的内容,不是对这本书的全面概括。有些我目前还没怎么用过的东西,比如GCD,现在读来还没什么感觉,所以没有详细记录;其他的尽量写得简单易懂一些,方便自己查阅。有疑问之处以原书为准。

Forward Declaration

[Item 2]

把声明(#import、协议、属性等)尽量移到.m文件而不是放在头文件里。如果A类的属性或方法中用到了B类,则在A.h中用@Class B告知编译器B类的存在即可,不要#import B.h除非A是B的子类。这样做可以:

  1. 当这个头文件被引用时,避免引用者知道不必要的细节,节省其编译时间
  2. 如果有两个类需要相互引用,头文件中互相#import xxx.h会造成交叉引用,移到.m文件即可避免
  3. 一些私有的方法、属性和协议等可以避免在头文件中暴露

Literal Syntax

[Item 3]

NSNumber不只可以包装纯数字:

NSNumber *boolNumber = @YES;
NSNumber *charNumber = @'a';

Literal Syntax只能创建不可变对象,对于可变对象,应该这样写:

NSMutableArray *mutableArray = [@[@1, @2, @3] mutableCopy];

Define Constants

[Item 4]

不要使用#define,因为它不包含类型信息,而且可能不知不觉地在别处被重定义。

  • 如果一个常量仅在一个.m文件中使用,应该这样写:
static const NSTimeInterval kAnimationDuration = 0.3;
  • 如果一个常量将被多个文件共享(比如NSNotification的名称),应该这样写:
// EOCView.h
extern NSString *const EOCViewDidAnimateNotification;

// EOCView.m
NSString *const EOCViewDidAnimateNotification = @"EOCViewDidAnimateNotification";

注意

  • 常量名的命名规范:只用于一个文件时应该以”k”为前缀,用于多个文件时应该以所在类的类名等为前缀

Enum

[Item 5]

  • 通用枚举:
typedef NS_ENUM(NSUInteger, EOCConnectionState) {...};
  • 二进制位枚举:
typedef NS_OPTIONS(NSUInteger, EOCDirection) {
    EOCDirectionUp      = 1 << 0,
    EOCDirectionLeft      = 1 << 1,
    EOCDirectionRight    = 1 << 2
};

这种枚举用于描述或的关系,比如方向为左或为右时执行某一方法,应该这样判断:

EOCDirection permittedDirection = EOCDirectionLeft | EOCDirectionRight;
if (direction & permittedDirection) {
  [self doSomething];
}

注意

  • switch语句中判断这样的枚举类型时不要default:。否则如果漏写了其中一种枚举的case xxx:,编译器不会提示。

Property and Instance Variable

[Item 7]

Property

@property相当于创建了加下划线的成员变量,以及settergetter方法。@property后面括号里的内容是为了给编译器自动创建settergetter提供依据,比如若不指定为nonatomic,则默认为atomic,系统会保证settergetter的完整执行,不会在set到一半时get;若有readonly,则编译器不生成setter

这个setter仅是对外部而言的,在类的内部仍然可以通过下划线加属性名直接访问对应的成员变量,从而修改其值。用点语法访问属性,一定会调用settergetter。若有readonly,则不能用点语法来赋值。

建议用点语法来set,方便在setter方法里设断点观察状态;用下划线来get,这样速度快。

注意

  • init方法一定用下划线来set,因为setter方法可能被子类重写,比如要求一个字符串不能为空;而父类初始化时就是将字符串置空,造成init失败。
  • 如果存在懒加载,即getter中判断实例不存在,才初始化一个实例,这时应该用点语法,保证调用getter,而不是用下划线,否则可能取到nil

Instance Variable

头文件里@interface后大括号里声明的是成员变量,可以被以下三种符号修饰:

  • @public 对所有类都可见
  • @protected 对该类及其子类可见
  • @private 仅对该类可见

这些变量是没有settergetter方法的,不能用点语法访问;在该类及其子类中可以直接用变量名来访问,在其他类中可以用实例名加->来访问。

isEqual:

[Item 8] [Item 14]

对于对象而言,==所比的是两个对象的地址是否相同(identical),而不是两个地址不同的对象的内容是否相同(equal)。后一种比较应该用isEqual:方法。系统类如UIColorNSArray等都可以直接调用该方法,自定义类则不然,isEqual:返回的是用==判断的结果,因此需要重写该方法。

注意到isEqual方法接受的参数是id类型,所以重写时第一步就是判断类型是否相同:

if (![object isMemberOfClass:self.class]) return NO;

然后一个一个地判断其他成员变量是否相同。

另外需要重写的是hash方法。NSSetNSDictionary判断两个元素是否相等时,首先比较hash值,若相等,再调用isEqual:hash值是由实例的内容决定的,equal的实例hash值一定相同,但hash值相同时不一定equal。

系统类都有hash方法,但自定义类的hash方法如果不重写,返回的是地址值,即只有identical时hash值才相同。重写时应该对所有成员变量hash值做异或(^):

- (NSUInteger)hash {
    NSUInteger firstNameHash = [_firstName hash];
    NSUInteger lastNameHash = [_lastName hash];
    NSUInteger ageHash = _age;
    return firstNameHash ^ lastNameHash ^ ageHash;
}

注意

  • 如果将可变对象加入NSSet,然后修改它,使之与集合中另一元素内容相同,那么对这个NSSetcopy的结果是元素会变少,因为不能向集合加入与已有元素equal的对象。必须注意count减少的问题,可能带来bug。(也可以利用这一特性,通过NSArray->NSSet->NSArray来去掉数组中重复的对象)
  • 判断类型应该用isMemberOfClass:isKindOfClass:,而不是[object class] == [self.class]。如果目标类利用了消息传递机制,比如NSProxy==比的是proxy类本身,而isMemberOfClass:isKindOfClass:比的是被代理的类,即真正接收到message的类。

Class Cluster

[Item 9]

初始化一个UIButton时,选择不同的type,实际上系统会switch(type),然后创建不同的UIButton的子类。向外暴露的只有UIButton这一个父类,而其每一个子类都重写了父类的所有方法。这就是类簇的设计模式,其他很多系统类如NSArrayNSString等都采用这一模式。

注意

  • 此时[newButton isMemberOfClass:UIButton.class]的结果一定是NO,应该用isKindOf:来判断。

Message Forwarding

[Item 12] [Item 13]

[Item 12]介绍了消息传递机制,以及为@dynamic属性创建settergetter的方法,与数据库交互的时候用得着。

[Item 13]介绍了在不创建子类的情况下为任何已有方法(如NSStringlowercaseString方法)添加附加操作(比如打印出lowercaseString执行前后的字符串),从而监控方法的执行。此法在debug时有用,但应该尽量少用。

Prefixing

[Item 15]

所有的自定义类名C函数名全局变量名都应该加前缀,可以是APP名、公司名缩写等。前缀应由不少于3个字母组成(苹果保留所有2个字母的前缀),比如本书常用的EOC

如果在自己创建的库中引入了第三方库,则应该给后者也加上自己的前缀(XYZLibrary -> EOCXYZLibrary),因为当自己创建的库再被其他人引用的时候,其他人可能又引用了同样的第三方库(可能是不同版本的),编译时可能报duplicated symbols错误。

Designated Initializer

[Item 16]

init方法可以有很多个,但应该有一个designated initializer,即只有这一个方法是会真正创建实例的,而其他init方法要么都调用这一方法,要么抛异常(尽量避免抛异常;可以赋默认值,然后正常实例化)。

注意

  • 如果在子类中换用另一方法作为designated initializer,记得重写父类的designated initializer,使之也调用这一方法来创建实例。

Description Information

[Item 17]

为了NSLog(@"%@", customObject);能打印出自定义类的实例的具体信息,必须重写其description方法:

- (NSString *)description {
    return [NSString stringWithFormat:@"%@ %@", 
            _firstName,
            _lastName];
}

另外为了断点时po customObject也能打印出具体信息,还应重写debugDescription方法:

- (NSString *)debugDescription {
    return [NSString stringWithFormat:@"<%@: %p, \"%@ %@\">", 
            self, 
            [self class], 
            _firstName, 
            _lastName];
}

具体信息的部分用字典来写就更漂亮一些:

- (NSString *)debugDescription {
    return [NSString stringWithFormat:@"<%@: %p, %@>", 
            self, 
            [self class], 
            @{@"firstName":_firstName,
              @"lastName":_lastName}
            ];
}

Immutable

[Item 18]

为了避免数据遭到不必要的修改:

  1. 不应该被外部直接修改的属性,应该声明(readonly)(可以在Extension中重新声明为(readwrite),使它对外只读,对内可读写)。
  2. 不要把NSMutable(Array/Dictionary/Set...)暴露出来,应该只留一个setter给外部使用,以免它们被其他类修改时,类自身难以察觉。
  3. 如果数据不是特别多,copy的代价不是特别大,留给其他类的getter应尽量用copy方法,返回一个不可变的对象。

Naming

[Item 19] [Item 20]

  1. 如果方法返回一个新生成的值,应该以该值的类型开头,例如stringWithFormat:。直接返回某个属性的方法除外,直接用属性名即可。
  2. 方法名里应该用整个单词(string),不要用缩写(str)。
  3. BOOL属性加前缀is。返回BOOL值的方法(但不是直接返回某个BOOL型的属性)应该以ishas为前缀。
  4. 用前缀p_来标示私有方法。不要只用下划线,因为苹果的保留这种方式,应避免无意中覆盖系统方法的情况。

Error Handling

[Item 21] [Item 32]

与JAVA、C++等语言不同,OC不会频繁地用到异常(NSException)。虽然也有@try@catch@finally这些处理异常的语句,但在纯OC文件中,异常一般只用于致命的、必须中断APP生命周期的情况,其他非致命情况下都用NSError来告知错误信息;而前述那些语句一般用在Objective-C++文件里,或者是调用第三方库、无法修改源代码时使用。

可以给被调用的方法传入一个NSError参数:

- (BOOL)doSomething:(NSError **)error {
    ......
    if (/* 出错 */) {
        *error = [NSError errorWithDomain:domain
                                     code:code
                                 userInfo:userInfo];
        return NO;
    }
}

然后做如下调用:

NSError *error = nil;
BOOL ret = [self doSomething:&error];
if (ret) {
    /* 查看error */
    ......
}

用额外的BOOL量来判断是否出错,而不是if(error),是因为调用者可能不关心具体错误是什么,所以这样调用:[self doSomething:nil]

注意

  • 传参时传的是*error的地址,所有有两个*。如果传参时只有一个*,那么传的就是指向error的指针的拷贝,在方法里面对它赋值,只是让这个拷贝指向了新的对象,而外面的指向error的指针以及error的内容均未改变。
  • ARC不会给NSException加上释放资源的代码,因为一般情况下出现异常都会引发APP终止,所有资源都被销毁。如果要像JAVA、C++等那样在@finally里面release资源,需要加上-fobjc-arc-exceptions的flag。

Copying

[Item 22]

要使自定义类具有copy方法,应该重写的是copyWithZone方法

- (id)copyWithZone:(NSZone *)zone {
    return [[[self class] allocWithZone:zone] init...];
}

然后调用copy方法即可实现复制。如果该类有可变的属性,需要使用mutableCopy,那么还需要重写mutableCopyWithZone方法。

注意

  • copyWithZone方法里应该用designated initializer来初始化实例。
  • 所有类copymutableCopy都不保证是深拷贝还是浅拷贝,只能根据文档判断。自定义时应该尽量用浅拷贝,减少系统开销。

Protocol and Delegate

[Item 23]

  1. 一个对象向多个对象传消息 -> NSNotification
  2. 多个对象向一个对象传消息 -> Protocol and Delegate

第二种的典型情况包括一个页面持有多个下载器,页面遵循一个protocol,作为下载器的delegate;下载器的下载进度有更新时,把下载进度传给页面,让页面以进度为参数执行某些协议中规定了的方法。

协议规定了一些方法,一些是@require的,即声明了遵循此协议的代理类必须实现的方法;另一些是@optional的,代理类可以选择性实现。对于后者,调用协议方法前必须用respondToSelector:来检查代理类是否实现了这个方法。比如前面的例子中,页面可能不关心下载的中间进度,只关心下载完成的信息,那么下载器在进度更新但下载未完成时,就不必也不能调用协议方法。

对于频繁调用的可选的协议方法(如下载器更新进度时调用的方法),为了避免多次检查respondToSelector:,可以只在初始化时检查一次,把结果存在全局的结构体里面,此后直接向结构体查询。

注意

  • delegate作为属性,必须用weak修饰。否则如前面下载器的例子,页面持有下载器,下载器又通过delegate持有页面,就造成循环引用,应该让后者为weak
@property (nonatomic, weak) id <EOCNetworkFetcherDelegate> delegate;

Category and Extension (Class-Continuation Category)

[Item 24] [Item 25] [Item 27]

Category

Category可以给已有的类(包括系统类)添加方法,但不能添加属性。比如给CustomClass添加一个名为SayHellocategory,Xcode会创建名为CustomClass+SayHello.h.m文件,然后可以添加方法,比如sayHello。在其他类中#import "CustomClass+SayHello.h",则初始化的CustomClass的实例就可以执行sayHello方法。如果CustomClass的子类也#import "CustomClass+SayHello.h",则sayHello方法也被子类继承。

每个类本身实质上都是单例,在某一个category里面定义了一个方法,就是把方法加到类自身的单例里面,并没有创建新的子类。所以如果既有#import "CustomClass+SayHello.h",又有#import "CustomClass+SayBye.h",则初始化的实例仍属于CustomClass,但既能执行sayHello,又能执行sayBye。实际上在任何地方,即使不引用category的头文件,也能利用消息传递机制直接调用category新加的方法,引用头文件只是让编译器知道有这个方法。

Category新加入的方法在APP整个生命周期中都有效。如果多个category都有同名的方法,它们会相互覆盖,最终执行的是系统最后load进来的方法。如果第三方库被他人引用(尤其是第三方库给系统类新加了category时),用户新创建category,且方法名与第三方库重复,就说不清最后执行的是哪个方法。所以应该给category及其方法都加上前缀(前者加EOC_,后者加eoc_)。

Extension (Class-Continuation Category)

Extension可以声明一些私有的成员变量和方法:


@interface CustomClass () <PrivateProtocol> {
    id m_variable;
}

@property (nonatomic, readwrite) id object;

- (void)p_method;

@end

在类名后加一对小括号就是extension,这段代码一般直接写在.m文件里。

  1. 大括号里可以声明私有变量,对其他类(包括子类)都是不可见的(也可以把私有变量写在@implemention后的大括号里,效果是一样的,只是习惯问题)。
  2. 可以用@property把已经在头文件里声明为readonly的属性声明为readwrite,这样这个属性对外只读,对本类可读可写
  3. 可以声明私有方法,应该以p_为前缀。
  4. 如果不想把遵循的协议暴露在头文件里,可以写在extension里面。

Anonymous Object Using Protocol

[Item 28]

隐藏类名可以通过id <Protocol> object;来实现。

有些情况下类实现的方法(遵循的协议)比类名更重要,比如某个方法需要返回某个操作数据库的类,但操作不同数据库(MySQL,PostgreSQL,…)的类来自不同的库,继承自不同父类,因此方法返回类型无法设为它们共同的父类,设为id然后再判断类型也很麻烦。

这种情况下可以令这些操作类都遵循统一的协议,都实现操作数据库需要的几种方法(connect,disconnect,performQuery:,…),然后方法返回的类型就设为id <EOCDatabaseConnection>,这样就不必判断具体类型,直接使用协议方法即可。

Dealloc Method

[Item 31]

dealloc方法应该处理:

  1. malloc分配的空间应该free
  2. CoreFoundation对象应该CFRelease
  3. NSNotificationCenter移除observer
  4. KVO移除observer

应该避免在dealloc方法里调用其他方法,因为后者用到的对象可能已经被销毁。不是所有的清理工作都应该由dealloc承担,比如以下情况:

  1. 一些比较expensive的资源,如fileDescriptorsocketsmalloc分配的内存,不应该多次销毁和重新创建,应该写openclose方法,多次使用。
  2. APP被突然中止时dealloc不会被调用,系统会直接销毁所有资源。需要在这时完成的工作,比如保存信息,应该写在AppDelegateapplicationWillTerminate:里。

Weak

[Item 33]

unsafe_unretainedweak的区别是,当被引用的对象被释放时,前者仍然指向那块内存,而weak指针在ARC的作用下变成nil

只有持有一个对象才用强指针。例如,在头文件中声明UI控件都用weak,是因为StoryBoard才是控件的真正持有者,其他类只是弱引用。

Autoreleasepool

[Item 34]

对于一段反复执行的代码,必须一个for循环,如果每次它都会创建临时对象,那么临时对象就会一直堆积;应该在代码段的首尾加上一个@autoreleasepool,使得每次执行后都释放临时对象:

for (/* 循环条件 */) {
    @autoreleasepool {
        ......
    }
}

Zombie Objects

[Item 35]

已经被释放的对象,只要它所在的内存区域还没有被重写,它就仍然可能接受消息并响应;即使内存被重写,新的对象也可能接收到发给旧对象的消息,作出不可预料的响应。为了记录运行过程中是否向已经被释放的对象发送了消息,可以在debug时Enable Zombie Objects。此时旧对象被释放后,这块内存不会被重写,如果尝试向它发送消息,会引起异常message sent to deallocated instance...

Block

[Item 37] [Item 38] [Item 39] [Item 40] [Item 52]

存储位置

如果一个block没有用到任何外部变量,它是global的,和其他静态变量存在一起。如果用到了外部变量,它将被创建在栈上;如果对block用了copy方法,或者在ARC环境下它被赋值给一个强指针,它就会被复制到堆上。

typedef

int (^EOCSomeBlock)(BOOL flag, int value) = ^(BOOL flag, int value) {...};

这样的写法会降低可读性,名称EOCSomeBlock被放到了括号里面。应该这样写:

typedef int (^EOCSomeBlock)(BOOL flag, int value);
EOCSomeBlock block = ^(BOOL flag, int value) {...};

如果有两个block的参数和返回值都一样,但完成的功能不同,也应该写成两个typefdef,分别用不同的名字,提高可读性。记得命名时要加上必要的前缀。

Handler

有时可以用block来代替delegate的设计模式,这样不需要把处理回调的代码写在单独的代理方法里面,而是就近写在调用处,增强可读性。也可以让block回传NSError来判断执行情况:

typedef void (^EOCNetworkFetcherCompletionHandler)(NSData *data, NSError *error);

另一个好处是block很容易通过GCD等安排在指定线程上执行。

Break Retain Cycle

block很容易造成循环引用。如果block里引用了当前类的成员变量,它就会保持对self的强引用;把它作为参数传给另一个实例(比如NetworkFetcher)的方法,则这个实例持有block_completionHandler),同时它又被self持有(_networkFetcher),造成循环引用。

一种解决方法是解除self对另一个实例的持有,即在block里面最后令方法所在实例为nil_networkFetcher = nil;),这就要求每次传进来的block都加这一句。如果是我们自己编写了NetworkFetcher类,作为第三方库被别人引用,很难保证用户一定写了这一句。

另一个办法是解除另一个实例对block的持有,即在另一个实例中用完block以后,把它释放掉:

_completionHandler(_downloadedData);
self.completionBlock = nil;

这样就在NetworkFetcher内部解决了循环引用的问题,比较安全。

还可以用__weak指针来解除blockself的持有,即:

__weak EOCClass *weakSelf = self;
EOCNetworkFetcherCompletionHandler completionHandler = ^(NSData *data, NSError *error) {
    EOCClass *strongSelf = weakSelf;
    [strongSelf doSomething];
    ......
}

block里面先转化为强指针,避免执行的过程中self被释放掉。执行完后遇到大括号,strongSelf就被销毁,再也不存在对self的强引用。

GCD

[Item 41] 安排同步/异步操作

[Item 42] 代替performSelector系列

[Item 43] 有些情况下更适合用NSOperationNSOperationQueue,因为相较于GCD,它们可以取消执行、设置依赖、KVO观察完成进度、设置单个操作(而不是整个队列)的优先级、可重用。不过它们是OC类,没有GCD(C API)那么轻便。

[Item 44] 安排分组任务

[Item 45] 对于只执行一次,而且要保证线程安全的代码,应该用dispatch_once,比如单例的sharedInstance方法,原来的写法是:

static EOCClass *sharedInstance = nil;
@synchronized(self) {
    if (!sharedInstance) {
        sharedInstance = [[self alloc] init...];
    }
}

可以改成:

static EOCClass *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
    sharedInstance = [[self alloc] init...];
});

后一种实现比使用@synchronized快得多。

Enumeration

[Item 48]

快速枚举for...in,可以这样实现反向枚举:

for (id object in [anArray reverseObjectEnumerator]) {...}

block枚举enumerate...UsingBlock:方法,它的block有一个参数BOOL *stop,用*stop = YES即可停止枚举。这种枚举的好处在于:

  • 枚举时可有选项,用enumerate...WithOptions:usingBlock:方法,第一个参数是NS_OPTIONS类型,可选枚举时是否并发是否反向
  • 枚举NSArray的时候能知道下标
  • 枚举NSDictionary可以同时得到keyvalue,而快速枚举只能枚举keyvalue,再用key去取value或相反

block里面keyvalue默认的类型是id,这是可以直接改的,比如改成:

[aDictionary enumerateKeysAndObjectsUsingBlock:
    ^(NSString *key, CustomClass *obj, BOOL *stop) {...}];

CF Objects

[Item 49]

CoreFoundation是C的API,其类以CF为前缀;Foundation是OC的库,其类以NS为前缀。

NS -> CF

  • __bridge是不移交所有权的转换,ARC仍然负责其引用计数
  • __bridge_retained则是移交控制权,ARC不再负责,需要手动调用CFRelease()

CF -> NS

  • __bridge仍是不移交所有权的转换,仍需要手动调用CFRelease()
  • __bridge_transfer则是把引用计数交给ARC

很多NS类都有对应的CF类,但两者有不同的特点。例如NSDictionary的内存管理策略是keycopyvalueretain,策略不能更改;而CFDictionary的内存管理都是可以自定义的。

NSCache and NSPurgeableData

[Item 50]

NSCache

对于比较expensive的资源,比如从网络下载的或者从硬盘读取的图片,应该在内存允许的情况下存进字典,下一次使用时直接从内存读取,而不是重新下载或从硬盘读取。但内存紧张时,如NSDictionary之类不会自动决定要释放哪些资源,为避免手动管理的麻烦,应该用NSCache

NSCacheNSDictionary很像,都有一对一对的keyvalue。但NSCache可以用不可copy的对象作为key,而且是线程安全的,可以在不加锁的状态下给它发数条读取命令。

NSCache用两个参数来辅助决定是否释放资源、释放哪些资源:一是keyvalue对的总数,而是所有valuecost的总和的上限。这两个参数都是手动设置的,前一个比较简单,也就是设定字典的count值的上限;第二个所谓的cost是需要自己定义的,建议直接用NSData的数据大小,不要用复杂的方法来计算cost,增加系统开销。代码可能是这样的:

_cache.countLimit = 100;
_cache.totalCostLimit = 5 * 1024 * 1024;

即最多装100个对象,cost总量的上限,也就是数据大小的上限值是5MB。

注意

  • 这些上限值仅作参考,并不是超过了总量就一定会释放、没超总量就一定不释放,全凭系统决定,不要手动干预这个过程。

NSPurgeableData

NSPurgeableData是可以配合NSCache使用的一个类,可以用NSData来创建。NSCache有一个BOOL型的属性evictsObjectsWithDiscardedContent,设为YES时,NSPurgeableData可以被系统直接移出NSCache;设为NO时不会被自动移除,但其数据可能被系统释放掉,使用之前须调用isContentDiscarded检查数据还在不在。

使用NSPurgeableData之前(除了刚刚初始化的时候),应该调用beginContenAcess方法,告知系统即将开始使用资源,不要在使用期间释放资源;用完以后要执行endContentAcess方法,告诉系统现在开始可以自行决定是否释放。

Load and Intialize Methods

[Item 51]

NSObject有一个叫load的类方法,当一个类或是category加入runtime时被调用一次。此时处于同一个library里面的其他类都不保证已被load,所以不要调用他们。这个方法里只应该做简单的打印,不要做太多操作,尤其是会阻塞主线程的。

另外有一个叫initialize的类方法,当一个类第一次被使用时调用一次(未被使用的类的initialize方法永远不被调用)。此时执行该类的其他方法或者调用其他类都是安全的,但仍应该避免做太多操作。除了打印以外,可以初始化一些编译时不能确定的量,如:

static NSMutableArray *anArray;

@implemention EOCClass

+ (void)load {
    NSLog("EOCClass loaded");
}

+ (void)initialize {
    if (self == [EOCClass class]) {
        anArray = [NSMutableArray new];
    }
}

注意

  • load方法不会被继承或重写,所以不需要判断classinitialize则参与继承和重写。因为静态变量只有一个,被父类和子类共享,所以要判断class,子类无需再初始化一遍。

NSTimer

[Item 52]

NSTimer也很容易造成循环引用,因为它创建时有一句target:self。一种解决方法是给NSTimer加一个category,于其内做初始化,target:self就相当于target:[NSTimer class],虽然循环引用仍然存在,但NSTimer类自身是个单例,自己循环引用自己是没问题的。

此时可以把原来要执行的方法装进block传到category,放在NSTimeruserInfo里面;新建一个方法,接收NSTimer作为参数,方法的内容就是从其userInfo里取出block并执行。将这一方法作为初始化NSTimer时用的@selectorcategory的代码如下:

+ (NSTimer *)eoc_scheduledTimerWithTimeInterval:(NSTimeInterval)interval
                                          block:(void(^)())block
                                        repeats:(BOOL)repeats {
    return [self scheduledTimerWithTimeInterval:interval
                                         target:self
                                       selector:@selector(eoc_blockInvoke:)
                                       userInfo:[block copy]
                                        repeats:repeats];
}

+ (void) eoc_blockInvoke:(NSTimer *)timer {
    void (^block)() = timer.userInfo;
    if (block) {
        block();
    }
}

因为用到了block,也要注意它的循环引用问题。

    原文作者:伦啊伦
    原文地址: https://www.jianshu.com/p/8de922ab0cdb
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞