下面看到头大,有空再看看
https://xietao3.com/2019/05/ARC/
arc都做了啥
- 主要体现在__weak/__strong object_retain、release和autorelease(包括优化) 这块
objc_storeStrong objc_storeweak objc_release等方法 所有的方法都会转成objc_msgsend,然后生成下面的代码 例如[NSArray array], 插入_objc_autoreleaseReturnValue方法, alloc 会调用 objc_release
void objc_storeStrong(id *location, id obj) {
id prev = *location;
if (obj == prev) {
return;
}
objc_retain(obj);
*location = obj;
objc_release(prev);
}
storeStrong 是将新的objretain、老的指针对象release
objc_autoreleaseReturnValue: 这个函数的作用相当于代替我们手动调用 autorelease, 创建了一个autorelease对象。编译器会检测之后的代码, 根据返回的对象是否执行 retain操作, 来设置全局数据结构中的一个标志位, 来决定是否会执行 autorelease操作。该标记有两个状态, ReturnAtPlus0代表执行 autorelease, 以及ReturnAtPlus1代表不执行 autorelease。
// Zoo
+ (instancetype)createZoo {
id temp = [self new];
return objc_autoreleaseReturnValue(temp);
}
// VC
- (void)testForARC {
objc_unsafeClaimAutoreleasedReturnValue([Zoo createZoo]);
}
objc_unsafeClaimAutoreleasedReturnValue: 这个函数的作用是对autorelease对象不做处理仅仅返回,对非autorelease对象调用objc_release函数并返回。所以本情景中它创建时执行了 autorelease操作了,就不会对其进行 release操作了。只是返回了对象,在合适的实际autoreleasepool会对其进行释放的。
// Prepare a value at +0 for return through a +0 autoreleasing convention.
id objc_retainAutoreleaseReturnValue(id obj)
{
if (prepareOptimizedReturn(ReturnAtPlus0)) return obj;
// not objc_autoreleaseReturnValue(objc_retain(obj))
// because we don't need another optimization attempt
return objc_retainAutoreleaseAndReturn(obj);
}
// 俩方法名不一样
// Accept a value returned through a +0 autoreleasing convention for use at +1.
id objc_retainAutoreleasedReturnValue(id obj)
{
if (acceptOptimizedReturn() == ReturnAtPlus1) return obj;
return objc_retain(obj);
}
- 为了节省了一个将对象注册到autoreleasePool的操作,在执行objc_autoreleaseReturnValue时,根据查看后续调用的方法列表是否包含objc_retainAutoreleasedReturnValue方法,以此判断是否走优化流程。
- 在执行objc_autoreleaseReturnValue时,优化流程将一个标志位存储在 TLS (Thread Local Storage) 中后直接返回对象。
- 执行后续方法objc_retainAutoreleasedReturnValue时检查 TLS 的标志位判断是否处于优化流程,如果处于优化流程中则直接返回对象,并且将 TLS 的状态还原。
id objc_autoreleaseReturnValue(id obj) {
// 如果走优化程序则直接返回对象
if (prepareOptimizedReturn(ReturnAtPlus1)) return obj;
// 否则还是走自动释放池
return objc_autorelease(obj);
}
static ALWAYS_INLINE bool prepareOptimizedReturn(ReturnDisposition disposition) {
assert(getReturnDisposition() == ReturnAtPlus0);
// 检查使用该函数的方法或调用方的的调用列表,如果紧接着执行 objc_retainAutoreleasedReturnValue ,将不注册到 autoreleasePool 中
if (callerAcceptsOptimizedReturn(__builtin_return_address(0))) {
// 设置标记 ReturnAtPlus1
if (disposition) setReturnDisposition(disposition);
return true;
}
return false;
}
// 将 ReturnAtPlus1 或 ReturnAtPlus0 存入 TLS
static ALWAYS_INLINE void setReturnDisposition(ReturnDisposition disposition) {
tls_set_direct(RETURN_DISPOSITION_KEY, (void*)(uintptr_t)disposition);
}
// 取出标记
static ALWAYS_INLINE ReturnDisposition getReturnDisposition() {
return (ReturnDisposition)(uintptr_t)tls_get_direct(RETURN_DISPOSITION_KEY);
}
/// -------------
id objc_retainAutoreleasedReturnValue(id obj) {
// 如果 TLS 中标记表示使用了优化程序,则直接返回
if (acceptOptimizedReturn() == ReturnAtPlus1) return obj;
return objc_retain(obj);
}
static ALWAYS_INLINE ReturnDisposition acceptOptimizedReturn() {
// 取出标记后返回
ReturnDisposition disposition = getReturnDisposition();
// 还原至未优化状态
setReturnDisposition(ReturnAtPlus0); // reset to the unoptimized state
return disposition;
}
objc_autoreleaseReturnValue代码逻辑大概分为:
- 检查调用者方法里后面是否紧跟着调用了objc_retainAutoreleasedReturnValue。
- 保存 ReturnAtPlus1 至 TLS 中。
- 使用了优化处理则返回对象,否则加入自动释放池。
objc_retainAutoreleasedReturnValue代码逻辑大概分为:
- 取出 TLS 中标记。
- 重置 TLS 中标记至 ReturnAtPlus0 。
- 判断使用了优化处理则返回对象,否则引用计数 + 1。
方法调剂 连续对同一个方法hook两次
a-aa b-bb c-cc 交换a,b后 a-bb b-aa c-cc 交换a-c后 a-cc c-bb b-aa 所以调用a()后,执行了cc,cc里面执行了c,所以打印的是bb, 即 cc、bb 所以很可能会出现死循环
load initialize调用顺序
前置条件:父类、子类、子类category 都写了 load 是父-子-category initialize 是父-category, 如果没有category则是父-子、如果没有category,子类也没有实现,会调用两次父类initialize的方法
子类的category里有+load 父类的category也有+load,则这两个的调用顺序跟build Phases 的 compile Source 顺序有关
Methods中 包含哪几个部分?对方法的绝对寻址和相对寻址有什么区别
- selecter(选择器或方法名称)、方法类型编码、IMP(函数指针)区别:使用32位偏移代替绝对64位地址,优点:1> 无论将库加载到内存的任何位置,偏移量都是相同的。不需要修正指针地址。 2> 可以保存在只读寄存器中。(clean meonry).3> 所需内存减半
- 相对寻址后,method Swizzling ,官方使用全局映射表来解决这个问题,映射表维护了调剂方法对应的实现函数指针地址。
tagged Pointer是什么?为甚可以加速访问和操作速度, 为什么在Intel 和ARM64架构下对Tagged Pointer 区别对待
- 特殊标记指针(特殊标记对象)。通过在最后一个bit位设置为特殊标记,并将数据保存在指针自身之中。在最低位设置为标记位,倒数3位(可以表示7中类型,如 string,number、 NSIndexPath、date)赋予类型意义,其中OBJC_TAG_7类型特殊, 它可以将后8位作为扩展地址,支持256中类型的tagger pointer。 比如UIColor之类的。
- ARM64架构的标记位置正好相反,在第一位。原因是优化objc_msgSend检索的速度。对比标记指针和nil,objc_msgSend的检索速度时间复杂度O(1)问题
- [NSString stringWithFormat:] [NSString alloc] initWithFormat:] 根据传入的字符串,长度小的是标记指针,大的就是普通指针,同理 number、nsindexpath等 但是直接 name = @”abc” 这种不是标记指针,存在全局数据里
- 如果禁用Tagged Pointer,只需设置环境变量 OBJC_DISABLE_TAGGED_POINTERS为YES 即可 (现在会crash,看下面的_objc_registerTaggedPointerClass方法)
- Tagged Pointer指针上存储的数据我们完全能够计算出来,此时数据暴露在外,及其危险!苹果为了数据安全问题,设计了数据混淆, 通过设置环境变量OBJC_DISABLE_TAG_OBFUSCATION为 YES,可以关闭Tagged Pointer的数据混淆
/// objc_debug_taggedpointer_obfuscator是一个全局变量,该变量就是一个随机生成的数字,通过编码函数、或者解码函数,用来与 value做位运算 /// static void initializeTaggedPointerObfuscator(void){ if (sdkIsOlderThan(10_14, 12_0, 12_0, 5_0, 3_0) || DisableTaggedPointerObfuscation) { // 对于链接到旧sdk的应用程序,如果它们依赖于tagged pointer表示,将混淆器设置为0, objc_debug_taggedpointer_obfuscator = 0; } else { // 将随机数据放入变量中,然后移走所有非净负荷位。 arc4random_buf(&objc_debug_taggedpointer_obfuscator, sizeof(objc_debug_taggedpointer_obfuscator)); objc_debug_taggedpointer_obfuscator &= ~_OBJC_TAG_MASK; } }
// 加载程序时,从 dyld 库 的_dyld_start()函数开始,经历了多般步骤,开始调用_objc_registerTaggedPointerClass()
void _objc_registerTaggedPointerClass(objc_tag_index_t tag, Class cls){
if (objc_debug_taggedpointer_mask == 0) {
_objc_fatal("tagged pointers are disabled");
}
Class *slot = classSlotForTagIndex(tag);//根据索引获取指定的类指针
if (!slot) {
_objc_fatal("tag index %u is invalid", (unsigned int)tag);
}
Class oldCls = *slot;//取出指针指向的类
if (cls && oldCls && cls != oldCls) {
//指定的索引被用于两个不同的类,终止程序
_objc_fatal("tag index %u used for two different classes (was %p %s, now %p %s)", tag,oldCls, oldCls->nameForLogging(),cls, cls->nameForLogging());
}
*slot = cls;//将入参 cls 赋值给该类指针指向的地址
if (tag < OBJC_TAG_First60BitPayload || tag > OBJC_TAG_Last60BitPayload) {
Class *extSlot = classSlotForBasicTagIndex(OBJC_TAG_RESERVED_7);
if (*extSlot == nil) {
extern objc_class OBJC_CLASS_$___NSUnrecognizedTaggedPointer;
*extSlot = (Class)&OBJC_CLASS_$___NSUnrecognizedTaggedPointer;//表示 TaggedPointer 类
}
}
}
1、首先判断 objc_debug_taggedpointer_mask是否为 0 ,也就是判断开发者是否把 OBJC_DISABLE_TAGGED_POINTERS 设置为 YES;如果禁用了 Tagged Pointer,那么不好意思,直接调用 objc_fatal()函数终止该程序,不让该程序启动! 只有启用 Tagged Pointer,程序才有执行下去的意义! 2、根据索引 tag去取出数组objc_tag_classes或数组objc_tag_ext_classes中指定的类指针classSlotForTagIndex(tag): 如果传递无效的索引 tag,获取一个 nil,还是要调用_objc_fatal()终止该程序; 3、尝试着去获取该指针指向的类Class oldCls = *slot:如果要注册的类和该处的类不是同一个?不好意思,_objc_fatal()终止程序! 只有类指针 slot指向的位置为 NULL,或者类指针 slot指向的位置就是存储着我们要注册的类,系统才能安稳的运行下去; 4、将入参cls赋值给类指针 slot指向的位置*slot = cls;到此,该函数的功能经过重重考验就已经实现了! 5、假如注册的不是基础类,而是第一次注册扩展类,该函数还有个额外功能:在OBJC_TAG_RESERVED_7出存储占位类 OBJC_CLASS$___NSUnrecognizedTaggedPointer
static Class *
classSlotForTagIndex(objc_tag_index_t tag)
{
if (tag >= OBJC_TAG_First60BitPayload && tag <= OBJC_TAG_Last60BitPayload) {
return classSlotForBasicTagIndex(tag);
}
if (tag >= OBJC_TAG_First52BitPayload && tag <= OBJC_TAG_Last52BitPayload) {
int index = tag - OBJC_TAG_First52BitPayload;
uintptr_t tagObfuscator = ((objc_debug_taggedpointer_obfuscator
>> _OBJC_TAG_EXT_INDEX_SHIFT)
& _OBJC_TAG_EXT_INDEX_MASK);
return &objc_tag_ext_classes[index ^ tagObfuscator];
}
return nil;
}
1. 根据负载数据容量是60bits还是52bits,区分为类标识是基础类标识还是扩展类标识。也可以说根据tag类标识区间判断。
2. tag是基础类标识,返回classSlotForBasicTagIndex(tag)的结果;
3. tag是扩展类标识,对tag进行位操作,然后取出存在objc_tag_ext_classes数组里的结果返回。
4. 数组 objc_tag_classes:存储苹果定义的几个基础类;
5. 数组 objc_tag_ext_classes:存储苹果预留的扩展类
什么时候创建的,这个标记指针存在哪里,这两个问题还未搞明白
kvo实现原理,如何创建一个新类
KVO 的本质其实就是基于被观察的实例的 isa 生成一个新的类并在这个类的 extra 空间中存放各种和 KVO 操作相关的关键数据,然后这个新的类以一个中间人的角色借助 extra 空间中存放各种数据完成复杂的方法调度。extra结构如下: 可以调用 runtime 的 objc_allocateClassPair、objc_registerClassPair 函数动态地生成新的类,然后调用 object_setClass 函数去将某个对象的 isa 替换为我们自建的临时类。(给新类命名时,需要加前后缀+随机数+类对象的地址),使用objc_disposeClassPair(cls)释放临时类。 ///链接1 链接2
kvo监听一个int类型后,调用set方法会走到这里。
/// C函数,然后进行分发
void _NSSetIntValueAndNotify(id obj, SEL sel, int number),
native-KVO 会持有一个全局的字典:_NSKeyValueContainerClassForIsa.NSKeyValueContainerClassPerOriginalClass 以 KVO 操作的原类为 key 和 NSKeyValueContainerClass 实例为 value 存储 KVO 类信息。
KVO类的生成函数 _NSKVONotifyingCreateInfoWithOriginalClass(Class originalClass)
/// objc_allocateClassPair 在 runtime.h 中的声明为 Class _Nullable objc_allocateClassPair(Class _Nullable superclass, const char * _Nonnull name, size_t extraBytes) ,
/// 苹果对 extraBytes 参数的解释为“The number of bytes to allocate for indexed ivars at the end of the class and metaclass objects.”,
/// 这就是说当我们在通过 objc_allocateClassPair 来生成一个新的类时可以通过指定 extraBytes 来为此类开辟额外的空间用于存储一些数据。系统在生成 KVO 类时会额外分配 0x68 字节的空间
typedef struct {
Class originalClass; // offset 0x0
Class KVOClass; // offset 0x8
CFMutableSetRef mset; // offset 0x10
CFMutableDictionaryRef mdict; // offset 0x18
pthread_mutex_t *lock; // offset 0x20
void *sth1; // offset 0x28
void *sth2; // offset 0x30
void *sth3; // offset 0x38
void *sth4; // offset 0x40
void *sth5; // offset 0x48
void *sth6; // offset 0x50
void *sth7; // offset 0x58
bool flag; // offset 0x60
} SDTestKVOClassIndexedIvars;
oc ro rw 结构
关联对象
weak的实现
类方法存放在哪里
meta-class里,父类优先子类,
load的调用顺序
父类优先子类, 子类优先category
isa和super_clss指向
instance的isa指向class class的isa指向meta-class meta-class的isa指向基类的meta-class class的superclass指向父类的class,如果没有父类,superclass指针为nil meta-class的superclass指向父类的meta-class;基类的meta-class的superclass指向基类的class
实例对象没有super_class 属性
[super class] 和[self class]的区别
在调用[super class]的时候,runtime会去调用objc_msgSendSuper方法,而不是objc_msgSend
在objc_msgSendSuper方法中,第一个参数是一个objc_super的结构体,这个结构体里面有两个变量,一个是接收消息的receiver (这个receiver是当前这个类实例),一个是当前类的父类super_class。
OBJC_EXPORT void objc_msgSendSuper(void /* struct objc_super *super, SEL op, ... */ )
/// Specifies the superclass of an instance.
struct objc_super {
/// Specifies an instance of a class.
__unsafe_unretained id receiver;
/// Specifies the particular superclass of the instance to message.
#if !defined(__cplusplus) && !__OBJC2__
/* For compatibility with old objc-runtime.h header */
__unsafe_unretained Class class;
#else
__unsafe_unretained Class super_class;
#endif
/* super_class is the first class to search */
};
// 由于是实例调用,所以是减号方法
- (Class)class { return object_getClass(self); }
所以上述方法里的self是自己,虽然这个方法是在父类的方法里找到的
检测 NSObject 对象持有的强指针
- 调用 runtime 中的 class_copyIvarList 得到类的所有 ivar:ivar包括属性的名称、类型、偏移量以及索引,
- 类型是通过类型编码来获取的,在 FBIvarReference 的实例初始化时,会通过私有方法 - _convertEncodingToType: 将类型编码转换为枚举类型:, 用于区分是object类型、block类型,还是基础类型
(FBType)_convertEncodingToType:(const char *)typeEncoding { if (typeEncoding[0] == '{') return FBStructType; if (typeEncoding[0] == '@') { if (strncmp(typeEncoding, "@?", 2) == 0) return FBBlockType; return FBObjectType; } return FBUnknownType; }
- 在 ObjC 运行时中的 class_getIvarLayout 可以获取某一个类的 Ivar Layout,而 XXObject 的 Ivar Layout 是什么样的呢? (lldb) po fullLayout “\x01\x12\x11” Ivar Layout 就是一系列的字符,每两个一组,比如 \xmn,每一组 Ivar Layout 中第一位表示有 m 个非强属性,第二位表示接下来有 n 个强属性
- 然后过滤出强引用类型对象
具体看 链接
alloc 流程
当我们写了一个[Class alloc]命令,会相应的调用objc_alloc(Class cls)(objc4-706,NSObject.mm文件,1781行)方法,继而执行callAlloc方法,如果是__OBJC2__进入上图流程,否则执行allocWithZone方法,而这两个流程最终会执行_class_createInstanceFromZone方法,如下:
id _class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone) {
void *bytes;
size_t size;
// Can't create something for nothing
if (!cls) return nil;
// Allocate and initialize
size = cls->alignedInstanceSize() + extraBytes;
// CF requires all objects be at least 16 bytes.
if (size < 16) size = 16;
if (zone) {
bytes = malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
} else {
bytes = calloc(1, size);
}
return objc_constructInstance(cls, bytes);
}
该方法在通过malloc_zone_calloc或calloc计算所需空间后,进入objc_constructInstance构造方法中,首先会初始化isa指针,然后如果当前类有构造方法,也就是读取我们前文提到的hasCxxCtor标识,会去执行object_cxxConstructFromClass方法,依次执行父类和当前类的.cxx_construct方法,返回生成对象,否则直接返回生成的对象.
总结起来,殊途同归,alloc方法最终通过calloc方法分配空间,初始化isa指针,如果存在构造函数.cxx_construct,则一并执行.
calloc方法最终会走到libsystem_malloc.dylib的malloc方法来分配内存.(可以查看内存相关的malloc)
__strong
clang 生成中间代码,如下
id dic = objc_msgSend(NSDictionary, @selector(alloc));
objc_msgSend(obj,selector(init));
objc_storeStrong(dic);
void objc_storeStrong(id *location, id obj) {
id prev = *location;
if (obj == prev) {
return;
}
/// 也就是在对象指针指向发生变化时,retain新值,并release旧值.
objc_retain(obj);
*location = obj;
objc_release(prev);
}
NSDictionary *dic = [NSDictionary dictionary];
生成如下:
id dic = objc_msgSend(NSDictionary, @selector(dictionary));
/// objc_objc_retainAutoreleasedReturnValue是autoreleasepool的一个内存优化命令
objc_objc_retainAutoreleasedReturnValue(dic);
objc_storeStrong(dic);
__weak
NSObject* sp = [NSObject new]; NSObject* __weak wp = sp; NSLog(@”%@”, wp);
在 ARC 模式下,获取 weak 变量时,会调用 objc_loadWeakRetained 然后在要出当前作用域时调用了一次 objc_release,之所以这样,是因为在 objc_loadWeakRetained 中会对 weak 指针指向的对象调用 objc_object::rootRetain 函数,使该对象的引用计数加 1,为了抵消这一次加 1,会在即将出作用域之前调用 objc_release 函数 若不能理解,参考这个即可 id temp = objc_loadWeakRetained(obj1);
- 通过弱引用指向的对象,获取弱引用表,并且将其上锁,防止在此期间被清除。
- 判断是否包含自定义retain方法,如果没有,则使用默认rootTryRetain方法,使引用计数 + 1 。
- 如果使用了自定义retain方法,则调用自定义方法,在调用之前会先判断该对象所属类是否已经初始化过,如果没有初始化会先进行初始化然后再调用。
id obj ;
objc_initWeak(&obj1,obj);
objc_destoryWeak(&obj1);
initWeak是封装的storeWeak,storeWeak 里做了清除旧值,并设置新值
template <HaveOld haveOld, HaveNew haveNew,
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;
retry:
// 从 SideTables 中取出存储弱引用表的 SideTable(为弱引用表 weak_table_t 的一层封装)
if (haveOld) {
oldObj = *location;
oldTable = &SideTables()[oldObj];
} else {
oldTable = nil;
}
if (haveNew) {
newTable = &SideTables()[newObj];
} else {
newTable = nil;
}
SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);
// location 指向的值发生改变,则重新执行获取 oldObj
if (haveOld && *location != oldObj) {
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
goto retry;
}
// 如果有新值
if (haveNew && newObj) {
// 如果该对象类还未初始化则进行初始化
Class cls = newObj->getIsa();
if (cls != previouslyInitializedClass &&
!((objc_class *)cls)->isInitialized())
{
// 创建一个非元类,并且初始化,会调用 +initialize 函数
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
_class_initialize(_class_getNonMetaClass(cls, (id)newObj));
previouslyInitializedClass = cls;
goto retry;
}
}
// 如果有旧值,清除旧值对应的弱引用表
if (haveOld) {
weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
}
// 如果赋予了新值,注册新值对应的弱引用表
if (haveNew) {
newObj = (objc_object *)
weak_register_no_lock(&newTable->weak_table, (id)newObj, location,
crashIfDeallocating);
// 设置 isa 标志位 weakly_referenced 为 true
if (newObj && !newObj->isTaggedPointer()) {
newObj->setWeaklyReferenced_nolock();
}
// Do not set *location anywhere else. That would introduce a race.
*location = (id)newObj;
}
else {
// No new value. The storage is not changed.
}
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
return (id)newObj;
}
这段代码大概做了这几件事:
- 从全局的哈希表SideTables中,利用对象本身地址进行位运算后得到对应下标,取得该对象的弱引用表。SideTables是一个 64 个元素长度的散列表,发生碰撞时,可能一个SideTable中存在多个对象共享一个弱引用表。
- 如果有分配新值,则检查新值对应的类是否初始化过,如果没有,则就地初始化。
- 如果 location 有指向其他旧值,则将旧值对应的弱引用表进行注销。
- 如果分配了新值,将新值注册到对应的弱引用表中。将isa.weakly_referenced设置为true,表示该对象是有弱引用变量,释放时要去清空弱引用表。
id weak_register_no_lock(weak_table_t *weak_table, id referent_id,
id *referrer_id, bool crashIfDeallocating)
{
objc_object *referent = (objc_object *)referent_id; // 被引用的对象
objc_object **referrer = (objc_object **)referrer_id; // 弱引用变量
if (!referent || referent->isTaggedPointer()) return referent_id;
// 检查当前对象没有在释放中
bool deallocating;
if (!referent->ISA()->hasCustomRR()) {
deallocating = referent->rootIsDeallocating();
}
else {
BOOL (*allowsWeakReference)(objc_object *, SEL) =
(BOOL(*)(objc_object *, SEL))
object_getMethodImplementation((id)referent,
SEL_allowsWeakReference);
if ((IMP)allowsWeakReference == _objc_msgForward) {
return nil;
}
deallocating =
! (*allowsWeakReference)(referent, SEL_allowsWeakReference);
}
// 如果正在释放中,则根据 crashIfDeallocating 判断是否触发 crash
if (deallocating) {
if (crashIfDeallocating) {
_objc_fatal("Cannot form weak reference to instance (%p) of "
"class %s. It is possible that this object was "
"over-released, or is in the process of deallocation.",
(void*)referent, object_getClassName((id)referent));
} else {
return nil;
}
}
weak_entry_t *entry; // 每个对象对应的一个弱引用记录
// 如果当前表中有该对象的记录则直接加入该 weak 表中对应记录
if ((entry = weak_entry_for_referent(weak_table, referent))) {
append_referrer(entry, referrer);
}
else {
// 没有在 weak 表中找到对应记录,则新建一个记录
weak_entry_t new_entry(referent, referrer);
// 查看 weak_table 表是否要扩容
weak_grow_maybe(weak_table);
// 将记录插入 weak 表中
weak_entry_insert(weak_table, &new_entry);
}
return referent_id;
}
weak_register_no_lock 检查是否正在被释放中,如果是则根据crashIfDeallocating判断是否触发 crash 。 检查weak_table中是否有被引用对象对应的entry,如果有则直接将弱引用变量指针地址加入该entry中。 如果weak_table没有找到对应的entry,则新建一个entry,并将弱引用变量指针地址加入entry中。同时检查weak_table是否需要扩容
void weak_unregister_no_lock(weak_table_t *weak_table, id referent_id,
id *referrer_id)
{
objc_object *referent = (objc_object *)referent_id; // 被引用的对象
objc_object **referrer = (objc_object **)referrer_id; // 弱引用变量
weak_entry_t *entry;
if (!referent) return;
if ((entry = weak_entry_for_referent(weak_table, referent))) {
// 找到 weak 表中对应记录后,将引用从记录中移除
remove_referrer(entry, referrer);
// 移除后检查该引用记录是否为空
bool empty = true;
if (entry->out_of_line() && entry->num_refs != 0) {
empty = false;
}
else {
for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
if (entry->inline_referrers[i]) {
empty = false;
break;
}
}
}
// 如果当前记录为空则移除记录
if (empty) {
weak_entry_remove(weak_table, entry);
}
}
}
weak_unregister_no_lock
- 从weak_table中根据找到被引用对象对应的entry,然后将弱引用变量指针referrer从entry中移除。
- 移除弱引用变量指针referrer之后,检查entry是否为空,如果为空将其从weak_table中移除。
其他函数
static weak_entry_t *
weak_entry_for_referent(weak_table_t *weak_table, objc_object *referent)
{
assert(referent);
weak_entry_t *weak_entries = weak_table->weak_entries;
if (!weak_entries) return nil;
size_t index = hash_pointer(referent) & weak_table->mask;
size_t hash_displacement = 0;
while (weak_table->weak_entries[index].referent != referent) {
index = (index+1) & weak_table->mask;
hash_displacement++;
if (hash_displacement > weak_table->max_hash_displacement) {
return nil;
}
}
return &weak_table->weak_entries[index];
}
static void weak_entry_insert(weak_table_t *weak_table, weak_entry_t *new_entry)
static void remove_referrer(weak_entry_t *entry, objc_object **old_referrer)
static void append_referrer(weak_entry_t *entry, objc_object **new_referrer)
weak_entry_for_referent(weak_table_t *weak_table, objc_object *referent) weak_table查找entry的过程,也是哈希表寻址过程,使用线性探测的方法解决哈希冲突的问题: 1。 通过被引用对象地址计算获得哈希表下标。
- 检查对应下标存储的是不是我们要找到地址,如果是则返回该地址。
- 如果不是则继续往下找,直至找到。在下移的过程中,下标不能超过weak_table最大长度,同时hash_displacement不能超过记录的max_hash_displacement最大哈希位移。max_hash_displacement是所有插入操作时记录的最大哈希位移,如果超过了,那肯定是出错了。
static void weak_entry_insert(weak_table_t *weak_table, weak_entry_t *new_entry) weak_table插入entry的的步骤:
- 通过被引用对象地址计算获得哈希表下标。
- 检查对应下标是否为空,如果不为空继续往下查找,直至找到空位。
- 将弱引用变量指针存入空位,同时更新weak_table的当前成员数量num_entries和最大哈希位移max_hash_displacement。
static void weak_entry_remove(weak_table_t *weak_table, weak_entry_t *entry) 从weak_table移除entry的的步骤:
释放entry和其中的弱引用变量。 更新 weak_table 对象数量,并检查是否可以缩减表容量
static void append_referrer(weak_entry_t *entry, objc_object **new_referrer)
往entry中添加referrer
entry的结构和weak_table相似,都使用了哈希表,并且使用线性探测法寻找对应位置。在此基础上有一点不同的地方:
- entry有一个标志位out_of_line,最初时该标志位为false,entry使用的是一个有序数组inline_referrers的存储结构。
- 当inline_referrers的成员数量超过了WEAK_INLINE_COUNT,out_of_line标志位变成true,开始使用哈希表存储结构。每当哈希表负载超过 3/4 时会进行扩容。
static void remove_referrer(weak_entry_t *entry, objc_object **old_referrer)
从entry移除referrer的步骤: out_of_line为false时,从有序数组inline_referrers中查找并移除。 out_of_line为true时,从哈希表中查找并移除。
struct weak_table_t {
weak_entry_t *weak_entries; // 存储对象地址 和 __weak 修饰变量的 hash table
size_t num_entries; // hash table 大小
uintptr_t mask; // 辅助计算 hash 索引的位遮罩
uintptr_t max_hash_displacement; // hash 索引最大偏移量 (下文会说明用处)
};
struct weak_entry_t {
DisguisedPtr<objc_object> referent;//被引用的对象
union {
struct {
weak_referrer_t *referrers;//The address of a __weak variable.
uintptr_t out_of_line_ness : 2;
uintptr_t num_refs : PTR_MINUS_2;
uintptr_t mask;
uintptr_t max_hash_displacement;
};
struct {
// out_of_line_ness field is low bits of inline_referrers[1]
weak_referrer_t inline_referrers[WEAK_INLINE_COUNT];//4
};
};
bool out_of_line() {
return (out_of_line_ness == REFERRERS_OUT_OF_LINE);
}
weak_entry_t& operator=(const weak_entry_t& other) {
memcpy(this, &other, sizeof(other));
return *this;
}
weak_entry_t(objc_object *newReferent, objc_object **newReferrer)
: referent(newReferent)
{
inline_referrers[0] = newReferrer;
for (int i = 1; i < WEAK_INLINE_COUNT; i++) {
inline_referrers[i] = nil;
}
}
};
其中referent是被引用对象(key),也就是示例代码中的obj,下面的union即存储了所有指向该对象的弱引用,其中referrers存储所有的弱引用对象的地址(value),且当引用少于4时,hash表被一个数组所代替。
具体来说,初始化时,objc_initWeak(&obj1,obj)将执行objc_storeWeak(&obj1, obj);,将被引用对象obj地址作为key值,将当前引用对象obj1作为value存储,而当obj引用计数为0时,objc_destoryWeak(&obj1)函数会执行objc_storeWeak(&obj1,0),把变量obj1的地址从 weak 表中删除.
调用栈 设置weak时 objc_initWeak / objc_storeWeak storeWeak weak_register_no_lock weak_entry_insert
移除weak时 objc_destroyWeak storeWeak weak_unregister_no_lock remove_referrer
retain release
id objc_object::sidetable_retain()
{
#if SUPPORT_NONPOINTER_ISA
assert(!isa.nonpointer);
#endif
SideTable& table = SideTables()[this];
table.lock();
size_t& refcntStorage = table.refcnts[this];
if (! (refcntStorage & SIDE_TABLE_RC_PINNED)) {
refcntStorage += SIDE_TABLE_RC_ONE;
}
table.unlock();
return (id)this;
}
可以看到,retain操作是读取当前对象的SideTable中refcnts属性,如果没有越界,会将其增加SIDE_TABLE_RC_ONE即(1UL«2),而不仅仅是我们熟知的1,这由于引用计数的第一位用来表示计数是否越界,后两位分别被弱引用以及析构状态两个标识位占领.
objc_retain objc_object::rootRetain
id objc_retain(id obj) {
if (!obj) return obj;
if (obj->isTaggedPointer()) return obj;
return obj->retain();
}
/// root_retain源码参考这个[链接](http://xietao3.com/2019/05/ARC/)
- TaggedPointer:值存在指针内,直接返回。
- !newisa.nonpointer:未优化的 isa ,使用sidetable_retain()。
- newisa.nonpointer:已优化的 isa , 这其中又分 extra_rc 溢出和未溢出的两种情况。
- 未溢出时,isa.extra_rc + 1 完事。
- 溢出时,将 isa.extra_rc 中一半值转移至sidetable中,然后将isa.has_sidetable_rc设置为true,表示使用了sidetable来计算引用次数。
release操作,也会首先读取当前对象SideTable中refcnts属性,然后加以判断. 如果引用计数为计数表中的最后一个,标记对象为正在析构SIDE_TABLE_DEALLOCATING状态,然后执行完成后发送 SEL_dealloc消息释放对象
objc_release objc_object::rootRelease (源码参考如上链接)
- TaggedPointer: 直接返回 false。
- !nonpointer: 未优化的 isa 执行 sidetable_release。
- nonpointer:已优化的 isa ,分下溢和未下溢两种情况。
- 未下溢: extra_rc–。
- 下溢:从 sidetable 中借位给 extra_rc 达到半满,如果无法借位则说明引用计数归零需要进行释放。其中借位时可能保存失败会不断重试。
objc_object::rootRetainCount方法是用来计算引用计数的。通过前面rootRetain和rootRelease的源码分析可以看出引用计数会分别存在isa.extra_rc和sidetable。中,这一点在rootRetainCount方法中也得到了体现。
dealloc
dealloc方法在最后一次release后被调用,但此时实例变量(Ivars)并未释放,父类的dealloc的方法将在子类dealloc方法返回后自动调用.析构对象,并释放空间..具体流程如图:
objc_clear_deallocating方法,除了清除SideTable中的引用计数外,也会对弱引用表进行清除.
NEVER_INLINE void objc_object::clearDeallocating_slow()
{
assert(isa.nonpointer && (isa.weakly_referenced || isa.has_sidetable_rc));
// 根据指针获取对应 weak_table
SideTable& table = SideTables()[this];
table.lock();
// 判断如果有被弱引用则清空该对象对应的 entry
if (isa.weakly_referenced) {
weak_clear_no_lock(&table.weak_table, (id)this);
}
// 清空该对象存储在 sidetable 中的引用计数
if (isa.has_sidetable_rc) {
table.refcnts.erase(this);
}
table.unlock();
}
sideTable
static StripedMap<SideTable>& SideTables() {
return *reinterpret_cast<StripedMap<SideTable>*>(SideTableBuf);
}
struct SideTable {
spinlock_t slock; // 自旋锁,防止多线程访问冲突
RefcountMap refcnts; // 对象引用计数map
weak_table_t weak_table; // 对象弱引用map
SideTable() {
memset(&weak_table, 0, sizeof(weak_table));
}
~SideTable() {
_objc_fatal("Do not delete SideTable.");
}
// 锁操作 符合StripedMap对T的定义
void lock() { slock.lock(); }
void unlock() { slock.unlock(); }
void forceReset() { slock.forceReset(); }
// Address-ordered lock discipline for a pair of side tables.
template<HaveOld, HaveNew>
static void lockTwo(SideTable *lock1, SideTable *lock2);
template<HaveOld, HaveNew>
static void unlockTwo(SideTable *lock1, SideTable *lock2);
};
什么对象自动加入到 autoreleasepool中
虽然在程序入口,已经帮我们加上了 autoreleasepool,但是并不是说大括号内的所有 对象都会交给autoreleasepool来处理
如下会加入:id obj = [NSMutableArray array];
- 使用__autoreleasing 标记的
- 系统的比如NSString stringWithFormat:@”xxx“, 其他比如[NSArray array]等
注:alloc/new/copy/mutableCopy不会加入
autorelease 和 weak指针的关系
/// id __weak obj1 = obj0;
/// id __autorealeasing tmp = obj1;
以上代码, weak指针的对象会做引用计数增加吗,
答案:其实autorelease和weak没一点关系, tmp = obj1的时候,tmp指向了obj1,obj1并未销毁, 此时加入autorelease的是这个对象实例,或者叫做这个对象地址。而不是指向这个对象的指针(比如tmp或obj1), 所以引用计数会加1