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很长,很变态,前面那么长只是类型,相当于NSObjectLGPerson这种,但是真正的内容只是后面几个,而这里只是用到了second,其他的压根就不用关心,这一波是不是就很舒服了,哈哈

iOS底层探索之类的加载(四):类的关联对象AssociatedObject相关推荐

  1. iOS 底层探索 - 消息转发

    一.动态方法解析流程分析 我们在上一章<消息查找>分析到了动态方法解析,为了更好的掌握具体的流程,我们接下来直接进行源码追踪. 我们先来到 _class_resolveMethod 方法, ...

  2. iOS 底层探索篇 —— KVC 底层原理

    iOS 底层探索篇 -- KVC 底层原理 1. Method Swizzling的坑与应用 1.1 method-swizzling 是什么? 1.2 坑点 坑点1:method-swizzling ...

  3. iOS底层探索二(OC 中 alloc 方法 初探)

    前言 相关文章: iOS底层探索一(底层探索方法) iOS底层探索三(内存对齐与calloc分析) iOS底层探索四(isa初探-联合体,位域,内存优化) iOS底层探索五(isa与类的关系) iOS ...

  4. iOS开发UI篇—懒加载

    iOS开发UI篇-懒加载 1.懒加载基本 懒加载--也称为延迟加载,即在需要的时候才加载(效率低,占用内存小).所谓懒加载,写的是其get方法. 注意:如果是懒加载的话则一定要注意先判断是否已经有了, ...

  5. iOS底层探索(二) - 写给小白看的Clang编译过程原理

    iOS底层探索(一) - 从零开始认识Clang与LLVM 写在前面 编译器是属于底层知识,在日常开发中少有涉及,但在我的印象中,越接近底层是越需要编程基本功,也是越复杂的.但要想提升技术却始终绕不开 ...

  6. [html] iOS下页面如何启动加载时显示画面图片?如何设置大小?它有什么好处?

    [html] iOS下页面如何启动加载时显示画面图片?如何设置大小?它有什么好处? <link rel="apple-touch-startup-image" href=&q ...

  7. 微信JSSDK多图片上传并且解决IOS系统上传一直加载的问题

    微信JSSDK多图片上传并且解决IOS系统上传一直加载的问题 参考文章: (1)微信JSSDK多图片上传并且解决IOS系统上传一直加载的问题 (2)https://www.cnblogs.com/co ...

  8. ios wkwebview弹框_iOS 加载WKWebView

    WKWebView是苹果在iOS 8之后推出的框架WebKit中的浏览器控件, 其加载速度比UIWebView快了许多, 但内存占用率却下降很多, 也解决了加载网页时的内存泄露问题. WKWebVie ...

  9. datax底层原理_Datax 插件加载原理

    Datax 插件加载原理 插件类型 Datax有好几种类型的插件,每个插件都有不同的作用. reader, 读插件.Reader就是属于这种类型的 writer, 写插件.Writer就是属于这种类型 ...

  10. OpenGL深入探索——使用Assimp加载模型

    转载自:第二十二课 使用Assimp加载模型 背景 到现在为止我们都在使用手动生成的模型.正如你所想的,指明每个顶点的位置和其他属性有点时候并不是十分方便.对于一个箱子.锥体和简单平面还好,但是像人们 ...

最新文章

  1. 机器学习必知必会10大算法!
  2. 云视频会议的“多、快、好、省”(下)
  3. FusionCharts参数的详细说明
  4. 动态调用WebService
  5. 【29.70%】【codeforces 723D】Lakes in Berland
  6. 转载:逻辑思维提升表达
  7. MicroProfile在Jakarta EE时代的作用
  8. 怎么自学linux操作系统,linux操作系统好学吗_要学什么
  9. 语言认知偏差_我们的认知偏差正在破坏患者的结果数据
  10. 软件开发质量的双保险 — 1.设计验证与软件测试
  11. JavaScript验证正则表达式大全
  12. 51单片机(STC15W408AS)映射printf函数 串口收发实现
  13. ENVI Landsat8影像掩膜裁剪
  14. JavaSE重点之集合、IO、多线程
  15. 苹果开发者关联封号扫盲贴
  16. 近日onedrive突然消失问题的解决
  17. 文献阅读:Improving neural networks by preventing co-adaptation of feature detectors
  18. Helix QAC企业级自动代码静态分析器
  19. 计算机二级Python真题(十)
  20. Linux服务器配置网络,可修改服务器Ip与Mac地址

热门文章

  1. C 语言中的指针和内存泄漏
  2. 在服务端合并和压缩JavaScript和CSS文件[转]
  3. 意大利面条:面向过程的代码模型
  4. IOCP实现聊天服务
  5. win10软件安装出现错误代码2503/2502
  6. 走遍中国《中国古镇全集》
  7. deepin安装mysql记录
  8. BZOJ 1503: [NOI2004]郁闷的出纳员
  9. 发布的站点自定义端口打不开解决方法
  10. 【VS开发】ConvertBSTRToString(filename) 不能将string转换为BSTR