近期了解了一下OC的Runtime,真的是OC中非常强大的一个机制,看起来比較底层,但事实上能够有非常多活用的方式。

什么是Runtime

我们尽管是用Objective-C写的代码,事实上在运行过程中都会被转化成C代码去运行。比方说OC的方法调用都会转成C函数 id objc_msgSend ( id self, SEL op, … ); 而OC中的对象事实上在Runtime中都会用结构体来表示,这个结构体中包括了类名、成员变量列表、方法列表、协议列表、缓存等。

类在Runtime中的表示:

struct objc_class {
Class isa;//指针,顾名思义。表示是一个什么,
//实例的isa指向类对象,类对象的isa指向元类 #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;
/* Use `Class` instead of `struct objc_class *` */

整个Runtime机制事实上能够挖的点非常多,这里仅仅是简单的介绍一些常见的使用方法,假设将其细细解析,相信一定会对OC的理解加深几个层面。

获取属性/方法/协议列表

最直接的一种使用方法,就是获取我们的结构体中存储的对象的属性、方法、协议等列表,从而获取其全部这些信息。

要获取也比較简单,可是自己尝试之前须要注意几点:

  • 一定要自己给类加几个属性、方法,遵循一些协议。否则当然是看不到输出信息的。

  • 要使用这些获取的方法。须要导入头文件 #import
#import <objc/runtime.h>

// 输出类的一些信息
- (void)logInfo {
unsigned int count;// 用于记录列表内的数量,进行循环输出 // 获取属性列表
objc_property_t *propertyList = class_copyPropertyList([self class], &count);
for (unsigned int i = 0; i < count; i++) {
const char *propertyName = property_getName(propertyList[i]);
NSLog(@"property --> %@", [NSString stringWithUTF8String:propertyName]);
} // 获取方法列表
Method *methodList = class_copyMethodList([self class], &count);
for (unsigned int i; i < count; i++) {
Method method = methodList[i];
NSLog(@"method --> %@", NSStringFromSelector(method_getName(method)));
} // 获取成员变量列表
Ivar *ivarList = class_copyIvarList([self class], &count);
for (unsigned int i; i < count; i++) {
Ivar myIvar = ivarList[i];
const char *ivarName = ivar_getName(myIvar);
NSLog(@"Ivar --> %@", [NSString stringWithUTF8String:ivarName]);
} // 获取协议列表
__unsafe_unretained Protocol **protocolList = class_copyProtocolList([self class], &count);
for (unsigned int i; i < count; i++) {
Protocol *myProtocal = protocolList[i];
const char *protocolName = protocol_getName(myProtocal);
NSLog(@"protocol --> %@", [NSString stringWithUTF8String:protocolName]);
}
}

方法调用的过程

调用方法分为调用实例方法和调用类方法,在结构体我们能够看到第一个就是一个 isa 指针。会指向类对象或者元类,什么叫元类呢?

每一个实例对象有个isa的指针,他指向对象的类。而类里也有个isa的指针, 指向meteClass(元类)。元类保存了类方法的列表。当类方法被调用时,先会从本身查找类方法的实现,假设没有。元类会向他父类查找该方法。同一时候注意的是:元类(meteClass)也是类,它也是对象。元类也有isa指针,它的isa指针终于指向的是一个根元类(root meteClass)。根元类的isa指针指向本身,这样形成了一个封闭的内循环。

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvQ2xvdWRveF8=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="" title="">

通过isa,就能够不断往上方去回溯自己的父类等,而方法的调用也利用了这个过程:

  1. 首先,当然在对象自己缓存的方法列表中去找要调用的方法,找到了就直接运行事实上现。
  2. 缓存里没找到。就去上面说的它的方法列表里找,找到了就运行事实上现。

  3. 还没找到。说明这个类自己没有了。就会通过isa去向其父类里运行1、2。
  4. 假设找到了根类还没找到,那么就是没有了,会转向一个拦截调用的方法,我们能够自己在拦截调用方法里面做一些处理。
  5. 假设没有在拦截调用里做处理。那么就会报错崩溃。

以上就是方法调用的过程。我们能够看到的是。所谓重写父类方法,并非抹除了父类方法。父类的方法还是存在的,仅仅是我们在子类里面找到了就不会再去父类里找了,是这个层面的“覆盖”。而我们熟悉的 super 调用父类的方法实现。就是告知要去调用父类实现的标识。

拦截调用与动态加入

上面说到了拦截调用,就是在全部地方都找不到方法的实现的话,会出发拦截调用,能够自己去做一些处理避免程序崩溃。那在什么地方做处理呢?NSObject有四个方法能够用来做处理:

// 调用不存在的类方法时触发。默认返回NO,能够加上自己的处理后返回YES
+ (BOOL)resolveClassMethod:(SEL)sel; // 调用不存在的实例方法时触发,默认返回NO,能够加上自己的处理后返回YES
+ (BOOL)resolveInstanceMethod:(SEL)sel; // 将调用的不存在的方法重定向到一个其它声明了这种方法的类里去。返回那个类的target
- (id)forwardingTargetForSelector:(SEL)aSelector; // 将调用的不存在的方法打包成 NSInvocation 给你,自己处理后调用 invokeWithTarget: 方法让某个类来触发
- (void)forwardInvocation:(NSInvocation *)anInvocation;

假设我们成功拦截下来了。那我们能够做什么处理呢?这个事实上就是依据详细情况看你要怎么处理了。但这里有一个久闻其名的东西就能够派上用场了:动态加入方法。

我们知道OC是动态的,也就是说非常多东西它不是在编译时去推断,而是在运行时去处理的,这事实上带来了非常大的灵活性。比方这里我们对于一些不存在的方法的调用。就能够动态去加入上一个方法来取代我们要调用的方法。

比方我们加入一个Button,点击事件我们关联到一个没有详细实现的实例方法,这样的情况下假设不做不论什么处理那么点击Button后一定会崩溃。可是假设我们拦截并动态加入一个方法来报警,就不会崩溃了:

@interface ViewController ()
@property (nonatomic, strong) UIButton *logBtn; - (void)notHas;// 要调用的实例方法。没有详细实现
@end - (void)viewDidLoad {
[super viewDidLoad]; // 加入button
self.logBtn = [[UIButton alloc] initWithFrame:CGRectMake(100, 200, 100, 20)];
[self.logBtn setTitle:@"測 试" forState:UIControlStateNormal];
[self.logBtn setTitleColor:[UIColor lightGrayColor] forState:UIControlStateNormal];
// 加入没有实现的点击事件
[self.logBtn addTarget:self action:@selector(notHas) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:self.logBtn]; } // 拦截对不存在的方法的调用
+ (BOOL)resolveInstanceMethod:(SEL)sel {
NSLog(@"notFind!");
// 给本类动态加入一个方法
if ([NSStringFromSelector(sel) isEqualToString:@"notHas"]) {
class_addMethod(self, sel, (IMP)runAddMethod, "v@:*");
}
// 注意要返回YES
return YES;
} // 要动态加入的方法,这是一个C方法
void runAddMethod(id self, SEL _cmd, NSString *string) {
NSLog(@"动态加入一个方法来提示");
}

依照上面的处理。点击button后就不会崩溃。而是转到了对 runAddMethod 方法的调用。事实上更明白地说,应该是重现了对要调用的方法的实现,将原本要调用的方法的实现,改为了一个新的实现。class_addMethod 方法的第二个參数是要重写的方法,这里用的就是传进来的參数sel。第三个參数就是重写后的实现。第四个參数是方法的签名。

关联对象

什么叫关联对象?说通俗一点。我们都知道用Category类别能够给一些已经存在的。比方系统的类加入方法,可是不能加入新属性,那怎么加入属性呢?一种直接的方法是继承,但假设仅仅是为了加入一个属性就去做一次继承。还是有点重,这时候,就能够用关联对象的方法。

有两个相关的方法:

  • objc_setAssociatedObject 方法用来给类关联一个属性。
  • objc_getAssociatedObject 方法用来获取之前关联的属性。

比方说给自己这个类关联一个字符串:

    // 关联对象
static char associatedObjectKey;
objc_setAssociatedObject(self, &associatedObjectKey, @"我就是要关联的字符串对象内容", OBJC_ASSOCIATION_RETAIN_NONATOMIC);
NSString *theString = objc_getAssociatedObject(self, &associatedObjectKey);
NSLog(@"关联对象:%@", theString);

我们先给self关联了一个字符串内容。然后通过get方法获取了关联的字符串内容。并输出。

从代码中事实上也能够猜到各个參数的意思。self的參数就是要处理的类;associatedObjectKey 的參数事实上就相似于key,用来标识区分你要关联的这个对象;第三个參数是要关联的对象;第四个參数是关联的策略,用命名就能够看出来全是在加入@property属性时用到的一些修饰符,有五种策略:

enum {
OBJC_ASSOCIATION_ASSIGN = 0,
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,
OBJC_ASSOCIATION_COPY_NONATOMIC = 3,
OBJC_ASSOCIATION_RETAIN = 01401,
OBJC_ASSOCIATION_COPY = 01403
};

熟悉@property属性修饰符的应该能直接明白了,不熟悉的能够看这篇文章:传送门:iOS中assign、retain、copy、weak、strong的差别以及nonatomic的含义

当然。你也能够和类别一起用。创建两个方法用来关联和获取对象。比方以下这样:

//加入关联对象
- (void)addAssociatedObject:(id)object{
objc_setAssociatedObject(self, @selector(getAssociatedObject), object, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
} //获取关联对象
- (id)getAssociatedObject{
return objc_getAssociatedObject(self, _cmd);
}

这样就既能通过Category类别来加入方法,用一起顺便提供了对属性的加入了。

以上是对Runtime的一点浅薄的理解和使用,Runtime的天地应该是非常广阔的。也能挖出非常多高级的使用方法来,对于理解OC的运行机制是非常有帮助的。


演示样例project:https://github.com/Cloudox/RuntimeDemo

版权全部:http://blog.csdn.net/cloudox_

OC中Runtime浅析的更多相关文章

  1. OC中runtime的使用

    一.runtime简介* RunTime简称运行时.OC就是“运行时机制”,也就是在运行时候的一些机制,其中最主要的是消息机制.* 对于C语言,“函数的调用在编译的时候会决定调用哪个函数”.* 对于O ...

  2. iOS运行时Runtime浅析

    运行时是iOS中一个很重要的概念,iOS运行过程中都会被转化为runtime的C代码执行.例如[target doSomething];会被转化成objc)msgSend(target,@select ...

  3. iOS开发——高级技术精选OC篇&Runtime之字典转模型实战

    Runtime之字典转模型实战 如果您还不知道什么是runtime,那么请先看看这几篇文章: http://www.cnblogs.com/iCocos/p/4734687.html http://w ...

  4. 设计模式之观察者模式(关于OC中的KVO\KVC\NSNotification)

    学习了这么久的设计模式方面的知识,最大的感触就是,设计模式不能脱离语言特性.近段时间所看的两本书籍,<大话设计模式>里面的代码是C#写的,有一些设计模式实现起来也是采用了C#的语言特性(C ...

  5. OC 中的 weak 属性是怎么实现的?

    OC 中的 weak 属性是怎么实现的,为什么在对象释放后会自动变成 nil?本文对这个问题进行了一点探讨.环境 mac OS Sierra 10.12.4 objc709参考答案 搜索后发现runt ...

  6. OC中加载html5调用html方法和修改HTML5内容

    1.利用webView控件加载本地html5或者网络上html5 2.设置控制器为webView的代理,遵守协议 3.实现代理方法webViewDidFinishLoad: 4.在代理方法中进行操作H ...

  7. java中的继承与oc中的继承的区别

    为什么要使用继承? 继承的好处: (1)抽取出了重复的代码,使代码更加灵活 (2)建立了类和类之间的联系 继承的缺点: 耦合性太强 OC中的继承 1.OC中不允许子类和父类拥有相同名称的成员变量名:( ...

  8. Swift: 比较Swift中闭包传值、OC中的Block传值

    一.介绍 开发者对匿名函数应该很清楚,其实它就是一个没有名字的函数或者方法,给人直观的感觉就是只能看到参数和返回值.在iOS开发中中,它又有自己的称呼,在OC中叫Block代码块,在Swift中叫闭包 ...

  9. OC中类别、扩展、协议与委托

    一.类别(category) 类别(category)——通过使用类别,我们可以动态地为现有的类添加新方法,而且可以将类定义模块化地分不到多个相关文件中.通常只在类别中定义方法.(类别,接口部分的定义 ...

随机推荐

  1. Iconfont在移动端应用的问题

    关于部分奇葩用户代理不显示字体图标 以酷派为代表的部分安卓手机自带浏览器.微信/QQ WebView 等用户代理无法正常显示 Icon Font,原因可能是这些用户代理无法正确处理伪元素 conten ...

  2. 【进阶修炼】——改善C#程序质量(4)

    46, 显示释放资源,需要实现IDisposable接口. 最好按照微软建议的Dispose模式实现.实现了IDisposable接口后,在Using代码块中,垃圾会得到自动清理. 47, 即使提供了 ...

  3. 问题解决:bash: fork: retry: Resource temporarily unavailable

    linux报错: bash: fork: retry: Resource temporarily unavailable 不管是执行什么 登陆不了服务器The server refused to st ...

  4. java操作大文件复制

    https://www.cnblogs.com/coprince/p/6594348.html https://blog.csdn.net/w592376568/article/details/796 ...

  5. oracle的启动和停用

    1.开始-运行-cmd-确定 2.cmd页面,输入set ORACLE_SID=(你的数据库实例名),回车,执行 3.继续输入‘sqlplus/nolog’,敲击回车键 4.sql输入栏,输入‘con ...

  6. loadrunner 中Error和failed transaction 的区别

    Down:没有运行 Pending:挂起 Init:初始化 Ready:准备就绪 Run:正在运行 Rendezvous:正在集结 Passed:运行通过 Failed:运行失败 Error:出现故障 ...

  7. 上下栏固定, 中间滚动的HTML模板

    因为用position是脱离文档流的,所以在最上面嘛, 中间用overflow:auto就会出现滚动效果 代码 <!DOCTYPE html> <html lang="en ...

  8. Android 8 AudioPolicy 初始化

    记录一下AudioPolicy初始化过程. frameworks\av\media\audioserver\audioserver.rc service audioserver /system/bin ...

  9. 为什么要有handler机制?handler机制的原理

    为什么要有handler机制? 在Android的UI开发中,我们经常会使用Handler来控制主UI程序的界面变化.有关Handler的作用,我们总结为:与其他线程协同工作,接收其他线程的消息并通过 ...

  10. Prolog学习:用八卦的精神走进Prolog

    最近枕头书是<七周七语言:理解多种编程范型>这本,前面两章分别看了Ruby和IO,都是命令式语言.虽然它们在语法上跟之前接触过的C,C#,Java这些C家族的语言差别很大,但是编程范型却是 ...