可能导致内存泄漏和性能问题的原因
- 循环引用:
- 当Block捕获了对象,而该对象又强引用了Block,就会形成循环引用,导致对象无法释放,造成内存泄漏。例如,在ViewController中定义一个Block,Block中访问了ViewController的属性,而ViewController又强引用了这个Block,就会出现这种情况。
- 代码示例:
@interface ViewController ()
@property (nonatomic, copy) void(^myBlock)(void);
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.myBlock = ^{
NSLog(@"%@", self.title);
};
self.myBlock();
}
@end
- 在上述代码中,
self.myBlock
捕获了self
,而self
又强引用了myBlock
,形成了循环引用。
- 不必要的Block拷贝:
- 频繁地对Block进行拷贝操作,可能会增加内存开销。比如在循环中每次都进行Block的拷贝,而实际上不需要这样做。
- 例如:
for (int i = 0; i < 1000; i++) {
void(^block)(void) = ^{
NSLog(@"%d", i);
};
dispatch_async(dispatch_get_main_queue(), [block copy]);
}
- 这里在循环中每次都对Block进行拷贝,增加了不必要的开销。
- Block中持有大量数据:
- 如果Block捕获了大量的数据,而这些数据在Block执行完毕后仍然被持有,就会导致内存浪费,影响性能。比如捕获了一个大的数组或字典等。
- 示例:
NSMutableArray *largeArray = [NSMutableArray arrayWithCapacity:10000];
// 填充大量数据
for (int i = 0; i < 10000; i++) {
[largeArray addObject:@(i)];
}
void(^block)(void) = ^{
NSLog(@"%@", largeArray);
};
block();
- 这里
block
捕获了largeArray
,即使block
执行完毕,largeArray
可能仍然因为block
的持有而无法释放。
优化方案
- 解决循环引用:
- 使用
__weak
或__unsafe_unretained
关键字来打破循环引用。推荐使用__weak
,因为__unsafe_unretained
可能会导致野指针问题。
- 修正前面ViewController的代码:
@interface ViewController ()
@property (nonatomic, copy) void(^myBlock)(void);
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
__weak typeof(self) weakSelf = self;
self.myBlock = ^{
__strong typeof(weakSelf) strongSelf = weakSelf;
if (strongSelf) {
NSLog(@"%@", strongSelf.title);
}
};
self.myBlock();
}
@end
- 这里先使用
__weak
修饰符创建一个弱引用weakSelf
,在Block内部再创建一个强引用strongSelf
,并进行判空操作,这样既避免了循环引用,又能安全地访问self
的属性。
- 避免不必要的Block拷贝:
- 确定是否真的需要对Block进行拷贝。如果只是在当前栈内使用Block,不需要拷贝。只有在需要将Block传递到其他线程或在对象生命周期外使用时,才进行拷贝。
- 对于前面循环中不必要的拷贝示例,可以改为:
for (int i = 0; i < 1000; i++) {
void(^block)(void) = ^{
NSLog(@"%d", i);
};
dispatch_async(dispatch_get_main_queue(), block);
}
- 减少Block中持有数据:
- 如果Block只需要部分数据,尽量只捕获需要的部分,而不是整个大的数据结构。
- 对于前面捕获大数组的示例,如果只需要数组的某一个元素,可以这样修改:
NSMutableArray *largeArray = [NSMutableArray arrayWithCapacity:10000];
// 填充大量数据
for (int i = 0; i < 10000; i++) {
[largeArray addObject:@(i)];
}
NSNumber *number = largeArray[0];
void(^block)(void) = ^{
NSLog(@"%@", number);
};
block();
- 这里只捕获了
largeArray
中的第一个元素number
,而不是整个largeArray
,减少了内存占用。
监控和排查潜在的内存问题
- 使用 Instruments:
- Leaks工具:在Xcode中,可以使用Instruments的Leaks工具来检测内存泄漏。运行应用并在Leaks工具中查看是否有对象被泄漏。如果有泄漏,Leaks工具会显示泄漏对象的相关信息,帮助定位问题代码。
- Allocations工具:Allocations工具可以监控应用的内存分配情况。可以查看对象的创建和销毁时间,分析内存增长趋势,找出可能存在内存问题的地方。例如,如果某个对象一直没有被销毁,可能存在内存泄漏。
- 添加日志:
- 在关键代码处添加日志,记录Block的创建、执行和释放情况。例如:
void(^block)(void) = ^{
NSLog(@"Block执行");
};
NSLog(@"Block创建");
block();
NSLog(@"Block执行完毕");
block = nil;
NSLog(@"Block释放");
- 通过分析日志,可以了解Block的生命周期,判断是否存在异常情况。
- 使用WeakProxy:
- 可以创建一个
WeakProxy
类,用于包装对象,在Block中使用WeakProxy
来避免直接捕获对象,从而减少循环引用的风险。WeakProxy
类的实现可以参考以下代码:
@interface WeakProxy : NSProxy
@property (nonatomic, weak) id target;
+ (instancetype)proxyWithTarget:(id)target;
@end
@implementation WeakProxy
+ (instancetype)proxyWithTarget:(id)target {
WeakProxy *proxy = [WeakProxy alloc];
proxy.target = target;
return proxy;
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
return [self.target methodSignatureForSelector:sel];
}
- (void)forwardInvocation:(NSInvocation *)invocation {
SEL sel = invocation.selector;
if ([self.target respondsToSelector:sel]) {
[invocation invokeWithTarget:self.target];
}
}
@end
@interface ViewController ()
@property (nonatomic, copy) void(^myBlock)(void);
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
WeakProxy *proxy = [WeakProxy proxyWithTarget:self];
self.myBlock = ^{
NSLog(@"%@", proxy.target.title);
};
self.myBlock();
}
@end
- 这样在Block中通过
WeakProxy
间接访问self
,降低了循环引用的可能性。