Objective-C Runtime2.0(-)简介

Objective-C Runtime2.0(-)简介

相关资料引用
https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Introduction/Introduction.html#//apple_ref/doc/uid/TP40008048-CH1-SW1
https://developer.apple.com/videos/play/wwdc2020/10163/

简介

Objective-C一门动态语言, 它能在编译的时候转换为对应的C函数, 而消息的接受者 (reciever) 和接受者执行的方法 selector , 只有在函数运行时才能确定, 所以说它是一门运行时语言. 通过在消息发送之前对函数的两个参数 recever (具体的执行对象)和 selector (执行的方法)进行不同的处理, 就可以实现各种黑魔法效果, 常见的有方法实现体(IMP)替换, 消息转发(Forward message), 同时为了配合运行时的特性, 它为每个方法和类型定义一套编码规则, Objective-C的对象和类型可以转换为Rumtime中指定的类型或这是标志符号, 保证他们都能有一套映射标准。

Runtime的版本和运行平台

Runtime一共有2个版本, Objective-C 1.0和Objective-C 2.0, 它们的一个主要的区别就是:
旧版本运行时中,如果更改类中实例变量的布局,则必须重新编译从该类继承的类。
新版本运行时中,如果更改类中实例变量的布局,则不必重新编译继承自该类的类。
支持平台包括 iOS , OS X , 而这些仅使用于苹果相关的应用

与Runtime之间的交互

  • Objective-C程序在三个不同的层次与运行时系统交互:

通过Objective-C源代码;
通过在基础框架的NSObject类中定义的方法;
通过直接调用运行时函数。

  • 通过编译器生成对应的源代码

在大多数情况下运行时胸在后台自动工作, 只需要编写和编译源代码就能使用它, 通常这些都是由Xcode来完成, 编译包含Objective-C类和方法的代码时,编译器将创建实现语言动态特性的数据结构和函数调用。数据结构捕获类和类别定义以及协议声明中的信息;它们包括在用Objective-C编程语言定义类和协议时讨论的类和协议对象,以及方法选择器、实例变量模板和从源代码中提取的其他信息。主运行时函数是发送消息的函数,如消息传递中所述。它由源代码消息表达式调用。

  • 直接调用NSObject中相关的方法

在Cocoa中的绝大部分对象都是基于 NSObject 生成的子类, 因此大部分类都继承了它的基本方法(NSProxy是一个另外), 同时也提供依稀诶抽象的模版方法, 如 description , 则需要子类去实现. 因为每个类的具体内容不大一样, 主要是为了方便 GDB print object命令 , 默认情况下它只包括一个类名称和地址。部分的 NSObject 方法只是查询运行时的系统信息, 这些方法可以对类型进行安全性检查

isKindOfClass
isMemenberOfClass
respondsToSelecctor
cconformsToProtocol
methodForSelector
  • 通过Runtime提供的函数调用

Swift工程中的api介绍
Source Code
提供了封装的函数调用,包括获取类的信息,添加类,获取实例对象的相关信息,获取类的定义,实例变量操作,对象关联,方法操作,库操作,方法选择器操作,协议操作,属性操作,还有 Objective-C 语言特有的功能,如bloc,weak操作。

消息发送

  • 在Objective-C中的方法调用 [Object method] 编译之后它是 objc_msgSend(receiver, selector) 这样的,此外还有 objc_msgSend(receiver, selector, arg1, arg2, ...) 带参数发送.

Object 作为 recevier , method 作为 selector 参数.

首先它会在 recevier(object)中查找 selector`(方法选择器)的函数实现体,
然后找到之后根据换入的参数执行该方法
最后将函数的返回值作为自己的返回值

  • 从上面的例子可以看出消息传递的关键在于 reciever 的类和它的结构,一个类主要包括2个关键的结构

父类的指针
一个class的调度表,此表中的维持了一个方法选择器和它的实现函数关联的索引. selector-address

  • 隐藏参数

在编译时, reciverselector 会自动被捕获做为 objc_msgSend 的两个参数,在代码中可以通过提供的关键字 _cmd 代表当前代码执行的方法.里用这些特性可以实现对方法和对象的校验,下面的官方simple给出的一个例子,它用于校验当前的method是否为 strange .以及target是否为当前对象本省.


* strange {

    id  target = getTheReceiver();
    SEL method = getTheMethod();
 
    if ( target == self || method == _cmd )
        return nil;
    return [target performSelector:method];
}
  • 获取方法的地址

规避动态绑定的唯一方法是获取方法的地址, 然后像调用函数一样去调用它, 当一个特定的方法被连续执行很多次的时候, 动态调用就会带来一定消息转发的开销, 会比较浪费性能, 我们可以通过获取函数地址直接调用, 这样和可以节省消息转发的开销

void (*setter)(id, SEL, BOOL); 
int i; 

setter = (void (*)(id, SEL, BOOL))[target

    methodForSelector:@selector(setFilled:)]; //methodForSelector由Runtime System提供,获取具体的方法地址,及函数指针

for ( i = 0 ; i < 1000 ; i++ ) {

    setter(targetList[i], @selector(setFilled:), YES); //通过直接执行函数,效率更高,减少循环带来的开销

}  

动态方法解析

  • 在某些特殊的场合我们可能需要实现方法的动态调用,利于在某些类不包含某个方法的时候,我们需要将其转发给特定的类处理来做一些异常搜集工作. Objective-C Runtime 提供了动态属性的的申明指令( @dynamic dirctive)。它会在编译的时候告诉编译器需要动态的查找和解析该属性.此外还可以通过实现以下2个方法来动态解析对应的 selector .
@implementation MyClass

* (BOOL)resolveInstanceMethod:(SEL)aSEL //这里也可以是实例方法,当这个类没有对应 `selector` 实现的时候就会找到这个方法,通过拦截它就可以对该方法做一些默认的处理.

{
    if (aSEL == @selector(resolveThisMethodDynamically)) {
          //为该方法做一些默认的实现操作,将它的实现写在 `dynamicMethodIMP` 方法中
          class_addMethod([self class], aSEL, (IMP) dynamicMethodIMP, "v@:");
          return YES;
    }
    return [super resolveInstanceMethod:aSEL];
}

void dynamicMethodIMP(id self, SEL _cmd) {
    // implementation ....
}
@end

完整执行流程如下

  1. 发送消息
  2. 在当前类的方法列表找找该IMP,
  3. 若找到直接执行并把IMP放到缓存汇总
  4. 若未找到从父类按照此逻辑继续查找知道找到NSObject
  5. 若NSObject中也没主动就开始调用resolveInstanceMethod方法
  6. 未找到执行ResolveInstanceMethod
  7. 未找到执行forwardingTargetForSelector
  8. 未找到执行methodSignatureForSelector
  9. 若返回methodSignature则执行forwardInvocation
  10. 否则无法处理消息
  • 动态loading

Objective-C程序可以在运行时加载和链接新的类和类别。新代码被合并到程序中,并与开始时加载的类和类别相同。
在Cocoa环境中,通常使用动态加载来定制应用程序,其他人编译的程序在运行时加载的模块,就像 Interface Builder 加载自己定义的调色板和OSX新系统首选项应用程序加载自定义的首选模块以下.
尽管一个运行时函数可以在Mach-O文件中(objc_loadModules,在objc/objcload.h中定义)中执行 Objective-C 模块动态加载,但是 Cocoa 的NSBundle类为动态加载提供了一个非常方便的接口,该接口面向对象并与相关的服务集成,可以参考 NSBundle 的相关使用方法,通过NSBundle我们可以动态的加载 framework 而不必在启动时加载,可以大幅度的减少冷启动的时候,但也有它自己的弊端,很多的方法需要对 selector 进行减少以防程序崩溃,使用时需要根据具体的场景考量。

常用的方法如下:


* (NSArray *)allBundles
* (NSArray *)allFrameworks
* (NSBundle *)bundleForClass:(Class)aClass
* (BOOL)load //动态的加载bundle的可执行文件代码到引用程序中
* (NSString *)localizedStringForKey:(NSString *)key value:(NSString *)value table:(NSString *)tableName

消息转发

想不处理消息的对象发送错误的消息时是错误的。但是, 在宣布错误之前, 运行时系统会给接收对象第二次处理消息的机会。

  • 转发消息

当发送一条消息给它不能处理的对象时, Runtime会将该消息内容进行包装, 然后调用对象的 forwardInvocation: 方法, 消息的参数是一个 NSInvocation 对象, 可以在当前对象中实现 forwardInvocation: 方法, 对这条不能处理的消息进行拦截和其他处理, 或者返回一个默认的信息, 在 NSObject 中有定义了 forwardInvocation: 方法的实现, 然鹅它只是简单的调用了 doesNotRecognizeSelector: 并会抛出一个错误, 继承于 NSObject 的子类通过重写 forwardInvocation: 这个方法可以对转发的消息进拦截。


* (void)forwardInvocation:(NSInvocation *)anInvocation

{

    if ([someOtherObject respondsToSelector:
            [anInvocation selector]])
        [anInvocation invokeWithTarget:someOtherObject];
    else
        [super forwardInvocation:anInvocation];

}
  • 多重继承实现

利用消息转发这一特性,可以实现多重继承。当子类本身继承某个类之后它还想实现其它类的方法,那就可以通过 forwardInvocation: 来实现消息转发。可以利用 NSProxy 来封装一个通用类,将该类继承的其他对象的方法做一个封装

NS_ROOT_CLASS
@interface NSProxy  {
    Class    isa;
}

* (id)alloc;
* (id)allocWithZone:(NSZone *)zone NS_AUTOMATED_REFCOUNT_UNAVAILABLE;
* (Class)class;

* (void)forwardInvocation:(NSInvocation *)invocation;
* (NSMethodSignature *)methodSignatureForSelector:(SEL)sel;
* (void)dealloc;
* (void)finalize;
* (NSString *)description;
* (NSString *)debugDescription;
* (BOOL)respondsToSelector:(SEL)aSelector;

* (BOOL)allowsWeakReference NS_UNAVAILABLE;
* (BOOL)retainWeakReference NS_UNAVAILABLE;

// - (id)forwardingTargetForSelector:(SEL)aSelector;

@end

* (void)forwardInvocation:(NSInvocation *)anInvocation

{
    [proxy forwardInvocation: anInvocation];
}

  • 消息封装

在对象转发消息之前, 还需要实现这个方法, 因为 NSInvocation 需要依赖于 NSMethodSignature 来创建。它的类方法是 + invocationWithMethodSignature: , 自带一个 NSMethodSignature 参数。 NSMethodSignature 是对Objective-C的方法参数进行编码。


* (NSMethodSignature*)methodSignatureForSelector:(SEL)selector

{

    NSMethodSignature* signature = [super methodSignatureForSelector:selector];
    if (!signature) {
       signature = [surrogate methodSignatureForSelector:selector];
    }
    return signature;

}
//包装消息
...

    NSMethodSignature *signature = [[self class] instanceMethodSignatureForSelector:aSelector];  
    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
    invocation.target = self;  //参数一
    invocation.selector = aSelector; //参数二
    invocation setArgument:&argument atIndex:2 //参数三  
    ... //其它参数

...

类型编码

为了帮助运行时系统,编译器为每个方法的返回和参数类型进行了编码,并将编码后的字符串于方法选择器进行关联.通过关键字 @encode(type) 注解来实现,它的返回值是一个字符串,runtime定义了他们的每个编码类型如下列表所示,我们也可以通过在工程中使用 print(%s), @encode(type)) 来确认编码后的结果.

Code Meaning
c char
i int
s short
l long
q
C unsigned char
I unsigned int
S unsigned short
L unsigned long
Q unsigned long long
f float
d double
B C++ bool or a C99 _Bool
v void
* char*, 一个字符串
@ 一个objet,id对象类型
# 一个Class对象
: 一个方法选择器(SEL)
arry type 一个数组
{name=type…} 结构体
bnum bit大小的枚举
^type 一个子针类型
? 一个未知类型,除此之外也用于函数指针

特殊类型: {examplestruct=@*i} , {NSObject=#} ,类型大都比较有规律,按照首字母和大写来区分。

Objective-C语言

Objective-C是一种独特的计算机语言,以其独特的方括号表达式让很多学习其他的语言的开发者都很难理解,但它的本质是为了提供复杂的面向对象编程,在早起的C语言中提供的对象只有结构体,和数组,使用操作起来不是很方便,它是对C语言的补充,并有一套自己特有的编译器clang,lvvm旨在打造高效率的面向对象编程语言.并以一种简单明了的方式实现,基本上所有的语法操作都是为绕这 sendMessage来展开的。
主要包括以下几个部分,在此链接中可以找到具体的定义和例子
Objects, Classes, and Messaging
Defining a Class
Protocols
Declared Properties
Categories and Extensions
Associative References
Fast Enumeration
Enabling Static Behavior
Selectors
Exception Handling
Threading

Objective-C编译

  • Objective-C之所以被称为运行时语言,是因为它在编译时会翻译成对应的C函数,下面通过它的翻译构建过程来看看它的具体实现过程层.

  • ViewController.m 中随便写些代码,然后执行运行,找到 Xcode 的构建历史记录可以看到如下消息从上面的构建记录可以看出,它主要是通过 clang 来完成代码的编译组装成最终的可以执行文件 (.o) , clang在编译时指定了一系列的参数,runtime环境配置,语法检查,中间编译产物的记录。

  • clang包含了很多的命令,下面也只是构建中的部分,具体可以参考 clang帮助文档,网上也有很多中文的解释,不过还是得结合具体的应用场景来理解。

...XcodeDefault.xctoolchain/usr/bin/clang  //编译器的执行文件路径
-x objective-c  // 指定编译文件的语言
-target x86_64-apple-ios12.0-simulator //指定需要生成code target,会根据这个arm指令生成查找对应的汇编
-fmessage-length=0 //只是为了控制控制台输出换行打印好看而已
-fdiagnostics-show-note-include-stack //诊断检查信息的提示包括对应的栈信息,提示用
-fmacro-backtrace-limit=0 //限定堆栈信息
-std=gnu11  //指定编译的语言版本
-fobjc-arc //开启Arc,buildSettings有指定,iOS5之后就支持了
-fmodules //Enable the 'modules' language feature,开启指定module的语言支持
-gmodules //Generate debug info with external references to clang modules or precompiled headers,生成clang modules或预编译头文件的debug信息 
...
-DDEBUG=1 //是否是debug环境
-DOBJC_OLD_DISPATCH_PROTOTYPES=0 
-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator13.7.sdk  // Set the system root directory (usually /)
 .... 
-MMD -MT dependencies -MF /x86_64/ViewController.d  //将依赖输出到指定的 ViewController.d文件,记录的依赖文件的路径
--serialize-diagnostics /x86_64/ViewController.dia //诊断文件
-c  ../ViewController.m  //预处理编译组装,不会自动执行run
-o  ../x86_64/ViewController.o //输出最终的编译产物文件
  • 通过Xcode工程文件clang执行的是 -c 命令, 直接输出了目标文件, 要想查看生成的中间文件则需要通过 -rewrite-objc 命令查看, 结合上面的构建步骤挑选出几个必选的命令

clang -rewrite-objc -fobjc-arc -fobjc-runtime=ios-12.0.0 -isysroot /Applications/Xcode-beta.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk ViewController.m , 实际这个文件一般不会产生, 不过它可以更好的帮助我们理解runtime的实现过程, 探究Runtime底层实现必不可少的工具, 下面是翻译之后的 Objective-C (简称OC)代码, 它编程了C++的文件, 二原来用 []Objective-C 写的那部分代码全部编程了C函数。

...
//用于定义类和类对象的结构
struct __rw_objc_super { 

	struct objc_object *object; 
	struct objc_object *superClass; 
	__rw_objc_super(struct objc_object *o, struct objc_object *s) : object(o), superClass(s) {} 

}; 

// ViewController: controller对象本身
// _cmd代表当前viewController需要执行的方法

static void _I_ViewController_viewDidLoad(ViewController * self, SEL _cmd) {

    
    //这个函数执行分为以下几个步骤
    //1. 创建了 `__rw_objc_super` 的结构体对象,参数1: viewController实例self,ViewController class
    //2. 并向 ViewController中注册了一个 `viewDidLoad` 方法
    //3. 返回了2个参数: self对应的 `__rw_objc_super` 结构体 和 `viewDidLoad` 的 `SEL`
    //4. __rw_objc_super查找viewDidLoad具体实现并执行
    ((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)  ((__rw_objc_super) {
       (id)self, (id)class_getSuperclass(objc_getClass("ViewController")) 
    }, sel_registerName("viewDidLoad"));

}
...

objc4-750初探

  • NSObject: 它是绝大部分的Objective-C的基类,封装了runtime最基本的实现,暴露了runtime最常用的接口,类和对象的安全检查以及消息转发,它包含了一个isa指针.所以对于任何继承于NSObject的子类,它们都可以转化为一个objc_class类型,它们的实例对象就是objc_class的结构体指针,相应的方法,属性,协议都能在objc_class和它相关的属性中找到.
OBJC_ROOT_CLASS
OBJC_EXPORT
@interface NSObject  {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
    Class isa  OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}
  • Class是一个objc_class结构体指针类型,在新版本的runtime中,它继承于objc_object,内部提供一个class_rw_t用来记录类初始化中会更新的数据(用class_data_bits_t来保存),很多关键属性通过RO_RW_开头的宏定义变量来定义,对于比较常的方法通过申明放式在外部单实现,它本省只负责管理一些class基本的操作,内存分配,类型检查,而关于它的方法,属性,协议相关的属性定义class_rw_t中间,由class_data_bits_t专门负责读写。
`typedef struct objc_class *Class;`

struct objc_class : objc_object {
    // Class ISA;
    Class superclass;
    cache_t cache;             // formerly cache pointer and vtable

    //保存`class_rw_t`的信息(read and write), 以下方法都是通过`bits`来进行读写更新`class_rw_t`的相关信息
    class_data_bits_t bits;   
    class_rw_t *data() {  ..
    void setData(class_rw_t *newData) { ...
    void setInfo(uint32_t set) { ...
    void clearInfo(uint32_t clear) { ...
    void changeInfo(uint32_t set, uint32_t clear) { ... 
    //引用计数相关的方法检查设置 retain/release/autorelease/retainCount/
    bool hasCustomRR() { 
    void setHasDefaultRR() { //设置默认的引用计数方法
    void setHasCustomRR(bool inherited = false);
    void printCustomRR(bool inherited);
    //检查和设置 alloc/allocWithZone
    bool hasCustomAWZ() {  ...
    void setHasDefaultAWZ() { ...
    void setHasCustomAWZ(bool inherited = false);
    void printCustomAWZ(bool inherited);  
    //requires raw isa
    bool instancesRequireRawIsa() { ..
    void setInstancesRequireRawIsa(bool inherited = false);
    void printInstancesRequireRawIsa(bool inherited);
    ...
    //ARC环境检测
    bool hasAutomaticIvars() { ... 
    bool isARC() { ...
    //对象关联
    bool instancesHaveAssociatedObjects() { ...
    void setInstancesHaveAssociatedObjects() { ... 
    //是否允许增加缓存
    bool shouldGrowCache() {  return true;
    bool isInitializing() {  return getMeta()->data()->flags & RW_INITIALIZING ...
    void setInitializing() {  ISA()->setInfo(RW_INITIALIZING);
    bool isInitialized() {  return getMeta()->data()->flags & RW_INITIALIZED; 
    void setInitialized();
  
    //抽象的实现
    bool isLoadable() {  return true;   ...
    
    //方法申明,代码解耦
    IMP getLoadMethod();

    // Locking: To prevent concurrent realization, hold runtimeLock.
    bool isRealized() {  return data()->flags & RW_REALIZED;

    // Returns true if this is an unrealized future class.
    // Locking: To prevent concurrent realization, hold runtimeLock.
    bool isFuture() {   return data()->flags & RW_FUTURE; ...
   
    //元类检测
    bool isMetaClass() { ... return data()->ro->flags & RO_META; ...
    
    //元类指向它本身,元类就是`objc_class`
    // NOT identical to this->ISA when this is a metaclass
    Class getMeta() {
        if (isMetaClass()) return (Class)this;
        else return this->ISA();
    }
    //当`objc_class`没有super时候的时候它就是`RootClass`
    bool isRootClass() {
        return superclass == nil;
    }
    bool isRootMetaclass() {
        return ISA() == (Class)this;
    }
    //获取class名称
    const char *mangledName() { 
        // fixme can't assert locks here
        assert(this);

        if (isRealized()  ||  isFuture()) {
            return data()->ro->name;
        } else {
            return ((const class_ro_t *)data())->name;
        }
    }
    //
    const char *demangledName(bool realize = false);
    const char *nameForLogging();

    
    uint32_t unalignedInstanceStart() {
        assert(isRealized());
        return data()->ro->instanceStart;
    }

    // 指定指针对齐方式
    uint32_t alignedInstanceStart() { ...
    uint32_t unalignedInstanceSize() {  ...
    uint32_t alignedInstanceSize() {  ...
    //指定初始化对象的大小,至少要求16字节以上
    size_t instanceSize(size_t extraBytes) { ...
    void setInstanceSize(uint32_t newSize) { ...
    void chooseClassArrayIndex();
    // 设置类索引
    void setClassArrayIndex(unsigned Idx) { ...
    unsigned classArrayIndex() { ...
};

  • class_rw_t: 维护一个class经常需要变动的内容
//添加propterties/protocols/methods时机
static void 
attachCategories(Class cls, category_list *cats, bool flush_caches)
{
    rw->methods.attachLists(mlists, mcount);  ...
    rw->properties.attachLists(proplists, propcount);  ...
    rw->protocols.attachLists(protolists, protocount); ...
    ...
static void methodizeClass(Class cls){ ...

struct class_rw_t {
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    uint32_t version; 
    //一个只读的class信息管理
    const class_ro_t *ro; 
    //定义动态更新的方法,属性和协议
    method_array_t c;
    property_array_t properties;
    protocol_array_t protocols; 
    //subclass记录,放便数据的快速读取
    Class firstSubclass;
    Class nextSiblingClass; 
    char *demangledName; 
    //flags修改和清除
    void setFlags(uint32_t set)  
    void clearFlags(uint32_t clear)  
    void changeFlags(uint32_t set, uint32_t clear) ...
};
  • class_ro_t: 只读class结构体,提供了class load时最原始的信息
struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize; 
    const uint8_t * ivarLayout; 
    const char * name;
    method_list_t * baseMethodList;
    protocol_list_t * baseProtocols;
    const ivar_list_t * ivars;
    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;

    method_list_t *baseMethods() const {
        return baseMethodList;
    }
};
  • cache_t: 记录当前类常用的方法列表
struct cache_t {
    struct bucket_t *_buckets;
    mask_t _mask; //用于控制缓存的扩容
    mask_t _occupied; //记录方法的数量(_buckets缓存数量)

public:
    //cache更新
    ...
};
struct bucket_t { //主要负责记录常用的方法并缓存
private:
    // IMP-first is better for arm64e ptrauth and no worse for arm64.
    // SEL-first is better for armv7* and i386 and x86_64.
#if __arm64__
    MethodCacheIMP _imp;
    cache_key_t _key;
#else
    cache_key_t _key;
    MethodCacheIMP _imp;
#endif 
  • objc的对象关联,它是通过另外一种方式添加的,通过一个全局的AssociationsHashMap来实现的。同样采用了DISGUISE来包装object指针,防止误检测内存泄漏
void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) { ...
        //hash_map
        AssociationsHashMap &associations(manager.associations());
       
}

Tagged Pointer

  • Tagged Point是苹果为了提高简单数据类型分配效率和节省内存开销而诞生的,举个栗子64位的空间中如果保存一个大小只占有32位或者空间更小的数据,这个数据是用指针表示,指针表示一个引用,指针比数据的内存大的多,这样通过指针访问就有点得不尝失了,所以就干脆直接把数据保存到指针中.通过对指针的内存空间重新layout得到tagged point

  • 用一个标志位tag来表示数据类型,

  • payload来指定它所容纳的数据

  • extended为扩展字段

适用于NSNumber、NSDate,小的NSString等类型,从它的内存分布来看.runtime根据targetPoint判断可以直接对number等小的对象且为targetPointer数据结构进行赋值,节省value的查找时间,比如NSNumber initWithInt可以直接设置数据到指针上.

__attribute__ 关键字

Attribute官方文档介绍

  • 在阅读源码中有发现使用了大量的的 __attribute__ 关键字,它是是 GUN C 提供的,能编译器可以根据 __attribute__ 的定义为函数,类型,对象根据约定的标志进行额外的操作,这样就可以在我们编写代码时对编译规则进行适当的修改.
 __attribute__ ((deprecated)) 
 __attribute__((unavailable))
 __attribute__((unused))
__attribute__((noreturn)) 

int x __attribute__ ((aligned (16))) = 0; //字节对齐,用更小的只来标示该类型,减少空间,最大不能超过链接器的最大对齐字节
struct student
{
    char name[7];
    uint32_t id;
    char subject[5];
} __attribute__ ((aligned(4)));  //总字节应为 12

short array[3] __attribute__ ((aligned (__BIGGEST_ALIGNMENT__))); //最大的机器字节码对齐

struct my_packed_struct
{
     char c;
     int i;
     struct my_unpacked_struct s;
}__attribute__ ((__packed__)); //按类型具体大小对齐,char+int

__attribute__((format (printf, 1, 2))); //对可变参数的函数格式进行校验,(1表示为格式化字符串位置,2表示可变参位置)
attribute__((used))用于告诉编译器在目标文件中保留一个静态函数或者静态变量,即使它没有被引用。
__attribute__((always_inline)) 实现函数内联,对于简短频繁调用的函数,直接放入符号表,减少堆栈开销
__attribute__((constructor)) 修饰的函数在main函数之前执行,配合`__attribute__((destructor))`使用,常用统计库的时间 
__attribute__((objc_designated_initializer)) 指定某个函数为初始化构造器,限制对象的创建接口,这点对于初始化封装内部必须实现的内部逻辑非常有

方法交换

Objective-C runtime 理论上会在加载和初始化类的时候调用两个类方法: load and initialize。在讲解 method swizzling 的原文中 Mattt 老师指出出于安全性和一致性的考虑,方法交叉过程 永远 会在 load() 方法中进行。每一个类在加载时只会调用一次 load 方法。另一方面,一个 initialize 方法可以被一个类和它所有的子类调用,比如说 UIViewController 的该方法,如果那个类没有被传递信息,那么它的 initialize 方法就永远不会被调用了。

IMPs
Selectors
OriginIMP
NewIMP
OriginSelector
NewSelector
  • Swift Load适配, 在 Swift 中 load 类方法永远不会被 runtime 调用,因此方法交叉就变成了不可能的事。但我们还有几个办法:
  1. 在initialize 中实现方法交叉,使用disptach_once加锁
  2. 定义一个全局的类统一调用,指定的类方法
  3. 通过获取所有的class列表,给swift类加上一个协议,通过反射获取.
    func load(){
        let classCount = Int(objc_getClassList(nil, 0))
        let types = UnsafeMutablePointer.allocate(capacity: classCount)
        objc_getClassList(AutoreleasingUnsafeMutablePointer.init(types), Int32(classCount))
        for index in 0..

元类/类/实例对象

元类: MetaClass, 元类是对类结构的描述,当前类的isA指针指向它的元类,包含了当前类所需的最基本的数据结构及objc运行时的数据结构.类可以在此基础上上扩展自己的方式和属性. super指向它的父类
类: 具有相似功能和行为特点的事务的集合,对抽象事物的描述,就像元类是对类结构的描述一样
实例对象: 它是根据类的描述创建出来的,可以有多个

下面是网上关于它们之间的关系非常经典的一张图:

Objective-C Runtime2.0(-)简介_第1张图片

验证方式


    //rootObject    NSObject *    0x600003d307e0    0x0000600003d307e0
    //isa    Class    NSObject    0x0000000103b56310 --> rootClass
    NSObject* rootObject = [[NSObject alloc]init];
    
    //rootClass    NSObject *    0x103b56310    0x0000000103b56310
    //isa    Class    0x103b562c0    0x0000000103b562c0 --> rootMetaClass
    NSObject* rootClass = [NSObject class];
 
    //rootSuperClass    NSObject *    nil    0x0000000000000000
    //isa    Class    0x0
    NSObject* rootSuperClass = [NSObject superclass];
    // rootMetaClass    Class    0x103b562c0    0x0000000103b562c0
    Class rootMetaClass = object_getClass(rootClass);
    
    // (NSObject *) $0 = 0x0000000103b562c0 = rootClass
    NSObject* rootMetaSuperClass = [rootMetaClass superclass];

    //superObject    Person *    0x600003d2c290    0x0000600003d2c290
    //isa    Class    Person    0x00000001035d0640 -> superClass
    NSObject* superObject = [[Person alloc]init];
    //superClass    NSObject *    0x1035d0640    0x00000001035d0640
    //isa    Class    0x1035d0618    0x00000001035d0618 --> superSuperClass
    NSObject* superClass = [Person class];
    //superSuperClass    NSObject *    0x103b56310    0x0000000103b56310
    //isa    Class    0x1035d0618    0x00000001035d0618
    NSObject* superSuperClass = [Person superclass];
    //(Class) superMetaClass = 0x00000001035d0618
    Class superMetaClass = object_getClass(superClass);
    
    //subObject    Student *    0x600003d30810    0x0000600003d30810
    //isa    Class    Student    0x00000001035d06e0 --> subClass
    NSObject* subObject = [[Student alloc]init];
    //subClass    NSObject *    0x10a06e6e0    0x000000010a06e6e0
    //isa    Class    0x10a06e6b8    0x000000010a06e6b8 --> subSuperClass
    NSObject* subClass = [Student class];
    //subSuperClass    NSObject *    0x1035d0640    0x00000001035d0640 -> subMetaClass
    //isa    Class    0x1035d0618    0x00000001035d0618
    NSObject* subSuperClass = [Student superclass];
    //(NSObject *) subSuperClass = 0x00000001035d0640
    Class subMetaClass = object_getClass(subClass);
    
  • objc中的最基础的对象,代表id类型,利用结构体首指针和对象指针转换的规律,可以看出所有类最终都能转换成objc_object,它定义了objc中类的最基本结构,内存管理,引用计数,isA指针处理,对象关联等
/// 代表了一个class实例,objc_object是Runtime中的元类,objc中的id类型默认就是它
struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY; //Class 为objc_class
    bool hasNonpointerIsa();
    bool isTaggedPointer();
    bool isBasicTaggedPointer();
    bool isExtTaggedPointer();
    bool isClass();

    // object may have associated objects?
    bool hasAssociatedObjects();
    void setHasAssociatedObjects();

    // object may be weakly referenced?
    bool isWeaklyReferenced();
    void setWeaklyReferenced_nolock();
    ...
};

/// A pointer to an instance of a class.
typedef struct objc_object *id;
  • Objc中的元类,所有类最终都指向它,isA指向它自己,同时它继承于objc_object,侧重于对类的属性,方法的管理
@interface NSObject  {
    Class isa;

struct objc_class : objc_object { 
   
    // Class ISA;
    Class superclass;           
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags

    bool isMetaClass() const { ...
        return data()->flags & RW_META;
    }

    bool isRootClass() {
        return getSuperclass() == nil;
    }
    bool isRootMetaclass() {
        return ISA() == (Class)this; //object_class
    } 
};
struct objc_super {
    /// Specifies an instance of a class.
    __unsafe_unretained _Nonnull id receiver;
    __unsafe_unretained _Nonnull Class super_class;
};

因为NSObject为OC中的基类,在NSObject.mm中它的isA是指向objc_class,isA指针

isa_t::setClass(Class cls, objc_object *obj)
{
    this->cls = cls;
}
inline void 
objc_object::initClassIsa(Class cls)
{
    initIsa(cls);
}

在objc收到dyld的loadImages完成的回调后开始加载类,将类转换为Class类型,并添加到全局的类表中,

* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 17.1
  * frame #0: 0x000000010079995c libobjc.A.dylib`add_class_to_loadable_list(cls=NSObject) at objc-loadmethod.mm:66:20
    frame #1: 0x00000001007a2040 libobjc.A.dylib`schedule_class_load(cls=NSObject) at objc-runtime-new.mm:3854:5
    frame #2: 0x00000001007a2037 libobjc.A.dylib`schedule_class_load(cls=__NSStackBlock__) at objc-runtime-new.mm:3852:5
    frame #3: 0x000000010079fda8 libobjc.A.dylib`prepare_load_methods(mhdr=0x00007ff818f25000) at objc-runtime-new.mm:3876:9
    frame #4: 0x000000010079fb9a libobjc.A.dylib`load_images(path="/usr/lib/system/libsystem_blocks.dylib", mh=0x00007ff818f25000) at objc-runtime-new.mm:3198:9
    frame #5: 0x0000000100023404 dyld`dyld4::RuntimeState::notifyObjCInit(dyld4::Loader const*) + 170
    frame #6: 0x000000010003c525 dyld`dyld4::APIs::runAllInitializersForMain() + 181
    frame #7: 0x000000010001a37d dyld`dyld4::prepare(dyld4::APIs&, dyld3::MachOAnalyzer const*) + 3443
    frame #8: 0x00000001000194d4 dyld`start + 388

classref_t转换为 object_class类型,所以所有objc的的类最终都会使用object_class结构体表示,class名字为object_class中定义的name

void prepare_load_methods(const headerType *mhdr)
{
    size_t count, i;

    runtimeLock.assertLocked();
  
    classref_t const *classlist = 
        _getObjc2NonlazyClassList(mhdr, &count);
    for (i = 0; i < count; i++) {
        schedule_class_load(remapClass(classlist[i]));
    }

你可能感兴趣的:(iOS,objective-c,xcode,cocoa)