应用程序加载(五)-- 类扩展和关联对象
应用程序加载(一) -- dyld流程分析
应用程序加载(二) -- dyld&objc关联以及类的加载初探
应用程序加载(三)-- 类的加载
应用程序加载(四)-- 分类的加载
应用程序加载(五)-- 类扩展和关联对象
1、类扩展extension
类扩展需要对应着分类(category)比较研究。
分类的作用:
- 添加方法,可以添加实例方法和类方法
- 添加协议
- 添加属性(@property),只会生成
getter
和setter
的声明,而不会生成方法的实现和带下划线的成员变量
类扩展的作用:
- 可以添加方法声明,需要在原类的.m中实现
- 可以添加属性
- 可以添加成员属性
扩展写法
通常在创建工程中时会有一个ViewController
类或者创建自定义的UIViewController
子类的时候,Xcode
会在类中自动创建一个类扩展,如图:
我们经常把不想暴露的一些属性定义在这里,需要外部调用的属性写在.h文件中。
需要注意类扩展有一个位置要求,必须写在类的interface
和implementation
之间。写一个简单的例子,就在VierController.m
文件中,写一个Person
类,然后为Person
添加一个类扩展。
正确的写法:
如果将类扩展写interface
之前或者implementation
之后,编译器会报错。
interface
之前:
implementation
之后:
扩展和分类的区别
分类中添加属性是没有getter
和setter
实现的,而扩展中是有的,我们可以通过c++层看到扩展添加属性会有什么效果。
首先在扩展中添加一个dzName
属性
然后进入到ViewController.m
文件所在的路径,执行xcrun -sdk iphonesimulator clang -rewrite-objc ViewController.m
命令,生成ViewController.cpp
文件,打开文件,搜索dzName
通过c++的代码可以看到,生成了属性对应的setter
和getter
方法,还有带有下划线的成员变量(_dzName
)
在扩展中还写了一个ext_method
方法,在c++中也能够看到
直接加入到方法列表中。这点可以说明,扩展是类的一部分,而且方法在编译期就加入到类中。
2、关联对象
上面了解了,扩展可以给类增加属性。但是分类添加属性的时候只是增加了getter
和setter
的声明。例如:
有如图中的分类,里面定义了一个属性cat_name
。当我们调用的时候:
此时编译器是不会报错的,说明cat_name
的setter
方法的sel
是可以找到的。当运行起来的时候,就会报错。因为找不到对应的imp
。所以说分类中添加属性只是添加了getter
和setter
方法的声明,而没有方法的实现。
那么如何分类中的属性的实现呢?就是通过系统给我们提供的关联对象。
关联对象用法
#import <objc/runtime.h>
@interface Person (DZ)
@property (nonatomic, copy) NSString *cate_name;
@end
@implementation Person (DZ)
- (void)setCate_name:(NSString *)cate_name{
/**
1: 对象
2: 标识符
3: value
4: 策略
*/
objc_setAssociatedObject(self, "cate_name", cate_name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)cate_name{
return objc_getAssociatedObject(self, "cate_name");
}
@end
- 分类中定义了一个属性
cate_name
- 实现了
cate_name
的getter
和setter
方法,方法中调用了关联对象的两个方法:- objc_setAssociatedObject:需要四个参数,分别是对象、标识符、值和关联策略
- objc_getAssociatedObject:相对简单,只有两个参数,分别是对象和标识符
- 注意,使用关联对象要引入
runtime
的头文件
用法很容易,但是底层是如何实现的呢?接下来我们来探索一下底层实现。
关联对象的底层实现
还是在objc
的源码中,可以看到相关源码,先来看看关联对象的set实现:
关联对象set
void
objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
{
SetAssocHook.get()(object, key, value, policy);
}
- 此处与以往的
objc
开源代码不同,在“781”
的这个版本的源码中,增加了一层代码封装,通过command
点击SetAssocHook
,能够看到这行代码:static ChainedHookFunction<objc_hook_setAssociatedObject> SetAssocHook{_base_objc_setAssociatedObject};
- 底层调用的就是
_base_objc_setAssociatedObject
函数
static void
_base_objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
{
_object_set_associative_reference(object, key, value, policy);
}
-
_base_objc_setAssociatedObject
实现中,调用的是_object_set_associative_reference
void
_object_set_associative_reference(id object, const void *key, id value, uintptr_t policy)
{
// This code used to work when nil was passed for object and key. Some code
// probably relies on that to not crash. Check and handle it explicitly.
// rdar://problem/44094390
//1、相关的容错处理
if (!object && !value) return;
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));
//2、包装了一下 对象
DisguisedPtr<objc_object> disguised{(objc_object *)object};
//3、 包装一下 policy - value
ObjcAssociation association{policy, value};
//4、根据关联策略对值处理
// retain the new value (if any) outside the lock.
association.acquireValue();
// 5、核心代码
{
AssociationsManager manager;
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 */
object->setHasAssociatedObjects();
}
/* establish or replace the association */
auto &refs = refs_result.first->second; // 空的桶子
auto result = refs.try_emplace(key, std::move(association));
if (!result.second) {
association.swap(result.first->second);
}
} else {
auto refs_it = associations.find(disguised);
if (refs_it != associations.end()) {
auto &refs = refs_it->second;
auto it = refs.find(key);
if (it != refs.end()) {
association.swap(it->second);
refs.erase(it);
if (refs.size() == 0) {
associations.erase(refs_it);
}
}
}
}
}
// release the old value (outside of the lock).
association.releaseHeldValue();
}
-
首先代码进行了相关的容错处理,这些不用关心,简单看看就好
-
将传入的参数
object
进行了一次包装。 -
对第三个参数
value
和第四个参数policy
包装一下。 -
association.acquireValue();
根据关联策略(policy
参数),对值进行处理,看看源码实现: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; } } }
- 此处根据关联策略对
value
进行retain
或者copy
的操作。
- 此处根据关联策略对
-
此处进入核心代码部分
{ AssociationsManager manager; 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 */ object->setHasAssociatedObjects(); } /* establish or replace the association */ auto &refs = refs_result.first->second; // 空的桶子 auto result = refs.try_emplace(key, std::move(association)); if (!result.second) { association.swap(result.first->second); } } else { auto refs_it = associations.find(disguised); if (refs_it != associations.end()) { auto &refs = refs_it->second; auto it = refs.find(key); if (it != refs.end()) { association.swap(it->second); refs.erase(it); if (refs.size() == 0) { associations.erase(refs_it); } } } } }
- 初始化一个
AssociationsManager
变量manager
,它不是一个单例,只是用它来给下面访问哈希表进行加锁
- 初始化一个
构造函数和析构函数就是加锁解锁操作。
- 通过manager.get()
函数获取到关联对象的哈希表的引用&associations
,类型是AssociationsHashMap
- 判断value
存在时:
- 从哈希表中读取disguised
对应的结果refs_result
。disguised
就是传入的object
参数。
- 通过结果refs_result
中的second
判断是不是第一次来。如果第一次来就会调用object->setHasAssociatedObjects();
函数
- setHasAssociatedObjects
就是给对象object
设置状态。代表这个对象是有关联对象存在的。如果object
是nonpointer
,那么这个关联状态就存储在isa
中
- 然后根据参数key
创建关联,如果之前关联过就进行替换关联。
- 此处就可以知道,是两层哈希表。第一层是以对象作为哈希函数,第二层是以闯入的参数key
为哈希函数。
- value
不存在时:传入的参数value
为nil。就进行擦除。因为是双层哈希,所以得做两层擦除。
关联对象set流程图:
关联对象get
此时对关联对象的set有所了解了,再看看get实现就很简单,就是通过传入的两个参数,进行哈希的双层查找。源码如下:
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());
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();
}
关联对象双层哈希示意图
- 图中包含示例中的关联对象例子
关联对象表的移除
表的创建我们了解后,就得看看什么时候对表进行移除操作的。移除是在dealloc
中处理的,看看源码:
- (void)dealloc {
_objc_rootDealloc(self);
}
⏬⏬⏬
void
_objc_rootDealloc(id obj)
{
ASSERT(obj);
obj->rootDealloc();
}
⏬⏬⏬
inline void
objc_object::rootDealloc()
{
if (isTaggedPointer()) return; // fixme necessary?
if (fastpath(isa.nonpointer &&
!isa.weakly_referenced &&
!isa.has_assoc &&
!isa.has_cxx_dtor &&
!isa.has_sidetable_rc))
{
assert(!sidetable_present());
free(this);
}
else {
object_dispose((id)this);
}
}
⏬⏬⏬
id
object_dispose(id obj)
{
if (!obj) return nil;
objc_destructInstance(obj);
free(obj);
return nil;
}
⏬⏬⏬
void *objc_destructInstance(id obj)
{
if (obj) {
// Read all of the flags at once for performance.
bool cxx = obj->hasCxxDtor();
bool assoc = obj->hasAssociatedObjects();
// This order is important.
if (cxx) object_cxxDestruct(obj);
if (assoc) _object_remove_assocations(obj);
obj->clearDeallocating();
}
return obj;
}
⏬⏬⏬
void
_object_remove_assocations(id object)
{
ObjectAssociationMap refs{};
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.get());
AssociationsHashMap::iterator i = associations.find((objc_object *)object);
if (i != associations.end()) {
refs.swap(i->second);
associations.erase(i);
}
}
// release everything (outside of the lock).
for (auto &i: refs) {
i.second.releaseHeldValue();
}
}
- 通过代码的层层调用,最后调用到
_object_remove_assocations
函数 - 在
_object_remove_assocations
函数中,找到object
为key的关联对象表(第二层哈希表),然后对表进行所有数据擦除