最近在重温这本OC经典之做《Effective Objective-C 2.0编写高质量iOS与OS X代码的52个有效方法》,这篇文章算是重温以后的产物吧,读完这篇文章你将快速读完这本书,因为我的能力有限,不免有一些遗漏或者错误,请各位看官不吝赐教!谢谢!同时若是有任何问题也能够在下方留言,欢迎一块儿交流进步!另外因为篇幅缘由,书中一些基础知识的介绍文中就省略掉了。php
Objective-C
从Smalltalk语言是从Smalltalk
语言演化而来,Smalltalk
是消息语言的鼻祖。Objective-C
是C语言
的超集,在C语言
基础上添加了面向对象等特性,可能一开始接触时你会以为语法有点奇怪,那是由于Objective-C
使用了动态绑定的消息结构
,而Java
,C++
等等语言使用的是函数调用。消息结构
与函数调用
的关键区别在于:函数调用的语言,在编译阶段由编译器
生成一些虚方法表
,在运行时从这个表找到所要执行的方法去执行。而使用了动态绑定的消息结构
在运行时接到一条消息,接下来要执行什么代码是运行期决定的,而不是编译器。@class xx.h
,这样作的好处会减小必定的编译时间。若是是用的#import
所有导入的话,会出现a.h
import了b.h
,当c.h
又import a.h
时,把b.h
也都导入了,若是只是用到类名,真的比较浪费,也不够优雅@class
向前声明,好比某个类要遵循一项协议,这个协议在另一个类中声明的,能够将协议这部分单独放在一个头文件,或者放在分类当中,以下降引用成本。1.多使用字面量语法来建立字符串,数组,字典等。
传统建立数组方法:git
NSArray *languages = [NSArray arrayWithObjects:@"PHP", @"Objective-C", someObject, @"Swift", @"Python", nil];
NSString *Swift = [languages objectAtIndex:2];
NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:@"key", @"value", nil];
NSString *value = [languages objectForKey:@"key"];
复制代码
字面量:github
NSArray *languages = @[@"PHP", @"Objective-C", someObject, @"Swift", @"Python"];
NSString *Swift = languages[2];
NSDictionary *dict = @{@"key" : @"value"};
NSString *value = languages[@"key"];
复制代码
这样作的好处:使代码更简洁,易读,也会避免nil问题。好比languages数据中 someObject 若是为nil时,字面量语法就会抛出异常,而使用传统方法建立的languages数组值确是@[@"PHP", @"Objective-C"];
由于字面量语法实际上是一种语法糖,效果是先建立了一个数组,而后再把括号中的对象都加到数组中来。
不过字面量语法有一个小缺点就是建立的数组,字符串等等对象都是不可变的,若是想要可变的对象须要本身多执行一步mutableCopy
,例如数据库
NSMutableArray *languages = [@[@"PHP", @"Objective-C", @"Swift", @"Python"] mutableCopy];
复制代码
这一条讲的是属性的基本概念,以及属性的各类修饰符,这些就很少啰嗦了,这里强调一下:缓存
readonly
。atomic
并不能保证多线程安全,例如一个线程连续屡次读取某个属性的值,而同时还有别的线程在修改这个属性值得时候,也仍是同样会读到不一样的值。atomic 的原理只是在 setter and getter 方法中加了一个@synchronized(self)
,因此iOS开发中属性都要声明为nonatomic
,由于atomic严重影响了性能,可是在Mac OSX上开发却一般不存在这个性能问题@property (nonatomic, strong) NSArray *arrayOfStrong;
@property (nonatomic, copy) NSArray *arrayOfCopy;
@property (nonatomic, strong) NSMutableArray *mutableArrayOfStrong;
@property (nonatomic, copy) NSMutableArray *mutableArrayOfCopy;
复制代码
具体运行示例点击查看
答案是正常应该这样声明安全
@property (nonatomic, copy) NSArray *arrayOfCopy;
@property (nonatomic, strong) NSMutableArray *mutableArrayOfStrong;
复制代码
思考下面输出什么?服务器
NSString *aString = @"iphone 8";
NSString *bString = [NSString stringWithFormat:@"iphone %i", 8];
NSLog(@"%d", [aString isEqual:bString]);
NSLog(@"%d", [aString isEqualToString:bString]);
NSLog(@"%d", aString == bString);
复制代码
答案是110
==
操做符只是比较了两个指针,而不是指针所指的对象网络
为何下面这段if 永远为false
id maybeAnArray = @[];
if ([maybeAnArray class] == [NSArray class]) {
//Code will never be executed
}
复制代码
由于[maybeAnArray class] 的返回永远不会是NSArray,NSArray是一个类族,返回的值一直都是NSArray的实体子类。大部分collection类都是某个类族中的’抽象基类’
因此上面的if想要有机会执行的话要改为
id maybeAnArray = @[];
if ([maybeAnArray isKindOfClass [NSArray class]) {
//Code probably be executed
}
复制代码
这样判断的意思是,maybeAnArray这个对象是不是NSArray类族中的一员
** 使用类族的好处:能够把实现细节隐藏再一套简单的公共接口后面 **
这条讲的是objc_setAssociatedObject
和objc_getAssociatedObject
,如何使用在这里就很少说了。值得强调的一点是,用关联对象可能会引入难于查找的bug,毕竟是在runtime阶段,因此可能要看状况谨慎选择
以前在了解Objective-C语言的起源
有提到过,Objective-C是用的消息结构
。这条就是让你理解一下怎么传递的消息。
objc_msgSend
,该函数定义以下:void objc_msgSend(id self, SEL cmd, ...)
复制代码
这是一个参数个数可变的函数,第一参数表明接收者
,第二个参数表明选择子
(OC函数名),后续的参数就是消息(OC函数调用)中的那些参数
id return = [git commit:parameter];
复制代码
上面的Objective-C方法在运行时会转换成以下函数:
id return = objc_msgSend(git, @selector(commit), parameter);
复制代码
objc_msgSend函数会在接收者所属的类中搜寻其方法列表
,若是能找到这个跟选择子名称相同的方法,就跳转到其实现代码,往下执行。如果当前类没找到,那就沿着继承体系继续向上查找,等找到合适方法以后再跳转 ,若是最终仍是找不到,那就进入消息转发
的流程去进行处理了。
快速映射表
(fast map)中,这样以来这个类一些频繁调用的方法会出如今fast map 中,不用再去一遍一遍的在方法列表
中搜索了。尾调用优化
,大概原理就是在函数末尾调用某个不含返回值函数时,编译器会自动的不在栈空间上从新进行分配内存,而是直接释放全部调用函数内部的局部变量,而后直接进入被调用函数的地址。关于这条这看看这篇文章:iOS理解Objective-C中消息转发机制附Demo
这条讲的主要内容就是 Method Swizzling,经过运行时的一些操做能够用另一份实现来替换掉原有的方法实现,每每被应用在向原有实现中添加新功能,好比扩展UIViewController,在viewDidLoad里面增长打印信息等。具体例子能够点击我查看
Objective-C类是由Class类型来表示的,它其实是一个指向objc_class结构体的指针。它的定义以下:
typedef struct objc_class *Class;
复制代码
在<objc/runtime.h>中能看到他的实现:
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY; ///< 指向metaClass(元类)
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE; ///< 父类
const char *name OBJC2_UNAVAILABLE; ///< 类名
long version OBJC2_UNAVAILABLE; ///< 类的版本信息,默认为0
long info OBJC2_UNAVAILABLE; ///< 类信息,供运行期使用的一些位标识
long instance_size OBJC2_UNAVAILABLE; ///< 该类的实例变量大小
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE; ///< 该类的成员变量链表
struct objc_method_list **methodLists OBJC2_UNAVAILABLE; ///< 方法定义的链表
struct objc_cache *cache OBJC2_UNAVAILABLE; ///< 方法缓存
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; ///< 协议链表
#endif
} OBJC2_UNAVAILABLE;
复制代码
此结构体存放的是类的“元数据”(metadata),例如类的实例实现了几个方法,具有多少实例变量等信息。
这里的isa指针指向的是另一个类叫作元类(metaClass)。那什么是元类呢?元类是类对象的类。也能够换一种容易理解的说法:
咱们来看一个很经典的图来加深理解:
能够总结为下:
Class
都有一个isa指针
指向一个惟一的Meta Class
Meta Class
的isa指针
都指向最上层的Meta Class
,这个Meta Class
是NSObject的Meta Class
。(包括NSObject的Meta Class
的isa指针
也是指向的NSObject的Meta Class
,也就是本身,这里造成了个闭环)Meta Class
的super class
指针指向它本来Class
的 Super Class的Meta Class
(这里最上层的NSObject的Meta Class
的super class
指针仍是指向本身)NSObject Class的super class
指向 nilObjective-C没有相似其余语言那样的命名空间机制(namespace),好比说PHP中的
<?php
namespace Root\Sub\subnamespace;
复制代码
这就会致使当你不当心实现了两个相同名字的类,或者把两个相对独立的库导入项目时而他们又刚好有重名的类的时候该类所对应的符号和Meta Class符号定义了两次。因此很容易产生这种命名冲突,让程序的连接过程当中出现出现重复的符号形成报错。
为了不这种状况,咱们要尽可能在类名,以及分类和分类方法上增长前缀,还有一些宏定义等等根据本身项目来定吧
若是建立类的实例的方式不止一种,那么这个类就会有多个初始化方法,这样作很好,不过仍是要在其中选定一个方法做为全能初始化方法,剩下的其他的初始化方法都要调用它,这样作的好处是之后若是初始化的逻辑更改了只需更改一处便可,或者是交给子类覆写的时候也只覆写这一个方法便可~
举个例子来讲:能够看一下NSDate的实如今NSDate.h中NSDate类中定义了一个全能初始化方法:
- (instancetype)initWithTimeIntervalSinceReferenceDate:(NSTimeInterval)ti NS_DESIGNATED_INITIALIZER;
复制代码
其他的相似初始化方式定义在NSDate (NSDateCreation) 分类中
- (instancetype)initWithTimeIntervalSinceNow:(NSTimeInterval)secs;
- (instancetype)initWithTimeIntervalSince1970:(NSTimeInterval)secs;
- (instancetype)initWithTimeInterval:(NSTimeInterval)secsToBeAdded sinceDate:(NSDate *)date;
复制代码
在NSDate文档中有一条:If you want to subclass NSDate to obtain behavior different than that provided by the private or public subclasses, you must do these things:
而后其中要作的有一步就是
Override [initWithTimeIntervalSinceReferenceDate:
](apple-reference-documentation://hcslylvSCo), one of the designated initializer methods`
复制代码
这个是咱们组织代码过程当中应该学习的地方!
这条讲的是能够经过覆写description方法或者debugDescription方法来在NSLog打印时或者LLDB打印时输出更多的自定义信息。(数据和字典的能够经过覆写descriptionWithLocale:
方法)
友情提示:不要在description中使用 NSLog("%@",self);
,否则会掉进无底深渊啊
这里我有一个有趣的想法,不过还没彻底实现,就是想经过覆写description能把任何一个对象的属性值名称,属性值都一一完整的记录下来,能够点击查看
这条主要讲尽可能使用不可变的对象,也就是在对外属性声明的时候要尽可能加上readonly修饰,默认是readwrite,这样一来,在外部就只能读取该数据,而不能修改它,使得这个类的实例所持有的数据更加安全。若是外部想要修改,能够提供方法来进行修改。
不要把可变的collection做为属性公开,而应提供相关方法,以此修改对象中的可变collection(这条我的感受通常在经常使用、重要的类才有必要,毕竟也增长了很多代码量)
好比例子:
//Language.h
@property (nonatomic, strong) NSSet *set;
复制代码
应该改成
//Language.h
@property (nonatomic, strong, readonly) NSSet *languages;
- (void)addLanguage:(NSString *)language;
- (void)removeLanguage:(NSString *)language;
//**.m
@implementation Language {
NSMutableSet *mutableLanguages;
}
- (NSSet *)languages {
return [_mutableLanguages copy];
}
- (void)addLanguage:(NSString *)language {
[_mutableLanguages addObject:language];
}
- (void)removeLanguage:(NSString *)language {
[_mutableLanguages removeObject:language];
}
复制代码
这条不用太强调了,具体也能够参照一下我以前拟的Objective-C编程规范及建议,后续可能会不断补充更新
这条讲的是应该为类内的私有方法增长前缀,以便区分,这个感受因人而异吧,感受只要你不随便把私有方法暴露在.h文件都能接受,曾遇到过这样的同事,感受其不太适合写程序吧。
不少语言都有异常处理机制,Objective-C也不例外,Objective-C也有相似的@throw,不过在OC中使用@throw可能会致使内存泄漏,多是它被设计的使用场景的问题。建议@throw只用来处理严重错误,也能够理解为致命错误(fatal error),那么处理通常错误的时候(nonfatal error)时可使用NSError。
在OC开发中,使用对象时常常须要拷贝它,咱们会经过copy/mutbleCopy
来完成。若是想让本身的类支持拷贝,那必需要实现NSCopying
协议,只须要实现一个方法:
- (id)copyWithZone:(NSZone*)zone
复制代码
固然若是要求返回对象是可变的类型就要用到NSMutableCopying
协议,相应方法
- (id)mutableCopyWithZone:(NSZone *)zone
复制代码
在拷贝对象时,须要注意拷贝执行的是浅拷贝仍是深拷贝。深拷贝在拷贝对象时,会将对象的底层数据也进行了拷贝。浅拷贝是建立了一个新的对象指向要拷贝的内容。通常状况应该尽可能执行浅拷贝。
这条讲的也比较基础,就是基本的delegate,protocal使用。
有一点稍微说一下:当某对象须要从另一个对象中获取数据时,可使用委托模式,这种用法常常被称为“数据源协议”(Data source Protocal)相似 UITableview
的UITableViewDataSource
另外在Swift中有一个很重要的思想就是面向协议编程。固然OC中也能够用协议来下降代码耦合性,必要的时候也能够替代继承,由于遵循同一个协议的类能够是任何,没必要是同一个继承体系下。
这条主要说的是经过分类机制,能够把类分红不少歌易于管理的小块。也是有一些前提的吧,多是这个类业务比较复杂,须要瘦身,须要解耦等等。做者还推荐把私有方法统一放在Private分类中,以隐藏实现细节。这个我的以为视状况而定吧。
向第三方类的分类名称加上你专用的前缀,这点没必要多说,😜
不要在分类中声明属性,除了“class-continuation”分类中。那什么是“class-continuation”分类呢,其实就是咱们常常在.m文件中用到的,例如:
//Swift.m
@interface Swift ()
//这个就是“class-continuation”分类
@end
@implementation Swift
@end
复制代码
这条跟以前的也有点重复,最终目的仍是要尽可能在公共接口中向外暴露的内容最小化,隐藏实现细节,只告诉怎么调用,怎么使用便可。具体实现以及属性的可修改权限尽量的隐藏掉。
id<someProtocal> object
。object对象的类型不限,只要能听从这个协议便可,在这个协议里面定义了这个对象所应该实现的方法。对照明设备所作的工做 | 对OC对象所作的动做 |
---|---|
开灯 | 生成对象 |
须要照明 | 持有 |
不须要照明 | 释放 |
关灯 | 废弃 |
内存管理的思考方式 | 对应OC方法 |
---|---|
本身生成的对象,本身所持有 | alloc/new/copy/mutableCopy等 |
非本身生成的对象(好比[NSArray array]),本身也能持有 | retain |
再也不须要本身持有的对象时释放 | release |
当对象不被任何其余对象持有时废弃 | dealloc |
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([HSAppDelegate class]));
}
}
复制代码
autoreleasepool能够延长对象的生命期,使其在跨越方法调用边界后依然能够存活一段时间,一般是在下一次“时间循环”(event loop)时释放,不过也可能会执行的早一点。
使用ARC,能够省略对于引用计数的操做,因此在ARC下调用对象的retain,release,autorelease,dealloc方法时系统会报错。
这里要注意CoreFoundation 对象不归ARC管理,开发中若是有用到仍是要谁建立谁释放,适时调用CFRetain/CFRelease。
不要在delloc方法中调用其余方法,尤为是须要异步执行某些任务又要回调的方法,这样的很危险的行为,极可能异步执行完回调的时候该对象已经被销毁了,这样就没得玩了,crash了。
在delloc方法里应该制做一些释放相关的事情,包括不限于一些KVO取消订阅,remove 通知等。
这条有点重复,以前已经说过了,OC中抛出异常的时候可能会引发内存泄漏,注意一下使用的时机,或者注意在@try捕获异常中清理干净。
这条比较简单,内容主旨就是标题:以弱引用避免保留环(Retain Cycle)
在遍历处理一些大数组或者大字典的时候,可使用自动释放池来下降内存峰值,例如:
NSArray *people = /*一个很大的数组*/
NSMutableArray *employeesArray = [NSMutableArray new];
for (NSStirng *name in people) {
@autoreleasepool {
MLEmployee *employee = [MLEmployee alloc] initWithName:name];
[employeesArray addObject:employee];
}
}
复制代码
如上图,勾选这里能够开启僵尸对象设置。开启以后,系统在回收对象时,不将其真正的回收,而是把它的isa指针指向特殊的僵尸类,变成僵尸对象。僵尸类可以响应全部的选择子,响应方式为:打印一条包含消息内容以及其接收者的消息,而后终止应用程序
在苹果引入ARC以后retainCount已经正式废弃,任什么时候候都不要调用这个retainCount方法来查看引用计数了,由于这个值实际上已经没有准确性了。可是在MRC下仍是能够正常使用
根据block在内存中的位置,block被分红三种类型:
void (^block)() = ^{
NSLog(@"I am a NSGlobalBlock");
}
复制代码
NSString *name = @"PHP";
void (^block)() = ^{
NSLog(@"世界上最好的编程语言是%@", name);
};
NSLog(@"%@", block);
复制代码
运行下你会发现控制台打印的是:
<__NSStackBlock__: 0x7fff5480fa18>
复制代码
什么,你说什么,你打印出来的是__ NSMallocBlock __
? 那是由于你在ARC下编译的,ARC下编译器编译时会帮你优化自动帮你加上了copy操做,你能够用-fno-objc-arc
关闭ARC再看一下
这条主要是为了代码更易读,也比较重要。
- (void)getDataWithHost:(NSString *)host success:(void (^)(id responseDic))success;
//以上要改为下面这种
typedef void (^SuccessBlock)(id responseDic);
- (void)getDataWithHost:(NSString *)host success:(SuccessBlock)success;
复制代码
在iOS开发中,咱们常常须要异步执行一些任务,而后等待任务执行结束以后通知相关方法。实现此需求的作法不少,好比说有些人可能会选择用委托协议。那么在这种异步执行一些任务,而后等待执行结束以后调用代理的时候,可能代码就会比较分散。当多个任务都须要异步,等等就显得比较不那么合理了。
因此咱们能够考虑使用block的方式设计,这样业务相关的代码会比较紧凑,不会显得那么凌乱。
这点比较基础了,可是要稍微说一下,不是必定得在block中使用weakself,好比下面:
[YTKNetwork requestBlock:^(id responsObject) {
NSLog(@"%@",self.name);
}];
复制代码
block 不是被self所持有的,在block中就可使用self
在iOS开发中,若是有多个线程要执行同一份代码,咱们可能须要加锁来实现某种同步机制。有人可能第一印象想到的就是@synchronized(self)
,例如:
- (void)synchronizedMethod {
@synchronized(self){
// Safe
}
}
复制代码
这样写法效率很低,并且也不能保证线程中以为的安全。若是有不少属性,那么每一个属性的同步块都要等其余同步块执行完毕才能执行。
应该用GCD来替换:
_syncQueue = dispatch_queue_create("syncQueue", DISPATCH_QUEUE_CONCURRENT);
//读取字符串
- (NSString*)someString {
__block NSString *localSomeString;
dispatch_sync(_syncQueue, ^{
localSomeString = _someString;
});
return localSomeString;
}
- (void)setSomeString:(NSString*)someString {
dispatch_barrier_async(_syncQueue, ^{
_someString = someString;
});
}
复制代码
Objective-C本质上是一门分厂动态的语言,开发者在开发中能够指定任何一个方法去调用,也能够延迟调用一些方法,或者指定运行方法的线程。通常咱们会想到performSelector
,可是在GCD出来以后基本就没那么须要performSelector
了,performSelector
也有不少缺点:
performSelector
咱们常常会看到编译器发出以下警告:warning: performSelector may cause a leak because its selector is unknown [-Warc-performSelector-leaks]
performSelector
的返回值只能是void或对象类型。performSelector
没法处理带有多个参数的选择子,最多只能处理两个参数。dispatch_async(dispatch_get_main_queue(), ^{
[self doSomething];
});
复制代码
替换掉
[self performSelectorOnMainThread:@selector(doSomething)
withObject:nil
waitUntilDone:NO];
复制代码
而后还能够用
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW,
(int64_t)(5.0 * NSEC_PER_SEC));
dispatch_after(time, dispatch_get_main_queue(), ^(void){
[self doSomething];
});
复制代码
替换
[self performSelector:@selector(doSomething)
withObject:nil
afterDelay:5.0];
复制代码
GCD技术确实很棒,可是也有一些局限性,或者说有一些场景并不适合。好比过想取消队列中的某个操做,或者须要后台执行任务。还有一种技术叫NSOperationQueue
,其实NSOperationQueue
跟GCD有不少相像之处。NSOperationQueue
在GCD以前就已经有了,GCD就是在其某些原理上构建的。GCD是C层次的API,而NSOperation
是重量级的Objective-C对象。
使用NSOperation
和NSOperationQueue
的优势:
cancel方法
,用以代表此任务不须要执行。不过已经启动的任务没法取消。GCD队列是没法取消的,GCD是“安排好以后就无论了(fire and forget)”。下载清单文件操做
执行完毕以后开始同时执行。这条主要是介绍dispatch group,任务分组的功能。他能够把任务分组,而后等待这组任务执行完毕时会有通知,开发者能够拿到结果真后继续下一步操做。
另外经过dispatch group在并发队列上同时执行多项任务的时候,GCD会根据系统资源状态来帮忙调度这些并发执行的任务。
这条讲的是经常使用的dispatch_once
+ (id)sharedInstance {
static EOCClass *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
复制代码
dispatch_once比较高效,没有重量级的同步机制。
在Objective-C中除了Foundation 与CoreFoundation以外还有不少系统库,其中包括但不限于下面列出的这些:
经过无缝桥接技术,能够在定义于Foundation框架中的类和CoreFoundation框架中的C语言数据结构之间来回转换。
下面代码展现了简单的无缝桥接:
NSArray *anNSArray = @[@1, @2, @3, @4, @5];
CFArrayRef aCFArray = (__bridge CFArrayRef)anNSArray;
NSLog(@"Size of array = %li", CFArrayGetCount(aCFArray));
//Output: Size of array = 5
复制代码
转换操做中的__bridge
告诉ARC如何传力转换所涉及的OC对象,也就是ARC仍然具有这个OC对象的全部权。__bridge_retained
与之相反。这里要注意用完了数组要本身释放,使用CFRelease(aCFArray)
前面有提到过的。
在构建缓存时应该尽可能选用NSCache而非NSDictionary,NSCache会在系统资源将要耗尽时自动删减缓存,而使用NSDictionary只能经过系统低内存警告方法去手动处理。此外NSCache还会看状况删减最久未使用的对象,并且是线程安全的。
在iOS开发中常常会用到定时器:NSTimer,因为NSTimer会生成指向其使用者的引用,而其使用者若是也引用了NSTimer,那就造成了该死的循环引用,好比下面这个例子:
#import <Foundation/Foundation.h>
@interface EOCClass : NSObject
- (void)startPolling;
- (void)stopPolling;
@end
@implementation EOCClass {
NSTimer *_pollTimer;
}
- (id)init {
return [super init];
}
- (void)dealloc {
[_pollTimer invalidate];
}
- (void)stopPolling {
[_pollTimer invalidate];
_pollTimer = nil;
}
- (void)startPolling {
_pollTimer = [NSTimer scheduledTimerWithTimeInterval:5.0
target:self
selector:@selector(p_doPoll)
userInfo:nil
repeats:YES];
}
- (void)p_doPoll {
// Poll the resource
}
@end
复制代码
若是建立了本类的实例,并调用其startPolling方法开始定时器,因为目标对象是self,因此要保留此实例,由于定时器是用成员变量存放的,因此self也保留了计时器,因此此时存在保留环。此时要么调用stopPolling
,要么令系统将此实例回收,只有这样才能打破保留环。
这是一个很常见的内存泄漏,那么怎么解决呢?这个问题能够经过block来解决。能够添加这样的一个分类:
#import <Foundation/Foundation.h>
//.h
@interface NSTimer (EOCBlocksSupport)
+ (NSTimer*)eoc_scheduledTimerWithTimeInterval:(NSTimeInterval)interval
block:(void(^)())block
repeats:(BOOL)repeats;
@end
//.m
@implementation NSTimer (EOCBlocksSupport)
+ (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();
}
}
@end
复制代码
EOF : 因为我的能力有限,不免有一些遗漏或者错误,请各位看官不吝赐教!谢谢!同时若是有任何问题也能够在下方留言,欢迎一块儿交流进步~最后感谢做者Matt Galloway以及译者!更多细节仍是请翻阅图书,能够点击这里下载PDF版,原版英文版PDF我也有存~本文已经同步到我的博客