Objective-C的runtime system

刚学OC的时候,不知道runtime这个东西,也不知道动态是什么概念。后来知道了runtime,但是看了一眼,什么class结构体、isa指针,不是说是“运行时”吗,这些跟运行什么关系?根本无法理解runtime是个什么东西,就是大脑里完全无法建立概念。

学OpenGL,一开始接触到“管线”这个词,看到说绘制管线,脑子里想到的是一根根的线、电路之类的,和图形绘制什么关系?后来理解,其实是“流水线”,绘制图形的过程像一个工厂流水线一样,流程基本是固定,但我们可以通过脚本对流程的一个个环节做处理。

同样,runtime的理解,我觉得不要受“运行时”这个翻译影响,也不要管“runtime”这个单词(至少一开始不要管)。首先定性,runtime是一个库、一个系统。runtime这个名字,只是这个系统要干的事,它的目的。

文档里介绍runtime的第一句话:

The Objective-C language defers as many decisions as it can from compile time and link time to runtime. Whenever possible, it does things dynamically. This means that the language requires not just a compiler, but also a runtime system to execute the compiled code. The runtime system acts as a kind of operating system for the Objective-C language; it’s what makes the language work.

OC想要把决定从编译期推迟到运行期,就是尽可能的动态。这是OC的特性,而runtime就是用来让OC具有这样的性质,OC是建立在runtime之上的,而runtime之下是C。

我的理解是,C本身不是动态的,也不适合面向对象的编程,那我想要有一个动态的语言,怎么办?我来弄一个库,它里面有一些数据结构,有一些方法,可以在C的基础之上构建一个新的语言,让我可以享受想要的这些性质。所以runtime是一个中间层,连接着C和OC。

所以看了runtime的代码后,会了解类、对象等等本质上是啥,了解OC里做的处理实际是干了什么,至少是一定程度的本质。

类,实际是一个结构体,它里面有变量保存父类,有保存方法列表、变量列表等等;对象也是一个结构体,只不过有个变量指向它的类结构体。而调用方法,实际是调用消息发送。

通过runtime里的方法,可以获取类的属性、方法,可以添加方法,甚至可以更改对象的类。一个对象的类是什么,就是它的isa变量存的class类型的变量的值,那把值改掉就好了。

runtime的存在,是用来支撑OC的运行和它的特性的,而不是用来帮助我们写iOS程序的,至少这不是它该有的意义。比如修改一个对象的类,可以做到,这种事有必要吗?还有,可以修改一个method的函数实现(IMP),让你代码里写的是调用A方法,但实际执行的是B函数。这样不就违背了原本面向对象的编程了?类A的对象A1,最后运行起来类B的方法,这样不是瞎搞?

但又觉得如果编程用不到,那么OC这么搞有什么意义?或许我该想想动态的目的,为什么要尽力在运行时做决定?这一切都是为了这个目的而设计的吧!

//9.13更,runtime库的函数们

画了张图

以类为核心,类可以构建生成对象;而类本身具有方法、成员变量和属性;然后,我们可以通过类别和协议给类在cocoa层面添加属性和变量。由这些行为,把主要的类型关联起来,即类、方法、成员变量、属性、类别、协议。

1.类和对象的关联

类的结构原型:

typedef struct objc_class *Class;
struct objc_class {
    Class isa  OBJC_ISA_AVAILABILITY; //指向元类metaClass

#if !__OBJC2__
    Class super_class     //父类                                  
    const char *name      //名字                            
    long version                                          
    long info                                              
    long instance_size                                     
    struct objc_ivar_list *ivars      //变量列表                   
    struct objc_method_list **methodLists       //方法列表           
    struct objc_cache *cache       //方法调用缓存                          
    struct objc_protocol_list *protocols        //协议列表            
#endif

} OBJC2_UNAVAILABLE;

对象原型:

struct objc_object {
    Class isa  OBJC_ISA_AVAILABILITY;
};

对象的isa 指向它自身的类,也就是由这个isa把类和对象关联起来。
而类本身也有isa变量,这个是指向metaClass的,对于一个类,实例方法存放自身的方法列表里,类方法存放在metadClass的方法列表里;成员变量也是一样。

类和它的父类是通过superclass关联起来,这样类-父类-对象整个的都关联起来了。

2.方法调用机制:
对于[A methodB:xxx],是怎么一个过程?
方法调用实际是给对象发送消息,

[A methodB:xxx]

就是,会把对象作为第一个参数,方法名构建SEL作为第二个参数,如有更多参数放在后面。

objc_msgSend(A, @selector(methodB:))

A是对象,可以由A取到它的类,然后取到方法列表,匹配方法,找到了调用,找不到到父类中继续找;用代码过程类似这样:

 NSObject *A = [[NSObject alloc]init]; //调用方法的对象
    SEL selector ; //调用的方法名构建的SEL
    
    Class classA = object_getClass(A);//获取对象的类
    IMP findFunc = NULL;
    while (classA) {
        
        IMP func = class_getMethodImplementation(classA, selector);
        if (func) {
            findFunc = func;
            break;
        }
        
        /*
         //或者,获取method再获取IMP;method有实例方法和类方法之分,分成两个函数分别获取
         Method m = class_getInstanceMethod(classA, selector);
         if (m) {
         findFunc = method_getImplementation(m);
         break;
         }
         m = class_getClassMethod(classA, selector);
         if (m) {
         findFunc = method_getImplementation(m);
         break;
         }
         */
        classA = class_getSuperclass(classA); //获取父类
    }

然后,为什么要获取IMP?
首先IMP是method的一部分,

struct objc_method {
    SEL method_name                                          OBJC2_UNAVAILABLE;
    char *method_types                                       OBJC2_UNAVAILABLE;
    IMP method_imp                                           OBJC2_UNAVAILABLE;
}                                                            OBJC2_UNAVAILABLE;

然后IMP本身:

// A pointer to the function of a method implementation. 
#if !OBJC_OLD_DISPATCH_PROTOTYPES
typedef void (*IMP)(void /* id, SEL, ... */ ); 
#else
typedef id (*IMP)(id, SEL, ...); 
#endif

这个宏没研究,运行试了下,是下面一种定义。
也就是IMP本质是一个函数指针,而且,第一个参数是调用这个方法的对象,第二个参数是这个方法的SEL(objc_method中的method_name,也是消息发送里的selector).
使用IMP类似这样:

typedef  id(*myFunc)(id object,SEL selector,...);
    myFunc func3 = (id (*)(id,SEL,...))findFunc;
    func3(A,selector);

所以对于一个方法objc_method,本身包含3部分,一个方法名,一个参数类型列表,和一个实际的函数实现。

所以如果把一个方法的IMP更改了,表面上,你还是在调用[A methodB:xxx],但实际执行的代码变成了另一个。在某些特殊情况下会用到吧,比如看不了别人源码时,把执行过程替换成自己的......

3.获取、修改属性和变量
对于有多个的成员,像属性、变量、协议,都会提供两个方法,一个是根据名字获取特定的,一个是返回所有的列表,如:

objc_property_t pro = class_getProperty(classA, "xxx");
   unsigned int count; //count用来返回个数
   objc_property_t* proList = class_copyPropertyList(classA, &count);

修改添加:

objc_property_attribute_t attri;
        unsigned int count;
        class_addProperty(classA, "name", &attri, count);
        class_replaceProperty(classA, "name",&attri, count);

objc_property_attribute_t是个结构体,包含name,value两个变量;
找个类测试了一下:

//类Book的一个属性:
@property (nonatomic,copy) NSString* name;

objc_property_t p = class_getProperty([Book class], "name");
    unsigned int count;
    objc_property_attribute_t *attri = property_copyAttributeList(p, &count);
    
    for (int i = 0; i<count; i++) {
        printf("%s = %s\n",attri[i].name,attri[i].value);
    }

输出结果为:

T = @"NSString"
C = 
N = 
V = _name

开头必须为T,值是属性的名字,"@"在Type Encodings里代表对象,如果是int那类型就是“i”; 结尾必须是V,值为属性的变量名(属性名为name,实际生成变量名是_name);其他的在文档的Declared Properties有详细说明。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,980评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,178评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,868评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,498评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,492评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,521评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,910评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,569评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,793评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,559评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,639评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,342评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,931评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,904评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,144评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,833评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,350评论 2 342

推荐阅读更多精彩内容