ARC
init 创建
- 无强指针指向
- (void)test {
[[MyObject alloc] init];
}
movq MyObject
callq objc_alloc_init
callq objc_release
- 有强指针指向
- (void)test {
MyObject *obj = [[MyObject alloc] init];
}
movq MyObject
callq objc_alloc_init
callq objc_storeStrong
类方法创建
- 类创建方法无强指针指向
- (void)test {
[MyObject obj];
}
movq MyObject
callq objc_msgSend
callq objc_unsafeClaimAutoreleasedReturnValue
- 类创建方法有强指针指向
- (void)test {
MyObject *obj = [MyObject obj];
}
movq MyObject
callq objc_msgSend
callq objc_retainAutoreleasedReturnValue
callq objc_storeStrong
调用方法
- 有无强指针相同,仅最后一句是调用
release还是stroeStrong
- (void)test {
[[[MyObject alloc] init] description];
}
movq MyObject
callq objc_alloc_init
callq objc_msgSend
callq objc_unsafeClaimAutoreleasedReturnValue
callq objc_release
方法附带返回值
- 无强指针指向
- (MyObject *)test {
return [[MyObject alloc] init];
}
movq MyObject
callq objc_alloc_init
jmp objc_autoreleaseReturnValue
- 有强指针指向
- (MyObject *)test {
MyObject *obj = [[MyObject alloc] init];
return obj;
}
movq MyObject
callq objc_alloc_init
callq objc_retain
callq objc_storeStrong
jmp objc_autoreleaseReturnValue
方法附带参数
- (void)test:(NSObject *)obj {
}
callq objc_storeStrong
callq objc_storeStrong
弱引用
- 仅声明
- (void)test {
__weak typeof(self) weakSelf = self;
}
callq objc_initWeak
callq obj_destroyWeak
- 调用 weakSelf
- (void)test {
__weak typeof(self) weakSelf = self;
[weakSelf description];
}
callq objc_initWeak
// 如果多次调用 weakSelf,下面会重复多次
===========================
callq objc_loadWeakRetained
callq objc_msgSend
callq objc_unsafeClaimAutoreleasedReturnValue
callq objc_release
===========================
callq objc_destroyWeak
callq objc_destroyWeak
block
- 声明一个强引用的 block
- (void)test {
void (^block)(void) = ^{
};
}
callq objc_retainBlock
callq objc_storeStrong
-
声明一个弱引用的 block 同声明一个普通的弱引用的对象
-
捕获 self
- (void)test {
void (^block)(void) = ^{
[self description];
};
block();
}
callq objc_msgSend
callq objc_unsafeClaimAutoreleasedReturnValue
block 作为属性
- 相互持有导致相互引用
- (void)test {
// 这里不能调用 self.block,不然汇编会有很污染
_block = ^{
[self description];
};
_block();
}
leaq __block_descriptor_40_e8_32s_e5_v8
leaq _block_invoke
movq _NSConcreteStackBlock
callq objc_retain
callq objc_retainBlock
callq objc_release
callq objc_storeStrong
__weak解决循环引用
- (void)test {
__weak typeof(self) _self = self;
_block = ^{
[_self description];
};
_block();
}
// 多次调用 _self 会导致下面四个方法执行多次
==============================
callq objc_loadWeakRetained
callq objc_msgSend 'description'
callq objc_unsafeClaimAutoreleasedReturnValue
callq objc_release
==============================
__strong解决block执行过程中提前释放(不能保证捕获的weakself有值,下面有例子)
- (void)test {
__weak typeof(self) _self = self;
_block = ^{
__strong typeof(self) self = _self;
[self description];
};
_block();
}
callq objc_loadWeakRetained
// 多次调用 self 的话下面这两个方法会执行多次
==============================
callq objc_msgSend 'description'
callq objc_unsafeClaimAutoreleasedReturnValue
==============================
callq objc_storeStrong
这里也有一个点就是 block 内用 strong 指针指向 weak 后,objc_loadWeakRetained/objc_release 方法就不会多次调用了,节约了性能。
不会导致循环引用的 block
只要不相互持有的都不会导致循环引用。(或者在某一时机被主动破开)
self不持有block,block持有self,如block中的例子3UIView.animatedUIAlertActionGCD- 其他等等
strong 捕获的 weak 被提前释放
- (void)test {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"test--: %@", self);
});
__weak typeof(self) _self = self;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
__strong id self = _self;
NSLog(@"test dance: %@", self);
});
}
// 打印结果
2022-01-10 17:12:37.256066+0800 iOSMemoryManagement[42640:1493873] test--: <TestBlock: 0x600002918300>
2022-01-10 17:12:37.256253+0800 iOSMemoryManagement[42640:1493873] TestBlock dealloc <_NSMainThread: 0x600003e08140>{number = 1, name = main}
2022-01-10 17:12:38.344510+0800 iOSMemoryManagement[42640:1493873] test dance: (null)
这里很明显能看到如果直接捕获 self,是可以打印出来的,而用 strong weak dance 捕获的值被释放了。
原因:(下面的解释还要验证,仅为猜测)
- 第一个打印对象的原因,由于
block持有self会导致self引用计数 +1,block执行完成后,会调用block_release,然后self引用计数 -1,此时引时计数归 0,self即被回收。 - 第二个打印为空的原因,由于
strong捕获的是weakself,并不会导致self引用计数增加,故当self被释放时,weakself还是会被置nil,所以strong的指向也是nil。
strong 防止 block 执行过程中被释放
B 页面立刻执行两个延时函数
- (void)test {
__weak typeof(self) _self = self;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
__strong typeof(self) self = _self; // 1
if (!self) return;
NSLog(@"1 %@", _self);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(4 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"2 %@", self);
});
});
}
如果注释 1 关闭,那么打印 1 后,2 有可能无法打印。如果注释 1 打开,如果可以打印 1,那么 2 是无法被打印的。
weak 可以防止由 block 捕获 self 可能延长的生命周期
在网络请求中尽管不会导致循环引用,但是弱引用也是有必要的,以防网络请求较慢时,发生 self 被延迟释放的问题。
循环引用
定时器(Timer)
iOS10以前,OC可以使用NSObject/NSProxy,swift中仅可以使用NSObject。(由于太简单,不写了)。iOS10之后,可以使用block的弱引用方式。- 如果不强持有
timer,那么就算捕获self,仍然不会发生循环引用。(加进RunLoop中也算持有) - 如果强持有
timer,而不捕获self也不会导致循环引用。 - 虽然
self不被强持有,可以被正常释放,但是仍然不能忘记,要在selfdeinit的时候,把timerinvalidate并置nil。
- 如果不强持有
定时器使用
定时器的闭包实现(YYKit)
+ (void)_yy_ExecBlock:(NSTimer *)timer {
if ([timer userInfo]) {
void (^block)(NSTimer *timer) = (void (^)(NSTimer *timer))[timer userInfo];
block(timer);
}
}
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)seconds block:(void (^)(NSTimer *timer))block repeats:(BOOL)repeats {
return [NSTimer scheduledTimerWithTimeInterval:seconds target:self selector:@selector(_yy_ExecBlock:) userInfo:[block copy] repeats:repeats];
}
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)seconds block:(void (^)(NSTimer *timer))block repeats:(BOOL)repeats {
return [NSTimer timerWithTimeInterval:seconds target:self selector:@selector(_yy_ExecBlock:) userInfo:[block copy] repeats:repeats];
}
notification
selector方式的通知现在不需要主动移除,既不会导致循环引用,也不会导致selector执行usingBlock这种方式使用[weak self]是必须的,否则会出现内存泄露。而且还需要主动调用removeObserve:token,不然会导致 block 仍然会执行。(我觉得这个是对之前改变的一些补充,在以前在大部分情况下,我们都需要主动去调removeObserve:self来破除循环引用,现在第一种方式不能使用了,但是如果真的不想释放,还有这种方式可以使用)
let center = NSNotificationCenter.defaultCenter()
let mainQueue = NSOperationQueue.mainQueue()
self.localeChangeObserver = center.addObserverForName(NSCurrentLocaleDidChangeNotification, object: nil, queue: mainQueue) { (note) in
print("The user's locale changed to: (NSLocale.currentLocale().localeIdentifier)")
}
Tip
To avoid a retain cycle, use a weak reference to self inside the block when selfcontains the observer as a strong reference.
Unregister an observer to stop receiving notifications. To unregister an observer, use removeObserver(_:) or removeObserver(_:name:object:) with the most specific detail possible. For example, if you used a name and object to register the observer, use the name and object to remove it.
You must invoke removeObserver(_:) or removeObserver(_:name:object:) before the system deallocates any object that addObserver(forName:object:queue:using:)specifies.
官方文档说的极其清楚,如果不想循环引用的话就用 [weak self] 吧,而且在你释放的时候必须执行 removeObserver 方法。
网络请求
- 这种情况下,如果不弱引用,必定造成内存泄露
URLSession(configuration: URLSessionConfiguration.default)
.dataTask(with: URL(string: "https://www.baidu.com")!) { data, respose, error in
print(self, error)
}
- 这种情况下,block 执行完成后,self 就会被 release(引用计数-1,并不一定会被释放,如果这个控制器本身没有 pop 的情况下,但也有一个问题,就是这段代码在控制器 pop 的后还是会触发)
URLSession(configuration: URLSessionConfiguration.default)
.dataTask(with: URL(string: "https://www.baidu.com")!) { data, respose, error in
print(self, error)
}.resume()
- 使用 Alamofire 还是会有方法2 的问题 所以我们在执行网络请求的时候 weak self 是必须的,而大部分情况下,strong self 也是不能加的,因为在 block 执行过程中 self 被释放了,那么后面的代码不再执行恰恰是我们期望的。(那我们还需要判断 weakself 还存在吗? 因为好像每一句代码执行前他都有可能被释放掉了,我感觉在这种情况下就不要判断了)。
block
- self 作为参数传入时不会造成循环引用
- (void)test {
TestBlock *sblock = [[TestBlock alloc] init];
sblock.block = ^(TestBlock * **_Nonnull** obj) {
// 如果这里捕获 sblock 的话,那么会出现循环引用的问题
NSLog(@"%@", obj);
};
sblock.block(sblock);
}
__weak破除循环引用- 不持有
block破除循环引用 - 执行完成后将
block置为nil破除循环引用。
- (void)test {
TestBlock *sblock = [[TestBlock alloc] init];
sblock.block = ^ {
NSLog(@"%@", sblock);
};
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
sblock.block = **nil**;
});
sblock.block();
}
封装传参解除循环循环引用
class Delegate {
var temp: (() -> Void)?
func delegate<T: AnyObject>(_ target: T, block: @escaping (T) -> Void) {
self.temp = { [weak target] in
guard let target = target else { return }
block(target)
}
}
func call() {
temp?()
}
func callAsFunction() {
temp?()
}
}
这个在 Kingfisher 三方库被使用到了,
内存优化
- 使用 autoreleasepool 降低内存峰值(解决内存抖动)
内存泄露检测
Profile->Leaks
Zombie Objects
- 原理:
相关 api 解析
autorelease
数据结构
以栈为节点的双向链表。
weak
数据结果
NSHashMap
MRC/CoreFoundation
Bridge
__bridge直接转换成 Core 相关 API,内存不管__bridge_retain转换成 Core 相关 API,引用计数 +1__bridge_transfer转换成 Core 相关 API,引用计数 -1,CFAutoRelease,会加进自动释放池
+ (NSString *)stringWithUUID {
CFUUIDRef uuid = CFUUIDCreate(NULL);
CFStringRef string = CFUUIDCreateString(NULL, uuid);
CFRelease(uuid);
return (__bridge_transfer NSString *)string;
// 这种写法也可以,上面的 YYKit 的写法
return CFAutoRelease(string);
}
CF_RETURNS_RETAINED,这个时候外部调用的时候需要主动调用CFRelease,当方法名中没有copy、create,那么需要标记一下。CF_RETURNS_RETAINED,同理,如果方法中有copy、create,但不需要主动调CFRelease也要标记一下。其实就是为了在我们静态检查的时候,编译器不要太笨蛋的误报了。
- (CTFontRef)CTFontRef CF_RETURNS_RETAINED {
CTFontRef font = CTFontCreateWithName((__bridge CFStringRef)self.fontName, self.pointSize, NULL);
return font;
}
Swift 和 CoreFoundation 框架交互
上述的代码直接被 struct UUID取代了。
UUID().uuidString
从这里还想吐槽一句,就是很多 oc 难用且较常用的 api,swift 也是做了封装的。
Unmanaged
- takeRetainedValue() -> Instance
- takeUnretainedValue() -> Instance
- passRetained(_ value: Instance) -> Unmanaged
- passUnretained(_ value: Instance) -> Unmanaged
- ...
总结
block作用属性的时候要万分注意是否会产生循环引用(无论是自定义组件还是单例中)。- 如
GCD、网络请求中的block,虽然不会造成循环引用,我们仍然需要根据实际情况判断是否需要使用weak self(绝大部分情况需要),因为这里会发生线程或者单例持有block导致block无法及时释放,从而导致self无法被释放,他们的self被释放发生在block执行完成之后。(但是大部分情况,例如页面退出后,实际上是无需再执行后面的代码,这时仍然需要使用弱引用的) - 使用
strong self可以保证block在执行过程中self不被释放。(不保证捕获的weak self不为nil)(所以在这里提前判断一下strong self为nil就十分有必要的,判断成功了,那么block后面所有的self不用考虑了,全部不为空),外部只要使用了weak self,如果不想后面的代码执行,一定要判断一下是否为nil。 - 本质就是引用计数,
alloc系列方法、retain、release配合计数,计数归零后dealloc