IOS之Runtime笔记

最近看了些关于object-c(以下简称oc)的runtime文章,了解了些oc的原理,下面阐述下oc的一些机制

oc是一门简单的语言,95%是C。只是在语言层面上加了些关键字和语法。真正让oc如此强大的是它的运行时。它很小但却很强大。它的核心是消息分发。

  1. runtime是开源的。可以去down.

  2. runtime是(基本上,还有部分是汇编,反正很多都是各种宏,in my opinion)由C语言实现的。

  3. runtime的两个版本。(这个不知道要表达个什么,大概是些历史什么的)

归纳了下,oc的核心思想是消息机制,先说在这。

大多数面向对象的语言里有 classes 和 objects 的概念。Objects通过Classes生成。但是在Objective-C中,classes本身也是objects(译者注:这点跟python很像。证据就在类的定义里,有个isa,指向了另外一个类,也叫元类metaclasses),也可以处理消息,这也是为什么会有类方法和实例方法。具体来说,Objective-C中的Object是一个结构体(struct),第一个成员是isa,指向自己的class。这是在objc/objc.h中定义的。

这里引入一段精辟的看不大清楚的话

(Objective-C is a class-based object system. Each object is an instance of some class; the object's isa pointer points to its class. That class describes the object's data: allocation size and ivar types and layout. The class also describes the object's behavior: the selectors it responds to and instance methods it implements.)

Object的class保存了方法列表,还有指向父类的指针。但classes也是objects,也会有isa变量,那么它又指向哪儿呢?这里就引出了第三个类型: metaclasses。一个 metaclass被指向class,class被指向object。它保存了所有实现的方法列表,以及父类的metaclass

这个的定义在runtime.h里:

struct objc_class {

Class isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__

Class super_class                                        OBJC2_UNAVAILABLE;

const char *name                                         OBJC2_UNAVAILABLE;

long version                                             OBJC2_UNAVAILABLE;

long info                                                OBJC2_UNAVAILABLE;

long instance_size                                       OBJC2_UNAVAILABLE;

struct objc_ivar_list *ivars                             OBJC2_UNAVAILABLE;

struct objc_method_list **methodLists                    OBJC2_UNAVAILABLE;

struct objc_cache *cache                                 OBJC2_UNAVAILABLE;

struct objc_protocol_list *protocols                     OBJC2_UNAVAILABLE;

#endif

} OBJC2_UNAVAILABLE;

从中可以清楚的看到一个Class到底里面是些什么东西,isa就是指向元类的类指针,父类,名字,还有一堆我们关心的方法,协议,缓存数据等(还有几个,我不好意思说)。

可以看到方法协议,什么的都用的还是struct。

下面我捡一些好听的来说。

当然我们肯定关心的是方法,首先看下方法列表的定义:

struct objc_method_list {

struct objc_method_list *obsolete                        OBJC2_UNAVAILABLE;

int method_count                                         OBJC2_UNAVAILABLE;

#ifdef __LP64__

int space                                                OBJC2_UNAVAILABLE;

#endif

/* variable length structure */

struct objc_method method_list[1]                        OBJC2_UNAVAILABLE;

}                                                            OBJC2_UNAVAILABLE;

大概是什么捏,我也看不大明白,没事继续看下方法的具体定义:

struct objc_method {
SEL method_name                                          OBJC2_UNAVAILABLE;

char *method_types                                       OBJC2_UNAVAILABLE;

IMP method_imp                                           OBJC2_UNAVAILABLE;

}                                                            OBJC2_UNAVAILABLE;

这下差不多了,这是具体方法的定义。这个内部的剖析,等下在说,先绕开往下说下objc_cache。

看看objc_cache的定义:

struct objc_cache {

unsigned int mask /* total = mask + 1 */                 OBJC2_UNAVAILABLE;

unsigned int occupied                                    OBJC2_UNAVAILABLE;

Method buckets[1]                                        OBJC2_UNAVAILABLE;

} /* GrP fixme should be OBJC2_UNAVAILABLE, but isn't because of spurious warnings in [super ...] calls */;

等下,这有个Method有点蕾丝objc_method哈,于是找到这个定义:

typedef struct objc_method *Method;

联系上鸟。

好吧,不绕了,讲讲方法吧,这里也就是上面要涉及但憋回去的消息机制。

说起方法就不得不说他的机制,oc里的方法就是消息,Look:

NSMutableArray *arr = [NSMutableArray arrayWithObjects:@"dd", nil];

[arr addObject:@"k"];

objc_msgSend(arr, @selector(addObject:),@"pp");

NSLog(@"arr:%@",arr);

他们可以等价,调用array的insertObject的方法,参数分别是foo,5。上面就是典型的oc试,下面就是等价的消息模式,亲侧可行.

跑了有点裤子穿偏了,回过神来继续说这个objc_method,逐个说下

SEL 类成员方法的指针:

可以理解 @selector()就是取类方法的编号,他的行为基本可以等同C语言的中函数指针,只不过C语言中,可以把函数名直接赋给一个函数指针,而Object-C的类不能直接应用函数指针,这样只能做一个@selector语法来取.

可以用这个NSSelectorFromString直接取,NSStringFromSelector反转。

简单说就是函数的地址,只不过函数的地址和函数实现的地址不在同一个地方。

method_types 描述方法的参数列表. 在运行时注册选择器时使用.那时候方法名就会包含方法的参数列表

method_imp 之 IMP 是”implementation”的缩写,它是objetive-C 方法(method)实现代码块的地址,可像C函数一样直接调用。通常情况下我们是通过[object method:parameter]或objc_msgSend()的方式向对象发送消息,然后Objective-C运行时(Objective-C runtime)寻找匹配此消息的IMP,然后调用它;但有些时候我们希望获取到IMP进行直接调用。

简单来说method_imp就是函数实现的代码块地址。

方法大概就这些,接下来就说下,对象是怎么找到方法的,这里涉及到一些原理,我们可以假装不知道的。

当一个对象去执行某一个方法的时候,首先去是cache里找method,因为这个最快(他们都是存到内存中的hash表里的),当找不到的时候才去那个objc_method_list里找,如果还没有就去super_class里找,这样循环到根上也就是NSObject,还是没有就不好意思crash了。

类方法也一样,先去元类里找,找不到就去元类的父亲哪找,直到找到NSObject的元类,还是找不到就再去找NSObject的父亲哪,但NSObject是root类了,他的父亲就是他自己,就回到NSObject里方法列表里找,找不到就crash了。

我们在这里扩展下Category:

这里我们要引申下,oc里类的成员变量,方法都是存在类固定的一个位置。

我们重温下类的定义那,方法是放在objc_method_list这个地址指向的struct里。

struct objc_method_list **methodLists

而成员变量则是存在objc_ivar_list这个struct里。

struct objc_ivar_list *ivars

这里有个伏笔,就是这个类的大小,是由其他部分+成员变量struct的大小(注意他是成员变量的所有大小之和)和方法列表指针的地址组成(他仅仅是一个地址,才不管地址指向的struct里的大小)。

btw:由此可以看出成员变量,都可以通过对象的地址+成员变量的偏移量来找到。

类在runtime的时候分配内存就是根据这些元素分配一个对应大小的空间,一旦编译完成,就固定了。

这就是为什么Category不能动态添加成员变量,但可以添加方法的原因,因为成员变量实际上参与了类大小的容积,而方法却没有,只有一个指向他们的指针参与了。

联合存储(Associated)这个东西,虽然表面上可以添加属性,但实际上他和成员变量不是一个概念,他仅仅是存在类的一个hashmap的单例里,不想成员变量直接存在objc_ivar_list里,效率也肯定不及成员变量的存取速度,他们是直接通过类地址+偏移量获取,而联合存储的东西,则要在hashmap里去遍历,所以联合存储不宜滥用。

接下来说下平常coding有用的东西了

class开头的方法是用来修改和自省classes。方法如class_addIvar, class_addMethod, class_addProperty和class_addProtocol允许重建classes。class_copyIvarList, class_copyMethodList, class_copyProtocolList和class_copyPropertyList能拿到一个class的所有内容。而class_getClassMethod, class_getClassVariable, class_getInstanceMethod, class_getInstanceVariable, class_getMethodImplementation和class_getProperty返回单个内容。

假设我要取回去某个对象的属性或者方法:

uint attrCount = 0,selfCount = 0;

objc_property_t *attrs = class_copyPropertyList([self class], &attrCount);

Method *method =  class_copyMethodList([self class], &selfCount);

for (int i=0;i<=count;++i) {

    objc_property_t property = attrs[i];

    NSLog(@"key:%s,value:%@",property_getName(property),[self valueForKey:[NSString stringWithFormat:@"%s",property_getName(property)]]);

}

for (int i=0;i<=count1;++i) {

SEL sel = method_getName(method[i]);

IMP imp = method_getImplementation(method[i]);

NSLog(@"sel:%s",sel_getName(sel));

}

这些都不算啥,现在说点动态的东西,比如动态添加属性,动态添加方法。

这里其实我们需要用class_addMethod就可以了。

这里有必要说下

+(BOOL)resolveInstanceMethod:(SEL)sel;

+(BOOL)resolveClassMethod:(SEL)sel;

字面上来说,一个是对象方法,一个是类方法,都是消息转发机制前调用的。

意思是如果我们劫持重写这个,就可以添加一切我们想要的方法。

下面拿一个世界上最干净的类之一来说事:

@interface DB : NSObject

@end

@implementation DB

@end

如果我现在

DB *d = [DB new];

[d alert];

有人要说我傻x了,好吧,我牺牲自己引入下文:

对,我们上面说到了劫持,所以我就继承了这个方法,并重写了下

ex:

//这个就是alert函数实现的地方。

ps:这里得多说几句,第一个参数是消息的发起方,就对象,self就是方法的指针。必须的,后面的参数就跟着依次。

void define IMPForAlert(id self,SEL _cmd){

  NSLog(@"xxxx");

}

ps:这里可以通过重写这个,当然也可以在外面直接用class_addMethod.

+(BOOL)resolveInstanceMethod:(SEL)sel{

//当在调用alert的时候,会自动添加这个方法进去。

if(sel == @selector(alert)){

//这里就劫持了并添加并实现了我想要的方法。最后那个参数是个奇葩见(1)说明。

class_addMethod([self class],sel,(IMP)defineIMPForAlert,[@"v@:" UTF8String]);

return YES;

}

return [super resolveClassMethod:sel];

}

哈。我的对象就具备了alert的方法。也就是说我的对象就可以来发送alert的消息。好吧,就是说我[d performSelector:@selector(alert)];就合法了(注意这里不能直接写[d alert],因为我那个是动态添加的应该是说要在运行的时候才有alert方法,所以IDE并不知道,编译也不会让你过)。尽管我什么alert都没定义。

记得上面说过属性也可以,是的,属性也可以。但网上的栗子都没成熟,所以,我就先放一个我测试过了的(看起来有点不舒服的栗子)。


NSString* name(id self,SEL _cmd){

return objc_getAssociatedObject(self, @"name");

}

void setName(id self,SEL _cmd,NSString *name){

objc_setAssociatedObject(self, @"name", name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

}

class_addMethod([DB class],@selector(name),(IMP)name,[@"@@:" UTF8String]);

class_addMethod([DB class],@selector(setName:),(IMP)setName,[@"v@:@" UTF8String]);

这样我就可以

[d performSelector:@selector(setName:) withObject:@"ddd"];

NSLog(@"self.name:%@",[d performSelector:@selector(name)]);

下面是些扩展用法。

@interface CC : NSObject

-(void)say;

-(void)sing;

@end

@implementation CC

-(void)say{

NSLog(@"os");

}

-(void)sing{

NSLog(@"sn");

}

@end

BB *b = [BB new];

//我让b去继承cc的

object_setClass(b, [CC class]); //实现方法。见(2)

//于是b就有了say的方法

[b performSelector:@selector(say)];

CC *c = [CC new];

//获取say的方法

Method methodSay = class_getInstanceMethod([c class], @selector(say));

Method methodSing = class_getInstanceMethod([c class], @selector(sing));

//交换2个方法

method_exchangeImplementations(methodSay, methodSing); //实现方法。见(3)

//于是有了结果的变化

[c say];

[c sing];

//b也跟着变了

[b performSelector:@selector(say)];

以上。

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

推荐阅读更多精彩内容