类扩展、分类的区别
1. Category(分类或者类别)
-
专门给主类添加新的方法
-
不能给分类添加成员变量(添加了也获取不到)
-
可以给分类添加属性,但是通过
@property
定义的属性,只会生成变量的setter
、getter
方法声明,不会生成方法实现(可以通过rentime
来重写setter
、getter
的实现,即关联对象
)以及带下划线的成员变量
2. Extension(类扩展)
-
可以当作是一个特殊的分类,也称作
匿名分类
-
可以给主类添加方法、属性、成员变量(都是私有的,即自己可见,外界不能调用)
类扩展的底层原理
类的扩展有两种创建方式
-
直接在主类中添加:永远都是在主类的声明之后、实现之前(.m 文件中添加)
-
通过
commond + N
新建 -> Objective-C File -> Extension
单独创建好的类扩展文件如下
类扩展的本质
有两种方法可以探究:clang
,源码
通过 clang 底层编译
下面我们通过 clang
底层编译,查看类扩展的底层实现
- 类扩展代码如下
@interface LGPerson : NSObject
- (void)instanceMethod;
+ (void)classMethod;
@end
@interface LGPerson ()
@property (nonatomic, copy) NSString *lg_name;
- (void)ext_instanceMethod;
+ (void)ext_classMethod;
@end
@implementation LGPerson
- (void)instanceMethod {
}
+ (void)classMethod {
}
- (void)ext_instanceMethod {
}
+ (void)ext_classMethod {
}
@end
- 通过
clang -rewrite-objc main.m -o main.cpp
命令生成cpp
文件,打开cpp
文件,搜索lg_name
- 再查看
LGPerson
的类扩展方法,在编译过程
中,方法就直接添加到了methodlist
中,作为类的一部分,即编译时期直接添加到本类里面
通过 objc 源码探索
- 创建
LGPerson
类,并创建LGPerson_Ext.h
主类的类扩展,声明两个方法
#import "LGPerson.h"
@interface LGPerson ()
@property (nonatomic, copy) NSString *ext_name;
- (void)ext_intanceMethod3;
- (void)ext_intanceMethod4;
@end
- 在
LGPerson
中实现上面两个方法
/* ------ LGPerson.h ------*/
@interface LGPerson : NSObject
- (void)lc_intanceMethod1;
- (void)lc_intanceMethod2;
- (void)lc_intanceMethod3;
- (void)lc_intanceMethod4;
@end
/* ------ LGPerson.m ------*/
@implementation LGPerson
+ (void)load {
}
- (void)lc_intanceMethod1 {
NSLog(@"%s", __func__);
}
- (void)lc_intanceMethod2 {
NSLog(@"%s", __func__);
}
- (void)lc_intanceMethod3 {
NSLog(@"%s", __func__);
}
- (void)lc_intanceMethod4 {
NSLog(@"%s", __func__);
}
- (void)ext_intanceMethod {
NSLog(@"%s", __func__);
}
- (void)ext_classMethod {
NSLog(@"%s", __func__);
}
@end
- 运行
objc
源码,在readClass
处下个断点,查看此时的ro
情况
总结
-
类扩展在编译时期会作为类的一部分,和主类一起编译进来
-
类扩展只是声明,它需要依赖当前主类,它没有 .m 文件
关联对象的底层原理
主要分为两部分:
-
通过
objc_setAssociatedObject
设值 -
通过
objc_getAssociatedObject
取值
在分类中添加属性 cate_name
,通过 runtime
的关联属性方法重写它的 set、get
方法,并在 main
函数中调用如下
@interface LGPerson : NSObject
@end
@implementation LGPerson
@end
@interface LGPerson (LG)
@property (nonatomic, copy) NSString *cate_name;
@end
@implementation LGPerson (LG)
- (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");
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
LGPerson *p = [[LGPerson alloc] init];
p.cate_name = @"lg_lc";
NSLog(@"%@", p.cate_name);
}
return 0;
}
关联对象的设值流程
- 在
main
函数中cate_name
赋值处和分类的setCate_name
方法中打个断点,运行程序
- 继续往下运行
其中 objc_setAssociatedObject
方法有四个参数:
- 参数1:要关联的对象,即给谁添加关联属性
- 参数2:标识符,方便下次查找
- 参数3:设置的value
- 参数4:属性策略,即
nonatomic、atomic、assign
等,枚举如下
/**
* Policies related to associative references.
* These are options to objc_setAssociatedObject()
*/
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
OBJC_ASSOCIATION_ASSIGN = 0, /**< Specifies a weak reference to the associated object. */
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**< Specifies a strong reference to the associated object.
* The association is not made atomically. */
OBJC_ASSOCIATION_COPY_NONATOMIC = 3, /**< Specifies that the associated object is copied.
* The association is not made atomically. */
OBJC_ASSOCIATION_RETAIN = 01401, /**< Specifies a strong reference to the associated object.
* The association is made atomically. */
OBJC_ASSOCIATION_COPY = 01403 /**< Specifies that the associated object is copied.
* The association is made atomically. */
};
- 进入
objc_setAssociatedObject
的源码实现
void
objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
{
_object_set_associative_reference(object, key, value, policy);
}
_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));
//object封装成一个数组结构类型,类型为DisguisedPtr
DisguisedPtr<objc_object> disguised{(objc_object *)object};//相当于包装了一下 对象object,便于使用
// 包装一下 policy - value
ObjcAssociation association{policy, value};
// retain the new value (if any) outside the lock.
association.acquireValue();//根据策略类型进行处理
//局部作用域空间
{
//初始化manager变量,相当于自动调用AssociationsManager的析构函数进行初始化
AssociationsManager manager;//并不是全场唯一,构造函数中加锁只是为了避免重复创建,在这里是可以初始化多个AssociationsManager变量的
AssociationsHashMap &associations(manager.get());//AssociationsHashMap 全场唯一
if (value) {
auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});//返回的结果是一个类对
if (refs_result.second) {//判断第二个存不存在,即bool值是否为true
/* it's the first association we make 第一次建立关联*/
object->setHasAssociatedObjects();//nonpointerIsa ,标记位true
}
/* establish or replace the association 建立或者替换关联*/
auto &refs = refs_result.first->second; //得到一个空的桶子,找到引用对象类型,即第一个元素的second值
auto result = refs.try_emplace(key, std::move(association));//查找当前的key是否有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);
}
}
}
}
}
// release the old value (outside of the lock).
association.releaseHeldValue();//释放
}
通过以上源码可知,_object_set_associative_reference
方法主要分为以下几部分:
- 创建一个
AssociationsManager
管理类
- 创建一个
- 获取全局唯一的静态哈希表:
AssociationsHashMap
- 获取全局唯一的静态哈希表:
-
- 判断插入的
value
值是否存在
-
3.1:存在走第四步
-
3.2:不存在则
移除关联对象
- 判断插入的
- 创建一个空的
ObjectAssociationMap
,通过try_emplace
方法去查询并获取键值对(如果发现没有这个key
就插入一个 空的BucketT
进去并返回true
)
- 创建一个空的
- 如果没有这个
key
,就标记为第一次创建
- 如果没有这个
- 用当前
policy
和value
组成了一个ObjcAssociation
替换原来的BucketT
或者创建新的
- 用当前
- 如果是第一次创建的,通过
setHasAssociatedObjects
方法标记对象存在关联对象,即设置isa
指针的has_assoc
属性为true
- 如果是第一次创建的,通过
源码调试流程
- 定义
AssociationsManager
变量,从源码可以得知,就是自动调用AssociationsManager
的构造函数和析构函数
AssociationsManager() { AssociationsManagerLock.lock(); }
~AssociationsManager() { AssociationsManagerLock.unlock(); }
加锁并不代表唯一,只是为了避免多线程重复创建,可以在外层定义多个 AssociationsManager
变量的
- 定义
AssociationsHashMap
类型的哈希map
// 定义变量
AssociationsHashMap &associations(manager.get());
进入 manager.get()
源码,如下
typedef DenseMap<const void *, ObjcAssociation> ObjectAssociationMap;
typedef DenseMap<DisguisedPtr<objc_object>, ObjectAssociationMap> AssociationsHashMap;
// class AssociationsManager manages a lock / hash table singleton pair.
// Allocating an instance acquires the lock
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();
}
};
从源码中我们可以得知,_mapStorage
是一个静态变量,静态变量又是全局唯一的,AssociationsHashMap
是从静态变量 _mapStorage
中获取的,所以 AssociationsHashMap
是全局唯一的
- 查看目前的数据结构
运行项目,走到断点处,打印变量的数据结构
- disguised : 其中的
value
是来自object
还原出来的 - association : 包装的策略类型处理
- manager :
AssociationsManager
管理类 - associations : 目前的
associations
为0x0
,表示还没有查找到相应的递归查找域中
继续向下执行,此时传入的 value
有值,所以走 if
流程(如果传入的 value
为空,就会走 移除关联函数
流程,即 else
流程),查看 refs_result
的数据结构,类型很长,可以进行拆解
// pair 表示有键值对
(std::pair<
objc::DenseMapIterator<DisguisedPtr<objc_object>,
objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> >,
objc::DenseMapValueInfo<objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> > >,
objc::DenseMapInfo<DisguisedPtr<objc_object> >,
objc::detail::DenseMapPair<DisguisedPtr<objc_object>, objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> > >,
false>,
bool>)
👇
// 简写
(std::pair<
objc,
bool>)
try_emplace 源码
进入 try_emplace
源码实现,如下
从源码可以看到,有两个返回:
-
通过查找桶子,如果 map 中存在,则直接返回,并将
make_pair
的第二个参数置为false
-
如果没找到,证明是第一次进入,则通过
InsertIntoBucket
插入 map,然后返回,并将make_pair
的第二个参数置为true
** LookupBucketFor 源码**
LookupBucketFor
源码有两个同名方法,区别是第一个方法中第二个参数有 const
修饰,通过 try_emplace
源码可知,调用的是第二个方法(重载函数),而第二个方法内部实现是调用第一个 LookupBucketFor
方法
- 第一个
LookupBucketFor
源码实现如下
- 第二个
LookupBucketFor
源码实现如下
继续往下走,运行至 TheBucket = InsertIntoBucket(TheBucket, Key, std::forward<Ts>(Args)...);
,查看此时的 TheBucket
可以看到 TheBucket
的类型与 refs_result
中属性的类型是一致的
- 继续执行,查看 refs 执行
refs.try_emplace
前后变化
** 两次 try_emplace 的区别**
-
第一次执行
try_emplace
查看AssociationsHashMap
全局哈希 map 中是否存在该对象的桶子,如果没有,则插入空的桶子 -
第二次执行
try_emplace
往上面的空桶子中插入ObjcAssociation (policy, value)
返回ture
- result.second 为 NO,证明哈希表中已存在,需要更换最新的
继续执行,断点在 object->setHasAssociatedObjects();
,源码如下
由此可知,通过 setHasAssociatedObjects
将 nonpointerIsa
的 has_assoc
标记为 true
,到此就将属性与 value
关联上了
关联对象设值流程图
从上面分析可以得出,关联对象的哈希 map 结构如下
-
AssociationsHashMap
中有很多的关联对象 map,它的key
是DisguisedPtr<objc_object>
,是一个包装的对象(例如 LGPerson、LGTeacher 等),它的value
是也是一个 map(ObjectAssociationMap)
typedef DenseMap<DisguisedPtr<objc_object>, ObjectAssociationMap> AssociationsHashMap;
-
ObjectAssociationMap
表中有很多的key-value
键值对,它的key
类型为const void *key
,这个key
就是分类里面传过来的;它的value
也是一个包装好的对象ObjcAssociation
typedef DenseMap<const void *, ObjcAssociation> ObjectAssociationMap;
-
ObjcAssociation
是用于包装policy
和value
的一个类
关联对象的取值流程
- 在
main.m
添加断点,如下
继续执行下一步,断点来到重写分类的属性 get
方法,进入 objc_getAssociatedObject
源码的实现
_object_get_associative_reference
其源码实现如下:
id
_object_get_associative_reference(id object, const void *key)
{
ObjcAssociation association{}; // 创建空的关联对象
{
AssociationsManager manager; // 创建一个 管理类
AssociationsHashMap &associations(manager.get()); // 获取全局唯一的静态哈希 map
AssociationsHashMap::iterator i = associations.find((objc_object *)object); // 根据 object 查找 AssociationsHashMap,即获取 buckets
if (i != associations.end()) { // 如果迭代器不是最后一个
ObjectAssociationMap &refs = i->second; // 找到 ObjectAssociationMap 的迭代查询器,获取一个经过属性修饰符修饰的 value
ObjectAssociationMap::iterator j = refs.find(key); //根据 key 查找 ObjectAssociationMap,即获取 bucket
if (j != refs.end()) {
association = j->second; // //获取 ObjcAssociation
association.retainReturnedValue(); // retain 处理
}
}
}
return association.autoreleaseReturnedValue(); // 返回 value
}
通过以上源码,可以将取值流程分为以下几部分:
- 创建一个
AssociationsManager
管理类变量
- 创建一个
- 通过管理类变量获取全局唯一的静态哈希表
AssociationsHashMap
- 通过管理类变量获取全局唯一的静态哈希表
- 通过
find
方法根据object
找到AssociationsHashMap
中的iterator
,迭代查询器
- 通过
- 如果这个迭代器不是最后一个,获取
ObjectAssociationMap
- 如果这个迭代器不是最后一个,获取
- 通过
find
方法根据key
找到ObjectAssociationMap
中的迭代查询器获取一个经过属性修饰符修饰的value
- 通过
- 返回
value
- 返回
调用流程
- 进入
find
方法,根据关联对象迭代查找AssociationsHashMap
,即buckets
,源码实现如下
- 打印 迭代器 i, 以及 i->second 查看它们的类型
- 再次通过
find
方法,在buckets
中查找与key
匹配的bucket
,打印find
方法执行前后j
的变化
总结
综上所述,关联对象的底层调用,主要就是两层哈希 map 的处理,即存取时都是两层处理。流程如下图所示
网友评论