1. 回顾
在前面的几篇博客中,主要讲了类的加载、包括分类的加载底层探索,本次就类的扩展和关联对象进行分析。
iOS底层探索之类的加载(三): attachCategories分析
核心主题2. 扩展
2.1 什么是分类和扩展
首先我们来看看什么是分类和扩展
category
: 类别/分类
- 专门用来给类添加新的方法
- 不能给类添加成员属性,添加了成员变量,也无法取到
注意
:其实可以通过runtime
给分类添加属性- 分类中用
@property
定义变量,只会生成变量的getter,setter
方法的声明,不能生成方法实现和带下划线的成员变量
。
extension
:类扩展
- 可以说成是特殊的分类,也称作
匿名分类
- 可以给类添加
成员属性
,但是是私有变量
- 可以给类添加方法,也是
私有方法
分类我们已经很熟悉了,这里就不必过多赘述了,下面介绍下扩展 extension
2.2 扩展
匿名扩展类扩展,我们平时用的是非常多的,如下
震惊what ? 什么,这就是扩展吗?天天用居然不知道!
是的,这就是扩展,平时用的是非常之多,但是很多人都不知道。
cpp代码结构
注意
:类扩展要放在声明之后,实现之前,否则会报错。给扩展加点属性、方法,下面我们看看底层
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 = 0x0000000100004190
Fix-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/44094390
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));
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结构可以看到
associations
的结构和refs_result
结构,这样看可能不太清楚,那么再lldb
看看
什么鬼啊!纳尼?什么鬼啊!
refs_result
这是个什么玩意啊!
靓仔,不用慌,淡定,请看看下面这个取值
if (refs_result.second) {
/* it's the first association we make */
isFirstAssociation = true;
}
虽然refs_result
很长,很变态,前面那么长只是类型,相当于NSObject
、LGPerson
这种,但是真正的内容只是后面几个,而这里只是用到了second
,其他的压根就不用关心,这一波是不是就很舒服了,哈哈😁
refs_result
是从associations.try_emplace(disguised, ObjectAssociationMap{})
来的
auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});
- *
try_emplace
template <typename... Ts>
std::pair<iterator, bool> try_emplace(const KeyT &Key, Ts &&... Args) {
BucketT *TheBucket;
if (LookupBucketFor(Key, TheBucket))
return std::make_pair(
makeIterator(TheBucket, getBucketsEnd(), true),
false); // Already in map.
// Otherwise, insert the new element.
TheBucket = InsertIntoBucket(TheBucket, Key, std::forward<Ts>(Args)...);
return std::make_pair(
makeIterator(TheBucket, getBucketsEnd(), true),
true);
}
这里会创建新的桶子BucketT
,这里的key
是LGPerson
的地址。
首先会进入LookupBucketFor
去查找是有已经有了桶子,看到两个实现,因为在try_emplace
中是BucketT
没有const
,所以走的是下面的实现。下面的实现会调用上面的实现。
在这里插入图片描述上面这里是两个同名的方法,其中一个参数不一样,下面的调用上面的方法,传入一个地址,说白了就是一个指针的传递。图中我也标记了。
- 获取哈希下标
- 开始死循环找
bucket
- 匹配处理
- 没找到就再哈希
插入bucket当第一次进来的时候,
LookupBucketFor(Key, TheBucket)
是找不到的,所以走到了下面。
插入bucket操作这里就插入一个空的桶子进去,并且还进行了
3/4
扩容2
倍的操作。
空桶子没有值第一次是个空的桶子里面还没有值,如下:
属性赋值第二次进来插入值了,如下
第二次桶子有值
设值流程小结
:
- 创建⼀个
AssociationsManager
管理类 - 获取唯⼀的全局静态哈希
Map
- 判断是否插⼊的关联值是否存在:
3.1: 存在⾛第4步
3.2: 不存在就⾛ : 关联对象插⼊空流程 - 创建⼀个空的
ObjectAssociationMap
去取查询的键值对 - 如果发现没有这个
key
就插⼊⼀个 空的BucketT
进去 返回 - 标记对象存在关联对象
- ⽤当前 修饰策略 和 值 组成了⼀个
ObjcAssociation
替换原来BucketT
中的 - 标记⼀下
ObjectAssociationMap
的第⼀次为false
3.2 插入空值流程
当value没有值的时当
value
没有值的时候会走到if
判断的else
里面
在
associations
表中查找disguised
,如果没有找到进行erase
清空操作。
关联对象插⼊空流程
- 根据
DisguisedPtr
找到AssociationsHashMap
中的iterator
迭代查询器 - 清理迭代器
- 其实如果插⼊空值,就是相当于清除的操作
3.3 取值流程
objc_getAssociatedObject
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());
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();
}
- 查找桶子
iterator find(const_arg_type_t<KeyT> Val) {
BucketT *TheBucket;
if (LookupBucketFor(Val, TheBucket))
return makeIterator(TheBucket, getBucketsEnd(), true);
return end();
}
const_iterator find(const_arg_type_t<KeyT> Val) const {
const BucketT *TheBucket;
if (LookupBucketFor(Val, TheBucket))
return makeConstIterator(TheBucket, getBucketsEnd(), true);
return end();
}
-
autoreleaseReturnedValue
返回_value
inline id autoreleaseReturnedValue() {
if (slowpath(_value && (_policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE))) {
return objc_autorelease(_value);
}
return _value;
}
- 创建⼀个
AssociationsManager
管理类 - 获取唯⼀的全局静态哈希
Map
- 根据
DisguisedPtr
找到AssociationsHashMap
中的iterator
迭代查询器 - 如果这个迭代查询器不是最后⼀个 获取 :
ObjectAssociationMap
(这⾥有策略和value
) - 找到
ObjectAssociationMap
的迭代查询器获取⼀个经过属性修饰符修饰的value
- 返回
_value
-
总结
: 其实就是两层哈希map
, 存取的时候两层处理(类似⼆位数组)
4. 总结
-
category
不能给类添加成员属性,添加了成员变量,也无法取到 - 可以通过
runtime
给分类添加属性 -
objc_setAssociatedObject
设置 -
objc_getAssociatedObject
取值 -
extension
是特殊的分类,也称作匿名分类
- 为了便于理解,画了如下结构图
AssociationsHashMap
更多内容持续更新
🌹 喜欢就点个赞吧👍🌹
🌹 觉得学习到了的,可以来一波,收藏+关注,评论 + 转发,以免你下次找不到我😁🌹
🌹欢迎大家留言交流,批评指正,互相学习😁,提升自我🌹
网友评论