//B页面中添加timer和对应的执行方法 A页面就仅仅添加push到B页面的代码
@property (nonatomic, strong) NSTimer *timer;
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(fireHome) userInfo:nil repeats:YES];
- (void)fireHome{
num++;
NSLog(@"hello word - %d",num);
}
复制代码
B
页面建立一个timer
,而后从A
页面push
到B
此时timer
开始执行而后再pop
回到A
页面,部分人可能会以为此时timer
会暂停执行,由于timer
是B
页面持有,pop
回来以后B
页面也就销毁了因此相应的timer
也因该被销毁,因此对应的应该是timer
中止执行。可是结果其实否则。能够看一下运行结果 pop
回来以后 timer
同样还在执行。B
不能释放,看一下下面的官方文档 timer
会对 self
进行强持有,而此时 self
有持有 timer
因此形成了循环引用,也就形成了 B
页面不能释放,因此即便 pop
计时器还在执行。__weak
去修饰 self
,此时 self
的引用计数不会加一,因此不会形成循环引用问题,在这里不妨试一下用 __weak
去修饰而后再看执行结果 __weak
修饰并不能解决循环引用的问题。一样的在文章Block的底层分析咱们知道,用 __weak
修饰的话底层 block
会走到 _Block_object_assign
方法,发现 block
底层其实仅仅存储了对象的指针地址也就是 weakSelf
的地址。这里咱们先分别打印一下 self
的引用计数和 __weak
修饰以后的引用计数,而后在分别打印一下 self
和 weakSelf
和这二者的地址 __weak
修饰的变量指向对象并不会形成引用计数加一的状况,其次经过地址打印、值打印咱们能够肯定的是 self
和 weakSelf
是两个变量指向了同一片的内存空间以下图所示 block
能经过存储的 weakSelf
的地址找到对象的地址从而获取对象的属性修改对象相关的属性等。而且也可以解决循环引用的问题。 可是 timer
就不同了,上图的官方文档咱们能够知道,timer
强持有的是对象,并非对象的指针地址了,因此 timer
的引用脸就是timer -> weakSelf -> 对象
timer
又被 runloop
持有,引用链以下:runloop -> timer -> weakSelf -> 对象
runloop
的生命周期又很长(大于对象和 timer
的生命周期)runloop
没有停那么 timer
就不会被释放,进而致使 weakSelf
以及对象都不会释放. 也就致使了不一样于 block
的解决循环引用的方法也就是 __weak
不能解决强持有的问题。timer
timer
持有的是当前对象因此对象不能被释放,因此解决办法其实也很简单就是pop
出去的时候只须要释放 timer
就行。上文的官方文档也有提到 timer
对象也就会被释放。因此只须要在 didMoveToParentViewController
方法中调用 [self.timer invalidate];
和 self.timer = nil;
就好了效果以下 timer
回调方法判断timer
可是除了 didMoveToParentViewController
方法中释放还能够考虑专门建立一个添加 timer
的类,在该类中新建一个方法,而后和传入的方法作交换,该方法中须要判断传入的 target
是否为空了,若是不为空则使用传入的 target
调用传入的方法。若是为空则释放 timer
。释放 timer
对应 target
引用计数就会减一。若是减到0就会被正常释放。一样的也能够解决问题具体代码以下 #import "LGTimerWapper.h"
#import <objc/message.h>
@interface LGTimerWapper()
@property (nonatomic, weak) id target;
@property (nonatomic, assign) SEL aSelector;
@property (nonatomic, strong) NSTimer *timer;
@end
@implementation LGTimerWapper
- (instancetype)lg_initWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo{
if (self == [super init]) {
self.target = aTarget; // vc
self.aSelector = aSelector; // 方法 -- vc 释放
if ([self.target respondsToSelector:self.aSelector]) {
Method method = class_getInstanceMethod([self.target class], aSelector);
const char *type = method_getTypeEncoding(method);
class_addMethod([self class], aSelector, (IMP)fireHomeWapper, type);
self.timer = [NSTimer scheduledTimerWithTimeInterval:ti target:self selector:aSelector userInfo:userInfo repeats:yesOrNo];
}
}
return self;
}
void fireHomeWapper(LGTimerWapper *warpper){
if (warpper.target) { // vc - dealloc
void (*lg_msgSend)(void *,SEL, id) = (void *)objc_msgSend;
lg_msgSend((__bridge void *)(warpper.target), warpper.aSelector,warpper.timer);
}else{ // warpper.target
[warpper.timer invalidate];
warpper.timer = nil;
}
}
- (void)lg_invalidate{
[self.timer invalidate];
self.timer = nil;
}
- (void)dealloc{
NSLog(@"%s",__func__);
}
@end
复制代码
proxy
虚基类的方式proxy
这里其实也相似,这里使用 proxy
的思想主要是想使用一个中间者,这样 timer
不会再持有对象而是 proxy
,因此对象的引用计数不会再加一,从而对象释放的时候对应的 timer
和 proxy
也就释放了也就解决了强持有的问题。具体代码以下; #import "LGProxy.h"
@interface LGProxy()
@property (nonatomic, weak) id object;
@end
@implementation LGProxy
+ (instancetype)proxyWithTransformObject:(id)object{
LGProxy *proxy = [LGProxy alloc];
proxy.object = object;
return proxy;
}
// 仅仅添加了weak类型的属性还不够,为了保证中间件可以响应外部self的事件,须要经过消息转发机制,让实际的响应target仍是外部self,这一步相当重要,主要涉及到runtime的消息机制。
// 转移
// 强引用 -> 消息转发
-(id)forwardingTargetForSelector:(SEL)aSelector {
return self.object;
}
复制代码
从这个官方文档中咱们能够知道,在
Runloop
开始的时候会自动建立一个自动释放池,当 Runloop
此次循环结束的时候,那么就会销毁自动释放池,从而释放全部 autorelease
对象,固然若是在一个事务中须要建立多个临时变量此时就能够本身手动建立一个自动释放池来管理这些对象能够很大程度地减小内存峰值。(例如一个代码块中须要建立循环建立10000个 image
对象而后渲染出来,此时彻底可使用自动释放池,正常状况下不使用自动释放池的话会等到这个代码块执行完成以后才能释放这10000个对象,而是用自动释放池以后每次循环完成自动释放池的代码也执行完成那么该对象也就会被释放。这样就减小了内存峰值) 结合文档和上图的理解总结:xcode
runloop
,建立完事件以后会建立一个自动释放池autorelease
对象放到自动释放池中去runloop
结束以前,会向自动释放池中全部对象发送release
消息,而后销毁自动释放池main
函数中使用自动释放池的区别xcode11
以前建立的项目是这样的
xcode11
以后建立的工程是这样的 能够发现
xcode11
以前整个程序都是放在自动释放池中的,当 runloop
启动会再建立一个自动释放池嵌套在 main
函数的这个释放池中,这样使用的结果是 main
函数自动释放池中建立的对象只有程序结束以后才能被释放,再看 xcode11
以后建立的 main
函数发现程序在自动释放池的外面,因此在自动释放池中建立的对象只要程序启动就能被释放,这样节省了程序的内存markdown
Clang
分析能够将 main
文件 clang
一下看编译后的源码 发现底层其实就是一个
__AtAutoreleasePool
对象。而后再全局搜索 __AtAutoreleasePool
而且自动释放池中的代码是使用 {}
包裹的 不出意外的是个结构体,里面有构造函数
objc_autoreleasePoolPush
返回了 atautoreleasepoolobj
对象,还有一个析构函数 objc_autoreleasePoolPop
须要传入 atautoreleasepoolobj
对象,上文也说了自动释放池的代码是在一个做用域中的,因此开始的时候就会调用构造方法,做用域结束的时候就会调用析构方法也能够经过断点调试查看汇编代码验证此结论 app
上文经过 clang
查看编译后的代码得知自动吃其实也就是个对象,就是个结构体,其中有构造方法和析构方法,接下来就能够经过源码查询构造和析构方法看源码是如何实现的同时也能够深刻探索自动释放池这个对象函数
AutoreleasePoolPage
中的方法点击 AutoreleasePoolPage
查看源码 AutoreleasePoolPage
来实现的注释中也说道了自动释放池的实现方法大概意思以下:
POOL_BOUNDARY
,它是自动释放池的边界。AutoreleasePoolPage
对象,从定义中能够看出AutoreleasePoolPage
是以栈为结点经过双向链表的形式组合而成,每一个页的大小是4096
,再看AutoreleasePoolPageData
结构 56
字节因此通常状况下共有 4096-56=4040
字节存储 autorelease
对象也就是一共能够存 4040/8=505
个对象,可是从定义中知道还有一个POOL_BOUNDARY
(注意哨兵对象只有在第一页中存在)因此第一页能够存储 504
个对象剩下的能够存储 505
个对象,这里可已经过打印自动释放池的状况验证(_objc_autoreleasePoolPrint
方法打印自动释放池的状况) hot
,而后第二页的第一个对象再也不是哨兵对象直接就是 autorelease
对象 具体内存分布图以下: objc_autoreleasePoolPush
源码分析AutoreleasePoolPage
是经过构造方法建立的 autoreleaseFullPage
方法 add
方法 next
指针,而后next++
。autorelease
源码分析autorelease
底层实现就是调用autoreleaseFast
方法objc_autoreleasePoolPop
源码分析
再看
releaseUntil
方法
kill
方法 具体流程图以下:
oop
AutoreleasePool
底层就是一个 AutoreleasePoolPage
对象 AutoreleasePoolPage
对象又是一个栈结构而且是个双向两边(应为每个 AutoreleasePoolPage
都是有大小限制的超出了再添加对象则须要建立新的页,因此是个双向连接结构)AutoreleasePool
是个栈结构而且是双向链表结构,因此 push
可添加对象就是压栈,栈压满了则建立新页面对象压栈到新页面中去,而后将新页面插入到链表结构中。 pop
就是出栈而后释放对象,释放pageAutoreleasePool
会在每次 runloop
启动的时候自动建立一个自动释放池,而后在这次循环结束的时候释放自动释放池,因此若是对象添加 __autoreleasing
属性修饰则将对象添加到了系统建立的自动释放池中,那么该对象的释放也就是系统干预释放了,也就是要等到这次 runloop
结束以后释放对象,AutoreleasePool
还一种状况是手动建立自动释放池也是就是经过 @autoreleasepool
建立自动释放池,在该做用域中建立的 autorelease
对象会放到手动建立的自动释放池中此时该对象就会在手动建立的自动释放池做用域结束以后就会被释放,这样作能够下降内存峰值