问题
当前self
取地址 和 weakSelf
取地址的值是不一样的
weakSelf没有对内存进行+1操作
目录
预备
正文
1. ARC & MRC
Objective-C
提供了两种内存管理机制
:
-
MRC
(Mannul Reference Counting
手动管理引用计数) -
ARC
(Automatic Reference Counting
自动管理引用计数)
MRC(手动管理引用计数)
- 通过
alloc
、new
、copy
、mutableCopy
生成的对象,持有时
,需要使用retain
、release
、autoRelease
管理引用计数
retain
:对象
的引用计数
+1release
:对象
的引用计数
-1autoRelease
:自动对作用域内
的对象
进行一次retain
和release
操作。
MRC模式
下,必须遵守:谁创建
,谁释放
,谁引用
,谁管理
ARC(自动管理引用计数)
ARC
在WWDC2011
上公布,iOS5
系统引入的自动管理机制
,是LLVM
和Runtime
配合的结果,在编译期
和运行时
都会进行内存管理
。ARC
中禁止
手动调用retain
、release
、retainCount
、dealloc
,转而使用weak
、strong
属性关键字。
- 现在都是直接使用
ARC
,由系统
自动管理引用计数了。
2. strong & weak
- 关于
strong
和weak
,可以在objc4源码中进行探索。 现在将流程图
和总结
记录一下:
2.1 weak
-
weak
是不处理
(对象)的引用计数
,而是使用
一个哈希结构
的弱引用表
进行信息
的保存
。 - 当
对象
本身的引用计数
为0
时,调用dealloc
函数,触发weak
表的释放
弱引用表
的存储
细节
-
weak
使用weakTable弱引用表
进行存储信息
,是sideTable散列表
(哈希表)结构。 - 创建
weak_entry_t
,将referent
引用计数加入到weak_entry_t
的数组inline_referrers
中。 - 支持
weak_table
扩容,把new_entry
加入到weak_table
中
2.2 strong
strong
修饰,实际是新值
的retain
和旧值
的release
:
-
weak
:不
处理引用计数
,使用弱引用表
进行信息存储
,dealloc
时移除记录
。 -
strong
:内部
使用retain
和release
进行引用计数
的管理
。
3. 强弱引用
- 以
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__); }
NSTimer
创建后,需要手动加入
到Runloop
中才可以运行
,但timer
会使得当前控制器
不走dealloc
方法,导致timer
和控制器
都无法释放。
下面,我们就来解决2个问题:
- 为什么?(
timer
加入后,控制器无法释放
) - 如何解决?
3.1 强持有
拓展:
无法释放
,一般是循环引用
导致
(注意: self
作为参数
传入,不会被【自动持有】
,除非内部
手动强引用
了self
。)
一般来说,
循环引用
可以通过加入弱引用
对象,打断循环
:self -> timer ->加入weakself
-> self对,
原理没错
。但前提是:timer
�仅被self
持有,且timer
仅拷贝weakself
指针!很不巧:
- 当前
timer
除了被self持有
,还被加入了[NSRunLoop currentRunLoop]
中- 当前
timer
直接指向self
的内存空间
,是对内存
进行强持有
,而不是
简单的指针拷贝
。
所以currentRunLoop
没结束,timer
就不会释放
,self
的内存空间
也不会释放
。
block
捕获外界变量:捕捉的是指针地址
。timer
捕捉的是对象本身(内存空间)
方法1:didMoveToParentViewController
手动打断循环
- (void)didMoveToParentViewController:(UIViewController *)parent{ // 无论push 进来 还是 pop 出去 正常跑 // 就算继续push 到下一层 pop 回去还是继续 if (parent == nil) { [self.timer invalidate]; self.timer = nil; NSLog(@"timer 走了"); } }
方法2:不加入Runloop
,使用官方闭包API
- (void)createTimer{ self.timer = [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) { NSLog(@"timer fire - %@",timer); }]; }
方法3:中介者模式(不使用self)
- 既然
timer
会强持有
对象(内存空间
),我们就给他一个中介者
的内存空间
,让他碰不到self
,我们再对中介者
操作和释放
。 -
HTTimer.h
文件:
@interface HTTimer : NSObject + (instancetype)scheduledTimerWithTimeInterval:(NSTimeInterval)interval target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)repeats; - (void)invalidate; - (void)fire; @end
-
HTTimer.m
文件:
@interface HTTimer () @property (nonatomic, strong) NSTimer * timer; @property (nonatomic, weak) id aTarget; @property (nonatomic, assign) SEL aSelector; @end @implementation HTTimer + (instancetype)scheduledTimerWithTimeInterval:(NSTimeInterval)timeInterval target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)repeats { HTTimer * timer = [HTTimer new]; timer.aTarget = aTarget; timer.aSelector = aSelector; timer.timer = [NSTimer scheduledTimerWithTimeInterval:timeInterval target:timer selector:@selector(run) userInfo:userInfo repeats:repeats]; [[NSRunLoop currentRunLoop] addTimer:timer.timer forMode:NSRunLoopCommonModes]; return timer; } - (void)run { //如果崩在这里,说明你没有在使用Timer的VC里面的deinit方法里调用invalidate方法 if(![self.aTarget respondsToSelector:_aSelector]) return; // 消除警告 #pragma clang diagnostic push #pragma clang diagnostic ignored "-Warc-performSelector-leaks" [self.aTarget performSelector:self.aSelector]; #pragma clang diagnostic pop } - (void)fire { [_timer fire]; } - (void)invalidate { [_timer invalidate]; _timer = nil; } - (void)dealloc { // release环境下注释掉 NSLog(@"计时器已销毁"); } @end
- 使用方法:
@interface TimerViewController () @property (nonatomic, strong) HTTimer * timer; @end @implementation TimerViewController - (void)viewDidLoad { [super viewDidLoad]; // 创建 self.timer = [HTTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(fireHome) userInfo:nil repeats:YES]; } - (void)fireHome{ NSLog(@"hello word" ); // 调用 } - (void)dealloc{ // 释放 [self.timer invalidate]; NSLog(@"%s",__func__); } @end
方法4: NSProxy虚基类
- 与
NSObject
同级,但内部什么都没有
,但是可以持有对象
,并将消息
全部转发
给对象
。
(ps: 我啥也没有
,但我也是对象
,我可以
把你需求
全部传递
给能办事
的对象
)
这就是代理模式
,timer
持有代理
,代理
weak弱引用
持有self
,再把所有消息转发
给self
。
-
HTProxy.h
文件
@interface HTProxy : NSProxy /// 麻烦把消息转发给`object` + (instancetype)proxyWithTransformObject:(id)object; @end
-
HTProxy.m
文件
#import "HTProxy.h" @interface HTProxy () @property (nonatomic, weak) id object; // 弱引用object @end @implementation HTProxy /// 麻烦把消息转发给`object` + (instancetype)proxyWithTransformObject:(id)object { HTProxy * proxy = [HTProxy alloc]; proxy.object = object; return proxy; } // 消息转发。 (所有消息,都转发给object去处理) - (id)forwardingTargetForSelector:(SEL)aSelector { return self.object; } // 消息转发 self.object(可以利用虚基类,进行数据收集) //- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel{ // // if (self.object) { // }else{ // NSLog(@"麻烦收集 stack111"); // } // return [self.object methodSignatureForSelector:sel]; // //} // //- (void)forwardInvocation:(NSInvocation *)invocation{ // // if (self.object) { // [invocation invokeWithTarget:self.object]; // }else{ // NSLog(@"麻烦收集 stack"); // } // //} -(void)dealloc { NSLog(@"%s",__func__); } @end
- 使用方法:
@interface TimerViewController () @property (nonatomic, strong) HTProxy * proxy; @property (nonatomic, strong) NSTimer * timer; @end @implementation TimerViewController - (void)viewDidLoad { [super viewDidLoad]; // 创建虚基类代理 self.proxy = [HTProxy proxyWithTransformObject: self]; self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self.proxy selector:@selector(fireHome) userInfo:nil repeats:YES]; } - (void)fireHome{ NSLog(@"hello word" ); // 调用 } - (void)dealloc{ // 释放 [self.timer invalidate]; NSLog(@"%s",__func__); } @end
虚基类
的代理模式
使用非常方便
,使用场景
也很多
。(注意proxy
中是weak
弱引用object
)
这样做的主要目的是将强引用的注意力转移成了消息转发
。虚基类只负责消息转发,即使用NSProxy
作为中间代理、中间者
这里有个疑问,定义的proxy
对象,在dealloc释放时,还存在吗?
-
proxy
对象会正常释放,因为vc
正常释放了,所以可以释放其持有者,即timer和proxy
,timer
的释放也打破了runLoop对proxy的强持有
。完美的达到了两层释放
,即vc -×-> proxy <-×- runloop
,解释如下:-
vc释放,导致了
proxy
的释放 -
dealloc方法中,timer进行了释放,所以runloop强引用也释放了
-
这样做的主要目的是将强引用的注意力转移成了消息转发
。虚基类只负责消息转发,即使用NSProxy
作为中间代理、中间者
这里有个疑问,定义的proxy
对象,在dealloc释放时,还存在吗?
-
proxy
对象会正常释放,因为vc
正常释放了,所以可以释放其持有者,即timer和proxy
,timer
的释放也打破了runLoop对proxy的强持有
。完美的达到了两层释放
,即vc -×-> proxy <-×- runloop
,解释如下:-
vc释放,导致了
proxy
的释放 -
dealloc方法中,timer进行了释放,所以runloop强引用也释放了
-
5:weakSelf 与 self
对于weakSelf
和 self
,主要有以下两个疑问
-
1、
weakSelf
会对引用计数进行+1
操作吗? -
2、
weakSelf
和self
的指针地址相同吗,是指向同一片内存吗? -
带着疑问,我们在
weakSelf
前后打印self
的引用计数
NSLog(@"%ld",CFGetRetainCount((__bridge CFTypeRef)self)); __weak typeof(self) weakSelf = self; NSLog(@"%ld",CFGetRetainCount((__bridge CFTypeRef)self));
因此可以得出一个结论:
weakSelf没有对内存进行+1操作
- 继续打印
weakSelf
和self
对象,以及指针地址
po weakSelf po self po &weakSelf po &self
结果如下
从打印结果可以看出,当前self
取地址 和 weakSelf
取地址的值是不一样的。意味着有两个指针地址,指向的是同一片内存空间
,即weakSelf 和 self 的内存地址是不一样,都指向同一片内存空间
的
-
从上面打印可以看出,此时
timer
捕获的是<LGTimerViewController: 0x7f890741f5b0>
,是一个对象
,所以无法通过weakSelf来解决强持有
。即引用链关系为:NSRunLoop -> timer -> weakSelf(<LGTimerViewController: 0x7f890741f5b0>)
。所以RunLoop对整个 对象的空间有强持有
,runloop没停,timer 和 weakSelf是无法释放的 -
而我们在
Block
原理中提及的block的循环引用
,与timer
的是有区别的。通过block底层原理的方法__Block_object_assign
可知,block
捕获的是对象的指针地址
,即weakself 是 临时变量的指针地址
,跟self
没有关系,因为weakSelf是新的地址空间
。所以此时的weakSelf相当于中间值
。其引用关系链为self -> block -> weakSelf(临时变量的指针地址)
,可以通过地址
拿到指针
所以在这里,我们需要区别下block
和timer
循环引用的模型
-
timer模型:
self -> timer -> weakSelf -> self
,当前的timer
捕获的是B界面的内存,即vc对象的内存
,即weakSelf
表示的是vc对象
-
Block模型:
self -> block -> weakSelf -> self
,当前的block捕获的是指针地址
,即weakSelf
表示的是指向self的临时变量的指针地址
注意
引用