__strong修饰符
命令行使用下面的命令来将 Objective-C 代码转成 LLVM 中间码
clang -S -fobjc-arc -emit-llvm main.m -o main.ll
LLVM中间码如下:
define i32 @main(i32 %0, i8** %1) #0 {
%3 = alloca i32, align 4
%4 = alloca i32, align 4
%5 = alloca i8**, align 8
%6 = alloca i8*, align 8
%7 = alloca i8*, align 8
store i32 0, i32* %3, align 4
store i32 %0, i32* %4, align 4
store i8** %1, i8*** %5, align 8
%8 = call i8* @llvm.objc.autoreleasePoolPush() #1
%9 = load %struct._class_t*, %struct._class_t** @"OBJC_CLASSLIST_REFERENCES_$_", align 8
%10 = bitcast %struct._class_t* %9 to i8*
%11 = call i8* @objc_opt_new(i8* %10)
%12 = bitcast i8* %11 to %0*
%13 = bitcast %0* %12 to i8*
store i8* %13, i8** %6, align 8
%14 = load i8*, i8** %6, align 8
%15 = call i8* @llvm.objc.retain(i8* %14) #1
store i8* %15, i8** %7, align 8
call void @llvm.objc.storeStrong(i8** %7, i8* null) #1
call void @llvm.objc.storeStrong(i8** %6, i8* null) #1
call void @llvm.objc.autoreleasePoolPop(i8* %8)
ret i32 0
}
简化如下:
%11 = call i8* @objc_opt_new(i8* %10) //创建对象
%15 = call i8* @llvm.objc.retain(i8* %14) #1 //引用计数+1
call void @llvm.objc.storeStrong(i8** %7, i8* null) #1 //release
call void @llvm.objc.storeStrong(i8** %6, i8* null) #1 //release
对于storeStrong,查看源码实现如下:
location:指针的地址
obj:指向对象的指针,存储对象的地址
逻辑可以理解为
- 检查参数obj与指针*location存储的地址是否相同
- 若相同,直接返回
- 若不同
-
- 对新的对象obj引用计数+1,当前 *location指向obj
-
- 对原来指向的对象引用计数-1
简化后的中间码storeStrong第二个参数为null,就相当于是对对象进行了release操作。
ARC在编译器、运行期做了哪些工作?
编译期:根据代码执行的上下文语境,在适当的位置插入retain,release
运行期:
- __weak修饰的变量能够在其指向的对象引用计数为0时,自动设置为nil。
- ARC可以在运行期检测出autorelease及紧跟其后的retain,此时不直接掉用autorelease方法,而是改为调用objc_autoreleaseReturnValue函数。这个函数会检查当前方法返回之后的即将要执行的代码。如果在返回的对象上执行retain操作,则设置全局数据结构中的一个标志位,而不执行autorelease操作。
与之相似,如果方法返回了一个自动释放的对象,此时不直接执行retain,而是改为执行objc_retainAutoreleaseReturnValue函数,这个函数会检测刚提到的标志位,如果已经置位,则不执行retain。
设置并检测标志位,要比调用autorelease和retain快。
内存管理语义在方法名中表现出来
alloc、new、copy、mutableCopy开头的方法,其返回的对象归调用者所有
类似于array这种非上述四种命名方式并返回对象的方法,其返回的对象并不归调用者所有,会调用autorelease方法,以确保可以多存活一段时间。
若紧跟其后会对该对象进行retain操作,那么这一对操作就是多余的,为了提高性能,可将二者删去,但又考虑到需要兼容不实用ARC的代码,所以上述的优化保证了向后兼容性
__weak修饰符
__weak在使用过程中调用的函数
汇编语言如下
实际调用如下
objc_alloc_init
objc_initWeak
objc_release
objc_destroyWeak
首先创建并初始化对象,通过objc_initWeak函数初始化附有__weak修饰符的变量,由于弱引用,该指针变量不持有对象,所以对象会立即释放,执行objc_release,最后调用objc_destroyWeak释放附有__weak修饰符的变量。
汇编:
实际调用:
objc_alloc_init
objc_initWeak
objc_destroyWeak
objc_storeStrong
与上述过程差不太多
汇编:
实际调用:
objc_alloc_init
objc_initWeak
objc_loadWeakRetained
NSLog
objc_release
objc_destroyWeak
objc_storeStrong
与之前不同的是,在打印__weak修饰的obj1前,调用了objc_loadWeakRetained,打印完后调用了objc_release。
objc_initWeak
objc_initWeak源码如下
id objc_initWeak(id *location, id newObj)
{
if (!newObj) {
*location = nil;
return nil;
}
return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
(location, (objc_object*)newObj);
}
该方法有两个参数location、newObj
- location:存储__weak指针的地址(即指针的指针),可通过*location对__weak指针进行修改赋值
- newObj:所引用的对象
通过源码可知,所引用的对象为nil则直接返回nil
真正的实现是storeWeak
storeWeak实现如下
storeWeak
// Template parameters.
enum HaveOld {
DontHaveOld = false, DoHaveOld = true }; //是否有旧值
enum HaveNew {
DontHaveNew = false, DoHaveNew = true }; //是否有新值
//操作正在释放中的对象是否会crash
enum CrashIfDeallocating {
DontCrashIfDeallocating = false, DoCrashIfDeallocating = true
};
template <HaveOld haveOld, HaveNew haveNew,
enum CrashIfDeallocating crashIfDeallocating>
static id
storeWeak(id *location, objc_object *newObj)
{
ASSERT(haveOld || haveNew);
if (!haveNew) ASSERT(newObj == nil);
Class previouslyInitializedClass = nil;
id oldObj;
SideTable *oldTable;
SideTable *newTable;
// Acquire locks for old and new values.
// Order by lock address to prevent lock ordering problems.
// Retry if the old value changes underneath us.
retry:
if (haveOld) {
//如果__weak指针之前弱引用过一个obj,则将这个obj对应的SideTable取出,赋值给oldTable
oldObj = *location;
oldTable = &SideTables()[oldObj];
} else {
//如果__weak指针之前没有弱引用过一个obj,则oldTable = nil
oldTable = nil;
}
if (haveNew) {
//如果__weak指针要弱引用一个新的obj,则将obj对应的sideTable从sidetables中取出,赋值给newTable
newTable = &SideTables()[newObj];
} else {
newTable = nil; //如果__weak指针不需要引用一个新obj,则newtbale为nil
}
//加锁操作,防止多线程中竞争冲突
SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);
//location应该与oldObj保持一致,如果不同,说明当前location已经处理过oldObj可是又被其他线程所修改
if (haveOld && *lo