Category、load、initialize、Class、关联对象、isa、superClass、方法缓存、方法查找、消息发送(objc_msgSend)、runtime

一 、Category的实现原理

Category编译之后的底层结构是struct category_t,里面存储着分类的对象方法、类方法、属性、协议信息。在程序运行的时候,runtime会将Category的数据,合并到类信息中(类对象、元类对象中)

Category和Class Extension的区别是什么?
Class Extension在编译的时候,它的数据就已经包含在类信息中
Category是在运行时,才会将数据合并到类信息中

Category中有load方法吗?load方法是什么时候调用的?load 方法能继承吗?
有load方法。load方法在runtime加载类、分类的时候调用
load方法可以继承,但是一般情况下不会主动去调用load方法,都是让系统自动调用

Category能否添加成员变量?如果可以,如何给Category添加成员变量?
不能直接给Category添加成员变量,但是可以利用runtime的API间接实现Category有成员变量的效果。

详细原因:因Calss定义内,class_rw_t结构体内有class_ro_t属性的结构体,只读的
class_ro_t结构体内有ivars **成员变量**、instanceSize,是在编译的时候就确定的,
 而Category是运行时才将数据合并到类信息中,默认情况下,因为分类底层结构的
 限制,不能添加成员变量到分类中。但可以通过关联对象来间接实现。

结构体如下
Category、load、initialize、Class、关联对象、isa、superClass、方法缓存、方法查找、消息发送(objc_msgSend)、runtime

  • 源码解读顺序
    objc-os.mm
    _objc_init
    map_images
    map_images_nolock

  • objc-runtime-new.mm
    _read_images
    remethodizeClass
    attachCategories
    attachLists
    realloc、memmove、 memcpy

  • 通过Runtime加载某个类的所有Category数据
    把所有Category的方法、属性、协议数据,合并到一个大数组中
    后面参与编译的Category数据,会在数组的前面
    将合并后的分类数据(方法、属性、协议),插入到类原来数据的前面
    分类中重写类中原有的某个方法,导致原方法被“覆盖”。其实不算覆盖,是因合并到类方法列表的前部,方法查找时,因为分类方法在前,就找到分类的方法响应,类远方法就未轮到响应,造成被覆盖的假象。

二、+load方法

  • +load方法会在runtime加载类、分类时调用
  • 每个类、分类的+load,在程序运行过程中只调用一次
  • 调用顺序
  • 先调用类的+load 按照编译先后顺序调用(先编译,先调用) 调用
    子类的+load之前会先调用父类的+load
  • 再调用分类的+load 按
    照编译先后顺序调用(先编译,先调用)
  • +load方法是根据方法地址直接调用,并不是经过objc_msgSend函数调用

objc4源码解读过程
objc-os.mm
_objc_init
load_images
prepare_load_methods
schedule_class_load
add_class_to_loadable_list
add_category_to_loadable_list

call_load_methods
call_class_loads
call_category_loads
(*load_method)(cls, SEL_load)

三、+initialize方法

  • +initialize方法会在类第一次接收到消息时调用 调用顺序 先调用父类的+initialize,再调用子类的+initialize (先
  • 初始化父类,再初始化子类,每个类只会初始化1次)
  • +initialize和+load的很大区别是,+initialize是通过objc_msgSend进行调用的。
  • 如果子类没有实现+initialize,会调用父类的+initialize(所以父类的+initialize可能会被调用多次)
  • 如果分类实现了+initialize,就覆盖类本身的+initialize调用

四、Class

class的结构
Category、load、initialize、Class、关联对象、isa、superClass、方法缓存、方法查找、消息发送(objc_msgSend)、runtime

class_rw_t里面的methods、properties、protocols是二维数组,是可读可写的,包含了类的初始内容、分类的内容
Category、load、initialize、Class、关联对象、isa、superClass、方法缓存、方法查找、消息发送(objc_msgSend)、runtime

class_ro_t里面的baseMethodList、baseProtocols、ivars、baseProperties是一维数组,是只读的,包含了类的初始内容
Category、load、initialize、Class、关联对象、isa、superClass、方法缓存、方法查找、消息发送(objc_msgSend)、runtime

method_t是对方法\函数的封装
Category、load、initialize、Class、关联对象、isa、superClass、方法缓存、方法查找、消息发送(objc_msgSend)、runtime

IMP代表函数的具体实现
Category、load、initialize、Class、关联对象、isa、superClass、方法缓存、方法查找、消息发送(objc_msgSend)、runtime

SEL代表方法\函数名,一般叫做选择器,底层结构跟char *类似
可以通过@selector()和sel_registerName()获得
可以通过sel_getName()和NSStringFromSelector()转成字符串
不同类中相同名字的方法,所对应的方法选择器是相同的

types包含了函数返回值、参数编码的字符串
Category、load、initialize、Class、关联对象、isa、superClass、方法缓存、方法查找、消息发送(objc_msgSend)、runtime

iOS中提供了一个叫做@encode的指令,可以将具体的类型表示成字符串编码
Category、load、initialize、Class、关联对象、isa、superClass、方法缓存、方法查找、消息发送(objc_msgSend)、runtime

方法缓存

  • Class内部结构中有个方法缓存(cache_t),用散列表(哈希表)来缓存曾经调用过的方法,可以提高方法的查找速度
    Category、load、initialize、Class、关联对象、isa、superClass、方法缓存、方法查找、消息发送(objc_msgSend)、runtime

  • 缓存查找
    objc-cache.mm
    bucket_t * cache_t::find(cache_key_t k, id receiver)

Category、load、initialize、Class、关联对象、isa、superClass、方法缓存、方法查找、消息发送(objc_msgSend)、runtime

五、关联对象

  • 添加关联对象
    void objc_setAssociatedObject(id object, const void * key,
    id value, objc_AssociationPolicy policy)

  • 获得关联对象
    id objc_getAssociatedObject(id object, const void * key)

  • 移除所有的关联对象
    void objc_removeAssociatedObjects(id object)

key的常见用法
1.静态方法变量 static void *MyKey = &MyKey;

objc_setAssociatedObject(obj, MyKey, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC)
objc_getAssociatedObject(obj, MyKey)

2.静态变量 static char MyKey;

objc_setAssociatedObject(obj, &MyKey, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC)
objc_getAssociatedObject(obj, &MyKey)

3.使用属性名作为key

objc_setAssociatedObject(obj, @"property", value, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
objc_getAssociatedObject(obj, @"property");

4.使用get方法的@selecor作为key

objc_setAssociatedObject(obj, @selector(getter), value, OBJC_ASSOCIATION_RETAIN_NONATOMIC)
objc_getAssociatedObject(obj, @selector(getter))

关联策略 objc_AssociationPolicy

objc_AssociationPolicy 对应的修饰符
OBJC_ASSOCIATION_ASSIGN assign
OBJC_ASSOCIATION_RETAIN_NONATOMIC strong, nonatomic
OBJC_ASSOCIATION_COPY_NONATOMIC copy, nonatomic
OBJC_ASSOCIATION_RETAIN strong, atomic
OBJC_ASSOCIATION_COPY copy, atomic

关联对象的原理

  • 实现关联对象技术的核心对象有
    AssociationsManager
    AssociationsHashMap
    ObjectAssociationMap
    ObjcAssociation
  • objc4源码解读:objc-references.mm

Category、load、initialize、Class、关联对象、isa、superClass、方法缓存、方法查找、消息发送(objc_msgSend)、runtime

  • 关联对象并不是存储在被关联对象本身内存中关联对象存储在全局的统一的一个AssociationsManager中,设置关联对象为nil,就相当于是移除关联对象

详见下图:
Category、load、initialize、Class、关联对象、isa、superClass、方法缓存、方法查找、消息发送(objc_msgSend)、runtime

六、isa、superClass

Category、load、initialize、Class、关联对象、isa、superClass、方法缓存、方法查找、消息发送(objc_msgSend)、runtime
对象的isa指针指向哪里?

  • instance对象的isa指向class对象
  • class对象的isa指向meta-class对象
  • meta-class对象的isa指向基类的meta-class对象

superClass

  • class的superclass指向父类的class,如果没有父类,superclass指针为nil

  • meta-class的superclass指向父类的meta-class,基类的meta-class的superclass指向基类的class

instance调用对象方法的轨迹

  • isa找到class,方法不存在,就通过superclass找父类

class调用类方法的轨迹

  • isa找meta-class,方法不存在,就通过superclass找父类

据以上Class结构体可知有个isa属性,在arm64架构之前,isa就是一个普通的指针,存储着Class、Meta-Class对象的内存地址从arm64架构开始,对isa进行了优化,变成了一个共用体(union)结构,还使用位域来存储更多的信息,见下图:
Category、load、initialize、Class、关联对象、isa、superClass、方法缓存、方法查找、消息发送(objc_msgSend)、runtime

  • nonpointer
    0,代表普通的指针,存储着Class、Meta-Class对象的内存地址
    1,代表优化过,使用位域存储更多的信息

  • has_assoc
    是否有设置过关联对象,如果没有,释放时会更快

  • has_cxx_dtor
    是否有C++的析构函数(.cxx_destruct),如果没有,释放时会更快

  • shiftcls
    存储着Class、Meta-Class对象的内存地址信息

  • magic
    用于在调试时分辨对象是否未完成初始化

  • weakly_referenced
    是否有被弱引用指向过,如果没有,释放时会更快

  • deallocating
    对象是否正在释放

  • extra_rc
    里面存储的值是引用计数器减1

  • has_sidetable_rc
    引用计数器是否过大无法存储在isa中
    如果为1,那么引用计数会存储在一个叫SideTable的类的属性中

七、objc_msgSend执行流程

OC 的消息发送机制

OC中的方法调用其实都是转成了objc_msgSend函数的调用,给receiver(方法调用者)发送了一条消息(selector方法名)
objc_msgSend底层有3大阶段

  • 消息发送(当前类、父类中查找)、
  • 动态方法解析
  • 消息转发

objc_msgSend执行流程 – 源码跟读
objc-msg-arm64.s

ENTRY _objc_msgSend
b.le	LNilOrTagged
CacheLookup NORMAL
.macro CacheLookup
.macro CheckMiss
STATIC_ENTRY __objc_msgSend_uncached
.macro MethodTableLookup
__class_lookupMethodAndLoadCache3

objc-runtime-new.mm

_class_lookupMethodAndLoadCache3
lookUpImpOrForward
getMethodNoSuper_nolock、search_method_list、log_and_fill_cache
cache_getImp、log_and_fill_cache、getMethodNoSuper_nolock、log_and_fill_cache
_class_resolveInstanceMethod
_objc_msgForward_impcache

objc-msg-arm64.s

STATIC_ENTRY __objc_msgForward_impcache
ENTRY __objc_msgForward

Core Foundation
__forwarding__(不开源)

objc_msgSend执行流程01-消息发送
Category、load、initialize、Class、关联对象、isa、superClass、方法缓存、方法查找、消息发送(objc_msgSend)、runtime

  • 如果是从class_rw_t中查找方法
    已经排序的,二分查找
    没有排序的,遍历查找
  • receiver通过isa指针找到receiverClass
  • receiverClass通过superclass指针找到superClass

objc_msgSend执行流程02-动态方法解析
Category、load、initialize、Class、关联对象、isa、superClass、方法缓存、方法查找、消息发送(objc_msgSend)、runtime

  • 开发者可以实现以下方法,来动态添加方法实现
    +resolveInstanceMethod:
    +resolveClassMethod:
  • 动态解析过后,会重新走“消息发送”的流程
    “从receiverClass的cache中查找方法”这一步开始执行

动态添加方法
Category、load、initialize、Class、关联对象、isa、superClass、方法缓存、方法查找、消息发送(objc_msgSend)、runtime

  • Method可以理解为等价于struct method_t *
  • @dynamic是告诉编译器不用自动生成getter和setter的实现,等到运行时再添加方法实现

objc_msgSend的执行流程03-消息转发
Category、load、initialize、Class、关联对象、isa、superClass、方法缓存、方法查找、消息发送(objc_msgSend)、runtime

  • 开发者可以在forwardInvocation:方法中自定义任何逻辑
  • 以上方法都有对象方法、类方法2个版本(前面可以是加号+,也可以是减号-)

生成NSMethodSignature
Category、load、initialize、Class、关联对象、isa、superClass、方法缓存、方法查找、消息发送(objc_msgSend)、runtime

八、super的本质

Category、load、initialize、Class、关联对象、isa、superClass、方法缓存、方法查找、消息发送(objc_msgSend)、runtime

  • super调用,底层会转换为objc_msgSendSuper2函数的调用,接收2个参数
    struct objc_super2
    SEL
  • receiver是消息接收者
  • current_class是receiver的Class对象

九、Runtime

OC是一门动态性比较强的编程语言,允许很多操作推迟到程序运行时再进行。
OC的动态性就是由Runtime来支撑和实现的,Runtime是一套C语言的API,封装了很多动态性相关的函数,编写的OC代码,底层都是转换成了Runtime API进行调用

Runtime的应用

  • 查看私有成员变量
    如:设置UITextField占位文字的颜色
    Category、load、initialize、Class、关联对象、isa、superClass、方法缓存、方法查找、消息发送(objc_msgSend)、runtime

  • 字典转模型
    1.利用Runtime遍历所有的属性或者成员变量
    2.利用KVC设值

  • 替换方法实现
    1.class_replaceMethod
    2.method_exchangeImplementations

**Runtime API 1 – 类 **

  • 动态创建一个类(参数:父类,类名,额外的内存空间)
    Class objc_allocateClassPair(Class superclass, const char *name, size_t extraBytes)

  • 注册一个类(要在类注册之前添加成员变量)
    void objc_registerClassPair(Class cls)

  • 销毁一个类
    void objc_disposeClassPair(Class cls)

  • 获取isa指向的Class
    Class object_getClass(id obj)

  • 设置isa指向的Class
    Class object_setClass(id obj, Class cls)

  • 判断一个OC对象是否为Class
    BOOL object_isClass(id obj)

  • 判断一个Class是否为元类
    BOOL class_isMetaClass(Class cls)

  • 获取父类
    Class class_getSuperclass(Class cls)

Runtime API 2 – 成员变量

  • 获取一个实例变量信息
    Ivar class_getInstanceVariable(Class cls, const char *name)

  • 拷贝实例变量列表(最后需要调用free释放)
    Ivar *class_copyIvarList(Class cls, unsigned int *outCount)

  • 设置和获取成员变量的值
    void object_setIvar(id obj, Ivar ivar, id value)
    id object_getIvar(id obj, Ivar ivar)

  • 动态添加成员变量(已经注册的类是不能动态添加成员变量的)
    BOOL class_addIvar(Class cls, const char * name, size_t size, uint8_t alignment, const char * types)

  • 获取成员变量的相关信息
    const char *ivar_getName(Ivar v)
    const char *ivar_getTypeEncoding(Ivar v)

Runtime API 3 – 属性

  • 获取一个属性
    objc_property_t class_getProperty(Class cls, const char *name)

  • 拷贝属性列表(最后需要调用free释放)
    objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)

  • 动态添加属性
    BOOL class_addProperty(Class cls, const char *name, const objc_property_attribute_t *attributes,
    unsigned int attributeCount)

  • 动态替换属性
    void class_replaceProperty(Class cls, const char *name, const objc_property_attribute_t *attributes,
    unsigned int attributeCount)

  • 获取属性的一些信息
    const char *property_getName(objc_property_t property)
    const char *property_getAttributes(objc_property_t property)

Runtime API 4 – 方法

  • 获得一个实例方法、类方法
    Method class_getInstanceMethod(Class cls, SEL name)
    Method class_getClassMethod(Class cls, SEL name)

  • 方法实现相关操作
    IMP class_getMethodImplementation(Class cls, SEL name)
    IMP method_setImplementation(Method m, IMP imp)
    void method_exchangeImplementations(Method m1, Method m2)

  • 拷贝方法列表(最后需要调用free释放)
    Method *class_copyMethodList(Class cls, unsigned int *outCount)

  • 动态添加方法
    BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)

  • 动态替换方法
    IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types)

  • 获取方法的相关信息(带有copy的需要调用free去释放)
    SEL method_getName(Method m)
    IMP method_getImplementation(Method m)
    const char *method_getTypeEncoding(Method m)
    unsigned int method_getNumberOfArguments(Method m)
    char *method_copyReturnType(Method m)
    char *method_copyArgumentType(Method m, unsigned int index)

  • 选择器相关
    const char *sel_getName(SEL sel)
    SEL sel_registerName(const char *str)

  • 用block作为方法实现
    IMP imp_implementationWithBlock(id block)
    id imp_getBlock(IMP anImp)
    BOOL imp_removeBlock(IMP anImp)

以上仅做个人知识回顾使用,有误之处,希望指正。不全之处,还请移步查询。