iOS开发——底层OC篇&运行时常用

运行时常用

什么是Runtime(前面的文章已经说的很清楚了,这里就简单的介绍一下)

  • 我们写的代码在程序运行过程中都会被转化成runtime的C代码执行,例如[target doSomething];会被转化成objc_msgSend(target, @selector(doSomething));。

OC中一切都被设计成了对象,我们都知道一个类被初始化成一个实例,这个实例是一个对象。实际上一个类本质上也是一个对象,在runtime中用结构体表示。

相关的定义:

1 /// 描述类中的一个方法
2 typedef struct objc_method *Method;
3 /// 实例变量
4 typedef struct objc_ivar *Ivar;
5 /// 类别Category
6 typedef struct objc_category *Category;
7 /// 类中声明的属性
8 typedef struct objc_property *objc_property_t;

类在runtime中的表示

 1 //类在runtime中的表示
 2 struct objc_class {
 3     Class isa;//指针,顾名思义,表示是一个什么,
 4     //实例的isa指向类对象,类对象的isa指向元类
 5 #if !__OBJC2__
 6     Class super_class;  //指向父类
 7     const char *name;  //类名
 8     long version;
 9     long info;
10     long instance_size
11     struct objc_ivar_list *ivars //成员变量列表
12     struct objc_method_list **methodLists; //方法列表
13     struct objc_cache *cache;//缓存
14     //一种优化,调用过的方法存入缓存列表,下次调用先找缓存
15     struct objc_protocol_list *protocols //协议列表
16     #endif
17 } OBJC2_UNAVAILABLE;
18 /* Use `Class` instead of `struct objc_class *` */

获取列表

有时候会有这样的需求,我们需要知道当前类中每个属性的名字(比如字典转模型,字典的Key和模型对象的属性名字不匹配)。

我们可以通过runtime的一系列方法获取类的一些信息(包括属性列表,方法列表,成员变量列表,和遵循的协议列表)。

 1 unsigned int count;
 2 //获取属性列表
 3 objc_property_t *propertyList = class_copyPropertyList([self class], &count);
 4 for (unsigned int i=0; i<count; i++) {         const char *propertyname =" property_getName(propertyList[i]);"         nslog(@"property----="">%@", [NSString stringWithUTF8String:propertyName]);
 5 }
 6 //获取方法列表
 7 Method *methodList = class_copyMethodList([self class], &count);
 8 for (unsigned int i; i<count; i++) {         method method =" methodList[i];"         nslog(@"method----="">%@", NSStringFromSelector(method_getName(method)));
 9 }
10 //获取成员变量列表
11 Ivar *ivarList = class_copyIvarList([self class], &count);
12 for (unsigned int i; i<count; i++) {         ivar myivar =" ivarList[i];"         const char *ivarname =" ivar_getName(myIvar);"         nslog(@"ivar----="">%@", [NSString stringWithUTF8String:ivarName]);
13 }
14 //获取协议列表
15 __unsafe_unretained Protocol **protocolList = class_copyProtocolList([self class], &count);
16 for (unsigned int i; i<count; i++) {         protocol *myprotocal =" protocolList[i];"         const char *protocolname =" protocol_getName(myProtocal);"         nslog(@"protocol----="">%@", [NSString stringWithUTF8String:protocolName]);

在Xcode上跑一下看看输出吧,需要给你当前的类写几个属性,成员变量,方法和协议,不然获取的列表是没有东西的。

注意,调用这些获取列表的方法别忘记导入头文件#import。

方法调用

让我们看一下方法调用在运行时的过程(参照前文类在runtime中的表示)

如果用实例对象调用实例方法,会到实例的isa指针指向的对象(也就是类对象)操作。

如果调用的是类方法,就会到类对象的isa指针指向的对象(也就是元类对象)中操作。

  • 首先,在相应操作的对象中的缓存方法列表中找调用的方法,如果找到,转向相应实现并执行。
  • 如果没找到,在相应操作的对象中的方法列表中找调用的方法,如果找到,转向相应实现执行
  • 如果没找到,去父类指针所指向的对象中执行1,2.
  • 以此类推,如果一直到根类还没找到,转向拦截调用。
  • 如果没有重写拦截调用的方法,程序报错。

以上的过程给我带来的启发:

  1. 重写父类的方法,并没有覆盖掉父类的方法,只是在当前类对象中找到了这个方法后就不会再去父类中找了。
  2. 如果想调用已经重写过的方法的父类的实现,只需使用super这个编译器标识,它会在运行时跳过在当前的类对象中寻找方法的过程。

拦截调用

在方法调用中说到了,如果没有找到方法就会转向拦截调用。

那么什么是拦截调用呢。

拦截调用就是,在找不到调用的方法程序崩溃之前,你有机会通过重写NSObject的四个方法来处理。

1 + (BOOL)resolveClassMethod:(SEL)sel;
2 + (BOOL)resolveInstanceMethod:(SEL)sel;
3 //后两个方法需要转发到其他的类处理
4 - (id)forwardingTargetForSelector:(SEL)aSelector;
5 - (void)forwardInvocation:(NSInvocation *)anInvocation;
  • 第一个方法是当你调用一个不存在的类方法的时候,会调用这个方法,默认返回NO,你可以加上自己的处理然后返回YES。
  • 第二个方法和第一个方法相似,只不过处理的是实例方法。
  • 第三个方法是将你调用的不存在的方法重定向到一个其他声明了这个方法的类,只需要你返回一个有这个方法的target。
  • 第四个方法是将你调用的不存在的方法打包成NSInvocation传给你。做完你自己的处理后,调用invokeWithTarget:方法让某个target触发这个方法。

动态添加方法

重写了拦截调用的方法并且返回了YES,我们要怎么处理呢?

有一个办法是根据传进来的SEL类型的selector动态添加一个方法。

首先从外部隐式调用一个不存在的方法:

 1 //隐式调用方法
 2 [target performSelector:@selector(resolveAdd:) withObject:@"test"];
 3 然后,在target对象内部重写拦截调用的方法,动态添加方法。
 4
 5
 6 void runAddMethod(id self, SEL _cmd, NSString *string){
 7     NSLog(@"add C IMP ", string);
 8 }
 9 + (BOOL)resolveInstanceMethod:(SEL)sel{
10     //给本类动态添加一个方法
11     if ([NSStringFromSelector(sel) isEqualToString:@"resolveAdd:"]) {
12         class_addMethod(self, sel, (IMP)runAddMethod, "[email protected]:*");
13     }
14     return YES;
15 }

其中class_addMethod的四个参数分别是:

  • Class cls 给哪个类添加方法,本例中是self
  • SEL name 添加的方法,本例中是重写的拦截调用传进来的selector。
  • IMP imp 方法的实现,C方法的方法实现可以直接获得。如果是OC方法,可以用+ (IMP)instanceMethodForSelector:(SEL)aSelector;获得方法的实现。
  • "[email protected]:*"方法的签名,代表有一个参数的方法。

关联对象

现在你准备用一个系统的类,但是系统的类并不能满足你的需求,你需要额外添加一个属性。

这种情况的一般解决办法就是继承。

但是,只增加一个属性,就去继承一个类,总是觉得太麻烦类。

这个时候,runtime的关联属性就发挥它的作用了。

1 //首先定义一个全局变量,用它的地址作为关联对象的key
2 static char associatedObjectKey;
3 //设置关联对象
4 objc_setAssociatedObject(target, &associatedObjectKey, @"添加的字符串属性", OBJC_ASSOCIATION_RETAIN_NONATOMIC); //获取关联对象
5 NSString *string = objc_getAssociatedObject(target, &associatedObjectKey);
6 NSLog(@"AssociatedObject = %@", string);

objc_setAssociatedObject的四个参数:

  • id object给谁设置关联对象。
  • const void *key关联对象唯一的key,获取时会用到。
  • id value关联对象。
  • objc_AssociationPolicy关联策略,有以下几种策略:
1 enum {
2     OBJC_ASSOCIATION_ASSIGN = 0,
3     OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,
4     OBJC_ASSOCIATION_COPY_NONATOMIC = 3,
5     OBJC_ASSOCIATION_RETAIN = 01401,
6     OBJC_ASSOCIATION_COPY = 01403
7 };

如果你熟悉OC,看名字应该知道这几种策略的意思了吧。

  • objc_getAssociatedObject的两个参数。
  • id object获取谁的关联对象。
  • const void *key根据这个唯一的key获取关联对象。

其实,你还可以把添加和获取关联对象的方法写在你需要用到这个功能的类的类别中,方便使用。

1 //添加关联对象
2 - (void)addAssociatedObject:(id)object{
3     objc_setAssociatedObject(self, @selector(getAssociatedObject), object, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
4 }
5 //获取关联对象
6 - (id)getAssociatedObject{
7     return objc_getAssociatedObject(self, _cmd);
8 }

注意:这里面我们把getAssociatedObject方法的地址作为唯一的key,_cmd代表当前调用方法的地址。

方法交换

  • 方法交换,顾名思义,就是将两个方法的实现交换。例如,将A方法和B方法交换,调用A方法的时候,就会执行B方法中的代码,反之亦然。

话不多说,这是参考Mattt大神在NSHipster上的文章自己写的代码。

 1 #import "UIViewController+swizzling.h"
 2 #import @implementation UIViewController (swizzling)
 3 //load方法会在类第一次加载的时候被调用
 4 //调用的时间比较靠前,适合在这个方法里做方法交换
 5 + (void)load{
 6     //方法交换应该被保证,在程序中只会执行一次
 7     static dispatch_once_t onceToken;
 8     dispatch_once(&onceToken, ^{
 9         //获得viewController的生命周期方法的selector
10         SEL systemSel = @selector(viewWillAppear:);
11         //自己实现的将要被交换的方法的selector
12         SEL swizzSel = @selector(swiz_viewWillAppear:);
13         //两个方法的Method
14         Method systemMethod = class_getInstanceMethod([self class], systemSel);
15         Method swizzMethod = class_getInstanceMethod([self class], swizzSel);
16         //首先动态添加方法,实现是被交换的方法,返回值表示添加成功还是失败
17         BOOL isAdd = class_addMethod(self, systemSel, method_getImplementation(swizzMethod), method_getTypeEncoding(swizzMethod));
18         if (isAdd) {
19             //如果成功,说明类中不存在这个方法的实现
20             //将被交换方法的实现替换到这个并不存在的实现
21             class_replaceMethod(self, swizzSel, method_getImplementation(systemMethod), method_getTypeEncoding(systemMethod));
22         }else{
23             //否则,交换两个方法的实现
24             method_exchangeImplementations(systemMethod, swizzMethod);
25         }
26     });
27 }
28 - (void)swiz_viewWillAppear:(BOOL)animated{
29     //这时候调用自己,看起来像是死循环
30     //但是其实自己的实现已经被替换了
31     [self swiz_viewWillAppear:animated];
32     NSLog(@"swizzle");
33 }
34 @end

在一个自己定义的viewController中重写viewWillAppear

- (void)viewWillAppear:(BOOL)animated{
    [super viewWillAppear:animated];
    NSLog(@"viewWillAppear");
}

Run起来看看输出吧!

我的理解:

  • 方法交换对于我来说更像是实现一种思想的最佳技术:AOP面向切面编程。
  • 既然是切面,就一定不要忘记,交换完再调回自己。
  • 一定要保证只交换一次,否则就会很乱。
  • 最后,据说这个技术很危险,谨慎使用。
时间: 2024-10-29 19:06:37

iOS开发——底层OC篇&运行时常用的相关文章

iOS开发——实战OC篇&amp;环境搭建之Xib(玩转UINavigationController与UITabBarController)

iOS开发——实战OC篇&环境搭建之Xib(玩转UINavigationController与UITabBarController) 前面我们介绍了StoryBoard这个新技术,和纯技术编程的代码创建界面,本篇我们将介绍一个老的技术,但是在很多的公司或者库里面还是使用这个技术,既然如此它肯定有他的好处,至于好处这里我就不一一介绍了.在Xcode5之前是只能使用Xib或者代码的,而代码又对于很多初学者来说算是一个难题.毕竟不知道怎么下手.所以我就总结了一下这段时间自己编写程序的一个实例来说明怎么

iOS开发——实战OC篇&amp;环境搭建之纯代码(玩转UINavigationController与UITabBarController)

iOS开发——实战OC篇&环境搭建之纯代码(玩转UINavigationController与UITabBarController) 这里我们就直接上实例: 一:新建一个项目singleView Controller,命名未iCocos 二:由于我们使用的纯代码实现的,所以删除其中的StoryBoard和Viewtroller的两个文件 三:新建一个继承自TabBar Controller的类,我们命名问iCocos ViewController 三:在Appdelegate的实现文件中导入刚刚

iOS开发——动画OC篇&amp;所有常用动画总结

所有常用动画总结 先来装下B,看不懂没关系,其实我也看不懂-?? iOS provides several different frameworks for adding graphics and animations to your apps. UIKit is an Objective-C API that provides basic 2D drawing, image handling, and ways to animate user interface objects. Core G

iOS开发——高级特性&amp;Runtime运行时特性详解

Runtime运行时特性详解 本文详细整理了 Cocoa 的 Runtime 系统的知识,它使得 Objective-C 如虎添翼,具备了灵活的动态特性,使这门古老的语言焕发生机.主要内容如下: 引言 简介 与Runtime交互 Runtime术语 消息 动态方法解析 消息转发 健壮的实例变量(Non Fragile ivars) Objective-C Associated Objects Method Swizzling 总结 引言 曾经觉得Objc特别方便上手,面对着 Cocoa 中大量

iOS开发——多线程OC篇&amp;多线程详解

多线程详解 前面介绍了多线程的各种方式及其使用,这里补一点关于多线程的概念及相关技巧与使用,相信前面不懂的地方看了这里之后你就对多线程基本上没有什么问题了! 1——首先ios开发多线程中必须了解的概念: 进程 正在进行中的程序被称为进程,负责程序运行的内存分配 每一个进程都有自己独立的虚拟内存空间 线程 线程是进程中一个独立的执行路径(控制单元) 一个进程中至少包含一条线程,即主线程 可以将耗时的执行路径(如:网络请求)放在其他线程中执行 创建线程的目的就是为了开启一条新的执行路径,运行指定的代

iOS开发——多线程OC篇&amp;多线程总结

多线程总结 1 //1.NSThread 2 /** 3 优点:NSThread 比其他两个轻量级. 4 缺点:需要自己管理线程的生命周期,线程同步,线程同步时对数据的加锁会有一定的系统开销. 5 cocoa给我提供了两种方法生成线程: 6 1: 7 - (id)initWithTarget:(id)target selector:(SEL)selector object:(id)argument 8 NSThread* thread = [[NSThread alloc] initWithTa

iOS开发——实用技术OC篇&amp;多线程整合

多线程整合 本文知识对iOS开发中多线程的一些知识整合,关于一些概念和技术问题并没有过多的介绍,如果你想了解更多请查看笔者之前写的iOS开发之多线程详解(比较完整):但是有部分涉及到之前文章中没有的技术点和常识,比如加锁的方式,面试相关的,还有一些关于GCD的高级用法,希望你能认真看完,或许可以收获到很多! http://www.cnblogs.com/iCocos/p/4553103.html http://www.cnblogs.com/iCocos/p/4553262.html ??先来看

iOS开发 底层抛析运行循环—— RunLoop

http://blog.csdn.net/zc639143029/article/details/50012527 一.RunLoop基本概念 概念:程序的运行循环,通俗的来说就是跑圈. 1. 基本作用(作用重大) (1) 保持程序的持续运行(ios程序为什么能一直活着不会死) (2) 处理app中的各种事件(比如触摸事件.定时器事件[NSTimer].selector事件[选择器·performSelector···]) (3)节省CPU资源,提高程序性能,有事情就做事情,没事情就休息 2.

iOS开发——控制器OC篇&amp;UINavigationController&amp;UITabBarController详解

UINavigationController&UITabBarController详解 一:UINavigationController 控制器的属性: UINavigationController以栈的形式保存子控制器 @property(nonatomic,copy) NSArray *viewControllers; @property(nonatomic,readonly) NSArray *childViewControllers; 导航控制器之间的跳转: 使用push方法能将某个控制