CADisplayLink、NSTimer使用注意
CADisplayLink、NSTimer会对target产生强引用,如果target又对它们产生强引用,那么就会引发循环引用 解决方案
- 使用block
2. 使用代理对象(NSProxy)进行消息转发
GCD定时器
- NSTimer依赖于RunLoop,如果RunLoop的任务过于繁重,可能会导致NSTimer不准时
- 而GCD的定时器会更加准时
copy和mutableCopy
浅拷贝就是对内存地址的复制,让目标对象指针和源对象指针指向同一片内存空间。
深拷贝就是拷贝地址中的内容,让目标对象产生新的内存区域,且将源内存区域中的内容复制到目标内存区域中
面试题:nonatomic和atomic的区别
- atomic是原子性的,nonatomic是非原子性的
- atomic和nonatomic系统自动生成的setter和getter方法不一样,atomic生成的是有加锁的,而nonatomic是没有加锁的
- atomic由于加锁,访问速度比nonatomic慢,但不一定是线程安全的,nonatomic访问速度快,线程不安全
- atomic加锁只是保证setter和getter的有序性
| atomic | nonatomic | |
|---|---|---|
| 原子性 | 是 | 非 |
| 锁 | 有 | 无 |
| 运行速度 | 慢 | 快 |
| 线程安全 | 非 | 非 |
浅拷贝和深拷贝
浅拷贝:并不拷贝对象本身,只是对指向对象的指针进行拷贝,改变原对象的属性会影响新的对象,内存中本质上还是一个对象。 深拷贝:直接拷贝生成一个新对象,改变原对象的属性不会影响新的对象,在内存中出现了两个独立的对象本身。
1、浅拷贝(指针copy)
1)相当于对指向对象的指针进行复制,产生一个新的指向对象的指针;
2)就有两个指针指向同一个对象;
3)这个对象销毁后,两个指针都应置空;
4)对象引用计数+1
2、深拷贝Copy(内容copy)
深copy不仅会复制对象本身,而且会递归复制每个指针类型的实例变量,直到两个对象没有任何公共的部分。
1)相当于对对象进行复制,产生一个新的对象;
自旋锁、互斥锁比较?
| 自旋锁 | 互斥锁 | |
|---|---|---|
| 等待时间 | 短 | 长 |
| 处理器 | 多核 | 单核 |
| CPU资源 | 紧张 | 不紧张 |
| 代码调用 | 经常调用但竞争少 | 竞争激烈 |
| 代码复杂度 | 一般 | 代码复杂,循环量大 |
weak实现原理?
初始化一个
weak对象时,runtime会调用一个objc_initWeak函数,初始化一个新的weak指针指向该对象的地址在objc_initWeak函数中会继续调用
objc_storeWeak函数,在这个过程是用来更新weak指针的指向,同时创建对应的弱引用表WeakTable
在对象释放时,会调用clearDeallocating函数,这个函数会根据对象地址获取所有weak指针数组,然后遍历这个数组置为nil。最后把该条对象的记录从weak表中删除。
weak指针指向对象,不会让对象的引用计数增加,所以block内部就不会持有self对象,破解循环引用。
OC对象的内存管理
- 在iOS中,使用
引用计数来管理OC对象的内存- 一个新创建的OC对象引用计数默认是
1,当引用计数减为0,OC对象就会销毁,释放其占用的内存空间- 调用
retain会让OC对象的引用计数+1,调用release会让OC对象的引用计数-1- 内存管理的经验总结
- 当调用
alloc、new、copy、mutableCopy方法返回了一个对象,在不需要这个对象时,要调用release或者autorelease来释放它想拥有某个对象,就让它的引用计数+1;不想再拥有某个对象,就让它的引用计数-1- 可以通过以下私有函数来查看自动释放池的情况
extern void _objc_autoreleasePoolPrint(void);
自动释放池
自动释放池的底层数据结构
- 自动释放池的主要底层数据结构是:
__AtAutoreleasePool、AutoreleasePoolPage- 调用了autorelease的对象最终都是通过
AutoreleasePoolPage对象来管理的- 调用
push方法会将一个POOL_BOUNDARY入栈,并且返回其存放的内存地址- 调用
pop方法时传入一个POOL_BOUNDARY的内存地址,会从最后一个入栈的对象开始发送release消息,直到遇到这个POOL_BOUNDARY
Runloop和Autorelease
- iOS在主线程的Runloop中注册了2个Observer
- 第1个Observer监听了kCFRunLoopEntry事件,会调用objc_autoreleasePoolPush()
- 第2个Observer
监听了kCFRunLoopBeforeWaiting事件,会调用objc_autoreleasePoolPop()、objc_autoreleasePoolPush()
监听了kCFRunLoopBeforeExit事件,会调用objc_autoreleasePoolPop()
NSTimer循环引用的几种解决方案
- 循环引用产生的原因:
timer强引用target,即timer强引用vc,然而vc并没有强引用timer,哪来的vc与timer循环引用?但是,如果vc没有强引用timer,timer是如何存活的? 其实,默认将timer加入到currentRunLoop中,currentRunLoop会强引用timer,而currentRunLoop就是mainRunLoop,mainRunLoop一直存活,所以timer可以存活。
解决方案:
- 调用
invalidate的时候,会执行destroy操作 选择合适的时机手动释放timer[self.timer invalidate];
- timer使用
block方式添加Target-Action还需要注意block可能引起的循环引用,所以使用
weakSelf
通过proxy转发的形式解决
考虑到循环引用的原因,改方案就是需要打破这些相互引用关系,因此添加一个中间件,
弱引用self,同时timer引用了中间件,这样通过弱引用来解决了相互引用proxy弱引用vc,所以vc可以释放,当vc执行dealloc,在dealloc内部销毁timer即可
iOS内存泄漏检测方法
- 通过Xcode中Product->Analyze静态分析代码,找出潜在的内存泄漏。
- 使用Xcode自带工具Instruments来检测内存泄漏。
方案1的优点是不需要写代码,只需要运行一次,就能检测出潜在的内存泄漏;缺点是由于是静态代码检查,无法覆盖全部场景(比如动态运行场景),有可能误报。
方案2的优点是不需要写代码,直接运行Instruments工具进行检测,适用于开发、测试来使用;缺点是需要一个一个页面去点击。
- 微信读书开源的内存泄漏检测库MLeaksFinder
功能:
- 能检测出内存泄漏和循环引用,并且弹框提醒。
- 只在Debug模式下起作用,Release不起作用。
- 支持检查VC和View里面任意对象的内存泄漏。
如何检测内存泄漏?
在viewDidDisappear调用之后,过2s后,VC、它的childViewControllers、presentedViewController、VC的view及view的子view肯定要立即销毁。通过
hook掉UIViewController的viewDidDisappear、viewWillAppear、dismissViewControllerAnimated方法以及UINavigationController的pushViewController、popViewControllerAnimated、popToViewController、popToRootViewControllerAnimated方法来实现。
Tagged Pointer
内存优化之Tagged Pointer
TaggerPointer: 并不是一个类,它是适用于64位处理器的一个内存优化机制,专门用来存储小对象,当存储不下时,则转为对象!例如NSString、NSNumber和NSDate等对象进行优化。
指针不再是地址了,而是经过标识过的值。它不再是一个对象了,只是普通变量而已。所以,它的内存并不存储在堆中,也不需要malloc和free。在内存读取上有着3倍的效率,创建时比以前快106倍。
注意:
Tagged Pointer并不是真正的对象,而是一个伪对象,对象都有isa指针,而Tagged Pointer是没有的,因为它不是真正的对象,不能直接访问Tagged Pointer的isa!
内存泄漏问题
- 循环引用(Retain Cycle)
在作为self的property的block中,使用self指针导致self被block强引用,形成引用循环。
-
Delegate/NSNotification 我们在使用代理设计模式的时候,一定要注意将 delegate 变量声明为
weak类型,像这样如使用strong或别的类型修饰的话将会导致循环引用,导致dealloc()不会被调用。NSNotification没有移除通知等都会触发一些意想不到的后果。 -
Block
目前在项目中出现的内存泄漏大部分是因为block的问题。
在 ARC 下,当 block 获取到外部变量时,由于编译器无法预测获取到的变量何时会被突然释放,为了保证程序能够正确运行,让 block 持有获取到的变量,向系统声明:我要用它,你们千万别把它回收了!然而,也正因 block 持有了变量,容易导致变量和 block 的循环引用,造成内存泄露。
- NSTimer
NSTimer在释放前,一定要调用
[timer invalidate],不调用的后果就是NSTimer无法释放其target,如果target正好是self,则会导致引用循环。
- (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay
系统依靠一个timer来保证延时触发,但是只有在runloop在default mode的时候才会执行成功,否则selector会一直等待runloop切换到default mode。根据我们之前关于timer的说法,在这里其实调用performSelector:afterDelay:同样会造成系统对target强引用,也即retain住。这样子,如果selector一直无法执行的话(比如runloop不是运行在default model下),这样子同样会造成target一直无法被释放掉,发生内存泄露。怎么解决这个问题呢?其实很简单,我们在适当的时候取消掉该调用就行了,系统提供了接口:
+ (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget
- Image内存过大
- Foundation与CoreFoundation的相互引用也会造成内存泄漏
- AFN 的NSURLSession不能释放
//解决办法:
//修改AFHTTPSessionManager 的manager方法,替换manager;
//或继承其,自己写个manager方法
//另一种写法,两个单例:
+ (AFHTTPSessionManager *)sharedHTTPSession{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
manager = [AFHTTPSessionManager manager];
manager.requestSerializer.timeoutInterval = 30;
[manager.requestSerializer setValue:@"XMLHttpRequest"
forHTTPHeaderField:@"X-Requested-With"];
});
return manager;
}
+ (AFURLSessionManager *)sharedURLSession{
static dispatch_once_t onceToken2;
dispatch_once(&onceToken2, ^{
urlsession = [[AFURLSessionManager alloc] initWithSessionConfiguration:
[NSURLSessionConfiguration defaultSessionConfiguration]];
});
return urlsession;
}
- 大次数循环内存暴涨问题 有道比较经典的面试题,查看如下代码有何问题:
for (int i = 0; i < 100000; i++) {
NSString *string = @``"Abc"``;
string = [string lowercaseString];
string = [string stringByAppendingString:@``"xyz"``];
NSLog(@``"%@"``, string);
}
该循环内产生大量的临时对象,直至当前runloop休眠前才释放掉,可能导致内存泄漏,解决方法为在循环中创建自己的autoReleasePool,及时释放占用内存大的临时变量,减少内存占用峰值。
for (int i = 0; i < 100000; i++) {
@autoreleasepool {
NSString *string = @"Abc";
string = [string lowercaseString];
string = [string stringByAppendingString:@"xyz"];
NSLog(@"%@", string);
}
}
内存泄漏的查询
Analyze静态分析 (command + shift + b)也就是编译
逻辑错误:访问空指针或未初始化的变量等;内存管理错误:如内存泄漏等;声明错误:从未使用过的变量;Api调用错误:未包含使用的库和框架
Instruments中的Leak动态分析内存泄漏,product->profile ->leaks打开工具主窗口
FBRetainCycleDetector
为什么AFNetWorking中的block不会造成循环引用问题?
系统的block或者AFN等block的调用
并不在当前控制器中调用,那么这个self就不代表当前控制器,那自然也就没有循环引用的问题。