不解之谜1,NSLog(@"2222=====%@",obj2); // crash 野指针
@implementation TestObj
- (instancetype)init
{
self = [super init];
if (self) {
__unsafe_unretained NSObject *obj1 = [TestObj getObj];
NSLog(@"1111=====%@",obj1); // 运行OK
__unsafe_unretained NSObject *obj2 = [TestObj getObj2];
NSLog(@"2222=====%@",obj2); // crash 野指针
}
return self;
}
+ (id)getObj {
return [NSObject new];
}
+ (id)getObj2 {
return [NSObject new];
}
不解之谜2,ViewController的viewDidLoad里面,NSLog不一样
- (void)viewDidLoad {
[super viewDidLoad];
__weak NSObject *obj1 = [ViewController getObj];
NSLog(@"=====%@",obj1); // nil
__weak NSObject *obj2 = [ViewController getObj];
NSLog(@"=====%@",obj2);// 输出对象
}
+ (id)getObj {
return [NSObject new];
}
Autorelease与Autoreleasepool
参考:
ARC环境下编译器到底对autorelease对象做了怎样的优化
黑幕背后的Autorelease
自动释放池的前世今生 ---- 深入解析 Autoreleasepool
Objective-C 小记(8)autorelease
autoreleasepool
1、自动释放池是由 AutoreleasePoolPage 以双向链表的方式实现的
2、当对象调用 autorelease 方法时,会将对象加入 AutoreleasePoolPage 的栈中
3、调用 AutoreleasePoolPage::pop 方法会向栈中的对象发送 release 消息
4、新建线程会新建AutoreleasePool,线程销毁AutoreleasePool释放对象且销毁
5、AutoreleasePoolPage 对象本身占 56 个字节, 所以 begin() 需要排除这 56 个字节, 真正用于存储 autorelease 对象地址的内存量为 end() - begin(), 共有 4040 个字节, 可存储 505 个 autorelease 变量.
@autoreleasepool{}
@autoreleasepool {
__autoreleasing NSObject *obj = [NSObject new];
}
伪代码
// 获取哨兵POOL_SENTINEL
void * atautoreleasepoolobj = objc_autoreleasePoolPush();
{
__autoreleasing NSObject *obj = [NSObject new];
}
// 就是release哨兵之后的autorelease对象。
objc_autoreleasePoolPop(atautoreleasepoolobj);
autorelease调用栈
- [NSObject autorelease]
└── id objc_object::rootAutorelease()
└─ id objc_object::rootAutorelease2()
└─ static id AutoreleasePoolPage::autorelease(id obj)
└─ static id AutoreleasePoolPage::autoreleaseFast(id obj)
├─ id *add(id obj)
├─ static id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page)
│ ├─ AutoreleasePoolPage(AutoreleasePoolPage *newParent)
│ └─ id *add(id obj)
└─ static id *autoreleaseNoPage(id obj)
├─ AutoreleasePoolPage(AutoreleasePoolPage *newParent)
└─ id *add(id obj)
一个autorelease对象在什么时刻释放?
1、手动指定Autoreleasepool:当前Autoreleasepool作用域大括号结束时释放;
2、不手动指定:autorelease对象会被添加到最近一次创建的autoreleasepool中,并在当前的runloop迭代结束时候释放。(子线程没有启动runloop是,在当前线程结束时候执行释放Autoreleasepool)
例如:主Runloop对Autoreleasepool管理的流程:
Runloop中,检测到触摸事件,创建事件,创建Autoreleasepool,autorelease对象加入pool中,事件完成,Runloop运行循环将要结束,释放Autoreleasepool,向pool中对象发送release消息,Runloop休眠。
autorelease 进行的非持有方法的优化
1、alloc/new/copy/mutableCopy---持有对象方法
2、其他类方法返回的对象,如果下面的createObj
@implementation BBObject
+ (instancetype)createObj {
return [self new];
}
需要了解下面的方法:
id objc_autoreleaseReturnValue(id obj)
{
// prepareOptimizedReturn判断是否可以TSL优化,可以则标记,YES--就不需要调用 objc_autorelease(),优化性能
if (prepareOptimizedReturn(ReturnAtPlus1)) return obj;
return objc_autorelease(obj);
}
id objc_retainAutoreleasedReturnValue(id obj)
{
// 如果之前 objc_autoreleaseReturnValue() 存入的标志位为 ReturnAtPlus1,则直接返回对象,无需调用 objc_retain(),优化性能
if (acceptOptimizedReturn() == ReturnAtPlus1) return obj;
return objc_retain(obj);
}
static ALWAYS_INLINE bool
prepareOptimizedReturn(ReturnDisposition disposition)
{
assert(getReturnDisposition() == ReturnAtPlus0);
if (callerAcceptsOptimizedReturn(__builtin_return_address(0))) {
if (disposition) setReturnDisposition(disposition);
return true;
}
return false;
}
static ALWAYS_INLINE ReturnDisposition
acceptOptimizedReturn()
{
ReturnDisposition disposition = getReturnDisposition();
setReturnDisposition(ReturnAtPlus0); // reset to the unoptimized state
return disposition;
}
TLS 全称为 Thread Local Storage(线程本地存储),是每个线程专有的键值存储,需要调用方与被调用方必须都是ARC的情况下(即全ARC环境下)
在某个线程上的函数调用栈上相邻两个函数对 TLS 进行了存取,这中间肯定不会有别的程序『插手』。
所以 getReturnDisposition() 和 setReturnDisposition() 的实现比较简单,不需要判断考虑是针对哪个对象的 Disposition 进行存取,因为当前线程上下文中只处理唯一的对象,保证不会乱掉。
static ALWAYS_INLINE void
setReturnDisposition(ReturnDisposition disposition)
{
tls_set_direct(RETURN_DISPOSITION_KEY, (void*)(uintptr_t)disposition);
}
callerAcceptsOptimizedReturn(__builtin_return_address(0))函数在不同架构的 CPU 上实现也是不一样的。
主要作用:
1、__builtin_return_address(0)获取当前函数返回地址。
2、callerAcceptsOptimizedReturn()方法判断调用方是否紧接着调用了 objc_retainAutoreleasedReturnValue或者 objc_unsafeClaimAutoreleasedReturnValue
如果是就直接当前对象地址,而不执行retain与autorelease操作.
总结:
MRC下:对象需要经历方法内部new->内部autorelease->外部retain->外部release这样四步流程
ARC下:对象需要经历方法内部new->外部release两步,省了中间两步“autorelease->retain-”
(TLS优化其实与OC内存管理“谁生产谁销毁谁持有谁释放”的黄金法则有所违背)
ARC 会视情况在调用方法时可能会添加 retain ,在方法内部返回时可能会添加 autorelease ,经过优化后很可能会抵消。
1、持有、无引用
- (void)test {
[BBObject new];
}
编译器编译后的伪代码
- (void)test {
objc_release([BBObject new]) ;
}
2、持有、局部变量引用
__strong
- (void)test {
__strong BBObject * obj = [BBObject new];
}
编译器编译后的伪代码
- (void)test {
id temp = [BBObject new];
objc_storeStrong(&tmp,nil);//相当于tmp指向对象执行release
}
__weak、__unsafe_unretained
// 这种写法xcode提示警告
__weak BBObject * obj = [BBObject new];
__unsafe_unretained BBObject * obj = [BBObject new];
3、持有、外部变量引用
- (void)test {
self.obj = [BBObject new];
}
编译器编译后的伪代码
- (void)test{
id temp = [BBObject new];
[self setObj:temp];//setter方法执行objc_storeStrong
objc_release(temp);
}
- (void)setObj:(id aObj) {
objc_storeStrong(&_obj, aObj);
}
4、不持有、无引用
- (void)test {
[BBObject createObj];
}
编译器编译后的伪代码
+ (instancetype) createObj {
id tmp = [self new];
return objc_autoreleaseReturnValue(tmp); // 系统可能会调用[tmp autorelease]
}
- (void)test {
objc_unsafeClaimAutoreleasedReturnValue([BBObject createObj]);
}
5、不持有、局部变量引用
- (void)test {
BBObject * obj1 = [BBObject createObj];
}
编译器编译后的伪代码
+ (instancetype) createObj {
id tmp = [self new];
return objc_autoreleaseReturnValue(tmp); // 系统可能会调用[tmp autorelease]
}
- (void)test {
id obj1 = objc_retainAutoreleasedReturnValue([BBObject createObj]);
objc_storeStrong(& obj1,nil);
}
发现obj1指向的对象不会加入autoreleasepool
6、不持有、外部变量引用
+ (instancetype) createObj {
id tmp = [self new];
return objc_autoreleaseReturnValue(tmp); // 系统可能会调用[tmp autorelease]
}
- (void)test {
self.obj = [BBObject createObj];
}
编译后的伪代码
- (void)test {
id tmp = _objc_retainAutoreleasedReturnValue([Foo createFoo]);
[self setObj:temp]; // setter方法执行objc_storeStrong
objc_release(temp);
}
查看autoreleasepool中的对象方法:
1、extern void _objc_autoreleasePoolPrint(void); //extern这个方法,需要查看的地方使用_objc_autoreleasePoolPrint();
2、需要查看的地方打断点,然后po _objc_autoreleasePoolPrint()