Runtime的认识及理解

21320954_182303674000_2.jpg

文章选自:新浪博客:http://blog.sina.com.cn/s/blog_9cd71e570102wn2j.html
感觉博客主人写的内容,得到了很多启发,也让我对Runtime有了更多的认识
什么是Runtime

Runtime是运行时,主要实现基于消息机制;在C当中函数的调用顺序在编译阶段就会被完全确定,不存在任何二义性;​而Objective_C在运行时才会真正的被确定,OC 方法的调用时基于方法选择器(SEL,C的一个结构体,int型的指针)的,每一个SEL存储一个唯一的方法名(注意:只是方法名,不包括参数列表),所以在OC一个类当中方法名是唯一的(不存在方法的重载);

​首先看一下OC中方法调用在Runtime中的表示:
我们写的代码在程序运行过程中都会被转化成runtime的C代码执行,例如[target doSomething];会被转化成objc_msgSend(target, @selector(doSomething));。

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

相关的定义:
/// 描述类中的一个方法
typedef struct objc_method *Method;
/// 实例变量
typedef struct objc_ivar *Ivar;
/// 类别Category
typedef struct objc_category *Category;
/// 类中声明的属性
typedef struct objc_property *objc_property_t;

类在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;

获取列表
有时候会有这样的需求,我们需要知道当前类中每个属性的名字;

比如:字典转模型,字典的Key和模型对象的属性名字不匹配。我们可以通过setValueForkeyWithDitionary: 转换匹配的KEY; setValue:forUndefinedKey: 忽略不匹配的KEY,对于不匹配的KEY,使用Runtime获取模型属性列表,单独处理不匹配的Key,封装自己的方法即可;

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

unsigned int count;
//获取属性列表
objc_property_t *propertyList = class_copyPropertyList([self class], &count);
for (unsigned int i=0; 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 
    Method method = methodList[i]; 
    NSLog(@"method---->%@", NSStringFromSelector(method_getName(method))); 
} 

//获取成员变量列表 
Ivar *ivarList = class_copyIvarList([self class], &count); 
for (unsigned int i; i 
    Ivar myIvar = ivarList[i]; 
    const char *ivarName = ivar_getName(myIvar); 
    NSLog(@"Ivar---->%@", [NSString stringWithUTF8String:ivarName]); 
}

方法调用
让我们看一下方法调用在运行时的过程

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

首先,在相应操作的对象中的缓存方法列表(Cache)中找调用的方法,如果找到,转向相应实现并执行。
如果没找到,在相应操作的对象中的方法列表中找调用的方法,如果找到,转向相应实现执行
如果没找到,去父类指针所指向的对象中执行1,2.
如果找到,则将method加 入到Cache中,以方便下次查找,并通过method中的函数指针(保存在方法选择器当中)跳转到对应的函数中去执行。
以此类推,如果一直到根类还没找到,转向拦截调用。
如果没有重写拦截调用的方法,程序报错(NSAssert)。
以上的过程给我带来的启发:

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

拦截调用
在方法调用中说到了,如果没有找到方法就会转向拦截调用。那么什么是拦截调用呢。拦截调用就是,在找不到调用的方法程序崩溃之前,你有机会通过重写NSObject的四个方法来处理。

  • (BOOL)resolveClassMethod:(SEL)sel;
  • (BOOL)resolveInstanceMethod:(SEL)sel;
    //后两个方法需要转发到其他的类处理
  • (id)forwardingTargetForSelector:(SEL)aSelector;

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

动态添加方法
重写了拦截调用的方法并且返回了YES,我们要怎么处理呢?有一个办法是根据传进来的SEL类型的selector动态添加一个方法。

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

//隐式调用方法
[target performSelector:@selector(resolveAdd:) withObject:@"test"];

然后,在target对象内部重写拦截调用的方法,动态添加方法。

void runAddMethod(id self, SEL _cmd, NSString *string){
NSLog(@"add C IMP ", string);
}

  • (BOOL)resolveInstanceMethod:(SEL)sel{ //给本类动态添加一个法
    if ([NSStringFromSelector(sel) isEqualToString:@"resolveAdd:"]) {
    class_addMethod(self, sel, (IMP)runAddMethod, "v@:*");
    } return YES;
    }
    其中class_addMethod的四个参数分别是:

Class cls
给哪个类添加方法,本例中是self

SEL name
添加的方法,本例中是重写的拦截调用传进来的selector。

IMP imp
方法的实现,C方法的方法实现可以直接获得。如果是OC方法,可以用+ (IMP)instanceMethodForSelector:(SEL)aSelector;获得方法的实现。

"v@:*"
方法的签名,代表有一个参数的方法。

那么问题来了,如果我动态添加的方法名并不是缺失的方法名呢?手动改一改就好了,那么如多及时上百处调用了呢? 重写缺失方法,调用我们的方法也可以; 介绍一个高大上的方法:方法交换(稍后讲解);

关联对象
现在你准备用一个系统的类,但是系统的类并不能满足你的需求,你需要额外添加一个属性。这种情况的一般解决办法就是继承。但是,只增加一个属性,就去继承一个类,总是觉得太麻烦类。这个时候,runtime的关联属性就发挥它的作用了。

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

的四个参数:

id object
给谁设置关联对象。

const void *key
关联对象唯一的key,获取时会用到。

id value
关联对象。

objc_AssociationPolicy
关联策略,有以下几种策略:

enum
{ OBJC_ASSOCIATION_ASSIGN = 0,

OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,

OBJC_ASSOCIATION_COPY_NONATOMIC = 3,

OBJC_ASSOCIATION_RETAIN = 01401,

OBJC_ASSOCIATION_COPY = 01403 };

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

objc_getAssociatedObject

的两个参数。

id object
获取谁的关联对象。

const void *key
根据这个唯一的key获取关联对象。

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

//添加关联对象

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

注意:

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

方法交换

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

import "UIViewController+swizzling.h"

import

@implementation UIViewController (swizzling)

//load方法会在类第一次加载的时候被调用
//调用的时间比较靠前,适合在这个方法里做方法交换

  • (void)load{
    //方法交换应该被保证,在程序中只会执行一次
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{

      //获得viewController的生命周期方法的selector 
      SEL systemSel = @selector(viewWillAppear:); 
      //自己实现的将要被交换的方法的selector 
      SEL swizzSel = @selector(swiz_viewWillAppear:); 
      //两个方法的Method 
      Method systemMethod = class_getInstanceMethod([self class], systemSel); 
      Method swizzMethod = class_getInstanceMethod([self class], swizzSel); 
    
      //首先动态添加方法,实现是被交换的方法,返回值表示添加成功还是失败 
      BOOL isAdd = class_addMethod(self, systemSel,    method_getImplementation(swizzMethod),      method_getTypeEncoding(swizzMethod)); 
      if (isAdd) { 
             //如果成功,说明类中不存在这个方法的实现 
             //将被交换方法的实现替换到这个并不存在的实现 
              class_replaceMethod(self, swizzSel,          method_getImplementation(systemMethod),            method_getTypeEncoding(systemMethod)); 
      }else{ 
          //否则,交换两个方法的实现 
          method_exchangeImplementations(systemMethod, swizzMethod);
      } 
    

    });
    }

  • (void)swiz_viewWillAppear:(BOOL)animated{
    //这时候调用自己,看起来像是死循环
    //但是其实自己的实现已经被替换了
    [self swiz_viewWillAppear:animated];
    NSLog(@"swizzle");
    }
    @end
    在一个自己定义的viewController中重写viewWillAppear

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

Run起来看看输出吧!

我的理解:

特别注意::: 方法交换实际交换的是方法的实现部分;

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

推荐阅读更多精彩内容

  • 转至元数据结尾创建: 董潇伟,最新修改于: 十二月 23, 2016 转至元数据起始第一章:isa和Class一....
    40c0490e5268阅读 1,678评论 0 9
  • 我们常常会听说 Objective-C 是一门动态语言,那么这个「动态」表现在哪呢?我想最主要的表现就是 Obje...
    Ethan_Struggle阅读 2,170评论 0 7
  • 这篇文章完全是基于南峰子老师博客的转载 这篇文章完全是基于南峰子老师博客的转载 这篇文章完全是基于南峰子老师博客的...
    西木阅读 30,541评论 33 466
  • 转载:http://yulingtianxia.com/blog/2014/11/05/objective-c-r...
    F麦子阅读 727评论 0 2
  • 每一次看到一对一对的恋人在分享他们的甜蜜,我从最开始的触动到现在的心静,安慰自己缘分还未到。不知道为什么不会像别人...
    洛痕阅读 278评论 0 0