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_name4个成员变量;- 在特点列表
_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类加载时,从
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的存储值为上一次相关的旧值。
这儿要开释掉的
_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打断点
相关目标总结
相关目标的存储结构为
- 总哈希表
AssociationsHashMap,存储的是相关目标和相关目标对应的相关表ObjectAssociationMap;- 相关表
ObjectAssociationMap中的Buckets为表的地址值;- 相关表中存储的是标识符
key,和将value与policy包装在一起的ObjcAssociation。
iOS 全网最新objc4 可调式/编译源码
编译好的源码的下载地址
iOS底层之类的加载
iOS底层之分类的加载
以上是对类扩展和相关目标的探究总结,难免有缺乏和错误之处,如有疑问请在谈论区留言。






















