iOS底层探索之类的加载(四):类的关联对象AssociatedObject
1. 回顾
在前面的几篇博客中,主要讲了类的加载、包括分类的加载底层探索,本次就类的扩展和关联对象进行分析。
iOS底层探索之类的加载(三): attachCategories分析
2. 扩展
2.1 什么是分类和扩展
首先我们来看看什么是分类和扩展
category
: 类别/分类
- 专门用来给类添加新的方法
- 不能给类添加成员属性,添加了成员变量,也无法取到
注意
:其实可以通过runtime
给分类添加属性- 分类中用
@property
定义变量,只会生成变量的getter,setter
方法的声明,不能生成方法实现和带下划线的成员变量
。
extension
:类扩展
- 可以说成是特殊的分类,也称作
匿名分类
- 可以给类添加
成员属性
,但是是私有变量
- 可以给类添加方法,也是
私有方法
分类我们已经很熟悉了,这里就不必过多赘述了,下面介绍下扩展 extension
2.2 扩展
类扩展,我们平时用的是非常多的,如下
what ? 什么,这就是扩展吗?天天用居然不知道!
是的,这就是扩展,平时用的是非常之多,但是很多人都不知道。
注意
:类扩展要放在声明之后,实现之前,否则会报错。给扩展加点属性、方法,下面我们看看底层
C++
是什么样子的。
从底层
C++
代码可以发现,类的扩展属性会添加到成员变量列表,方法也会放在方法列表里面。
思考
:那么扩展是否也会像分类一样,影响主类的加载呢?
- 建立一个单独的扩展文件
LGPerson.m
导入#import "LGPerson+Ext.h"
头文件,实现扩展里面的方法
- (void)ext_sayHello {NSLog(@"%s",__func__);
}
+ (void)ext_classMehod{NSLog(@"%s",__func__);
}
- 在
objc
源码里面,把断点断在realizeClassWithoutSwift
处 lldb
打印ro
方法列表
(lldb) p ro.baseMethods()
(method_list_t *) $0 = 0x0000000100004190Fix-it applied, fixed expression was: ro->baseMethods()
(lldb) p *$0
(method_list_t) $1 = {entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 24, count = 8)
}
(lldb) p $1.get(0).big()
(method_t::big) $2 = {name = "saySomething"types = 0x0000000100003e09 "v16@0:8"imp = 0x00000001000039f0 (ObjcBuild`-[LGPerson(LGA) saySomething])
}
(lldb) p $1.get(1).big()
(method_t::big) $3 = {name = "cateA_instanceMethod1"types = 0x0000000100003e09 "v16@0:8"imp = 0x0000000100003a20 (ObjcBuild`-[LGPerson(LGA) cateA_instanceMethod1])
}
(lldb) p $1.get(2).big()
(method_t::big) $4 = {name = "cateA_instanceMethod2"types = 0x0000000100003e09 "v16@0:8"imp = 0x0000000100003a50 (ObjcBuild`-[LGPerson(LGA) cateA_instanceMethod2])
}
(lldb) p $1.get(3).big()
(method_t::big) $5 = {name = "saySomething"types = 0x0000000100003e09 "v16@0:8"imp = 0x00000001000038a0 (ObjcBuild`-[LGPerson saySomething])
}
(lldb) p $1.get(4).big()
(method_t::big) $6 = {name = "sayHello1"types = 0x0000000100003e09 "v16@0:8"imp = 0x00000001000038d0 (ObjcBuild`-[LGPerson sayHello1])
}
(lldb) p $1.get(5).big()
(method_t::big) $7 = {name = "ext_sayHello"types = 0x0000000100003e09 "v16@0:8"imp = 0x0000000100003900 (ObjcBuild`-[LGPerson ext_sayHello])
}
(lldb) p $1.get(6).big()
(method_t::big) $8 = {name = "name"types = 0x0000000100003de3 "@16@0:8"imp = 0x0000000100003930 (ObjcBuild`-[LGPerson name])
}
(lldb)
从调式信息可以看到,在
$1.get(5).big()
打印了扩展的ext_sayHello
方法,这也就证明了,类的扩展信息,也会作为类一部分加载到类里面。
3. 关联对象
在分类里面是不可以直接添加成员变量的,但是我们可以间接的添加,这就涉及到关联对象的知识了。
// 获取关联对象
id
objc_getAssociatedObject(id object, const void *key)
{return _object_get_associative_reference(object, key);
}// 设置关联对象
void
objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
{_object_set_associative_reference(object, key, value, policy);
}// 移除关联对象
void objc_removeAssociatedObjects(id object)
{if (object && object->hasAssociatedObjects()) {_object_remove_assocations(object, /*deallocating*/false);}
}
objc_getAssociatedObject
获取关联对象objc_setAssociatedObject
设置关联对象objc_removeAssociatedObjects
移除关联对象
3.1 设值流程
我们来看看下objc_setAssociatedObject
设置关联对象方法的里面调用了_object_set_associative_reference
方法
_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/44094390if (!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));DisguisedPtr<objc_object> disguised{(objc_object *)object};ObjcAssociation association{policy, value};// retain the new value (if any) outside the lock.association.acquireValue();bool isFirstAssociation = false;{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 */isFirstAssociation = true;}/* 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);}}}}}// 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.if (isFirstAssociation)object->setHasAssociatedObjects();// release the old value (outside of the lock).association.releaseHeldValue();
}
四个主要的参数是:
objc
: 要关联的对象,即给谁添加关联属性key
: 标识符,方便下次查找value
: 要存的值policy
: 关联策略
DisguisedPtr
方法是一种包装策略,就像快递一样,打包成一个个包裹,方便存储和查找
class DisguisedPtr {uintptr_t value;static uintptr_t disguise(T* ptr) {return -(uintptr_t)ptr;}static T* undisguise(uintptr_t val) {return (T*)-val;}
对ptr
进行了处理,也就是value
的处理,也就是对object
包装了一下,包装成统一
的数据结构。
AssociationsManager
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;}
- 很多人第一次看到
AssociationsManager
肯定以为是单例
,但是这并不是一个单例,而是通过构造函数加锁
、析构函数解锁
,以此来达到线程安全。 AssociationsManager
只是用来调用AssociationsHashMap
方法的作用,但是AssociationsHashMap
是一个单例,因为它通过_mapStorage.get()
获取,_mapStorage
是一个全局静态变量,放在任何地方都是唯一的。
新建一个分类,里面写几个属性和方法进行验证
@interface LGPerson (LGA)@property (nonatomic, copy) NSString *cate_name;
@property (nonatomic, copy) NSString *cate_age;- (void)saySomething;- (void)cateA_instanceMethod1;
- (void)cateA_instanceMethod2;+ (void)cateA_classMethod1;
+ (void)cateA_classMethod2;@end- (void)setCate_name:(NSString *)cate_name{objc_setAssociatedObject(self, "cate_name", cate_name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}- (NSString *)cate_name{return objc_getAssociatedObject(self, "cate_name");
}- (void)setCate_age:(NSString *)cate_age{objc_setAssociatedObject(self, "cate_age", cate_age, OBJC_ASSOCIATION_COPY_NONATOMIC);
}- (NSString *)cate_age{return objc_getAssociatedObject(self, "cate_age");
}
然后再属性赋值,断点提示
LGPerson * person = [LGPerson alloc];person.cate_name = @"jp";person.cate_age = @"20";[person saySomething];
可以看到
associations
的结构和refs_result
结构,这样看可能不太清楚,那么再lldb
看看
纳尼?什么鬼啊!
refs_result
这是个什么玩意啊!
靓仔,不用慌,淡定,请看看下面这个取值
if (refs_result.second) {/* it's the first association we make */isFirstAssociation = true;
}
虽然refs_result
很长,很变态,前面那么长只是类型,相当于NSObject
、LGPerson
这种,但是真正的内容只是后面几个,而这里只是用到了second
,其他的压根就不用关心,这一波是不是就很舒服了,哈哈
iOS底层探索之类的加载(四):类的关联对象AssociatedObject相关推荐
- iOS 底层探索 - 消息转发
一.动态方法解析流程分析 我们在上一章<消息查找>分析到了动态方法解析,为了更好的掌握具体的流程,我们接下来直接进行源码追踪. 我们先来到 _class_resolveMethod 方法, ...
- iOS 底层探索篇 —— KVC 底层原理
iOS 底层探索篇 -- KVC 底层原理 1. Method Swizzling的坑与应用 1.1 method-swizzling 是什么? 1.2 坑点 坑点1:method-swizzling ...
- iOS底层探索二(OC 中 alloc 方法 初探)
前言 相关文章: iOS底层探索一(底层探索方法) iOS底层探索三(内存对齐与calloc分析) iOS底层探索四(isa初探-联合体,位域,内存优化) iOS底层探索五(isa与类的关系) iOS ...
- iOS开发UI篇—懒加载
iOS开发UI篇-懒加载 1.懒加载基本 懒加载--也称为延迟加载,即在需要的时候才加载(效率低,占用内存小).所谓懒加载,写的是其get方法. 注意:如果是懒加载的话则一定要注意先判断是否已经有了, ...
- iOS底层探索(二) - 写给小白看的Clang编译过程原理
iOS底层探索(一) - 从零开始认识Clang与LLVM 写在前面 编译器是属于底层知识,在日常开发中少有涉及,但在我的印象中,越接近底层是越需要编程基本功,也是越复杂的.但要想提升技术却始终绕不开 ...
- [html] iOS下页面如何启动加载时显示画面图片?如何设置大小?它有什么好处?
[html] iOS下页面如何启动加载时显示画面图片?如何设置大小?它有什么好处? <link rel="apple-touch-startup-image" href=&q ...
- 微信JSSDK多图片上传并且解决IOS系统上传一直加载的问题
微信JSSDK多图片上传并且解决IOS系统上传一直加载的问题 参考文章: (1)微信JSSDK多图片上传并且解决IOS系统上传一直加载的问题 (2)https://www.cnblogs.com/co ...
- ios wkwebview弹框_iOS 加载WKWebView
WKWebView是苹果在iOS 8之后推出的框架WebKit中的浏览器控件, 其加载速度比UIWebView快了许多, 但内存占用率却下降很多, 也解决了加载网页时的内存泄露问题. WKWebVie ...
- datax底层原理_Datax 插件加载原理
Datax 插件加载原理 插件类型 Datax有好几种类型的插件,每个插件都有不同的作用. reader, 读插件.Reader就是属于这种类型的 writer, 写插件.Writer就是属于这种类型 ...
- OpenGL深入探索——使用Assimp加载模型
转载自:第二十二课 使用Assimp加载模型 背景 到现在为止我们都在使用手动生成的模型.正如你所想的,指明每个顶点的位置和其他属性有点时候并不是十分方便.对于一个箱子.锥体和简单平面还好,但是像人们 ...
最新文章
- 机器学习必知必会10大算法!
- 云视频会议的“多、快、好、省”(下)
- FusionCharts参数的详细说明
- 动态调用WebService
- 【29.70%】【codeforces 723D】Lakes in Berland
- 转载:逻辑思维提升表达
- MicroProfile在Jakarta EE时代的作用
- 怎么自学linux操作系统,linux操作系统好学吗_要学什么
- 语言认知偏差_我们的认知偏差正在破坏患者的结果数据
- 软件开发质量的双保险 — 1.设计验证与软件测试
- JavaScript验证正则表达式大全
- 51单片机(STC15W408AS)映射printf函数 串口收发实现
- ENVI Landsat8影像掩膜裁剪
- JavaSE重点之集合、IO、多线程
- 苹果开发者关联封号扫盲贴
- 近日onedrive突然消失问题的解决
- 文献阅读:Improving neural networks by preventing co-adaptation of feature detectors
- Helix QAC企业级自动代码静态分析器
- 计算机二级Python真题(十)
- Linux服务器配置网络,可修改服务器Ip与Mac地址