前言
- 之前我们分析过内存管理的一些操作, OC中的内存管理是通过引用计数器来实现的。一个对象的声明周期取决于它是否还没其他对象引用,即retainCount是否等于0。 但在有些情况下,在某个对象的生命周期中,我们并不希望对象的销毁时间由是否被其他对象应用来决定,而是这个对象本该是什么时候销毁就什么时候被销毁。因此引入对象的生命周期、、弱引用、强引用的概念。
准备:
- 有关内存管理技术的理解:
iOS 底层探索:内存管理 (上)
iOS 底层探索:内存管理 (下)
内容:
- 对象的生命周期
- strong & weak
- 强弱引用
一、对象的生命周期
定义:
在OC中一个对象的生命周期就是指,这个对象从创建到销毁的运行时(runtime)的生命过程。从内存管理引用计数的层面来讲,就是引用计数从1变成0的过程。
- 在
MRC
中,手动管理内存,一个对象的生命周期经历了alloc、retain、release、dealloc等一些列的过程,直到对象的引用计数为0,被释放结束了。 - 在
ARC
中,内存是系统(LLVM和Runtime的共同结果)自动管理的,其实 ARC 内部机制原理也是来源于mrc,一个对象的生命周期,大多是由系统自动管理的;
对象的创建途径:
- 程序显示的创建并初始化;
- 对象作为另一个对象的副本;
- unArchiving:从已归档的二进制数据流中解码,如果一个对象是从一个nib文件中被unArchive的话,在所有的nib文件中的对象都被装载到内存之后,就会收到一个名叫 awakeFromNib 的消息
图解对象生命周期
总结:在对象的创建和初始化之后,只要对象的retainCount的值比0大,那么它就会一直存在在内存中。通过想一个对象发送retain消息,或者进行copy操作。其他的对象可以引用并持有该对象的所有权。同时,移除引用的时候要发送release消息。
二、weak 和 strong
weak的底层实现原理
举例:
int main(){
NSObject *obj = [[NSObject alloc] init];
id __weak obj1 = obj;
}
clang 编译报错提示:
int main(){
NSObject *obj = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));
id __attribute__((objc_ownership(weak))) obj1 = obj;
}
-
objc_ownership
字面意思是:获得对象的所有权,是对对象weak的初始化的一个操作
- 调用
objc_initWeak
存入sidetable表;- 调用
objc_loadWeakRetain
返回自身,并引用计数+1(refcnts的value+固定增量值);- __weak修饰的
obj1
是个作用域内的临时变量,所以出了作用域就被释放了。
以objc_initWeak
为出发点去Objc-4源码中查看:
/**
* Initialize a fresh weak pointer to some object location.
* It would be used for code like:
*
* (The nil case)
* __weak id weakPtr;
* (The non-nil case)
* NSObject *o = ...;
* __weak id weakPtr = o;
*
* This function IS NOT thread-safe with respect to concurrent
* modifications to the weak variable. (Concurrent weak clear is safe.)
*
* @param location Address of __weak ptr.
* @param newObj Object ptr.
*/
id
objc_initWeak(id *location, id newObj)
{
if (!newObj) {
*location = nil;
return nil;
}
return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
(location, (objc_object*)newObj);
}
//location : __weak指针 的地址 ,存储指针的地址,这样便可以在最后将其指向的对象置为nil。
// newObj :所引用的对象。即例子中的obj 。
- 从上面的代码可以看出
objc_initWeak
方法只是一个深层次函数调用的入口,在该方法内部调用了storeWeak
方法。下面我们来看下storeWeak
方法的实现代码。
static id
storeWeak(id *location, objc_object *newObj)
{
ASSERT(haveOld || haveNew);
if (!haveNew) ASSERT(newObj == nil);
Class previouslyInitializedClass = nil;
id oldObj;
SideTable *oldTable;
SideTable *newTable;
// Acquire locks for old and new values.
// Order by lock address to prevent lock ordering problems.
// Retry if the old value changes underneath us.
retry:
if (haveOld) {// 如果weak ptr之前弱引用过一个obj,则将这个obj所对应的SideTable取出,赋值给oldTable
oldObj = *location;
oldTable = &SideTables()[oldObj];
} else {
oldTable = nil;// 如果weak ptr之前没有弱引用过一个obj,则oldTable = nil
}
if (haveNew) { // 如果weak ptr要weak引用一个新的obj,则将该obj对应的SideTable取出,赋值给newTable
newTable = &SideTables()[newObj];
} else {
newTable = nil; // 如果weak ptr不需要引用一个新obj,则newTable = nil
}
// 加锁操作,防止多线程中竞争冲突
SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);
// location 应该与 oldObj 保持一致,如果不同,说明当前的 location 已经处理过 oldObj 可是又被其他线程所修改
if (haveOld && *location != oldObj) {
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
goto retry;
}
// Prevent a deadlock between the weak reference machinery
// and the +initialize machinery by ensuring that no
// weakly-referenced object has an un-+initialized isa.
if (haveNew && newObj) {
Class cls = newObj->getIsa();
if (cls != previouslyInitializedClass &&
!((objc_class *)cls)->isInitialized()) // 如果cls还没有初始化,先初始化,再尝试设置weak
{
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
class_initialize(cls, (id)newObj);
// If this class is finished with +initialize then we're good.
// If this class is still running +initialize on this thread
// (i.e. +initialize called storeWeak on an instance of itself)
// then we may proceed but it will appear initializing and
// not yet initialized to the check above.
// Instead set previouslyInitializedClass to recognize it on retry.
previouslyInitializedClass = cls; // 这里记录一下previouslyInitializedClass, 防止改if分支再次进入
goto retry; // 重新获取一遍newObj,这时的newObj应该已经初始化过了
}
}
// Clean up old value, if any.
if (haveOld) {
weak_unregister_no_lock(&oldTable->weak_table, oldObj, location); // 如果weak_ptr之前弱引用过别的对象oldObj,则调用weak_unregister_no_lock,在oldObj的weak_entry_t中移除该weak_ptr地址
}
// Assign new value, if any.
if (haveNew) { // 如果weak_ptr需要弱引用新的对象newObj
// (1) 调用weak_register_no_lock方法,将weak ptr的地址记录到newObj对应的weak_entry_t中
newObj = (objc_object *)
weak_register_no_lock(&newTable->weak_table, (id)newObj, location,
crashIfDeallocating);
// weak_register_no_lock returns nil if weak store should be rejected
// (2) 更新newObj的isa的weakly_referenced bit标志位
// Set is-weakly-referenced bit in refcount table.
if (newObj && !newObj->isTaggedPointer()) {
newObj->setWeaklyReferenced_nolock();
}
// Do not set *location anywhere else. That would introduce a race.
// (3)*location 赋值,也就是将weak ptr直接指向了newObj。可以看到,这里并没有将newObj的引用计数+1
*location = (id)newObj;
}
else {
// No new value. The storage is not changed.
}
// 解锁,其他线程可以访问oldTable, newTable了
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
// 返回newObj,此时的newObj与刚传入时相比,weakly-referenced bit位置1
return (id)newObj;
}
storeWeak
具体流程如下:
storeWeak
方法实际上是接收了5个参数,分别是 haveOld、haveNew和crashIfDeallocating ,这三个参数都是以模板的方式传入的,是三个bool类型的参数。分别表示weak指针之前是否指向了一个弱引用,weak指针是否需要指向一个新的引用,若果被弱引用的对象正在析构,此时再弱引用该对象是否应该crash。- 该方法维护了 oldTable 和 newTable 分别表示旧的引用弱表和新的弱引用表,它们都是
SideTable 的hash表
。- 如果weak指针之前指向了一个弱引用,则会调用 weak_unregister_no_lock 方法将旧的weak指针地址移除。
- 如果weak指针需要指向一个新的引用,则会调用 weak_register_no_lock 方法将新的weak指针地址添加到
弱引用表中
。- 调用 setWeaklyReferenced_nolock 方法修改weak新引用的对象的bit标志位
有关SideTable
在iOS 底层探索:内存管理 (上) 中讲的有喔,这里主要讲下:weak_table_t
/**
* The global weak references table. Stores object ids as keys,
* and weak_entry_t structs as their values.
*/
struct weak_table_t {
weak_entry_t *weak_entries; //hash数组,用来存储弱引用对象的相关信息weak_entry_t
size_t num_entries; //hash数组中的元素个数
uintptr_t mask; //hash数组长度-1,会参与hash计算。(注意,这里是hash数组的长度,而不是元素个数。比如,数组长度可能是64,而元素个数仅存了2个)
uintptr_t max_hash_displacement; //可能会发生的hash冲突的最大次数,用于判断是否出现了逻辑错误(hash表中的冲突次数绝不会超过改值)
};
-
weak_table_t
是一个典型的hash结构
。 -
weak_entries
是一个动态数组
,用来存储weak_entry_t
类型的元素,这些元素实际上就是OC对象的弱引用信息
。 -
weak_entry_t
是存储在弱引用表中的一个内部结构体,它负责维护和存储指向一个对象的所有弱引用hash表。其定义如下:
typedef objc_object ** weak_referrer_t; //objc_object是weak_entry_t表中weak弱引用对象的范型对象的结构体结构。
struct weak_entry_t {
DisguisedPtr<objc_object> referent; //范型
union {
struct {
weak_referrer_t *referrers;
uintptr_t out_of_line : 1; //最低有效位,也是标志位。当标志位 0 时,增加引用表指针纬度。
uintptr_t num_refs : PTR_MINUS_1; //引用数值。这里记录弱引用表中引用有效数字,因为弱引用表使用的是静态 hash 结构,所以需要使用变量来记录数目。
uintptr_t mask; //计数辅助量。
uintptr_t max_hash_displacement; //hash 元素上限阀值。
};
struct {
// out_of_line=0 is LSB of one of these (don't care which)
weak_referrer_t inline_referrers[WEAK_INLINE_COUNT];
};
}
}
weak
的整体实现流程如图:
【总结】:
1:⾸先我们知道有⼀个⾮常⽜逼的家伙-sideTable
2:得到sideTable的weakTable 弱引⽤表
3:创建⼀个weak_entry_t
4:把referent加⼊到weak_entry_t的数组inline_referrers
5:把weak_table扩容⼀下
6:把new_entry加⼊到weak_table中
objc_loadWeakRetain
的执行流程如下:
/*
Once upon a time we eagerly cleared *location if we saw the object
was deallocating. This confuses code like NSPointerFunctions which
tries to pre-flight the raw storage and assumes if the storage is
zero then the weak system is done interfering. That is false: the
weak system is still going to check and clear the storage later.
This can cause objc_weak_error complaints and crashes.
So we now don't touch the storage until deallocation completes.
*/
id
objc_loadWeakRetained(id *location)
{
id obj;
id result;
Class cls;
SideTable *table;
retry:
// fixme std::atomic this load
obj = *location;
if (!obj) return nil;
if (obj->isTaggedPointer()) return obj;
table = &SideTables()[obj];
table->lock();
if (*location != obj) {
table->unlock();
goto retry;
}
result = obj;
cls = obj->ISA();
if (! cls->hasCustomRR()) {
// Fast case. We know +initialize is complete because
// default-RR can never be set before then.
ASSERT(cls->isInitialized());
if (! obj->rootTryRetain()) {
result = nil;
}
}
else {
// Slow case. We must check for +initialize and call it outside
// the lock if necessary in order to avoid deadlocks.
if (cls->isInitialized() || _thisThreadIsInitializingClass(cls)) {
BOOL (*tryRetain)(id, SEL) = (BOOL(*)(id, SEL))
class_getMethodImplementation(cls, @selector(retainWeakReference));
if ((IMP)tryRetain == _objc_msgForward) {
result = nil;
}
else if (! (*tryRetain)(obj, @selector(retainWeakReference))) {
result = nil;
}
}
else {
table->unlock();
class_initialize(cls, obj);
goto retry;
}
}
table->unlock();
return result;
}
- 这段代码的核心就是:
retainWeakReference
->......rootRetain
的过程。进行引用计数加1
weak释放为nil过程
1、调用objc_release
2、因为对象的引用计数为0,所以执行dealloc
3、在dealloc中,调用了_objc_rootDealloc函数
4、在_objc_rootDealloc中,调用了object_dispose函数
5、调用objc_destructInstance
6、最后调用objc_clear_deallocating
我们只看下objc_clear_deallocating
:
void objc_clear_deallocating(id obj)
{
assert(obj);
assert(!UseGC);
if (obj->isTaggedPointer()) return;
obj->clearDeallocating();
}
//执行 clearDeallocating方法
inline void objc_object::clearDeallocating()
{
sidetable_clearDeallocating();
}
// 执行sidetable_clearDeallocating,找到weak表中的value值
void objc_object::sidetable_clearDeallocating()
{
SideTable *table = SideTable::tableForPointer(this);
// clear any weak table items
// clear extra retain count and deallocating bit
// (fixme warn or abort if extra retain count == 0 ?)
spinlock_lock(&table->slock);
RefcountMap::iterator it = table->refcnts.find(this); //这里可以再研究下
if (it != table->refcnts.end()) {
if (it->second & SIDE_TABLE_WEAKLY_REFERENCED) {
//清理对象
weak_clear_no_lock(&table->weak_table, (id)this);
}
table->refcnts.erase(it);
}
spinlock_unlock(&table->slock);
}
clearDeallocating
函数首先根据对象地址获取所有weak指针地址的数组,然后遍历这个数组把其中的数据设为nil,最后把这个entry从weak表中删除,最后清理对象的记录。objc_clear_deallocating
该函数 具体流程如下:
1、从weak表中获取废弃对象的地址为键值的记录
2、将包含在记录中的所有附有 weak修饰符变量的地址,赋值为nil
3、将weak表中该记录删除
4、从引用计数表中删除废弃对象的地址为键值的记录
总结
weak
是Runtime维护了一个hash(哈希)表,用于存储指向某个对象的所有weak指针。weak表其实是一个hash(哈希)表,Key是所指对象的地址,Value是weak指针的地址(这个地址的值是所指对象指针的地址)数组。
strong的底层实现原理
如果是用属性strong,同样可以用Clang去查看源码。这里只做汇编调试如下:
我们进入objc_storeStrong
源码:
void
objc_storeStrong(id *location, id obj)
{
id prev = *location;
if (obj == prev) {
return;
}
objc_retain(obj);//retain新值
*location = obj;
objc_release(prev);//release旧值
}
- 可以看出:
strong
: 内部使用retain
和release
进行引用计数的管理。关于retain和release的分析请看 iOS 底层探索:内存管理 (上)
三、弱引用 & 强引用
概念
弱引用
(Weak Reference):当前对象的声明周期不被是否由其他其他对象引用限制,它本该什么时候销毁就什么时候销毁。计时它的引用没断,但是当它的生存周期到了就会被销毁。
强引用
(Strong Reference ):当前对象被其他对象引用时,会执行retain,引用计数+1.当retainCount=0时,该对象才会被销毁。 默认情况下是强引用方式。
简单的说:
当用指针指向某个对象时,你可以通过retain/release管理它的内存,也可以不管理。
如果你管理了,就拥有对这个对象的强引用;
如果你没有管理,那么你拥有的就是弱引用。
使用
__weak
- weak严格的说应当叫“ 归零弱引用 ”,weak相当于老版本的assign,即当对象被销毁后,会自动的把它的指针置为nil,这样可以防止野指针错误。
- weak 作为属性的关键字的作用弱引用,所引用对象的计数器不会加一,并在引用对象被释放的时候自动被设置为 nil。
__weak NSObject *obj;
__strong
- 变量声明默认都带有strong关键字,如果变量什么关键字都不写,那么就默认为强引用,strong相当于老版本的retain ;
__strong NSObject *obj;
验证
将obj2声明改为__weak
- 从上面可以看出使用__strong 和__weak的区别,因为__strong修饰的对象会使对象本身dretainCount+1,而weak的并不会。
所以第一个例子的retainCount为2,obj1=nil之后retainCount为1,并不会对obj2造成影响,而第二个例子obj1=nil之后retainCount 为0了,内存也跟着释放了,所以obj2也为nil。
weakSelf 与 self
NSLog(@"%ld",CFGetRetainCount((__bridge CFTypeRef)self));
__weak typeof(self) weakSelf = self;
NSLog(@"%ld",CFGetRetainCount((__bridge CFTypeRef)self));
打印weakSelf 和 self对象,以及指针地址:
- 当前self取地址 和 weakSelf取指针的地址的值是不一样的。意味着有两个指针地址,指向的是同一片内存空间,即weakSelf 和 self 的内存地址是不一样,都指向同一片内存空间的
强引用的举例分析:NSTimer(计时器)
- (void)createTimer {
self.timer = [NSTimer timerWithTimeInterval:1 target: self selector:@selector(fireHome) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
}
- (void)fireHome{
num++;
NSLog(@"hello word - %d",num);
}
- (void)dealloc{
[self.timer invalidate];
self.timer = nil;
NSLog(@"%s",__func__);
}
- 我们运行程序,进行push-pop跳转,发现定时器方法仍然在执行,并没有执行B的dealloc方法;
- 我们知道:
NSTimer
创建后,需要手动加入到Runloop
中才可以运行,但timer
会使得当前控制器不走dealloc
方法,导致timer
和控制器
都无法释放
。
解决方式一: pop时在其他方法中销毁timer
- 重写didMoveToParentViewController方法
- (void)didMoveToParentViewController:(UIViewController *)parent{
// 无论push 进来 还是 pop 出去 正常跑
// 就算继续push 到下一层 pop 回去还是继续
if (parent == nil) {
[self.timer invalidate];
self.timer = nil;
NSLog(@"timer 走了");
}
}
解决方式二:
定义timer时,采用闭包的形式,因此不需要指定target:
- (void)blockTimer{
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(@"timer fire - %@",timer);
}];
}
- 这两个解决方法不是我们要研究的,为了对强引用进行拓展研究。所以回到最开始的地方;
先看看官方文档NSTimer
的timerWithTimeInterval:target:selector:userInfo:repeats:
方法
- 从文档中可以看出,timer对传入的target具有强持有,即timer持有self。由于timer是定义在B界面中,所以self也持有timer,因此
self -> timer -> self
构成了循环引用 - 我们我们尝试通过
__weak
即弱引用来解决,代码修改如下:
//typeof(self)是获取到self的类型,这样定义的weakSelf就是和self一个类型的,加上__weak是建立一个弱引用
__weak typeof(self) weakSelf = self; //定义了一个弱引用性质的替身.
self.timer = [NSTimer timerWithTimeInterval:1 target:weakSelf selector:@selector(fireHome) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
我们再次运行程序,进行push-pop跳转。发现问题还是存在,即定时器方法仍然在执行,并没有执行B的dealloc方法。
为什么呢?因为我们的分析并不全面,此时还有一个Runloop对timer的强持有,因为Runloop的生命周期比B界面更长,所以导致了timer无法释放。
拓展循环引用的模型:
timer模型
:self -> timer -> weakSelf -> self,当前的timer捕获的是B界面的内存,即vc对象的内存,即weakSelf表示的是vc对象
Block模型
:self -> block -> weakSelf -> self,当前的block捕获的是指针地址,即weakSelf表示的是指向self的临时变量的指针地址
解决方式三:中介者模式,即不使用self,依赖于其他对象
将target换成NSObject对象,将fireHome交给target执行:
//**********1、定义其他对象**********
@property (nonatomic, strong) id target;
//**********2、修改target**********
self.target = [[NSObject alloc] init];
class_addMethod([NSObject class], @selector(fireHome), (IMP)fireHomeObjc, "v@:");
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self.target selector:@selector(fireHome) userInfo:nil repeats:YES];
//**********3、imp**********
void fireHomeObjc(id obj){
NSLog(@"%s -- %@",__func__,obj);
}
运行发现timer还是会继续执行。原因是解决了中介者的释放,但是没有解决中介者的回收,即self.target的回收。
所以还需要释放timer
解决方式四:自定义封装timer
这种方式是根据思路三的原理,自定义封装timer,其实现如下
//*********** .h文件 ***********
@interface CJLTimerWapper : NSObject
- (instancetype)cjl_initWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;
- (void)cjl_invalidate;
@end
//*********** .m文件 ***********
#import "CJLTimerWapper.h"
#import <objc/message.h>
@interface CJLTimerWapper ()
@property(nonatomic, weak) id target;
@property(nonatomic, assign) SEL aSelector;
@property(nonatomic, strong) NSTimer *timer;
@end
@implementation CJLTimerWapper
- (instancetype)cjl_initWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo{
if (self == [super init]) {
//传入vc
self.target = aTarget;
//传入的定时器方法
self.aSelector = aSelector;
if ([self.target respondsToSelector:self.aSelector]) {
Method method = class_getInstanceMethod([self.target class], aSelector);
const char *type = method_getTypeEncoding(method);
//给timerWapper添加方法
class_addMethod([self class], aSelector, (IMP)fireHomeWapper, type);
//启动一个timer,target是self,即监听自己
self.timer = [NSTimer scheduledTimerWithTimeInterval:ti target:self selector:aSelector userInfo:userInfo repeats:yesOrNo];
}
}
return self;
}
//一直跑runloop
void fireHomeWapper(CJLTimerWapper *wapper){
//判断target是否存在
if (wapper.target) {
//如果存在则需要让vc知道,即向传入的target发送selector消息,并将此时的timer参数也一并传入,所以vc就可以得知`fireHome`方法,就这事这种方式定时器方法能够执行的原因
//objc_msgSend发送消息,执行定时器方法
void (*lg_msgSend)(void *,SEL, id) = (void *)objc_msgSend;
lg_msgSend((__bridge void *)(wapper.target), wapper.aSelector,wapper.timer);
}else{
//如果target不存在,已经释放了,则释放当前的timerWrapper
[wapper.timer invalidate];
wapper.timer = nil;
}
}
//在vc的dealloc方法中调用,通过vc释放,从而让timer释放
- (void)cjl_invalidate{
[self.timer invalidate];
self.timer = nil;
}
- (void)dealloc
{
NSLog(@"%s",__func__);
}
@end
使用方式:
@property (nonatomic, strong) LGTimerWapper *timerWapper;
//定义
self.timerWapper = [[CJLTimerWapper alloc] cjl_initWithTimeInterval:1 target:self selector:@selector(fireHome) userInfo:nil repeats:YES];
//释放
- (void)dealloc{
[self.timerWapper cjl_invalidate];
}
运行结果如下:
解决方式五:利用NSProxy虚基类的子类
NSProxy子类也是处理timer强引用最常用的方式。
- 首先定义一个继承自NSProxy的子类
//************NSProxy子类************
@interface CJLProxy : NSProxy
+ (instancetype)proxyWithTransformObject:(id)object;
@end
@interface CJLProxy()
@property (nonatomic, weak) id object;
@end
@implementation CJLProxy
+ (instancetype)proxyWithTransformObject:(id)object{
CJLProxy *proxy = [CJLProxy alloc];
proxy.object = object;
return proxy;
}
-(id)forwardingTargetForSelector:(SEL)aSelector {
return self.object;
}
- 将timer中的target传入NSProxy子类对象,即timer持有NSProxy子类对象
//************解决timer强持有问题************
@property (nonatomic, strong) LGProxy *proxy;
self.proxy = [CJLProxy proxyWithTransformObject:self];
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self.proxy selector:@selector(fireHome) userInfo:nil repeats:YES];
- (void)fireHome{
num++;
NSLog(@"hello word - %d",num);
}
//在dealloc中将timer正常释放
- (void)dealloc{
[self.timer invalidate];
self.timer = nil;
}
这样做的主要目的是将强引用转移成消息转发。虚基类只负责消息转发,即使用NSProxy作为中间代理、中间者;
vc释放,导致了proxy的释放
dealloc方法中,timer进行了释放,所以runloop强引用也释放了