iOS 全网最新objc4 可调式/编译源码
编译好的源码的下载地址
iOS底层之类的加载
iOS底层之分类的加载
序文
在前面文章中,从底层源码探究了类
和分类
的加载流程,今天从源码层面探究一下类扩展及相关目标的本质。
类扩展
经过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 可调式/编译源码
编译好的源码的下载地址
iOS底层之类的加载
iOS底层之分类的加载
以上是对类扩展和相关目标的探究总结,难免有缺乏和错误之处,如有疑问请在谈论区留言。