runtime专题

前言:Objective C语言把能在编译期做的事情就推迟到运行期再决定。这就意味着,Objective C不仅需要一个编译器,而且需要一个运行期环境。这个运行期环境就是Runtime。

1.类和对象

1.类的数据结构

//objc.h文件

typedef struct objc_class *Class;

//runtime.h文件

struct objc_class {

   Class isa  OBJC_ISA_AVAILABILITY;  //指向meta-class(元类)

#if!__OBJC2__    

Class super_class    OBJC2_UNAVAILABLE;  //父类,如果是根类,则为NULL

const char*name     OBJC2_UNAVAILABLE;//类名

long version             OBJC2_UNAVAILABLE;//类的版本信息,默认为0

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;

  //用于缓存最近使用的方法。一个接收者对象接收到一个消息时,它会根据isa指针去查找能够响应这个消息的对象。

//在实际使用中,这个对象只有一部分方法是常用的,很多方法很少用或者根本用不上。这种情况下,如果每次消息来时都是methodLists中遍历一遍,性能势必很差。这时,cache就派上用场了。在我们每次调用过一个方法后,这个方法就会被缓存到cache列表中,下次调用的时候runtime就会优先去cache中查找,如果cache没有,才到methodLists中查找方法。

struct objc_protocol_list *protocols      OBJC2_UNAVAILABLE;//协议链表

#endif

} OBJC2_UNAVAILABLE;

cache的结构:

struct objc_cache {

    //指定分配的缓存bucket的总数unsignedintmask/* total = mask + 1 */                OBJC2_UNAVAILABLE;

    //指定实际占用的缓存bucket的总数unsignedint occupied                                    OBJC2_UNAVAILABLE;

    //指向Method数据结构指针的数组Method buckets[1]                                        OBJC2_UNAVAILABLE;

};

2.对象

struct objc_object {

    Class isa  OBJC_ISA_AVAILABILITY;

};/// A pointer to an instance of a class.

typedef struct  objc_object * id;

objc_object结构体也只是一个指向其类的isa指针;这样,当我们向一个Objective-C对象发送消息时,运行时库会根据实例对象的isa指针找到这个实例对象所属的类;runtime库会在类的方法列表及父类的方法列表中去寻找与消息对应的selector指向的方法,找到后运行这个方法。


类和对象的结构图

2.方法查找过程

Objective-C是动态语言,每个方法在运行时会被动态转为消息发送一个对象的方法  [obj test],编译器转成消息发送objc_msgSend(obj, test),Runtime时执行的流程是这样的:

1.首先,通过objisa指针找到它的class;

2.查找是否存在对应的方法缓存,如果存在直接返回调用

为了优化性能,方法的缓存使用了散列表的方式

3.未找到缓存,到类本身或顺着类结构向上查找方法实现,返回的method_t *类型也被命名为Method,如果在这个步骤中找到了方法的实现,那么将它加入到方法缓存中以便下次调用能快速找到(objc在向一个对象发送消息时,runtime库会根据对象的isa指针找到该对象实际所属的类)

3.未找到任何方法实现,触发消息转发机制进行最后补救

3.消息转发机制:

具体过程如下:

 Monkey*monkey = [[Monkeyalloc]init];

 ((void(*)(id,SEL))objc_msgSend)(monkey,@selector(fly));

1、动态方法解析,先调用resolveInstanceMethod方法,如果你使用class_addMethod方法可完成消息转发:

+(BOOL)resolveInstanceMethod:(SEL)sel{

class_addMethod(self, sel, class_getMethodImplementation(self, sel_registerName("jump")), "v");

    return [super resolveInstanceMethod:sel];

}

2、备援接收者 如果依然没有找到就调用forwardingTargetForSelector,如果返回一个正确的对象也可以完成转发:

-(id)forwardingTargetForSelector:(SEL)aSelector{

   return [[Bird alloc]init];

}

3、完整的消息转发,当运行时系统检测到第二步中用户未返回能处理相应选择子的对象时,那么来到这一步就要启动完整的消息转发机制了。该方法可以改变消息调用目标,运行时系统根据所改变的调用目标,向调用目标方法列表中查询对应方法的实现并实现跳转,这种方式和第二步的操作非常相似。当然你也可以修改方法的选择子,亦或者向所调用方法中追加一个参数等来跳转到相关方法的实现。

最后,如果消息转发的第三步还未能处理该未知选择子的话,那么最终会调用NSObject类的如下方法用以异常的抛出,表明该选择子最终未能处理。

- (void)doesNotRecognizeSelector:(SEL)aSelector;

注意:第三步首先要使用forwardInvocation必须先要进行方法签名,系统将方法签名封装成NSinvocation

-(NSMethodSignature*)methodSignatureForSelector:(SEL)aSelector{

     //获取方法签名进入下一步,进行消息转发

    return [NSMethodSignature signatureWithObjCTypes:"v@:"];

}

- (void)forwardInvocation:(NSInvocation *)anInvocation{

}

forwardInvocation

过程图解如下:

消息转发过程

4.常用的名词:

SEL:

typedef struct objc_selector *SEL;  SEL是指向一个C String的指针,SEL只是方法编号。

[myObject addObject:yourObject]将翻译为objc_msgSend(myObject, 12, yourObject)(假设 addObject 的 selector 为 12);objec_msgSend()函数将会使用 myObjec 的 isa 指针来找到 myObject 的类空间结构并 在类空间结构中查找 selector 12 所对应的方法的IMP

SEL selector = NSStringFromSelector(@selector(fristview);

 IMP imp = [self methodForSelector:selector];

void (*func)(id,SEL) = (void *)imp;

 func(self,selector);

IMP:

IMP-指向实际执行函数体的函数指针

为什么不直接获得函数指针,而要从SEL这个编号走一圈再回到函数指针呢?

有了SEl的这个中间过程,可以对一个编号和什么方法映射做些操作,也就是说一个SEL可以指向不同的函数指针,这样就完成可以一个方法名在不同时候对应不同的函数体;;另外可以将SEL作为参数传递给不同的类执行.也就是说某些业务我们 只知道方法名但是要根据不同情况让不同的类执行的时候,SEL可以帮助我们.

#if !OBJC_OLD_DISPATCH_PROTOTYPES

typedef void (*IMP)(void /* id, SEL, ... */ ); 

#else 

typedef id (*IMP)(id, SEL, ...); 

#endif

_cmd:

SEL 类型的一个变量,Objective C的函数的前两个隐藏参数为self 和 _cmd

method:

指向Objective C中的方法的指针

typedef struct objc_method *Method;

struct objc_method {

    SEL method_name                                          OBJC2_UNAVAILABLE;

    char *method_types                                      OBJC2_UNAVAILABLE;

    IMP method_imp                                          OBJC2_UNAVAILABLE;

}     

Ivar:

属性(property),成员变量(ivar)的关系:

property = ivar + getter + setter;

objective C中的实例变量

typedef struct objc_ivar *Ivar;

struct objc_ivar {

    char *ivar_name                                          OBJC2_UNAVAILABLE;

    char *ivar_type                                          OBJC2_UNAVAILABLE;

    int ivar_offset                                          OBJC2_UNAVAILABLE;

#ifdef __LP64__    int space                                                

        OBJC2_UNAVAILABLE;

#endif

5.常用的方法和面试题:

runtime怎么添加属性、方法:

class_addIvar

class_addMethod

class_addProperty

class_addProtocol

class_replaceProperty


runtime如何通过selector找到对应的IMP地址?(分别考虑类方法和实例方法)

注意:实例对象和类对象的区别

Person *p = [[Person alloc] init]; 

Class c1 = [p class]; 

Class c2 = [Person class]; 

NSLog(@”%d”, c1 == c2);   //输出 1 因此类对象获取Class得到的是其本身

每一个类对象中都一个对象方法列表(对象方法缓存)

类方法列表是存放在类对象中isa指针指向的元类对象中(类方法缓存)

方法列表中每个方法结构体中记录着方法的名称,方法实现,以及参数类型,其实selector本质就是方法名称,通过这个方法名称就可以在方法列表中找到对应的方法实现.

当我们发送一个消息给一个NSObject对象时,这条消息会在对象的类对象方法列表里查找

当我们发送一个消息给一个类时,这条消息会在类的Meta Class对象的方法列表里查找

使用runtime Associate方法关联的对象,需要在主对象dealloc的时候释放么?

无论在MRC下还是ARC下均不需要,它们会在被 NSObject -dealloc 调用的object_dispose()方法中释放

补充:对象的内存销毁时间表,分四个步骤

1、调用 -release :引用计数变为零

* 对象正在被销毁,生命周期即将结束.

* 不能再有新的 __weak 弱引用,否则将指向 nil.

* 调用 [self dealloc]

2、 父类调用 -dealloc

* 继承关系中最直接继承的父类再调用 -dealloc

* 如果是 MRC 代码 则会手动释放实例变量们(iVars)

* 继承关系中每一层的父类 都再调用 -dealloc

3、NSObject 调 -dealloc

* 只做一件事:调用 Objective-C runtime 中object_dispose() 方法

4. 调用 object_dispose()

* 为 C++ 的实例变量们(iVars)调用 destructors

* 为 ARC 状态下的 实例变量们(iVars) 调用 -release

* 解除所有使用 runtime Associate方法关联的对象

* 解除所有 __weak 引用

* 调用 free()

什么是method swizzling(俗称黑魔法)

简单说就是进行方法交换

在Objective-C中调用一个方法,其实是向一个对象发送消息,查找消息的唯一依据是selector的名字。利用Objective-C的动态特性,可以实现在运行时偷换selector对应的方法实现,达到给方法挂钩的目的

每个类都有一个方法列表,存放着方法的名字和方法实现的映射关系,selector的本质其实就是方法名,IMP有点类似函数指针,指向具体的Method实现,通过selector就可以找到对应的IMP

交换前


交换后

交换方法的几种实现方式:

利用 method_exchangeImplementations 交换两个方法的实现

利用 class_replaceMethod 替换方法的实现

利用 method_setImplementation 来直接设置某个方法的IMP

Method imageNameMethod = class_getClassMethod(self,@selector(imageNamed:));

Method xmg_imageNameMethod = class_getClassMethod(self, @selector(xmg_imageNamed:));

method_exchangeImplementations(imageNameMethod, xmg_imageNameMethod);

runtime 常见作用:

动态交换两个方法的实现 

动态添加属性 

动态添加方法 

实现字典转模型的自动转换 

发送消息或者防止数组越界或者字典传入空值

拦截并替换方法 

实现 NSCoding 的自动归档和解档

其它用法:

需求:加载一张图片直接用[UIImage imageNamed:@”image”];是无法知道到底有没有加载成功。给系统的imageNamed添加额外功能(是否加载图片成功)。

实现步骤: 

1.给系统的方法添加分类 

2.自己实现一个带有扩展功能的方法 

3.交换方法,只需要交换一次。

- (void)viewDidLoad {

    [super viewDidLoad];

    // 方案二:交换 imageNamed 和 ln_imageNamed 的实现,就能调用 imageNamed,间接调用 ln_imageNamed 的实现。

    UIImage *image = [UIImage imageNamed:@"123"];

}

#import

@implementation UIImage (Image)

/**

load方法: 把类加载进内存的时候调用,只会调用一次

方法应先交换,再去调用

*/

+ (void)load {

    // 1.获取 imageNamed方法地址

    // class_getClassMethod(获取某个类的方法)

    Method imageNamedMethod = class_getClassMethod(self, @selector(imageNamed:));

    // 2.获取 ln_imageNamed方法地址

    Method ln_imageNamedMethod = class_getClassMethod(self, @selector(ln_imageNamed:));

    // 3.交换方法地址,相当于交换实现方式;「method_exchangeImplementations 交换两个方法的实现」

    method_exchangeImplementations(imageNamedMethod, ln_imageNamedMethod);

}

// 加载图片 且 带判断是否加载成功

+ (UIImage *)ln_imageNamed:(NSString *)name {

    UIImage *image = [UIImage ln_imageNamed:name];

    if (image) {

        NSLog(@"runtime添加额外功能--加载成功");

    } else {

        NSLog(@"runtime添加额外功能--加载失败");

    }

    return image;

}

/**

不能在分类中重写系统方法imageNamed,因为会把系统的功能给覆盖掉,而且分类中不能调用super

所以第二步,我们要 自己实现一个带有扩展功能的方法.

+ (UIImage *)imageNamed:(NSString *)name {

}

*/

runtime 给分类动态添加属性:

注解:系统 NSObject 添加一个分类,我们知道在分类中是不能够添加成员属性的,虽然我们用了@property,但是仅仅会自动生成get和set方法的声明,并没有带下划线的属性和方法实现生成。但是我们可以通过runtime就可以做到给它方法的实现。

@interface NSObject (Property)

// @property分类:只会生成get,set方法声明,不会生成实现,也不会生成下划线成员属性

@property NSString *name;

@property NSString *height;

@end


@implementation NSObject (Property)

- (void)setName:(NSString *)name {

    // objc_setAssociatedObject(将某个值跟某个对象关联起来,将某个值存储到某个对象中)

    // object:给哪个对象添加属性

    // key:属性名称

    // value:属性值

    // policy:保存策略

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

}

- (NSString *)name {

  return objc_getAssociatedObject(self, @"name");

}

// 调用

NSObject *objc = [[NSObject alloc] init];

objc.name = @"123";

NSLog(@"runtime动态添加属性name==%@",objc.name);

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

推荐阅读更多精彩内容