Objective-C Runtime(一)

Objective-c runtime (一)

一 运行时系统相关特性

  • 使用Objective-C中的消息传递特性可以调用类和对象中的方法。对象消息传递是一种动态特性,既接收器和接收器中被调用的方法是在运行时确定的。
  • 消息传递表达式包含接收器(接受消息的对象/类)和消息,而消息又由选择器和相应的输入参数构成。如图。


  • 选择器是一种分段的文本字符串,每个分段以冒号结尾并且后跟参数。如上图。
  • 选择器数据类型(SEL)是一种特殊的Objective-C数据类型,它用于在编译源代码时使用具有唯一性的标志符替换选择器。使用 @selector关键字或Foundation框架中的NSSelectorFromString()函数,可以创建类型为 SEL的变量。
  • 使用动态类型功能可以在运行时决定对象的类型,因而能够由运行时因素决定在程序中使用哪种类型的对象,Objective-C通过id数据类型支持动态类型。
  • 使用动态方法决议能够以动态方式实现方法。使用Objective-C@dynamic指令可以告知编译器与某个属性关联的方法会以动态方式实现。NSObject中含有resolveInstanceMethod:resolveClassMethod:方法,使用他们能够以动态方式分别实现由选择器指定的实例和类方法。
  • Foundation框架中NSObject类的API含有非常多用于执行对象内省的方法。在运行程序时,这些API能够以动态方式查询方法的信息。它们还可以测试方法的继承性、行为和一致性。

二 运行时系统的结构

运行时系统的组成部分

Objective-C 的运行时系统由两个主要部分构成:编译器和运行时系统库。

编译器

Objective-C语言中的面向对象元素和动态特性都是通过运行时系统实现的。概括来讲,运行时系统由下列部分组成:

  • 类元素(接口、实现代码、协议、分类、方法、属性、实例变量)
    • 类实例(对象)
    • 对象消息传递(包括动态类型和动态绑定)
    • 动态方法决议
    • 动态加载
    • 对象内省
      当编译器解析使用了这些语言元素和特性的Objective-C源代码时,它会使用适当的运行时系统库数据结构和实现该语言特定行为的函数,生成可执行代码。例如
  1. 生成对象消息传递代码

    当编译解析对象消息时,如:
    [obj show]
    它会生成调用运行时系统库中函数objc_msgSend()的代码。该函数将接收器、选择器和消息传递的参数作为输入参数。因此编译器会将源代码中的所有消息传递表达式转换为调用运行时系统库函数objc_msgSend()的代码。
    源文件1.m内容如下:
    #import <Foundation/Foundation.h>

    @interface MyClass:NSObject
    
    - (void)show;
    @end
    
    @implementation MyClass
    
    - (void)show{
        NSLog(@"%@", @"show");
    }
    
    @end
    
    
    int main(int argc, char *argv[]) {
        @autoreleasepool {
            MyClass *obj = [[MyClass alloc] init];
            
            [obj show];
        }
    }
    

    经过clang -rewrite-objc 1.m会得到编译后的cpp文件,相关内容如下:

  2. **生成类和对象的代码
    当编译器解析含有类定义和对象的源代码时,它会生成相应的运行时数据结构。
    Objective-C中的类与运行时系统中的Class数据结构对应。Class是指向objc_class不透明数据类型的指针。
    typedef struct objc_class Class
    Objective-C对形象与运行时系统中的objc_object数据结构对应。
    struct objc_object
    {
    Class isa;
    //含有实例变量的长度可变数据
    }
    实际上所有的Objective-C对象的类和对象对应的运行时数据结构(objc_objectobjc_class)都是以isa指针开头(对应的运行时数据结构的包含的第一个元素是isa)。
    下面的实例程序可以验证。
    #import <Foundation/Foundation.h>
    #import "LZPerson.h"
    #import <objc/message.h>
    #import <objc/runtime.h>
    @interface LZDog: NSObject{
    NSInteger _age;
    }

    @end
    
    @implementation LZDog
    
    - (instancetype)initWithAge:(NSInteger) age{
        
        self = [super init];
        if (self) {
            _age = age;
        }
        return self;
    }
    
    @end
        
    
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            LZDog *dog1 = [[LZDog alloc] initWithAge:0x123456780a0b0c0d];
            
            LZDog *dog2 = [[LZDog alloc] initWithAge:0xaabbccdd12345678];
            //get the objec size
            NSInteger objcSize = class_getInstanceSize([LZDog class]);
            
            //打印对象的内存数据
            NSData *dog1Data = [NSData dataWithBytes:(__bridge void*)dog1 length:objcSize];
            
            NSData *dog2Data = [NSData dataWithBytes:(__bridge void*)dog2 length:objcSize];
            
            //查看对象内存
            NSLog(@"dog1: %@", dog1Data);
            NSLog(@"dog2: %@", dog2Data);
            //打印类地址
            NSLog(@"class address:%p", [LZDog class]);
            
            //查看类内存
            id dogClass = objc_getClass("LZDog");
            NSInteger classSize = class_getInstanceSize([dogClass class]);
            
            NSData *classData = [NSData dataWithBytes:(__bridge void*)dogClass length:classSize];
            
            NSLog(@"dogclass: %@", classData);
            
            //打印父类(NSobject)地址
            NSLog(@"superclass address: %p", [LZDog superclass]);
            
            
            
        }
       
        return 0;
    }
    

    运行结果如下:
    2015-10-04 11:25:01.297 testRuntime01[1186:87309] dog1: <78240000 01000000 0d0c0b0a 78563412>
    2015-10-04 11:25:01.298 testRuntime01[1186:87309] dog2: <78240000 01000000 78563412 ddccbbaa>
    2015-10-04 11:25:01.298 testRuntime01[1186:87309] class address:0x100002478
    2015-10-04 11:25:01.298 testRuntime01[1186:87309] dogclass: <a0240000 01000000 f0300c78 ff7f0000>
    2015-10-04 11:25:01.298 testRuntime01[1186:87309] superclass address: 0x7fff780c30f0
    分析结果如图:


运行时系统库

苹果公司提供的Objective-C运行时系统库实现了Objective-C的面向对象特性和动态属性。我们可以在代码中直接运用运行时系统提供的API。下面代码实例展示了如何在代码中使用运行时系统API。
import <Foundation/Foundation.h>
import "LZPerson.h"
import <objc/message.h>
import <objc/runtime.h>

/*
1.  动态创建一个类 LZStudent(继承自NSObject)
2.  动态添加实例变量 name
3.  动态添加成员方法 show
 */

static void show(id self, SEL _cmd){
NSLog(@"show");
}

int main(int argc, const char * argv[]) {
@autoreleasepool {
//创建一个类,继承自NSobject
  Class LZStudent =   objc_allocateClassPair([NSObject class], "LZStudent", 0);

BOOL isOk;
//向该类添加一个实例变量 NSString *_name
isOk = class_addIvar(LZStudent, "_name", sizeof(NSString *), log2(sizeof(id)), @encode(NSString));

if (!isOk) {
NSLog(@"addivar fail");
}
//想该类添加一个实例方法。
isOk = class_addMethod(LZStudent, NSSelectorFromString(@"show"), (IMP)show, "v@:");

if (!isOk) {
NSLog(@"addMethod fail");
}
objc_registerClassPair(LZStudent);

unsigned int count ;
Ivar *arr = class_copyIvarList(LZStudent, &count);
//打印类变量的个数
NSLog(@"variable count: %u", count);
//打印变量的名字和类型
for (NSInteger i = 0; i < count; i ++) {
const char* ivarName = ivar_getName(arr[i]);
const char* ivarType = ivar_getTypeEncoding(arr[i]);
NSLog(@"name: %s, type: %s", ivarName, ivarType);
}


id obj = [[LZStudent alloc] init];
[obj performSelector:NSSelectorFromString(@"show")];
}
   
return 0;
}

运行结果如下
2015-10-04 15:25:19.057 testRuntime01[1537:152205] variable count: 1
2015-10-04 15:25:19.058 testRuntime01[1537:152205] name: _name, type: {NSString=#}
2015-10-04 15:25:19.058 testRuntime01[1537:152205] show

运行时系统库数据类型和函数为运行时系统库提供了实现各种Objective-C特性(如消息传递)所必须的数据类型核函数。如图


当程序想对象发送消息时,运行时系统会通过自定义代码中的类方法缓存和虚函数表,查找类的实例方法。为了找到相应的方法运行时系统会搜索整个类的层次结构,找到方法后,它就会执行方法的实现代码。运行时系统库含有非常多用于实现消息传递的设计机制。下面分别介绍这几个机制:

  1. 通过虚函数表查找方法
    运行时系统定义了一种方法数据类型(objc_method)代码如下
    struct objc_method
    {
    SEL method_name;
    char *method_type;
    IMP method_imp;
    }
    typedef struct objc_method Method;
    因为在执行程序的过程中调用方法的操作可能执行数百万次,所以运行时系统使用了一种快速高效的方法查询和调用机制。虚函数表是运行时系统使用的动态绑定的支持机制。Objective-C运行时系统实现了一种自定义的虚函数分派机制,专门用于最大限度的提高性能和灵活性。
    虚函数表是一个用于存储IMP (Objective-C方法的实现代码)的数据数组。每个运行时系统类实例(obcj_class)都有一个纸向虚函数表的指针。
    每个尅实例还拥有最近调用过方法的指针缓存。运行时系统库先搜索缓存,如果没找到,再去虚函数表去查找,如果在虚函数表找到了就将IMP存储到缓存中备用。
  2. 消息分派
    当我们向对象发送消息时比如[obj message ],编译器会根据对象找到对应的类,然后从类(及其父类)的缓存和虚函数表(参考1)中查找,并将其转换为一条标准的C语言函数调用objc_msgSend()()。如果没有找到符合的方法,那就执行消息转发。
    消息转发分为两大阶段。第一阶段先征询接受者所属的类,看其能否动态添加方法,这叫做动态方法决议(resolveInstanceMethod:)。如果我们没有动态的添加方法实现那么热就会进入第二阶段。首先运行时系统请接受着看看能否将消息转发给其他的对象,如能则该消息有其他对象处理。这成为快速转发(forwardingTargetForSelector:)。如果没有合适的对象处理消息,运行时系统会把与消息有关的全部细节都封装到NSInvocation中,再给接受者最后一次机会。整个过程如图所示。

    示例代码
  3. 访问类实例方法
    当我们向类发送消息时,如[NSobject alloc],运行时系统是通过元类实现的。
    元类是一种特殊的类对象,运行时系统使用其中含有的信息能够找到并调用类方法。每个类都有一个独一无二的元类,元类中存放着类的类方法列表(类的实例方法存放在类里,类方法列表存放在元类那里)。
    Objective-C的对象的isa指针指向类,类的isa指向元类。
    综上所述运行时系统通过下列方式对实例和类方法执行消息传递操作。
    • 当向对象发送消息时,会通过类的缓存和虚函数表,获得相应的方法。
    • 当向类发送消息时, 会通过元类的缓存和虚函数表,获得相应的方法。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 151,688评论 1 330
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 64,559评论 1 273
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 101,749评论 0 226
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 42,581评论 0 191
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 50,741评论 3 271
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 39,684评论 1 192
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,122评论 2 292
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 29,847评论 0 182
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,441评论 0 228
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 29,939评论 2 232
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,333评论 1 242
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 27,783评论 2 236
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,275评论 3 220
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,830评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,444评论 0 180
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 34,553评论 2 249
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 34,618评论 2 249

推荐阅读更多精彩内容