前言
在之前的文章中,我们已经分析了分类
的本质和加载原理,不熟悉的同学可以查阅iOS-类的加载(下),那么接下来我们探索一下类拓展
以及关联对象
的底层实现原理,毕竟面试的时候经常会问我们类扩展和分类
的区别....
类扩展 与 分类 的区别
1.
category 类别、分类
- 专门用来给类添加新的
方法
- 不能给类添加
成员属性
,添加了成员属性,也无法取到- 注意:其实可以通过
runtime
给分类添加属性,即属性关联
,重写setter、getter
方法- 分类中用
@property
定义变量,只会生成变量的setter、getter
方法的声明
,不能生成方法实现
和带下划线
的成员变量(在分类用@property
声明属性,用本类能够赋值,就是因为生成了getter,setter声明,但是由于没有实现,会导致崩溃)
2.
extension 类扩展
- 可以说成是
特殊的分类
,也可称作匿名分类
- 可以给类添加
成员属性
,但是是私有变量
- 可以给类添加方法,也是
私有方法
类扩展底层原理探索
通常在创建工程中时会有一个ViewController
类或者创建自定义的UIViewController
子类的时候,Xcode
会在类中自动创建一个类扩展,如图:
我们经常把不想暴露的一些属性定义在这里,需要外部调用的属性写在.h文件中。
需要注意类扩展有一个位置要求,必须写在类的interface
和implementation
之间。
clang探索类扩展本质
我们在main.m
文件中建一个ZGTercher
类扩展
@interface ZGTercher : NSObject
@property (nonatomic, copy) NSString *zg_name;
@property (nonatomic, assign) int zg_age;
- (void)zg_instanceMethod1;
- (void)zg_instanceMethod2;
- (void)zg_instanceMethod3;
+ (void)zg_sayClassMethod;
@end
@implementation ZGTercher
- (void)zg_instanceMethod3{
NSLog(@"%s",__func__);
}
- (void)zg_instanceMethod1{
NSLog(@"%s",__func__);
}
- (void)zg_instanceMethod2{
NSLog(@"%s",__func__);
}
+ (void)zg_sayClassMethod{
NSLog(@"%s",__func__);
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
// ZGPerson *person = [ZGPerson alloc];
// [person zg_instanceMethod1];
}
return 0;
}
通过clang -rewrite-objc main.m -o main.cpp
命令生成cpp
文件,打开cpp文件,搜索我们定义的zg_name
属性
typedef struct objc_object ZGTercher;
typedef struct {} _objc_exc_ZGTercher;
#endif
extern "C" unsigned long OBJC_IVAR_$_ZGTercher$_zg_name;//下划线属性
extern "C" unsigned long OBJC_IVAR_$_ZGTercher$_zg_age;
struct ZGTercher_IMPL {
struct NSObject_IMPL NSObject_IVARS;
int _zg_age;
NSString *_zg_name;
};
// @property (nonatomic, copy) NSString *zg_name;
// @property (nonatomic, assign) int zg_age;
// - (void)zg_instanceMethod1;
// - (void)zg_instanceMethod2;
// - (void)zg_instanceMethod3;
// + (void)zg_sayClassMethod;
/* @end */
// @implementation ZGTercher
static void _I_ZGTercher_zg_instanceMethod3(ZGTercher * self, SEL _cmd) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_3q_sv9blc555sb1tfw4cl4wgl2h0000gn_T_main_b46ba9_mii_0,__func__);
}
static void _I_ZGTercher_zg_instanceMethod1(ZGTercher * self, SEL _cmd) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_3q_sv9blc555sb1tfw4cl4wgl2h0000gn_T_main_b46ba9_mii_1,__func__);
}
static void _I_ZGTercher_zg_instanceMethod2(ZGTercher * self, SEL _cmd) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_3q_sv9blc555sb1tfw4cl4wgl2h0000gn_T_main_b46ba9_mii_2,__func__);
}
static void _C_ZGTercher_zg_sayClassMethod(Class self, SEL _cmd) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_3q_sv9blc555sb1tfw4cl4wgl2h0000gn_T_main_b46ba9_mii_3,__func__);
}
//get方法
static NSString * _I_ZGTercher_zg_name(ZGTercher * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_ZGTercher$_zg_name)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);
//set方法
static void _I_ZGTercher_setZg_name_(ZGTercher * self, SEL _cmd, NSString *zg_name) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct ZGTercher, _zg_name), (id)zg_name, 0, 1); }
查看 ZGTeacher
类拓展的方法,在编译过程中,方法就直接添加到了 methodlist
中,作为类的一部分,即编译时期直接添加到本类里面
static struct /*_ivar_list_t*/ {
unsigned int entsize; // sizeof(struct _prop_t)
unsigned int count;
struct _ivar_t ivar_list[2];
} _OBJC_$_INSTANCE_VARIABLES_ZGTercher __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_ivar_t),
2,
{{(unsigned long int *)&OBJC_IVAR_$_ZGTercher$_zg_age, "_zg_age", "i", 2, 4},
{(unsigned long int *)&OBJC_IVAR_$_ZGTercher$_zg_name, "_zg_name", "@\"NSString\"", 3, 8}}
};
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[7];
} _OBJC_$_INSTANCE_METHODS_ZGTercher __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
7,
{{(struct objc_selector *)"zg_instanceMethod3", "v16@0:8", (void *)_I_ZGTercher_zg_instanceMethod3},
{(struct objc_selector *)"zg_instanceMethod1", "v16@0:8", (void *)_I_ZGTercher_zg_instanceMethod1},
{(struct objc_selector *)"zg_instanceMethod2", "v16@0:8", (void *)_I_ZGTercher_zg_instanceMethod2},
{(struct objc_selector *)"zg_name", "@16@0:8", (void *)_I_ZGTercher_zg_name},
{(struct objc_selector *)"setZg_name:", "v24@0:8@16", (void *)_I_ZGTercher_setZg_name_},
{(struct objc_selector *)"zg_age", "i16@0:8", (void *)_I_ZGTercher_zg_age},
{(struct objc_selector *)"setZg_age:", "v20@0:8i16", (void *)_I_ZGTercher_setZg_age_}}
};
而我们在类中定义的方法也是直接加入到方法列表 _method_list_t
中。这点可以说明,扩展
是类的一部分,而且方法在编译期
就加入到类中。
objc源码验证
新建一个ZGPerson+ZGExt
的类扩展
@interface ZGPerson ()
@property (nonatomic, copy) NSString *ext_name;
- (void)ext_instanceMethod;
+ (void)ext_sayClassMethod;
@end
并且在ZGPerson中实现- (void)ext_instanceMethod;和 + (void)ext_sayClassMethod;
方法
#import "ZGPerson.h"
@implementation ZGPerson
//+ (void)load{
//
//}
- (void)kc_instanceMethod3{
NSLog(@"%s",__func__);
}
- (void)kc_instanceMethod1{
NSLog(@"%s",__func__);
}
- (void)kc_instanceMethod2{
NSLog(@"%s",__func__);
}
+ (void)kc_sayClassMethod{
NSLog(@"%s",__func__);
}
- (void)ext_instanceMethod{
}
+ (void)ext_sayClassMethod{
}
运行objc源码程序,在readClass
中打个断点,然后我们去查看类的方法里列表中加载了哪些方法?
然后我们LLDB
指令p kc_ro->baseMethodList
读取baseMethodList
内容,并读出其中p $0->get(0) ~ p $0->get(8)
的数组元素内容
当我们
p $0->get(3)
的时候就已经发现我们的类扩展方法,刚好验证了我们上面的结论。
总结
分类和类扩展如果是单独的文件都需要被导入。分类一般用来为类添加方法
,类扩展可以用来减少不想对外暴露的属性、方法
,但并不是真正的私有。
分类关联对象原理探索
我们在分类中cate_name增加一个cate_name
属性
@interface ZGPerson (ZGA)
@property (nonatomic, strong) NSString *cate_name;//分类属性
@property (nonatomic, assign) int cate_age;
- (void)cateA_instanceMethod1;
- (void)cateA_instanceMethod3;
- (void)cateA_instanceMethod2;
- (void)cateA_classMethod3;
@end
在main
中对cate_name
赋值
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
ZGPerson *person = [ZGPerson alloc];
person.cate_name = @"zg";
[person kc_instanceMethod1];
}
return 0;
}
会报错
但是当我们在分类中对属性进行关联对象处理
#import <objc/runtime.h>
static const void *cate_name_str = &cate_name_str;
@implementation ZGPerson (ZGA)
- (NSString *)cate_name {
return objc_getAssociatedObject(self, cate_name_str);
}
- (void)setCate_name:(NSString *)cate_name{
objc_setAssociatedObject(self, cate_name_str, cate_name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
运行后发现可以正常读取cate_name
的值
其底层原理的实现,主要分为两部分:
- 通过
objc_setAssociatedObject
设值流程- 通过
objc_getAssociatedObject
取值流程
其中objc_setAssociatedObject
方法有四个参数,分别表示:
参数1:要关联的对象,即给谁添加关联属性
参数2:标识符,方便下次查找
参数3:value
-
参数4:属性的
策略
,即nonatomic、atomic、assign
等,如下所示
objc_setAssociatedObject源码实现
void
objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
{
SetAssocHook.get()(object, key, value, policy);
}
此处与以往的objc开源代码不同,在781
的这个版本的源码中,增加了一层代码封装,按住command
点击SetAssocHook
,能够看到这行代码:
static ChainedHookFunction<objc_hook_setAssociatedObject> SetAssocHook{_base_objc_setAssociatedObject};
进入_base_objc_setAssociatedObject
源码实现:_base_objc_setAssociatedObject -> _object_set_associative_reference
,通过断点调试,确实会来到这
_object_set_associative_reference 方法
进入_object_set_associative_reference
源码实现,关于关联对象 底层原理的探索 主要是看value
存到了哪里, 以及如何取出value
,以下是源码
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) {
//try_emplace,在这disguised作为key查找,如果已经在associations表中,就把查找到的桶作为DenseMapIterator的位置指针进行初始化,然后用pair包装后返回;key没在associations表中就把disguised作为key,ObjectAssociationMap{}作为value存入桶中,然后把该桶作为DenseMapIterator的位置指针进行初始化,然后用pair包装后返回。返回值类型std::pair<DenseMapIterator, bool>
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 { //value为nil, 取消关联。看懂了上面这里就很简单了。
//先从associations表中找到disguised对应的ObjectAssociationMap表,又用pair包装后返回。
auto refs_it = associations.find(disguised);
//如果从associations表中找到了disguised对应的ObjectAssociationMap表,就走进去
if (refs_it != associations.end()) {
//从pair中拿到ObjectAssociationMap表
auto &refs = refs_it->second;
//从ObjectAssociationMap表中查找key对应的association,然后把它作为DenseMapIterator的位置指针初始化后返回
auto it = refs.find(key);
//如果找到了就进去
if (it != refs.end()) {
//这里交换值是为了把要擦除的association记录下来,因为下面还要进行releaseHeldValue
association.swap(it->second);
//从ObjectAssociationMap表中擦除association以及其他相应的操作
refs.erase(it);
if (refs.size() == 0) {
//说明没有关联的值了,从associations表中擦除ObjectAssociationMap表
associations.erase(refs_it);
}
}
}
}
}
// release the old value (outside of the lock).
association.releaseHeldValue();
}
其中流程如下
- 1: 创建一个
AssociationsManager
管理类 - 2: 获取唯一的全局静态
哈希Map
- 3: 判断是否插入的
关联值
是否存在:- 3.1: 存在走第
4
步 - 3.2: 不存在就走 :
关联对象插入空流程
- 3.1: 存在走第
- 4: 创建一个空的
ObjectAssociationMap
去取查询的键值对 - 5: 如果发现没有这个
key
就插入一个 空的BucketT
进去返回 - 6:
标记
对象存在关联对象 - 7: 用当前 修饰
策略
和值
组成了一个ObjcAssociation
替换原来BucketT
中的空 - 8: 标记一下
ObjectAssociationMap
的第一次为false
关联对象插入空流程
- 1: 根据
DisguisedPtr
找到AssociationsHashMap
中的iterator
迭代查询器 - 2: 清理迭代器
- 3: 其实如果插入空置 相当于
清除
其中try_emplace
的源码如下
// Inserts key,value pair into the map if the key isn't already in the map.
// The value is constructed in-place if the key is not in the map, otherwise
// it is not moved.
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);
}
有两个返回,都是通过std::make_pair
生成相应的键值对
通过LookupBucketFor
方法查找桶子,如果map
中已经存在,则直接返回,其中make_pair
的第二个参数bool
值为false
如果没有找到,则通过InsertIntoBucket
插入map
,其中make_pair
的第二个参数bool
值为true
下面通过LLDB
断点打印来帮助理解源码中的内容,可以看到设置的值zg
接下来打印最初的
association
和associations
(lldb) p association
(objc::ObjcAssociation) $0 = {
_policy = 3
_value = 0x0000000100002038 "zg"
}
(lldb) p associations
(objc::AssociationsHashMap) $1 = {
Buckets = 0x0000000000000000
NumEntries = 0
NumTombstones = 0
NumBuckets = 0
}
接着打印了refs_result
的一些内容,下面会省略一些类型的参数来方便查看
(lldb) p refs_result
(std::__1::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>) $1 = {
first = {
Ptr = 0x0000000101027410
End = 0x0000000101027490
}
second = true
}
(lldb) p *refs_result.first
(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>::value_type) $2 = {
std::__1::pair<DisguisedPtr<objc_object>, objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> > > = {
first = (value = 18446744069407759344)
second = {
Buckets = 0x0000000100683030
NumEntries = 1
NumTombstones = 0
NumBuckets = 4
}
}
}
(lldb) p refs_result.first->second
(objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> >) $3 = {
Buckets = 0x0000000100683030
NumEntries = 1
NumTombstones = 0
NumBuckets = 4
}
(lldb) p result
(std::__1::pair<objc::DenseMapIterator<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation>, false>, bool>) $4 = {
first = {
Ptr = 0x0000000100683078
End = 0x0000000100683090
}
second = true
}
(lldb)
最后再打印一下association
和associations
(lldb) p association
(objc::ObjcAssociation) $5 = {
_policy = 0
_value = nil
}
(lldb) p associations
(objc::AssociationsHashMap) $6 = {
Buckets = 0x0000000101027410
NumEntries = 1
NumTombstones = 0
NumBuckets = 4
}
(lldb)
所以到目前为止,关联属性涉及的map
结构如下
AssociationsHashMap
表是以伪装后的objc_object
指针为key
,ObjectAssociationMap
是以const void *
类型的指针为key
。
objc_getAssociatedObject源码分析
id objc_getAssociatedObject(id object, const void *key)
{
return _object_get_associative_reference(object, key);
}
id _object_get_associative_reference(id object, const void *key)
{
//先初始化一个用来接收值的association
ObjcAssociation association{};
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.get());
//用object作为key从associations表中找到对应的ObjectAssociationMap表
AssociationsHashMap::iterator i = associations.find((objc_object *)object);
if (i != associations.end()) {
ObjectAssociationMap &refs = i->second;
//用key在ObjectAssociationMap表中搜索对应的ObjcAssociation
ObjectAssociationMap::iterator j = refs.find(key);
if (j != refs.end()) {
//找到后赋值给association,然后retain
association = j->second;
association.retainReturnedValue();
}
}
}
return association.autoreleaseReturnedValue();
}
- 1:创建一个
AssociationsManager
管理类 - 2:获取唯一的全局静态哈希Map:
AssociationsHashMap
- 3:通过
find
方法根据DisguisedPtr
找到AssociationsHashMap
中的iterator
迭代查询器 - 4:如果这个迭代查询器不是最后一个 获取 :
ObjectAssociationMap (policy和value)
- 5:通过
find
方法找到ObjectAssociationMap
的迭代查询器获取一个经过属性修饰符修饰的value
- 6:返回
value
关联对象释放
在对象调用dealloc
方法释放时,通过_objc_rootDealloc->rootDealloc->object_dispose->objc_destructInstance
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_assocations(obj);
obj->clearDeallocating();
}
return obj;
}
void _object_remove_assocations(id object)
{
ObjectAssociationMap refs{};
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.get());
AssociationsHashMap::iterator i = associations.find((objc_object *)object);
if (i != associations.end()) {
refs.swap(i->second);
associations.erase(i);
}
}
// release everything (outside of the lock).
for (auto &i: refs) {
i.second.releaseHeldValue();
}
}
从中可以看出关联对象不用手动移除
,在对象释放
时会自动移除。
总结
所以,综上所述,所以关联对象的底层调用流程
如下图所示
总的来说,关联对象
主要就是两层哈希map的处理
,即存取时都是两层处理,类似于二维数组