内存泄露场景
# 1、解决循环引用的两种宏
方式一:
/// 引用
#define WEAK_SELF __weak __typeof(&*self)weakSelf = self;
#define STRONG_SELF __strong __typeof(&*weakSelf) strongSelf = weakSelf;
/// 检查是否为空
#define CHECK_WEAK_SELF if (weakSelf == nil) { return; }
#define CHECK_STRONG_SELF if (strongSelf == nil) { return; }
使用示例:
WEAK_SELF
[self.view1 xxActionWithBlock:^(NSObject * _Nonnull obj) {
STRONG_SELF
CHECK_STRONG_SELF
// 逻辑代码
[strongSelf.view1 configData];
}];
方式二:
参考 RAC 或者 SDWebImage 等库的实现,选择一个就好,不要重复
以 RAC 宏为例:
/// 定义
#define weakify(...) \
rac_keywordify \
metamacro_foreach_cxt(rac_weakify_,, __weak, __VA_ARGS__)
#define strongify(...) \
rac_keywordify \
_Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Wshadow\"") \
metamacro_foreach(rac_strongify_,, __VA_ARGS__) \
_Pragma("clang diagnostic pop")
使用示例:
id foo = [[NSObject alloc] init];
id bar = [[NSObject alloc] init];
@weakify(self, foo, bar);
// this block will not keep 'foo' or 'bar' alive
BOOL (^matchesFooOrBar)(id) = ^ BOOL (id obj){
// but now, upon entry, 'foo' and 'bar' will stay alive until the block has
// finished executing
@strongify(self, foo, bar);
// 逻辑代码 (直接使用self)
[self.view1 configData];
return [foo isEqual:obj] || [bar isEqual:obj];
};
⚠️ Block 内部重新声明的 self 和 block 外部的不是一个对象,只是名称相同,可以理解为block 内部定义了一个局部变量名为self的对象,而恰巧这个场景是允许的,优先取block内部的此对象。所以在内部并不会导致循环引用。
# 2、常规使用Block场景
// 使用场景
@weakify(self);
[self.moreButton addTapActionWithBlock:^(UIGestureRecognizer * _Nonnull gestureRecoginzer) {
@strongify(self);
// self
!self.didClickMoreBlock ?: self.didClickMoreBlock(YES);
}];
// 使用场景
WEAK_SELF
[self.moreButton addTapActionWithBlock:^(UIGestureRecognizer * _Nonnull gestureRecoginzer) {
STRONG_SELF
!strongSelf.didClickMoreBlock ?: strongSelf.didClickMoreBlock(YES);
}];
# 3、Block内使用指针
// 声明变量方式
@interface xxxVC ()<UITableViewDelegate, UITableViewDataSource>
{
NSMutableArray *_dataArray;
NSInteger _pageNum;
NSInteger _pageSize;
}
// 错误使用场景
WEAK_SELF
[self.moreButton addTapActionWithBlock:^(UIGestureRecognizer * _Nonnull gestureRecoginzer) {
STRONG_SELF
...
[strongSelf->_dataArray xxx];
...
}];
=======================================
// 原因: 因为非@property 声明的属性,只能通过 strongSelf->_dataArray 的方式,而 -> 是直接通过内存地址访问实例变量的值,而不是点语法的通过属性访问器方法,这样在block延迟调用而self已经释放的情况下可能会导致直接访问内存地址异常
// 修改后
@property (nonatomic, strong) NSMutableArray *dataArray;
// 使用属性访问器方法
[self.moreButton addTapActionWithBlock:^(UIGestureRecognizer * _Nonnull gestureRecoginzer) {
STRONG_SELF
...
[strongSelf.dataArray xxx];
...
}];
# 4、super 导致的内存泄露
部分场景block中使用super也是会内存泄露的,所以尽量避免
[self.viasInputView setBlock_viasInputViewEditedCompleted:^(AMapPOI * _Nonnull startPoi, AMapPOI * _Nonnull endPoi, NSArray * _Nonnull viaPois) {
STRONG_SELF
...
// 直接使用了super
[super sc_routerEventWithName:@"xxxx" userInfo:nil];
}];
// 上述场景是实际项目中的内存泄露代码,
[self.viasInputView setBlock_viasInputViewEditedCompleted:^(AMapPOI * _Nonnull startPoi, AMapPOI * _Nonnull endPoi, NSArray * _Nonnull viaPois) {
STRONG_SELF
...
// 改用strongSelf 方法调用
[strongSelf setupRouterEventWithName:@"ViaEditedSuccessShowDesignView" userInfo:nil];
}];
- (void)setupRouterEventWithName:(NSString *)eventName userInfo:(NSDictionary *)userInfo {
[super sc_routerEventWithName:eventName userInfo:userInfo];
}
# 5、addSubview 子view在block中调用了自己
UIButton *leftBtn = [[UIButton alloc] initWithFrame:CGRectMake(10, 6, 64, 28)];
leftBtn.layer.masksToBounds = YES;
leftBtn.layer.cornerRadius = 14;
WEAK_SELF
[leftBtn addTapActionWithBlock:^(UIGestureRecognizer * _Nonnull gestureRecoginzer) {
leftBtn.selected = YES;
weakSelf.carSwitchIndex = 1;
}];
[self.view addSubview:leftBtn];
// 上述场景 中addSubview 的对象在 block 中调用了自己,这种场景也是可能导致内存泄露的
// 修改: 使用 weak_leftBtn
@weakify(self, leftBtn);
[leftBtn addTapActionWithBlock:^(UIGestureRecognizer * _Nonnull gestureRecoginzer) {
@strongify(self, leftBtn);
leftBtn.selected = YES;
self.carSwitchIndex = 1;
}];
[self.view addSubview:leftBtn];
# 6、 数组中包含对象使用block
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewXXCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];
...
// cell 数组中包含对象使用block
cell.rightButtons = @[[MGSwipeButton buttonWithTitle:@"删除" backgroundColor:UIColor.redColor callback:^BOOL(MGSwipeTableCell * _Nonnull cell) {
if (self.dataArray.count < 1) {
self.tableView.mj_footer.hidden = YES;
self.emptyView.hidden = NO;
}
}]];
...
// 上述场景虽然不是属性直接使用,但是由于其数组依然是强引用,所以也是导致内存泄露的一个原因
// 修改: 使用弱引用
@weakify(self);
cell.rightButtons = @[[MGSwipeButton buttonWithTitle:@"删除" backgroundColor:UIColor.redColor callback:^BOOL(MGSwipeTableCell * _Nonnull cell) {
@strongify(self);
if (self.dataArray.count < 1) {
self.tableView.mj_footer.hidden = YES;
self.emptyView.hidden = NO;
}
}]];
# 7、Block中使用含 self 的宏导致内存泄露
(这个肉眼比较难定位)
// 以下代码存在内存泄露
...
[self.myFamilyVM queryRelationTagListCompleteBlock:^(BOOL result) {
WEAK_SELF // 这里在block内部
[weakSelf.myFamilyVM familyAddRelationTag:tagStr CompleteBlock:^(BOOL result) {
STRONG_SELF
[strongSelf.tableHeadView needHidenSwitchButton:YES];
}];
...
}];
// 原因:嵌套block中 WEAK_SELF 宏本身置于block中了,而WEAK_SELF 的 本质是包含self 的代码块,编译插入等于block中使用了self。从而导致内存泄露
// #define WEAK_SELF __weak __typeof(&*self)weakSelf = self;
// 修改:
...
WEAK_SELF // 应该放在最外层
[self.myFamilyVM queryRelationTagListCompleteBlock:^(BOOL result) {
STRONG_SELF
[strongSelf.myFamilyVM familyAddRelationTag:tagStr CompleteBlock:^(BOOL result) {
STRONG_SELF
[strongSelf.tableHeadView needHidenSwitchButton:YES];
}];
...
}];
# 8、嵌套block使用
...
WEAK_SELF
// 第一层block
[self.myFamilyVM queryRelationTagListCompleteBlock:^(BOOL result) {
STRONG_SELF
...
// 第二层block
[strongSelf.myFamilyVM familyAddRelationTag:tagStr CompleteBlock:^(BOOL result) {
... // 其它业务
[strongSelf.tableHeadView needHidenSwitchButton:YES];
}];
...
}];
// 原因:
// 上面的示例。 第二层block使用的是第一层 STRONG_SELF 宏 。这个情况什么时候会内存泄露呢
// 正常是没问题的,但是第二层回调的时候strongSelf 不一定在,这个时候业务逻辑就不对了
// 如果不确认是否存在耗时方法等业务逻辑,尽量嵌套block 都加上吧
// 修改:
WEAK_SELF
// 第一层block
[self.myFamilyVM queryRelationTagListCompleteBlock:^(BOOL result) {
STRONG_SELF // 加上1
...
// 第二层block
[strongSelf.myFamilyVM familyAddRelationTag:tagStr CompleteBlock:^(BOOL result) {
STRONG_SELF // 加上2
... // 其它业务
[strongSelf.tableHeadView needHidenSwitchButton:YES];
}];
...
}];
# 9、Block中使用 下划线变量
[self.myFamilyVM familyAddRelationTag:tagStr CompleteBlock:^(BOOL result) {
[_tableHeadView needHidenSwitchButton:YES];
}];
// 原因:
// 上面的示例。 _tableHeadView 使用下划线,本质上还是self,所以要使用 weakSelf.tableHeadView
// 修改:
WEAK_SELF
[self.myFamilyVM familyAddRelationTag:tagStr CompleteBlock:^(BOOL result) {
[weakSelf.tableHeadView needHidenSwitchButton:YES];
}];
# 10、Target代理强持有导致循环引用
NSTimer 为例:
self.scrollTimer = [NSTimer scheduledTimerWithTimeInterval:timeInterval
target:self
selector:@selector(scrollTimerDidFire:)
userInfo:nil
repeats:NO];
[[NSRunLoop currentRunLoop] addTimer:self.scrollTimer forMode:NSRunLoopCommonModes];
// 原因:
// 上面的示例。iOS10.0以下系统时使用系统默认scheduledTimer方法都会被循环引用,就算是 target:传入 weakSelf 也是一样的
// 用户可以在DidDisaper 等方法进行关闭定时器,但是不可以在 dealloc 方法中销毁timer,时机太晚了,dealloc不会被调用。
// 修改:
// 对于 10 以上系统可以使用 block的方法
[NSTimer scheduledTimerWithTimeInterval:1 block:^(NSTimer * _Nonnull timer) {
} repeats:YES];
// 兼容iOS10.0以下系统时,要自己处理,方式有很多种
// 1、进行一个 block 的 copy
#pragma mark - NSTimer(private) 兼容iOS10.0以下系统时使用
+ (NSTimer *)weak_scheduledTimerWithTimeInterval:(NSTimeInterval)inerval repeats:(BOOL)repeats block:(void (^)(void))block {
return [NSTimer scheduledTimerWithTimeInterval:inerval target:self selector:@selector(handle:) userInfo:[block copy] repeats:repeats];
}
+ (void)handle:(NSTimer *)timer {
void(^block)(void) = timer.userInfo;
if (block) {
block();
}
}
⚠️ 另外:
1、兼容iOS10.0以下系统时使用,项目最低系统升级10以上时请使用系统方法
2、scrollView中启动
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
3、请在dealloc中 invalidate 并置nil
if (_timer) {
[_timer invalidate];
_timer = nil;
}
// 2、 使用weak delegate 方式,
// NSProxy 做中间代理等
// NSProxy 方式是 通过 invocation 消息转发方式处理
# 11、 单例方法导致内存泄露
1、不是所有的单例都不释放的,部分情况的单例在短时间内持有了大量内存, 在不使用的时候建议适当清理
2、单例不要持有UI对象,避免无法释放
# 12 、MRC与ARC混编
// 如果文件是指定 --fno-objc-arc 的 手动管理模式,那么代码就需要手动对对象进行 release。 不然就会内存泄露
// 这个场景现在比较少,一些老的项目中存在,MRC 文件代码往往因为ARC 已成习惯忘了手动进行是否,也就是手动调用 release
[xxx release];
// 还有就是使用 autorelease
[[[xxx aloc] initWith xxx] autorelease];
# 13 、CoreFoundation与Foundation的桥接
// 桥接其实也是 混编 场景,也是容易导致内存泄露的一种方式,因为使用习惯的问题
1、ARC: 针对的是 Foundation (OC 语法)代码,进行了一个引用计数管理
2、CF:而Core Foundation(CF)中的对象是用C语言实现的,分配给CF对象的内存需要手动释放,否则会造成内存泄漏
3、__bridge 桥接:只做类型转换,不会改变对象的内存管理权逻辑
// 3.1 被桥接的对象是OC对象,那么桥接以后不需要手动释放
NSString *aNSStr = [[NSString alloc] initWithFormat:@"test"];
CFStringRef aCFStr = (__bridge CFStringRef)aNSStr;
// 3.2 被桥接的对象是CF对象,那么桥接以后需要手动释放
CFStringRef aCFStr = CFStringCreateWithCString(NULL, "test", kCFStringEncodingUTF8);
NSString *aNSStr = (__bridge NSString *)aCFStr;
CFRelease(aCFStr); // 这里必须释放CF对象
4、__bridge_transfer 桥接:除了类型转换,还会把CF对象的所有权移交到OC对象
// 使用__bridge_transfer 桥接以后不需要手动释放
CFStringRef aCFStr = CFStringCreateWithCString(NULL, "test", kCFStringEncodingUTF8);
4.1)NSString *aNSStr = (__bridge_transfer NSString *)aCFStr;
// __bridge_transfer,等价于 CFBridgingRelease()
4.2)NSString *aNSStr = (NSString *)CFBridgingRelease(aCFStr);
5、使用了 __bridge_retained 将 OC 桥接成CF (__bridge_retained,等价于CFBridgingRetain())
// __bridge_retained除了类型转换,还会把OC对象的所有权抢过来给CF对象。因此需要手动释放了
NSString *aNSStr = [[NSString alloc] initWithFormat:@"test"];
5.1)CFStringRef aCFStr = (__bridge_retained CFStringRef) aNSStr;
5.2)CFStringRef aCFStr = (CFStringRef)CFBridgingRetain(aNSStr);
CFRelease(aCFStr); // 这里必须释放CF对象
6、其他:
在将CoreFoundation对象进行计数减少后,为避免再次访问该对象可能造成野指针访问,建议及时将对象置为NULL。
对于CoreFoundation框架对象来说,不可重复使用CFRelease(未检测NULL)函数进行计数减少,过度释放时会发生崩溃。
对于CGImageRef对象可以使用CGImageRelease函数(未检测NULL)
对于CGFontRef对象可以使用CGFontRelease函数(会检测NULL)
# 14 、malloc申请内存未 free导致内存泄漏
很显然, malloc 也是C 方法,需要手动管理内存
# 15 、NSURLSessionTask 使用
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *dataTask = [session dataTaskWithURL:[NSURL URLWithString:@"http://www.baidu.com"]];
生成NSURLSessionTask及其子类对象时,该对象会处于挂起状态,此时该对象会一直常驻内存
需要为该对象调用cancel或resume方法 进行释放
# 16 、分类使用 dealloc 打印日志
⚠️ ⚠️⚠️⚠️ 分类里不要使用dealloc方法,不然可能会出现各种异常,,,,,
# 17 、VC 中 dealloc 释放一些对象导致异常
有时候我们会遇到在dealloc 进行一些对象释放的代码,大部分是能正常运行,但是,有些就是可以的
本人遇到一些,比如在 dealloc 中 使用 self 释放 一些 self.delegate 导致异常
// 修复:
不要使用self ,改使用 下划线 _
# 18、类方法导致的内存泄露
正常使用类方法+block 不会导致循环引用,但是使用了静态变量或全局变量,就有可能会 发生内存泄漏。
在iOS Objective-C中,类对象Block(Class Object Block)也称为静态Block,指的是在类
方法中使用的Block。
类对象Block的内存管理方式与实例对象Block不同,因为类对象Block不会引用实例变量,也
就不会对self进行强引用。但是,如果在Block内部使用了静态变量或全局变量,就有可能会
发生内存泄漏。
具体来说,如果在类对象Block中使用了静态变量或全局变量,并且这些变量被Block所引
用,那么就有可能会导致内存泄漏。这是因为,静态变量或全局变量的生命周期与应用程序
的生命周期相同,如果Block被长期持有,就可能导致这些变量一直无法释放,
为了避免这种情况发生,可以使用block修饰符来修饰静态变量或全局变量。_block修饰
符可以使得Block内部对这些变量的引用变成弱引用,这样即使Block被长期持有,也不会导
致这些变量无法释放。
需要注意的是,在使用类对象Blockl时,一定要注意Block中对静态变量或全局变量的引用关
系,避免出现内存泄漏问题。同时,在Block中使用block修饰符时,需要注意变量所在的
作用域和Block所在的作用域,以免出现访问无效指针的情况。