iOS底层之类扩展和关联对象
iOS 全网最新objc4 可调式/编译源码
编译好的源码的下载地址
序言
在前面文章中,从底层源码探索了类
和分类
的加载流程,今天从源码层面探索一下类扩展及关联对象的本质。
类扩展
通过cpp文件查看类扩展
定义类LGStudent
的属性方法,以及类扩展实现
@interface LGStudent : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) int age;
- (void)instanceMethod;
+ (void)classMethod;
@end
// 类扩展
@interface LGStudent ()
@property (nonatomic, copy) NSString *ext_name;
@property (nonatomic, assign) int ext_age;
- (void)ext_instanceMethod;
+ (void)ext_classMethod;
@end
@implementation LGStudent
- (void)instanceMethod{
NSLog(@"%s", __func__ );
}
+ (void)classMethod{
NSLog(@"%s", __func__ );
}
- (void)ext_instanceMethod{
NSLog(@"%s", __func__ );
}
+ (void)ext_classMethod{
NSLog(@"%s", __func__ );
}
@end
通过clang
然后生成cpp
文件,查看类扩展属性
在类扩展中定义的属性
ext_name
和ext_age
,在cpp
文件和主类的属性name
和age
一样处理生成带下划线
的成员变量
。
这里对比可以看到,
- 在成员列表
_ivar_list_t
中是有_age
、_ext_age
、_name
、_ext_name
4个成员变量;- 在属性列表
_prop_list_t
中只有name
和age
两个属性,说明在类扩展中的属性为私有;
看一下类扩展中的方法
类扩展中定义的实例方法
ext_instanceMethod
以及属性ext_name
和ext_age
的setter
和getter
方法,都编译在主类实例方法中。
类扩展中定义的类方法
ext_classMethod
和主类的类方法classMethod
在编译在主类的类方法中。
通过类的加载查看类扩展
定义类LGPerson
和类扩展LGPerson+LG
在《iOS底层之类的加载》中我们知道了
非懒加载类
加载到内存是在启动时完成的。
为方便调试在主类实现+load
方法,类扩展方法以ext
开头,在LGperson
的.m
中不实现类扩展方法ext_saySomthing
。
在methodizeClass
方法中对LGPerson
准确下断点,运行调试
先来到的为
LGPerson
的元类,在从ro
读取的method_list_t
中,包含主类中的类方法+load
和类扩展中的类方法ext_classMethod
过掉断点会再次进来,这次是对类LGPerson
的加载
LGPerson类加载时,从
ro
读取的method_list_t
中,包含主类中的实例方法sayHello
和sayByeBye
,同时还有分类中的实例方法ext_instanceMethod
,属性ext_name
的setter
和getter
方法,并没有未实现的方法ext_saySomthing
。
类扩展总结
- 类扩展可以给类添加成员属性,但是是私有变量;
- 类扩展可以给类添加方法,也是私有方法;
关联对象
分类的作用是给类添加新的方法,但是不能添加属性
,即使添加了属性
,也只会生成属性的setter
和getter
方法声明,不能生成方法实现和带下划线的成员变量
。
我们可以通过Runtime
给分类添加属性,就是通过关联对象
的形式实现,今天从源码层面实例探究一下关联对象
是如何实现的给分类
添加属性的。
源码分析
关联对象存储
关联对象存储值时使用的api
为objc_setAssociatedObject
,参数有4个
object
: 关联对象;key
:标识符;value
:关联值;policy
:关联策略;
void
objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
{
_object_set_associative_reference(object, key, value, policy);
}
在objc_setAssociatedObject
直接调用_object_set_associative_reference
,这样做是维持最上层的api
稳定,不管底层怎么更新,我们做关联对象使用的就是objc_setAssociatedObject
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.
// 关联对象为nil切关联值也为nil
if (!object && !value) return;
// object不允许关联对象,直接崩
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<objc_object> disguised{(objc_object *)object};
// 将value和policy包装成ObjcAssociation类型
ObjcAssociation association{policy, value};
// retain the new value (if any) outside the lock.
association.acquireValue();
bool isFirstAssociation = false; // 首次关联对象标识
{
AssociationsManager manager; // 实例化关联对象处理manager
AssociationsHashMap &associations(manager.get()); // 获取关联对象表
if (value) { // 关联值判断
// 从关联对象表中,根据disguised获取关联对象存储地址,没有就创建插入
auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});
if (refs_result.second) { // 是否为第一次关联值
/* it's the first association we make */
isFirstAssociation = true;
}
/* establish or replace the association */
// 获取disguised的关联对象存储地址
auto &refs = refs_result.first->second;
// 以key-value的形式,存储association
auto result = refs.try_emplace(key, std::move(association));
if (!result.second) {
// 如果之前存储过关联值,用之前的值first替换调value,后面将association释放
association.swap(result.first->second);
}
} else { // 关联值位nil,如果之前有key对应关联值,则清除
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()) { // 判断key对应的关联值,有的话就清除释放掉
association.swap(it->second);
refs.erase(it);
if (refs.size() == 0) { // 关联对象中没有关联值,则释放掉关联对象
associations.erase(refs_it);
}
}
}
}
}
// Call setHasAssociatedObjects outside the lock, since this
// will call the object's _noteAssociatedObjects method if it
// has one, and this may trigger +initialize which might do
// arbitrary stuff, including setting more associated objects.
// 给关联对象object的isa中的has_assoc设置值true,表示有关联对象
if (isFirstAssociation)
object->setHasAssociatedObjects();
// release the old value (outside of the lock).
association.releaseHeldValue(); // 如果有新值替换则释放旧值
}
在_object_set_associative_reference
中的工作处理流程:
1.value有值
- 实例化全局关联表
associations
;associations
调用try_emplace
将关联对象disguised
作为key
,获取disguised
对应的关联对象表ObjectAssociationMap
的地址,如果没有就创建一个ObjectAssociationMap
,返回为refs_result
;- 如果
refs_result.second
有值,则disguised
为第一次做关联值;- 通过
refs_result.first->second
获取关联对象表的存储地址refs
;refs
调用try_emplace
以标识符key
存储关联值association
,返回为result
;- 如果
result.second
为false,说明之前已经做过key
对应的关联值,调用association.swap
方法存储旧值地址存入association
准备释放旧值;- 如果为第一次关联值,调用
setHasAssociatedObjects
将isa
的has_assoc
设置为true
,表示object
有关联对象,在dealloc
时将关联表释放。- 如果不是第一次关联,此时
association
存储的是旧值,调用releaseHeldValue
释放掉旧值
2.value为空值
- 如果关联值为空,获取
disguised
的关联表,清除掉key
对应的关联值,如果disguised
关联表中没有关联值,则将disguised
关联表释放。
关联对象取值
关联对象取值时使用的api
为objc_getAssociatedObject
,参数为关联对象object
和标识符key
id
objc_getAssociatedObject(id object, const void *key)
{
return _object_get_associative_reference(object, key);
}
同样是一层封装过渡,直接调用_object_get_associative_reference
id
_object_get_associative_reference(id object, const void *key)
{
ObjcAssociation association{};
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.get());
// 通过关联对象object获取关联表
AssociationsHashMap::iterator i = associations.find((objc_object *)object);
if (i != associations.end()) {
// 获取关联表的地址
ObjectAssociationMap &refs = i->second;
// 通过key值获取关联值
ObjectAssociationMap::iterator j = refs.find(key);
if (j != refs.end()) {
association = j->second;
association.retainReturnedValue(); // retain处理
}
}
}
return association.autoreleaseReturnedValue(); // 返回value
}
- 通过关联对象object获取关联表;
- 获取关联表的地址,通过key值获取关联值
关联对象销毁
对象销毁时会调用dealloc
方法
- (void)dealloc {
_objc_rootDealloc(self);
}
void
_objc_rootDealloc(id obj)
{
ASSERT(obj);
obj->rootDealloc();
}
inline void
objc_object::rootDealloc()
{
// TaggedPointer小对象直接返回
if (isTaggedPointer()) return; // fixme necessary?
/**
* nonpointer & !weakl & !has_*assoc & !has_cxx_dtor & !has_sidetable_rc
* nonpointer类型
* 无弱引用
* 无关联对象
* 无C++析构函数
* 无类的C++析构函数
* 无引用计数
*/
if (fastpath(isa().nonpointer &&
!isa().weakly_referenced &&
!isa().has_assoc &&
#if ISA_HAS_CXX_DTOR_BIT
!isa().has_cxx_dtor &&
#else
!isa().getClass(**false**)->hasCxxDtor() &&
#endif
!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_associations(obj, /*deallocating*/true);
obj->clearDeallocating();
}
return obj;
}
inline bool
objc_object::hasAssociatedObjects()
{
if (isTaggedPointer()) return true;
if (isa().nonpointer) return isa().has_assoc;
return true;
}
dealloc
中调用_objc_rootDealloc
;_objc_rootDealloc
中调用对象的rootDealloc
方法;rootDealloc
中判断对象为TaggedPointer
不用处理;- 不是
TaggedPointer
,如果为nonpointer
指针且无弱引用
、无关联对象
、无C++析构函数
、无类的C++析构函数
、无引用计数
则直接free
,否则就调用object_dispose
;- 在
object_dispose
中先调用objc_destructInstance
,然后再free
;- 在
objc_destructInstance
中根据hasAssociatedObjects
判断有无关联对象,如果有就调用_object_remove_associations
移除关联对象;- 在
hasAssociatedObjects
中nonpointer
类型的isa
,根据值has_assoc
判断有无关联对象;
看一下_object_remove_associations
的源码是怎么移除关联对象的
void
_object_remove_associations(id object, bool deallocating)
{
ObjectAssociationMap refs{};
{
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) { // deallocating值为true
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();
}
}
- 全局关联表
associations
通过对象object
获取关联对象表对应的表i
; - 获取表地址空间
refs
; -
for
循环取出所有的关联值,先清除释放策略不是OBJC_ASSOCIATION_SYSTEM_OBJECT
,策略为OBJC_ASSOCIATION_SYSTEM_OBJECT
放入laterRefs
; - 再对
laterRefs
循环清除。
实例验证
定义主类LGPerson
和分类LGPerson+CatA
,在分类中设置属性cate_name
,并在分类中实现cate_name
的setter
和getter
方法
- (void)setCate_name:(NSString *)cate_name{
/**对象,标识符,value,策略* */
objc_setAssociatedObject(self, "cate_name", cate_name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)cate_name{
return objc_getAssociatedObject(self, "cate_name");
}
在mian
函数中给cate_name
赋值
在_object_set_associative_reference
中加断点调试
第一次关联值
- 第一次关联值为
Hello cat!
,这里isFirstAssociation
值为true
- 此时
refs
还没有调用try_emplace
,Buckets
的值为nil
;
继续Step Over
一下
这里
refs
的存储了值,result.second
为true
说明第一次关联不需要association.swap
在setHasAssociatedObjects
打断点,进入调试
这里对
newisa
的has_assoc
赋值为true
。
在releaseHeldValue
打断点,进入调试
这里
_value
为nil
,且_policy
为0
。
第二次关联值
放掉断点,进入第二次关联值流程,断点来到refs.try_emplace
处
- 第二次关联值为
Hello dog!
,这里isFirstAssociation
值为false
;refs
的存储了值,Buckets
的值为关联值的地址;
继续Step Over
一下
在
refs.try_emplace
后result.second
的值为false
,进入swap
方法,association
的存储值为上一次关联的旧值。
在releaseHeldValue
打断点,进入调试
这里要释放掉的
_value
为上一次关联的Hello cat!
。
关联值值nil
过掉断点,进入关联值为nil
的流程,断点到erase
处
- 这里
value
为nil
;association.swap
的值为second
,_value
为Hello dog!
,最后存储的关联值,准备释放;refs.size
的值为1
,有一个关联值;
继续Step Over
一下,过掉erase
refs.size
的值为0
,没有关联值,调用associations
的erase
方法释放关联对象表refs_it
。
关联对象取值
注释掉关联对象赋值为nil
,打印cate_name
在_object_get_associative_reference
加断点调试
这里取到的值为ObjectAssociationMap
的second
最新值为Hello dog!
。
关联对象释放
走完main
的作用域,person
就会进入dealloc
流程
在dealloc
中根据字符串匹配LGPerson
精准下断点,然后进入rootDealloc
这里通过isa()
输出,has_assoc
值为1
,进入object_dispose
流程,在_object_remove_associations
打断点
看到这里object
为LGPerson
对象,关联值有1
个
输出值为Hello dog!
。
关联对象总结
关联对象的存储结构为
- 总哈希表
AssociationsHashMap
,存储的是关联对象
和关联对象对应的关联表ObjectAssociationMap
;- 关联表
ObjectAssociationMap
中的Buckets
为表的地址值;- 关联表中存储的是标识符
key
,和将value
与policy
包装在一起的ObjcAssociation
。
iOS 全网最新objc4 可调式/编译源码
编译好的源码的下载地址
以上是对类扩展和关联对象的探索总结,难免有不足和错误之处,如有疑问请在评论区留言。