一、类扩展
-
类扩展
extension
在咱们的开发进程中其实常常运用的,下图红框部分便是一个ViewController
的类扩展。
- 图:
- 类扩展实际上是一个特别的分类,也称作匿名分类,创立的类扩展只有
.h
文件,没有.m
文件。
- 如下图所示:
- 假如不通过创立文件的办法,类扩展的代码只能写在类声明与类完成之间,跟
ViewController
相同。
分类与类扩展的区别
-
category
:类别,分类。
-
①. 专门给类增加新的办法。
-
②. 不能给类增加成员特点,即便增加了成员变量,也无法获取。
-
③. 能够通过
runtime
给分类增加特点。 -
④. 分类顶用
@property
界说变量,只会生成变量的getter
,setter
办法的声明,不能生成办法完成和带下划线的成员变量。
-
extension
:类扩展
-
①. 能够说成是特别的分类,也称作匿名分类。
-
②. 能够给类增加成员特点,可是是私有变量。
-
③. 能够给类增加办法,也是私有办法。
验证extension
扩展的加载办法
声明一个类CJStudent
,为其增加extension
扩展的特点与办法。
- 自界说源码:
// 自界说类
@interface CJStudent : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger number;
+ (void)classMethod;
- (void)instanceMethod;
@end
// 类扩展
@interface CJStudent()
@property (nonatomic, copy) NSString *ext_name;
@property (nonatomic, assign) NSInteger ext_number;
+ (void)ext_classMethod;
- (void)ext_InstanceMethod;
@end
@implementation CJStudent
+ (void)load {
NSLog(@"%s", __func__);
}
- (void)instanceMethod {
NSLog(@"%s", __func__);
}
+ (void)classMethod {
NSLog(@"%s", __func__);
}
- (void)ext_InstanceMethod {
NSLog(@"%s", __func__);
}
+ (void)ext_classMethod {
NSLog(@"%s", __func__);
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
CJStudent *student = [SJStudent alloc];
}
return 0;
}
- 在终端运用指令
xcrun -sdk iphonesimulator clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp
将OC
源码转化为c++
代码:
-
c++
代码:
struct CJStudent_IMPL {
struct NSObject_IMPL NSObject_IVARS;
NSString *_name;
NSInteger _number;
NSString *_ext_name;
NSInteger _ext_number;
};
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[18];
} _OBJC_$_INSTANCE_METHODS_CJStudent __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
18,
{{(struct objc_selector *)"instanceMethod", "v16@0:8", (void *)_I_CJStudent_instanceMethod},
{(struct objc_selector *)"ext_InstanceMethod", "v16@0:8", (void *)_I_CJStudent_ext_InstanceMethod},
{(struct objc_selector *)"name", "@16@0:8", (void *)_I_CJStudent_name},
{(struct objc_selector *)"setName:", "v24@0:8@16", (void *)_I_CJStudent_setName_},
{(struct objc_selector *)"number", "q16@0:8", (void *)_I_CJStudent_number},
{(struct objc_selector *)"setNumber:", "v24@0:8q16", (void *)_I_CJStudent_setNumber_},
{(struct objc_selector *)"ext_name", "@16@0:8", (void *)_I_CJStudent_ext_name},
{(struct objc_selector *)"setExt_name:", "v24@0:8@16", (void *)_I_CJStudent_setExt_name_},
{(struct objc_selector *)"ext_number", "q16@0:8", (void *)_I_CJStudent_ext_number},
{(struct objc_selector *)"setExt_number:", "v24@0:8q16", (void *)_I_CJStudent_setExt_number_},
{(struct objc_selector *)"name", "@16@0:8", (void *)_I_CJStudent_name},
{(struct objc_selector *)"setName:", "v24@0:8@16", (void *)_I_CJStudent_setName_},
{(struct objc_selector *)"number", "q16@0:8", (void *)_I_CJStudent_number},
{(struct objc_selector *)"setNumber:", "v24@0:8q16", (void *)_I_CJStudent_setNumber_},
{(struct objc_selector *)"ext_name", "@16@0:8", (void *)_I_CJStudent_ext_name},
{(struct objc_selector *)"setExt_name:", "v24@0:8@16", (void *)_I_CJStudent_setExt_name_},
{(struct objc_selector *)"ext_number", "q16@0:8", (void *)_I_CJStudent_ext_number},
{(struct objc_selector *)"setExt_number:", "v24@0:8q16", (void *)_I_CJStudent_setExt_number_}}
};
- 在可编译的objc4的源码的realizeClassWithoutSwift函数里将
"CJPerson"
改成"CJStudent"
就在能够在类加载进程,断点调试了。
- 如下图:
- 通过命令打印输出
CJStudent
类中一切的办法:
(lldb) x/6gx cls
0x100008800: 0x00000001000087d8 0x0000000100721140
0x100008810: 0x0000000100719cb0 0x0034000000000000
0x100008820: 0x8000600000238080 0x00000001007210f0
(lldb) p (class_data_bits_t *)0x100008820
(class_data_bits_t *) $1 = 0x0000000100008820
(lldb) p $1->data()
(class_rw_t *) $2 = 0x0000600000238080
(lldb) p *$2
(class_rw_t) $3 = {
flags = 2148007936
witness = 0
ro_or_rw_ext = {
std::__1::atomic<unsigned long> = {
Value = 4295000696
}
}
firstSubclass = nil
nextSiblingClass = 0x0000000100721168
}
(lldb) p $3.ro()
(const class_ro_t *) $4 = 0x0000000100008278
(lldb) p *$4
(const class_ro_t) $5 = {
flags = 388
instanceStart = 8
instanceSize = 40
reserved = 0
= {
ivarLayout = 0x0000000100003d5b "\U00000001\U00000011"
nonMetaclass = 0x0000000100003d5b
}
name = {
std::__1::atomic<const char *> = "CJStudent" {
Value = 0x0000000100003d51 "CJStudent"
}
}
baseMethods = {
ptr = 0x0000000100008098
}
baseProtocols = nil
ivars = 0x00000001000081a8
weakIvarLayout = 0x0000000000000000
baseProperties = 0x0000000100008230
_swiftMetadataInitializer_NEVER_USE = {}
}
(lldb) p $5.ivars
(const ivar_list_t *const) $6 = 0x00000001000081a8
(lldb) p *$6
(const ivar_list_t) $7 = {
entsize_list_tt<ivar_t, ivar_list_t, 0, PointerModifierNop> = (entsizeAndFlags = 32, count = 4)
}
(lldb) p $7.get(0)
(ivar_t) $8 = {
offset = 0x00000001000086d8
name = 0x0000000100003e16 "_name"
type = 0x0000000100003f97 "@\"NSString\""
alignment_raw = 3
size = 8
}
(lldb) p $7.get(1)
(ivar_t) $9 = {
offset = 0x00000001000086e0
name = 0x0000000100003e1c "_number"
type = 0x0000000100003fa3 "q"
alignment_raw = 3
size = 8
}
(lldb) p $7.get(2)
(ivar_t) $10 = {
offset = 0x00000001000086e8
name = 0x0000000100003e24 "_ext_name"
type = 0x0000000100003f97 "@\"NSString\""
alignment_raw = 3
size = 8
}
(lldb) p $7.get(3)
(ivar_t) $11 = {
offset = 0x00000001000086f0
name = 0x0000000100003e2e "_ext_number"
type = 0x0000000100003fa3 "q"
alignment_raw = 3
size = 8
}
(lldb) p *$5.baseMethods.ptr
(method_list_t) $12 = {
entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 24, count = 11)
}
(lldb) p $12.get(0).big()
(method_t::big) $13 = {
name = "instanceMethod"
types = 0x0000000100003f69 "v16@0:8"
imp = 0x0000000100003310 (KCObjcBuild`-[CJStudent instanceMethod] at main.m:46)
}
(lldb) p $12.get(1).big()
(method_t::big) $14 = {
name = "ext_InstanceMethod"
types = 0x0000000100003f69 "v16@0:8"
imp = 0x0000000100003340 (KCObjcBuild`-[CJStudent ext_InstanceMethod] at main.m:54)
}
(lldb) p $12.get(2).big()
(method_t::big) $15 = {
name = "name"
types = 0x0000000100003f71 "@16@0:8"
imp = 0x0000000100003370 (KCObjcBuild`-[CJStudent name] at main.m:26)
}
(lldb) p $12.get(3).big()
(method_t::big) $16 = {
name = "setName:"
types = 0x0000000100003f79 "v24@0:8@16"
imp = 0x0000000100003390 (KCObjcBuild`-[CJStudent setName:] at main.m:26)
}
(lldb) p $12.get(4).big()
(method_t::big) $17 = {
name = "number"
types = 0x0000000100003f84 "q16@0:8"
imp = 0x00000001000033c0 (KCObjcBuild`-[CJStudent number] at main.m:27)
}
(lldb) p $12.get(5).big()
(method_t::big) $18 = {
name = "setNumber:"
types = 0x0000000100003f8c "v24@0:8q16"
imp = 0x00000001000033e0 (KCObjcBuild`-[CJStudent setNumber:] at main.m:27)
}
(lldb) p $12.get(6).big()
(method_t::big) $19 = {
name = "ext_name"
types = 0x0000000100003f71 "@16@0:8"
imp = 0x0000000100003400 (KCObjcBuild`-[CJStudent ext_name] at main.m:34)
}
(lldb) p $12.get(7).big()
(method_t::big) $20 = {
name = "setExt_name:"
types = 0x0000000100003f79 "v24@0:8@16"
imp = 0x0000000100003420 (KCObjcBuild`-[CJStudent setExt_name:] at main.m:34)
}
(lldb) p $12.get(8).big()
(method_t::big) $21 = {
name = "ext_number"
types = 0x0000000100003f84 "q16@0:8"
imp = 0x0000000100003450 (KCObjcBuild`-[CJStudent ext_number] at main.m:35)
}
(lldb) p $12.get(9).big()
(method_t::big) $22 = {
name = "setExt_number:"
types = 0x0000000100003f8c "v24@0:8q16"
imp = 0x0000000100003470 (KCObjcBuild`-[CJStudent setExt_number:] at main.m:35)
}
(lldb) p $12.get(10).big()
(method_t::big) $23 = {
name = ".cxx_destruct"
types = 0x0000000100003f69 "v16@0:8"
imp = 0x0000000100003490 (KCObjcBuild`-[CJStudent .cxx_destruct] at main.m:41)
}
(lldb)
-
总结:
通过打印成果,咱们发现其实类扩展中的办法以及特点并不是像加载分类数据那样加载的,而是与主类中界说完成的办法以及特点的加载办法相同。
二、相关目标
相关目标的根本运用
在咱们日常开发的进程中,有时咱们需要给体系或许某个库中的某个类增加特点,可是咱们又无法改变源码,因而咱们只能创立此类的一个分类,因为咱们在分类中界说的特点实际上只能用作计算特点,可是假如想在这个类的实例目标中存取特点,能够运用runtime
的API
相关目标,这种办法并不是在类的特点列表中增加新的特点,而是通过在大局的哈希表里两次哈希map
的形式存取相关目标的值。
- 在
CJPerson
的分类CA
中想要相关一个CJPet
目标,能够编写如下的代码:
// CJPerson类
@interface CJPerson : NSObject
@end
@implementation CJPerson
@end
// CJPerson+CA分类
@interface CJPerson (CA)
@property (nonatomic, copy) NSString *ca_name;
@property (nonatomic, assign) NSInteger ca_age;
@property (nonatomic, strong) CJPet *ca_pet;
@end
static NSString *kCAPet = @"pet";
static NSString *kCAName= @"name";
static NSString *kCAAge = @"age";
@implementation CJPerson (CA)
- (void)setCa_pet:(CJPet *)ca_array {
objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kCAPet), ca_array, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (CJPet *)ca_pet{
return objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kCAPet));
}
- (void)setCa_name:(NSString *)ca_name {
objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kCAName), ca_name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)ca_name {
return objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kCAName));
}
- (void)setCa_age:(NSInteger)ca_age {
objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kCAAge), [NSNumber numberWithInteger:ca_age], OBJC_ASSOCIATION_ASSIGN);
}
- (NSInteger)ca_age {
NSNumber *age = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kCAAge));
if(!age) {
return 0;
} else {
return [age integerValue];
}
}
@end
// main
int main(int argc, const char * argv[]) {
@autoreleasepool {
CJPerson * person = [CJPerson alloc];
CJPet *dog = [CJPet new];
dog.name = @"狗仔";
dog.age = 1;
person.ca_pet = dog;
NSLog(@"pet:%@, age:%ld", person.ca_pet.name, person.ca_pet.age);
}
return 0;
}
- 编译运转程序,打印信息如下所示:
2023-03-10 01:46:57.193890+0800 KCObjcBuild[6374:190428] pet:狗仔, age:1
相关目标完成原理
通过相关目标的根本运用之后,咱们再来了解相关目标的作业原理。这儿最表面便是知道相关目标的设值与相关目标的取值。
相关目标的外部与内部战略
- 而在这之前,得先知道相关目标的外部战略(即对外接口可查可用的枚举)。
-
objc
中的相关目标战略objc_AssociationPolicy
:
/**
* Policies related to associative references.
* These are options to objc_setAssociatedObject()
*/
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
OBJC_ASSOCIATION_ASSIGN = 0, /**< Specifies a weak reference to the associated object. */
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**< Specifies a strong reference to the associated object.
* The association is not made atomically. */
OBJC_ASSOCIATION_COPY_NONATOMIC = 3, /**< Specifies that the associated object is copied.
* The association is not made atomically. */
OBJC_ASSOCIATION_RETAIN = 01401, /**< Specifies a strong reference to the associated object.
* The association is made atomically. */
OBJC_ASSOCIATION_COPY = 01403 /**< Specifies that the associated object is copied.
* The association is made atomically. */
};
-
总结:
由
objc_AssociationPolicy
这个枚举可知,这五种战略,刚好对应了设置类的特点property
的修饰词。objc_AssociationPolicy战略 特点property修饰词 OBJC_ASSOCIATION_ASSIGN @property (assign) OBJC_ASSOCIATION_RETAIN_NONATOMIC @property (nonatomic, strong) OBJC_ASSOCIATION_COPY_NONATOMIC @property (nonatomic, copy) OBJC_ASSOCIATION_RETAIN @property (atomic, strong) OBJC_ASSOCIATION_COPY @property (atomic, copy)
- 而在相关目标
c++
源码内部的枚举却有所不同,它是区别setter
与getter
状况的?
- 内部战略枚举的
c++
源码:
enum {
OBJC_ASSOCIATION_SETTER_ASSIGN = 0,
OBJC_ASSOCIATION_SETTER_RETAIN = 1,
OBJC_ASSOCIATION_SETTER_COPY = 3, // NOTE: both bits are set, so we can simply test 1 bit in releaseValue below.
OBJC_ASSOCIATION_GETTER_READ = (0 << 8),
OBJC_ASSOCIATION_GETTER_RETAIN = (1 << 8),
OBJC_ASSOCIATION_GETTER_AUTORELEASE = (2 << 8),
OBJC_ASSOCIATION_SYSTEM_OBJECT = _OBJC_ASSOCIATION_SYSTEM_OBJECT, // 1 << 16
};
-
定论:
由相关的战略外部枚举与外部枚举比照可知:
-
①. 外部战略枚举设置值刚好对应内部枚举前3个值。即外部战略枚举
objc_AssociationPolicy
就只能直接影响内部战略枚举的settter
部分。 -
②. 而
getter
部分暂时没有进行深化验证分析,可是通过下文对相关目标设值时,对相关目标的引证计数器的控制。可知setter
与getter
是对相关目标设值与取值时,是否需要引证计数器变化,或许延时开释autorelease
。这部分留到分析iOS内存管理时解析。
-
相关目标的设值
从上面咱们现已先了解内部与外部的相关目标战略的区别,可是要探究一下相关目标的完成原理,就要先要查看objc_setAssociatedObject
相关目标设值函数,发现它主要功能仍是在_object_set_associative_reference
函数里。
- 其源码如下:
// 设置相关目标
void
objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
{
_object_set_associative_reference(object, key, value, policy);
}
void
_object_set_associative_reference(id object, const void *key, id value, uintptr_t policy)
{
// 当为目标和键传递nil时,这段代码一向有用。
// 一些代码或许依赖于此而不会崩溃。检查并显式处理它。
// rdar://problem/44094390
// 被相关目标为空或许相关值为空,直接回来
if (!object && !value) return;
// isa有一位信息为制止相关目标,假如这个被相关目标禁用类相关目标,抛出过错
if (object->getIsa()->forbidsAssociatedObjects())
_objc_fatal("objc_setAssociatedObject called on instance (%p) of class %s which does not allow associated objects", object, object_getClassName(object));
// 将当时被相关目标包装成DisguisedPtr类型,便是目标进行正负取反加密
DisguisedPtr<objc_object> disguised{(objc_object *)object};
// ObjcAssociation是对相关目标的值以及相关战略的C++类包装类型
ObjcAssociation association{policy, value};
// 在锁外retain保存新值(假如有)。
// 持有相关目标,设置特点信息
association.acquireValue();
bool isFirstAssociation = false;
{
// 调用结构函数,结构函数内加锁操作
AssociationsManager manager;
// 获取大局的HashMap
AssociationsHashMap &associations(manager.get());
// 假如值不为空
if (value) {
// 去相关目标表中找目标对应的二级表,假如没有内部会从头生成一个
auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});
// 假如没有找到
if (refs_result.second) {
/* it's the first association we make */
// 阐明是第一次设置相关目标,把是否相关目标设置为YES
isFirstAssociation = true;
}
/* establish or replace the association */
auto &refs = refs_result.first->second;
// 在二级表中找key对应的内容
auto result = refs.try_emplace(key, std::move(association));
// 假如现已有内容了,没有内容上面association现已刺进了值,所以啥也不必干
if (!result.second) {
// 替换掉
association.swap(result.first->second);
}
// 假如value为空
} else {
// 通过被假装的目标值找到对应的二级表
auto refs_it = associations.find(disguised);
// 假如有
if (refs_it != associations.end()) {
auto &refs = refs_it->second;
// 通过key再在二级表里面找到对应内容
auto it = refs.find(key);
// 假如有
if (it != refs.end()) {
// 删去掉
association.swap(it->second);
refs.erase(it);
if (refs.size() == 0) {
associations.erase(refs_it);
}
}
}
}
}
// 在锁外部调用setHasAssociatedObjects,
// 因为假如目标有_noteAssociatedObject办法,
// 这将调用该办法,而且这或许会触发+initialize,这或许会履行任意操作,包含设置更多相关目标。
if (isFirstAssociation)
object->setHasAssociatedObjects();
// release开释旧值(锁外)。
association.releaseHeldValue();
}
-
定论:
objc_setAssociatedObject
相关目标设置函数,发现它主要功能仍是在_object_set_associative_reference
函数里:- ①. 假装被相关目标为
DisguisedPtr
,将相关目标的值以及相关战略封装成ObjcAssociation
- ②. 通过相关目标管理者
AssociationsManager
找到大局相关目标表AssociationsHashMap
,通过DisguisedPtr
假装目标在相关目标表中找对应的二级表。 - ③. 通过
key
找对应ObjcAssociation
,有就替换,没有加刺进。
- ①. 假装被相关目标为
DisguisedPtr
// DisguisedPtr<T> acts like pointer type T*, except the
// stored value is disguised to hide it from tools like `leaks`.
// nil is disguised as itself so zero-filled memory works as expected,
// which means 0x80..00 is also disguised as itself but we don't care.
// Note that weak_entry_t knows about this encoding.
template <typename T>
class DisguisedPtr {
uintptr_t value;
static uintptr_t disguise(T* ptr) {
return -(uintptr_t)ptr;
}
static T* undisguise(uintptr_t val) {
return (T*)-val;
}
public:
DisguisedPtr() { }
DisguisedPtr(T* ptr)
: value(disguise(ptr)) { }
DisguisedPtr(const DisguisedPtr<T>& ptr)
: value(ptr.value) { }
DisguisedPtr<T>& operator = (T* rhs) {
value = disguise(rhs);
return *this;
}
DisguisedPtr<T>& operator = (const DisguisedPtr<T>& rhs) {
value = rhs.value;
return *this;
}
operator T* () const {
return undisguise(value);
}
T* operator -> () const {
return undisguise(value);
}
T& operator * () const {
return *undisguise(value);
}
T& operator [] (size_t i) const {
return undisguise(value)[i];
}
// pointer arithmetic operators omitted
// because we don't currently use them anywhere
};
-
总结:
在
_object_set_associative_reference
函数中创立的disguised
变量,调用了DisguisedPtr
的结构函数,传入的是被相关目标的目标地址,通过disguise
函数的假装(指针值转换为十进制,指针值转换为负数)存储到disguised
的成员变量value
中。
ObjcAssociation
创立的association
变量是ObjcAssociation
的C++
类类型,初始化调用了其结构函数,将相关目标以及相关战略别离存储到其成员变量_value
以及_policy
中保存起来。
-
c++
类结构:
// 扩展的战略位。
// 因为设置相关目标的战略objc_AssociationPolicy只有5个,还不区别setter与getter状况
// 这儿做了setter与getter的区别,主要是相关目标的引证计数器的状况区别。
// 咱们自界说的相关目标一般只会处理按到前三个setter的战略
enum {
OBJC_ASSOCIATION_SETTER_ASSIGN = 0,
OBJC_ASSOCIATION_SETTER_RETAIN = 1,
OBJC_ASSOCIATION_SETTER_COPY = 3, // NOTE: both bits are set, so we can simply test 1 bit in releaseValue below.
OBJC_ASSOCIATION_GETTER_READ = (0 << 8),
OBJC_ASSOCIATION_GETTER_RETAIN = (1 << 8),
OBJC_ASSOCIATION_GETTER_AUTORELEASE = (2 << 8),
OBJC_ASSOCIATION_SYSTEM_OBJECT = _OBJC_ASSOCIATION_SYSTEM_OBJECT, // 1 << 16
};
spinlock_t AssociationsManagerLock;
namespace objc {
class ObjcAssociation {
uintptr_t _policy; // 相关战略便是特点的修饰类型(atomic,nonatomic,assign,strong,copy)
id _value; // 被相关的目标
public:
ObjcAssociation(uintptr_t policy, id value) : _policy(policy), _value(value) {}
ObjcAssociation() : _policy(0), _value(nil) {}
ObjcAssociation(const ObjcAssociation &other) = default;
ObjcAssociation &operator=(const ObjcAssociation &other) = default;
ObjcAssociation(ObjcAssociation &&other) : ObjcAssociation() {
swap(other);
}
inline void swap(ObjcAssociation &other) {
std::swap(_policy, other._policy);
std::swap(_value, other._value);
}
inline uintptr_t policy() const { return _policy; }
inline id value() const { return _value; }
// 重点:相关目标赋值时,完成引证计数器+1或许copy
inline void acquireValue() {
if (_value) {
switch (_policy & 0xFF) {
case OBJC_ASSOCIATION_SETTER_RETAIN:
_value = objc_retain(_value);
break;
case OBJC_ASSOCIATION_SETTER_COPY:
_value = ((id(*)(id, SEL))objc_msgSend)(_value, @selector(copy));
break;
}
}
}
inline void releaseHeldValue() {
if (_value && (_policy & OBJC_ASSOCIATION_SETTER_RETAIN)) {
objc_release(_value);
}
}
inline void retainReturnedValue() {
if (_value && (_policy & OBJC_ASSOCIATION_GETTER_RETAIN)) {
objc_retain(_value);
}
}
inline id autoreleaseReturnedValue() {
if (slowpath(_value && (_policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE))) {
return objc_autorelease(_value);
}
return _value;
}
};
....
....
}
调试ObjcAssociation
的acquireValue
运转代码,在_object_set_associative_reference
的源代码增加断点,然后association
调用函数acquireValue
持有此相关目标。
-
如下所示:
-
定论:
在
acquireValue
这个函数中会依据相关战略的不同,进行不同的存储假如相关战略是OBJC_ASSOCIATION_RETAIN
或许OBJC_ASSOCIATION_RETAIN_NONATOMIC
类型-
①. 就会运用
objc_retain
函数持有这个目标,然后这个目标的引证计数值就会加1
,在objc_retain
函数调用前后打印此目标isa
的引证计数值。-
验证如下所示:
-
成果:
- 能够看到这个目标中isa中倒数第二位由
2
变成了3
,也便是其引证计数的值增加了1
,其实这也能够叫做浅复制。
- 能够看到这个目标中isa中倒数第二位由
-
-
③. 而
OBJC_ASSOCIATION_SETTER_COPY
则需要此目标所属类完成NSCopying
协议中的copyWithZone
实例办法,终究决定是深复制或许浅复制。 -
②. 可是假如相关值不是类目标,而是根本数据类型或许
TaggedPointer
,便是直接存储其值。
-
AssociationsManager
在_object_set_associative_reference
函数里界说了isFirstAssociation
变量用来判别是不是首次相关,然后界说了一个C++
类类型为AssociationsManager
的变量manager
。
-
AssociationsManager
这个类界说代码:
// class AssociationsManager manages a lock / hash table singleton pair.
// Allocating an instance acquires the lock
class AssociationsManager {
using Storage = ExplicitInitDenseMap<DisguisedPtr<objc_object>, ObjectAssociationMap>;
static Storage _mapStorage;
public:
AssociationsManager() { AssociationsManagerLock.lock(); }
~AssociationsManager() { AssociationsManagerLock.unlock(); }
AssociationsHashMap &get() {
return _mapStorage.get();
}
static void init() {
_mapStorage.init();
}
};
AssociationsManager::Storage AssociationsManager::_mapStorage;
-
定论:
其中变量
_mapStorage
为Storage
(ExplicitInitDenseMap
类的别名)类型的部分静态变量,也便是说_mapStorage
只会被初始化一次,而且其生命周期为从初始化一向到应用程序运转完毕,因而不管以后再创立多少个AssociationsManager
类型变量,_mapStorage
仅初始化一次而且一向存在,类似于一个单例。
ExplicitInitDenseMap
而ExplicitInitDenseMap
这个C++
类代码。
- 如下所示:
// Convenience class for Dense Maps & Sets
template <typename Key, typename Value>
class ExplicitInitDenseMap : public ExplicitInit<DenseMap<Key, Value>> { };
// We cannot use a C++ static initializer to initialize certain globals because
// libc calls us before our C++ initializers run. We also don't want a global
// pointer to some globals because of the extra indirection.
//
// ExplicitInit / LazyInit wrap doing it the hard way.
template <typename Type>
class ExplicitInit {
alignas(Type) uint8_t _storage[sizeof(Type)];
public:
template <typename... Ts>
void init(Ts &&... Args) {
new (_storage) Type(std::forward<Ts>(Args)...);
}
Type &get() {
return *reinterpret_cast<Type *>(_storage);
}
};
-
定论:
代码很少,可是主要代码是在其父类
ExplicitInit
中,ExplicitInitDenseMap
便是个hashmap
.
ExplicitInit
- 代码如下所示:
namespace objc {
// We cannot use a C++ static initializer to initialize certain globals because
// libc calls us before our C++ initializers run. We also don't want a global
// pointer to some globals because of the extra indirection.
//
// ExplicitInit / LazyInit wrap doing it the hard way.
template <typename Type>
class ExplicitInit {
alignas(Type) uint8_t _storage[sizeof(Type)];
public:
template <typename... Ts>
void init(Ts &&... Args) {
new (_storage) Type(std::forward<Ts>(Args)...);
}
Type &get() {
return *reinterpret_cast<Type *>(_storage);
}
};
-
定论:
在
ExplicitInit
这个模板类中有一个uint8_t
(无符号8
位整型,1
字节巨细)类型的数组成员变量_storage
,这个数组初始化巨细为模板类传入的类型的巨细。
AssociationsManager
的作用
回到_object_set_associative_reference
函数里,调用manager
变量中的get
函数获取到AssociationsHashMap
类型的变量associations
,而实际上associations
是一个哈希表,键值对别离为包装后的被相关目标类型DisguisedPtr
以及存储这个被相关目标所相关目标信息的哈希表。
-
①. 假如相关目标的值
value
不为空,首要就会调用associations
的try_emplace
函数尝试设置相关值哈希表中键值为disguised
所对应的值为一个空的ObjectAssociationMap
,因为被相关目标在还未设置相关目标之前,是不会被参加到相关值哈希表中的。 -
②. 假如是首次参加,还需要为其创立一个空的
ObjectAssociationMap
变量,实际上ObjectAssociationMap
也是一个哈希表,是用来存储其相关目标信息,这两种类型的界说如下图所示: -
③. 其实这两种类型都是属于
DenseMap
这个C++
模板类,只不过它们对应的模板不同罢了,DenseMap
模板类中界说了如下图所示几个成员变量。
try_emplace
而try_emplace
函数代码如下所示:
class DenseMapBase {
...
// Inserts key,value pair into the map if the key isn't already in the map.
// The value is constructed in-place if the key is not in the map, otherwise
// it is not moved.
template <typename... Ts>
std::pair<iterator, bool> try_emplace(const KeyT &Key, Ts &&... Args) {
BucketT *TheBucket;
// 假如现已存在了
if (LookupBucketFor(Key, TheBucket))
return std::make_pair(
makeIterator(TheBucket, getBucketsEnd(), true),
false); // Already in map.
// Otherwise, insert the new element.
// 不存在就刺进一个新的目标
TheBucket = InsertIntoBucket(TheBucket, Key, std::forward<Ts>(Args)...);
return std::make_pair(
makeIterator(TheBucket, getBucketsEnd(), true),
true);
}
...
}
-
定论:
- ①. 这个办法回来的类型是std::pair中的结构模板将2个数据组合成一个数据,将迭代器
iterator
与布尔值bool
合并回来,通过first
与second
访问。 - ②.
iterator
是DenseMapIterator
迭代器;假如有内容回来对应的迭代器,假如没有的话,增加一个,并回来DenseMapIterator
迭代器。
- ①. 这个办法回来的类型是std::pair中的结构模板将2个数据组合成一个数据,将迭代器
DenseMapIterator
-
c++
源码:
template <typename KeyT, typename ValueT, typename ValueInfoT,
typename KeyInfoT, typename Bucket, bool IsConst>
class DenseMapIterator {
friend class DenseMapIterator<KeyT, ValueT, ValueInfoT, KeyInfoT, Bucket, true>;
friend class DenseMapIterator<KeyT, ValueT, ValueInfoT, KeyInfoT, Bucket, false>;
using ConstIterator = DenseMapIterator<KeyT, ValueT, ValueInfoT, KeyInfoT, Bucket, true>;
public:
using difference_type = ptrdiff_t;
using value_type =
typename std::conditional<IsConst, const Bucket, Bucket>::type;
using pointer = value_type *;
using reference = value_type &;
using iterator_category = std::forward_iterator_tag;
private:
pointer Ptr = nullptr;
pointer End = nullptr;
public:
DenseMapIterator() = default;
DenseMapIterator(pointer Pos, pointer E,
bool NoAdvance = false)
: Ptr(Pos), End(E) {
if (NoAdvance) return;
AdvancePastEmptyBuckets();
}
// 将ctor从非常数迭代器转换为常量迭代器。
// SFINAE用于常量迭代器目的地,因而它不会终究成为用户界说的复制结构函数。
template <bool IsConstSrc,
typename = typename std::enable_if<!IsConstSrc && IsConst>::type>
DenseMapIterator(
const DenseMapIterator<KeyT, ValueT, ValueInfoT, KeyInfoT, Bucket, IsConstSrc> &I)
: Ptr(I.Ptr), End(I.End) {}
reference operator*() const {
return *Ptr;
}
pointer operator->() const {
return Ptr;
}
bool operator==(const ConstIterator &RHS) const {
return Ptr == RHS.Ptr;
}
bool operator!=(const ConstIterator &RHS) const {
return Ptr != RHS.Ptr;
}
inline DenseMapIterator& operator++() { // Preincrement
++Ptr;
AdvancePastEmptyBuckets();
return *this;
}
DenseMapIterator operator++(int) { // Postincrement
DenseMapIterator tmp = *this; ++*this; return tmp;
}
private:
void AdvancePastEmptyBuckets() {
ASSERT(Ptr <= End);
const KeyT Empty = KeyInfoT::getEmptyKey();
const KeyT Tombstone = KeyInfoT::getTombstoneKey();
while (Ptr != End && (KeyInfoT::isEqual(Ptr->getFirst(), Empty) ||
KeyInfoT::isEqual(Ptr->getFirst(), Tombstone)))
++Ptr;
}
void RetreatPastEmptyBuckets() {
ASSERT(Ptr >= End);
const KeyT Empty = KeyInfoT::getEmptyKey();
const KeyT Tombstone = KeyInfoT::getTombstoneKey();
while (Ptr != End && (KeyInfoT::isEqual(Ptr[-1].getFirst(), Empty) ||
KeyInfoT::isEqual(Ptr[-1].getFirst(), Tombstone)))
--Ptr;
}
};
而conditional是选择器
能够看到运用了两次try_emplace
办法,能够得知他是嵌套两层的HashMap
结构,依据上面代码的了解,能够得到以下结构图:
LookupBucketFor
能够看到在这个函数中会调用LookupBucketFor
函数查找这个被相关目标在相关哈希表中是否存在目标相关哈希表,LookupBucketFor
函数代码如下:
- 第一个
LookupBucketFor
函数代码如下所示:
/// LookupBucketFor-查找Val的适当bucket,并将其回来到FoundBucket中。
/// 假如bucket包含键和值,则回来true,
/// 否则回来带有空标记或墓碑的bucket,并回来false。
template<typename LookupKeyT>
bool LookupBucketFor(const LookupKeyT &Val,
const BucketT *&FoundBucket) const {
// 获取首个bucket的地址指针
const BucketT *BucketsPtr = getBuckets();
// 获取buckets的数量
const unsigned NumBuckets = getNumBuckets();
if (NumBuckets == 0) {
// 假如Buckets的数量为0,便是没有找到相应的bucket,直接回来false
FoundBucket = nullptr;
return false;
}
// FoundTombstone - Keep track of whether we find a tombstone while probing.
const BucketT *FoundTombstone = nullptr;
const KeyT EmptyKey = getEmptyKey();
const KeyT TombstoneKey = getTombstoneKey();
assert(!KeyInfoT::isEqual(Val, EmptyKey) &&
!KeyInfoT::isEqual(Val, TombstoneKey) &&
"Empty/Tombstone value shouldn't be inserted into map!");
// 调用hash函数获取Val在buckets中的索引
unsigned BucketNo = getHashValue(Val) & (NumBuckets-1);
// hash探索次数
unsigned ProbeAmt = 1;
while (true) {
// 获取对应索引方位的bucket
const BucketT *ThisBucket = BucketsPtr + BucketNo;
// Found Val's bucket? If so, return it.
if (LLVM_LIKELY(KeyInfoT::isEqual(Val, ThisBucket->getFirst()))) {
// 假如val等于当时Bucket中的key值,就找到了bucket,回来true
FoundBucket = ThisBucket;
return true;
}
// If we found an empty bucket, the key doesn't exist in the set.
// Insert it and return the default value.
if (LLVM_LIKELY(KeyInfoT::isEqual(ThisBucket->getFirst(), EmptyKey))) {
// 假如当时bucket的key值为空,阐明Val代表key及其Value还未刺进到buckets中,就直接回来false
// If we've already seen a tombstone while probing, fill it in instead
// of the empty bucket we eventually probed to.
FoundBucket = FoundTombstone ? FoundTombstone : ThisBucket;
return false;
}
// If this is a tombstone, remember it. If Val ends up not in the map, we
// prefer to return it than something that would require more probing.
// Ditto for zero values.
if (KeyInfoT::isEqual(ThisBucket->getFirst(), TombstoneKey) &&
!FoundTombstone)
FoundTombstone = ThisBucket; // Remember the first tombstone found.
if (ValueInfoT::isPurgeable(ThisBucket->getSecond()) && !FoundTombstone)
FoundTombstone = ThisBucket;
// Otherwise, it's a hash collision or a tombstone, continue quadratic
// probing.
if (ProbeAmt > NumBuckets) {
// 假如hash次数大于Bucket的数量,都没有找到对应Bucket,出现过错
FatalCorruptHashTables(BucketsPtr, NumBuckets);
}
// 再hash
BucketNo += ProbeAmt++;
BucketNo &= (NumBuckets-1);
}
}
template <typename LookupKeyT>
bool LookupBucketFor(const LookupKeyT &Val, BucketT *&FoundBucket) {
const BucketT *ConstFoundBucket;
bool Result = const_cast<const DenseMapBase *>(this)
->LookupBucketFor(Val, ConstFoundBucket);
FoundBucket = const_cast<BucketT *>(ConstFoundBucket);
return Result;
}
-
定论:
这个函数中的代码逻辑是从
Buckets
中查找Val
这个Key
所对应的Bucket
,首要获取到Buckets
中的首个Bucket
的首地址以及Buckets
数量-
- 假如
Buckets
数量为0
,阐明AssociationsHashMap
为一个空的hash
表,当然找不到任何Bucket
,就直接回来false
- 假如
-
- 假如不为空,就通过
hash
函数获取到Val
这个Key
值在Buckets
所对应的存储方位
-
①. 假如此方位的
Bucket
存在且Key
值为Val
,那么就阐明之前现已创立过了这个Val
所对应的ObjectAssociationMap
,那么就获取这个方位的Bucket
并回来true
-
②. 假如此方位的
Bucket
为空,就阐明还未刺进Val
作为Key
的Bucket
,那么就获取这个方位的Bucket
并回来false
,获取到这个方位的Bucket
是方便存值 -
③. 假如此方位的
Bucket
存在但不等于Val
对应Key
的Bucket
,那么就发生了hash
磕碰,那么就再hash
获取,获取下一个Bucket
再次进行以上判别,直到发生磕碰的次数大于了Buckets
的数量,假如此刻都未找到Val
所对应的Bucket
,那么就阐明发生了过错。
- 假如不为空,就通过
-
InsertIntoBucketWithLookup
try_emplace
调用完LookupBucketFor
函数后,假如找到了Val
所对应的Bucket
,就会直接回来pair<iterator, bool>
这种类似于元组的值,其中iterator
实际上是一个名为DenseMapIterator
的C++
模板类,在try_emplace
函数调用所回来的iterator
类型的值中,Ptr
是指向所找到对应Val
的Bucket
的指针,End
是指向AssociationsHashMap
的Buckets
中最终一个Bucket
的指针,然后pari
中第二个bool
值设置为true
是表明找到了,可是假如调用完LookupBucketFor
也没有找到Val
所对应的Bucket
,就会调用InsertIntoBucket
函数在Buckets
中刺进一个新值,也便是初始化Val
所对应方位的Bucket
。
- 其代码如下所示:
class DenseMapBase {
...
template <typename LookupKeyT>
BucketT *InsertIntoBucketWithLookup(BucketT *TheBucket, KeyT &&Key,
ValueT &&Value, LookupKeyT &Lookup) {
TheBucket = InsertIntoBucketImpl(Key, Lookup, TheBucket);
TheBucket->getFirst() = std::move(Key);
::new (&TheBucket->getSecond()) ValueT(std::move(Value));
return TheBucket;
}
...
}
InsertIntoBucketImpl
而在这个函数中又调用了InsertIntoBucketImpl
函数。
- 代码如下所示:
template <typename LookupKeyT>
BucketT *InsertIntoBucketImpl(const KeyT &Key, const LookupKeyT &Lookup,
BucketT *TheBucket) {
// If the load of the hash table is more than 3/4, or if fewer than 1/8 of
// the buckets are empty (meaning that many are filled with tombstones),
// grow the table.
//
// The later case is tricky. For example, if we had one empty bucket with
// tons of tombstones, failing lookups (e.g. for insertion) would have to
// probe almost the entire table until it found the empty bucket. If the
// table completely filled with tombstones, no lookup would ever succeed,
// causing infinite loops in lookup.
unsigned NewNumEntries = getNumEntries() + 1;
unsigned NumBuckets = getNumBuckets();
if (LLVM_UNLIKELY(NewNumEntries * 4 >= NumBuckets * 3)) {
this->grow(NumBuckets * 2);
LookupBucketFor(Lookup, TheBucket);
NumBuckets = getNumBuckets();
} else if (LLVM_UNLIKELY(NumBuckets-(NewNumEntries+getNumTombstones()) <=
NumBuckets/8)) {
this->grow(NumBuckets);
LookupBucketFor(Lookup, TheBucket);
}
ASSERT(TheBucket);
// Only update the state after we've grown our bucket space appropriately
// so that when growing buckets we have self-consistent entry count.
// If we are writing over a tombstone or zero value, remember this.
if (KeyInfoT::isEqual(TheBucket->getFirst(), getEmptyKey())) {
// Replacing an empty bucket.
incrementNumEntries();
} else if (KeyInfoT::isEqual(TheBucket->getFirst(), getTombstoneKey())) {
// Replacing a tombstone.
incrementNumEntries();
decrementNumTombstones();
} else {
// we should be purging a zero. No accounting changes.
ASSERT(ValueInfoT::isPurgeable(TheBucket->getSecond()));
TheBucket->getSecond().~ValueT();
}
return TheBucket;
}
-
定论:
能够看到其实这个函数首要会判别是否需要对
AssociationsHaseMap
的Buckets
进行扩容:- 假如其存储容量超过了总容量的
3/4
(装载因子),就会调用grow
函数创立一个容量为当时容量的两倍巨细的hash
表,而且将之前旧表中的Bucket
增加到这个新的hash
表中,然后依据传入的Key
(此刻也便是所包装的被相关目标)调用LookupBucketFor
函数从头获取对应方位Bucket
,然后Buckets
总数量加1
,回来这个Bucket
,而在InsertIntoBucket
函数中获取到这个Bucket
之后,就会对这个Bucket
的Key
以及value
进行赋值,将包装的被相关目标通过函数forward
赋值给Key
,将新创立的ObjectAssociationMap
通过包装赋值给Value
。
- 假如其存储容量超过了总容量的
setHasAssociatedObjects
接着回到_object_set_associative_reference
函数中,在调用完try_emplace
函数获取到refs_result
这个变量后,会依据其第二个值判别被相关目标是否是第一次参加到AssociationsHaseMap
中,假如是将之前的isFirstAssociation
设置为true
,然后获取到被相关目标的ObjectAssociationMap
,也便是refs
,假如此刻是第一次为这个被相关目标增加相关目标,那么refs
表肯定是个空表,此刻调用refs
的函数try_emplace
获取传入的key
所对应的相关目标信息(也便是ObjcAssociation
包装了policy
(相关战略)以及value
(相关目标)的类型),并传入当时的association
作为参数,此刻refs
调用try_emplace
函数的逻辑是与associations
调用try_emplace
函数的逻辑是相同的,这儿就不具体论述了,可是别的有一点不同的是,假如refs
调用try_emplace
函数获取到Bucket
是之前存在了的,就需要将association
中的管理战略以及相关与refs
中key
所对应的bucket
中的value
(ObjcAssociation
类型)管理战略以及相关目标别离进行交换。
然后又会判别isFirstAssociation
是否为真,假如为真,就会调用被相关目标的setHasAssociatedObjects
- 其
c++
源码:
inline void
objc_object::setHasAssociatedObjects()
{
if (isTaggedPointer()) return;
if (slowpath(!hasNonpointerIsa() && ISA()->hasCustomRR()) && !ISA()->isFuture() && !ISA()->isMetaClass()) {
void(*setAssoc)(id, SEL) = (void(*)(id, SEL)) object_getMethodImplementation((id)this, @selector(_noteAssociatedObjects));
if ((IMP)setAssoc != _objc_msgForward) {
(*setAssoc)((id)this, @selector(_noteAssociatedObjects));
}
}
isa_t newisa, oldisa = LoadExclusive(&isa().bits);
do {
newisa = oldisa;
if (!newisa.nonpointer || newisa.has_assoc) {
ClearExclusive(&isa().bits);
return;
}
newisa.has_assoc = true;
} while (slowpath(!StoreExclusive(&isa().bits, &oldisa.bits, newisa.bits)));
}
-
定论:
setHasAssociatedObjects
函数设置这个被相关目标的isa
的值,将has_assoc
这个段的数据设置为true
,然后调用releaseHeldValue
函数开释旧的association
中的_value
,这便是相关目标的设置流程。
单个相关目标的删去
假如你想要删去某个被相关目标的某个相关目标,只需要在调用objc_setAssociatedObject
函数时将传入的value
值设置为nil
就能够了,而在_object_set_associative_reference
函数中会判别value
是否为空,假如value
不为空,履行的便是相关目标的设值流程,假如value
为空,履行的便是相关目标的删去流程。
- 测试代码:
// main
int main(int argc, const char * argv[]) {
@autoreleasepool {
CJPerson * person = [CJPerson alloc];
CJPet *dog = [CJPet new];
dog.name = @"狗仔";
dog.age = 1;
person.ca_pet = dog;
NSLog(@"pet:%@, age:%ld", person.ca_pet.name, person.ca_pet.age);
person.ca_pet = nil;
}
return 0;
}
-
_objc_set_associative_reference
的删去相关目标的代码如下图:
associations.find(disguised)
首要会调用associations
的find
函数,找到被相关目标所对应的AssociationHashMap
表,find
函数代码如下所示:
iterator find(const_arg_type_t<KeyT> Val) {
BucketT *TheBucket;
if (LookupBucketFor(Val, TheBucket))
return makeIterator(TheBucket, getBucketsEnd(), true);
return end();
}
- 定论:
-
- 在
find
函数中也是通过LookupBucketFor
函数进行查找到,假如查找到了,就会回来iterator
类型的变量。
- 在
-
- 假如查找不到就会回来调用
end()
函数之后的回来值。
- 假如查找不到就会回来调用
-
associations.find(key)
回到_object_set_associative_reference
函数,紧接着会判别是否能在AssociationHashMap
中找到被相关目标所对应的ObjectAssociationMap
表。
-
假如找不到就什么都不必做了。
-
假如找到了对应的
ObjectAssociationMap
表,就会调用find
函数在ObjectAssociationMap
表中查找对应的key
的value
(也便是ObjcAssociation
)是否存在?
因为通过假装目标
查找ObjectAssociationMap
,跟通过key
查找ObjcAssociation
目标都是DenseMap
的find
函数。
- 定论:
-
①. 假如
ObjcAssociation
不存在,也什么都不必做。 -
②. 假如
ObjcAssociation
存在,就会将ObjectAssociationMap
表中key
所对应的value
中的policy
以及value
设置为相应战略以及nil
值,然后调用erase
函数擦除ObjectAssociationMap
中这对键值。- 再判别
ObjectAssociationMap
表中Buckets
是否为空,假如为空,就会调用erase
函数擦除AssociationHashMap
中被相关目标与ObjectAssociationMap
这对键值。
- 再判别
-
associations.erase
这个函数是擦除key
对应的ObjcAssociation
,以及diguised
对应的ObjectAssociationMap
对应的办法.
- 擦除的
c++
源码:
class DenseMapBase {
...
void erase(iterator I) {
BucketT *TheBucket = &*I;
TheBucket->getSecond().~ValueT();
TheBucket->getFirst() = getTombstoneKey();
decrementNumEntries();
incrementNumTombstones();
compact();
}
...
}
相关目标的取值
想要获取相关目标的值,能够通过调用objc_getAssociatedObject
函数来获取。而在objc_getAssociatedObject
函数中是通过_object_get_associative_reference
取值的
- 如下所示:
id
objc_getAssociatedObject(id object, const void *key)
{
return _object_get_associative_reference(object, key);
}
id
_object_get_associative_reference(id object, const void *key)
{
ObjcAssociation association{};
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.get());
// 传入objc_object目标,在find函数底层会转化为DisguiedPtr
AssociationsHashMap::iterator i = associations.find((objc_object *)object);
if (i != associations.end()) {
ObjectAssociationMap &refs = i->second;
ObjectAssociationMap::iterator j = refs.find(key);
if (j != refs.end()) {
association = j->second;
association.retainReturnedValue();
}
}
}
return association.autoreleaseReturnedValue();
}
-
定论:
能够发现相关目标的取值流程与相关目标的删去流程是极其类似的,
-
假如
association
不为空,因为相关战略是OBJC_ASSOCIATION_SETTER_RETAIN
类型,只会在设置相关目标时,才对这个相关目标的引证计数值加1
。可是取值时,并不会对相关目标加1
。 -
最终回来这个相关目标值,这便是相关目标取值的整个进程了。
- 打印相关目标:
-
(lldb) p association
(objc::ObjcAssociation) $4 = {
_policy = 1
_value = 0x0000600000200040
}
(lldb) x/4gx $0
0x600000200040: 0x021d800100008765 0x00000001000040c8
0x600000200050: 0x0000000000000001 0x0000000000000000
(lldb)
retainReturnedValue
这个函数是在c++
类ObjcAssociation
里的内联函数,其实便是判别设置战略是否为OBJC_ASSOCIATION_GETTER_RETAIN
,要不要通过objc_retain
进行引证计数器+1
?
-
其代码如下图所示:
-
定论:
因为设置相关目标战略是
OBJC_ASSOCIATION_RETAIN_NONATOMIC
,所以判别条件不成立,不会进入objc_retain
。
autoreleaseReturnedValue
这个函数也是在c++
类ObjcAssociation
里的内联函数,其实便是判别设置战略是否为OBJC_ASSOCIATION_GETTER_AUTORELEASE
,要不要通过objc_autorelease
进行自动开释来延时开释?
-
其代码如下图所示:
-
定论:
因为设置相关目标战略是
OBJC_ASSOCIATION_RETAIN_NONATOMIC
,刚好对应了所以判别条件不成立,不会进入objc_autorelease
,而是直接回来_value
即CJPet
目标它的引证计数器刚好为2
。
留意点:
在里面C++
类objc_AssociationPolicy
中的相关目标的战略只有5
种,跟ObjcAssociation
的7
种战略是有所不同的。关键是这7种战略会区别setter
与getter
状况。
删去一切相关目标
但目前为止,咱们现已知道了单个相关目标的设值、删去、以及取值流程,那么假如一个被相关目标调用dealloc
办法开释的时分,其一切的相关目标又是如何处理的呢?其实在查看objc4-866.9相关源码的时分咱们现已留意到了一个函数,便是objc_removeAssociatedObjects
。
- 其代码如下:
void objc_removeAssociatedObjects(id object)
{
if (object && object->hasAssociatedObjects()) {
_object_remove_associations(object, /*deallocating*/false);
}
}
_object_remove_assocations
在这个函数中,首要会判别被相关目标是否存在而且被相关目标是否有相关目标,假如为真,就会调用_object_remove_assocations
函数。
在里面参加自己类CJPerson
的判别条件,就能够断点调试了。
- 代码如下:
// Unlike setting/getting an associated reference,
// this function is performance sensitive because of
// raw isa objects (such as OS Objects) that can't track
// whether they have associated objects.
void
_object_remove_associations(id object, bool deallocating)
{
ObjectAssociationMap refs{};
//------ 测试代码为了能在断点时暂停,源码里不存在
const char *personName = "CJPerson";
if (strcmp(object_getClassName(object), personName) == 0) {
printf("毁掉相关目标");
}
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.get());
AssociationsHashMap::iterator i = associations.find((objc_object *)object);
if (i != associations.end()) {
refs.swap(i->second);
// If we are not deallocating, then SYSTEM_OBJECT associations are preserved.
bool didReInsert = false;
if (!deallocating) {
for (auto &ref: refs) {
if (ref.second.policy() & OBJC_ASSOCIATION_SYSTEM_OBJECT) {
i->second.insert(ref);
didReInsert = true;
}
}
}
if (!didReInsert)
associations.erase(i);
}
}
// Associations to be released after the normal ones.
SmallVector<ObjcAssociation *, 4> laterRefs;
// release everything (outside of the lock).
for (auto &i: refs) {
if (i.second.policy() & OBJC_ASSOCIATION_SYSTEM_OBJECT) {
// If we are not deallocating, then RELEASE_LATER associations don't get released.
if (deallocating)
laterRefs.append(&i.second);
} else {
i.second.releaseHeldValue();
}
}
for (auto *later: laterRefs) {
later->releaseHeldValue();
}
}
-
总结:
在这个函数中,首要创立一个
ObjectAssociationMap
类型的暂时变量refs
,其成员变量初始化为对应的空值。-
①. 然后会依据
manager
变量的get()
函数获取到AssociationsHashMap
这张大局哈希表(寄存的是一切被相关目标以及其对应的ObjectAssociationMap
表), -
②. 在这张表中查找传入的被相关目标
object
是否存在对应的ObjectAssociationMap
表,假如找到了,就将这个被相关目标object
的ObjectAssociationMap
表中的数据域refs
中的数据进行交换, -
③. 初始化一个
bool
类型的变量didReInsert
(用来判别是不是从头刺进),初始值为false
,判别传入的参数deallocating
(开释分配空间)的值是否为false
,假如不为false
,那么便是从头刺进值, -
然后会遍历
refs
(ObjectAssociationMap
表,寄存的是设置相关目标时传入的key
以及对应的ObjcAssociation
(包装了相关战略以及相关目标)变量)中Buckets
中每个Bucket
,调用Bucket
对中second
的policy()
函数获取此刻这个Bucket
中相关目标的相关战略,&
上OBJC_ASSOCIATION_SYSTEM_OBJECT
这个枚举值, -
假如值为真,也便是表明是此相关目标为体系目标,就将这个相关目标从头刺进到被相关目标在
AssociationsHashMap
表中所对应的ObjectAssociationMap
表中,然后将didReInsert
赋值为true
, -
假如遍历完整个
ObjcAssociationMap
表都没有需要从头刺进的体系目标,就调用erase
函数将AssociationsHashMap
表中此被相关目标对应的相关目标信息抹除, -
然后再次遍历
refs
这个表中一切的key
以及对应的相关目标信息,调用releaseHeldValue
函数将一切非体系目标的相关目标的引证计数值减1
,然后界说一个SmallVector
类型的laterRefs
存储一切体系的相关目标,最终将laterRefs
中一切的相关目标引证计数值减1
。
-
_object_remove_assocations
的调用状况
可是什么状况下会调用_object_remove_assocations
这个函数呢?
通过lldb
调试,bt
指令打印整个函数调用栈。
- lldb打印:
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 22.1
frame #0: 0x00000001006f081c libobjc.A.dylib`_object_remove_associations(object=0x0000600000209dc0, deallocating=true) at objc-references.mm:233:9
frame #1: 0x00000001006ba87e libobjc.A.dylib`objc_destructInstance(obj=0x0000600000209dc0) at objc-runtime-new.mm:8600:20
frame #2: 0x00000001006baafd libobjc.A.dylib`object_dispose(obj=0x0000600000209dc0) at objc-runtime-new.mm:8618:5
frame #3: 0x0000000100704f08 libobjc.A.dylib`objc_object::rootDealloc(this=0x0000600000209dc0) at objc-object.h:581:9
frame #4: 0x0000000100704dc0 libobjc.A.dylib`_objc_rootDealloc(obj=0x0000600000209dc0) at NSObject.mm:2113:10
frame #5: 0x0000000100708689 libobjc.A.dylib`-[NSObject dealloc](self=0x0000600000209dc0, _cmd="dealloc") at NSObject.mm:2706:5
frame #6: 0x00000001006ff335 libobjc.A.dylib`objc_object::performDealloc(this=0x0000600000209dc0) at NSObject.mm:1538:9
frame #7: 0x00000001006fc158 libobjc.A.dylib`_objc_release [inlined] objc_object::rootRelease(this=0x0000600000209dc0, performDealloc=true, variant=FastOrMsgSend) at objc-object.h:898:15
frame #8: 0x00000001006fb7f0 libobjc.A.dylib`_objc_release [inlined] objc_object::release(this=0x0000600000209dc0) at objc-object.h:714:5
frame #9: 0x00000001006fb792 libobjc.A.dylib`_objc_release(obj=0x0000600000209dc0) at NSObject.mm:1888:17
* frame #10: 0x00000001006fb6fb libobjc.A.dylib`objc_storeStrong(location=0x00007ff7bfeff1e0, obj=0x0000000000000000) at NSObject.mm:281:5
frame #11: 0x00000001000034fe KCObjcBuild`main(argc=1, argv=0x00007ff7bfeff4d8) at main.m:280:5 [opt]
frame #12: 0x00007ff80569a310 dyld`start + 2432
-
定论:
通过打印成果可发现删去相关目标的流程是:
-
[NSObject dealloc]
->_objc_rootDealloc
->objc_object::rootDealloc()
->object_dispose
->objc_destructInstance
->_object_remove_associations
-
-
咱们来反向查找一下,大局搜索
_object_remove_assocations
关键字,也能够发现在如下图所示的函数中被调用: -
而
objc_destructInstance
函数又在object_dispose
函数中被调用,如下图所示: -
而
object_dispose
函数在objc_object::rootDealloc
函数中被调用,如下图所示: -
而
objc_object::rootDealloc
函数在_objc_rootDealloc
函数中被调用,如下图所示: -
而最终,
_objc_rootDealloc
函数在dealloc
办法中被调用,当目标的引证计数为0
的时分体系会对其dealloc
办法进行调用,这便是删去目标的一切相关目标的调用流程了。
三、总结
通过以上探讨,咱们可知关于一个目标的相关目标的存储在底层中实际上是一个两层哈希表结构。
- 如下图所示: