runtime -oc

最近公司开发的模块对接外部厂商时,遇到方法相互调用的问题.想起来了万能的runtime.网上找了一些资料基本都是碎片化,之言片语.通过相互融合整理了一下.


1.谈一下我对runtime的理解

对c语言而言在编译的时候就决定了调用顺序,如果没有实现会报错.对于OC来说的话,只要声明过即使不实现也不会报错.

我对OC的称呼为是 预编译消息发送类型的语言,在编译时并不能决定真正调用那个函数.只有在运行的时候根据收到的消息来调用函数.runtime就是把消息的内容给它动态的换掉.放到古代可以说是假传圣旨. 

runtime发送消息,源码解释

/ 创建对象 -> 调用方法

erha *d = [[erha alloc]init];// 调用方法 -> 实例方法

Class d = objc_allocateClassPair([erha class], "d", 0);

// [d run];

// 系统底层本质 -> 让对象发消息objc_msgSend(d,@selector(run));// 等同于  [d run];// 调用方法 -> 类方法

// [erha eat];

objc_msgSend([Dog class], @selector(eat));  // 等同于  [erha eat];

2.谈谈runtime具有那些常用的功能,以及与其它实现的对比优劣.

runtime的基本常用场景:能获得某个类的所有成员变量, 能获得某个类的所有属性, 能获得某个类的所有方法,交换方法实现 , 能动态添加一个成员变量, 能动态添加一个属性,字典转模型,runtime归档/反归档.   如果 用JSPatch实现热更新的话,上面的场景都是重点.

下面重点讲解几个

1. 动态的添加对象的成员变量和方法 

动态的添加成员变量和方法 与直接在父类里面做修改作对比. 其实在父类里面直接添加 成员变量与方法 子类继承父类会比较更好一点.

2. 动态交换两个方法的实现 

一般是在项目没有做好容错 或者已经开发完成的项目出现了问题 或可能出现问题 用这个时候runtime就要大显身手,在运行的时候替换掉原来的方法.推荐使用.

3. 实现分类也可以添加属性 

分类里面添加方法大家都知道,分类里面添加属性,正常来说是不行,缺乏key与value的绑定关系.所以会报错.后面我会讲解用runtime绑定key与value. 

给系统的类添加分类扩展方法和属性是一件很美好的事情,推荐使用/

4. 实现NSCoding的自动归档和解档 

主要还是通过class_copyIvarList,遍历对象属性,来做事情.

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

优秀的JSON转模型第三方库JSONModel、YYModel等都利用runtime对属性进行获取,赋值等操作,要比KVC进行模型转换更加强大,更有效率。阅读YYModel的源码可以看出,YY大神对NSObject的内容进行了又一次封装,添加了许多描述内容。其中YYClassInfo是对Class进行了再次封装,而YYClassIvarInfo、YYClassMethodInfo、YYClPropertyInfo分别是对Class的Ivar、Method和property进行了封装和描述。在提取Class的相关信息时都运用了Runtime。

3.关键词Ivar介绍 


1.Ivar的定义

Ivar是objc_ivar的指针,包含变量名,变量类型等成员,代码形式如下.

typedef objc_ivar * Ivar; struct objc_ivar { 

char *ivar_name; 

char *ivar_type;

 int ivar_offset; 

#ifdef __LP64__ int space; 

#endif } 

2.Ivar的一些常用方法

  //获取Ivar的名称 const char *ivar_getName(Ivar v);

  //获取Ivar的类型编码,  const char *ivar_getTypeEncoding(Ivar v)

  //通过变量名称获取类中的实例成员变量  Ivar class_getInstanceVariable(Class cls, const char *name)

  //通过变量名称获取类中的类成员变量  Ivar class_getClassVariable(Class cls, const char *name)

  //获取指定类的Ivar列表及Ivar个数  Ivar *class_copyIvarList(Class cls, unsigned int *outCount)

  //获取实例对象中Ivar的值  id object_getIvar(id obj, Ivar ivar)

  //设置实例对象中Ivar的值  void object_setIvar(id obj, Ivar ivar, id value)


4.方法实现上源码

1. 动态的添加对象的成员变量和方法 

1.1添加成员变量

用到了class_addProperty,这个可以给已经存在的类添加新的property,而且能够用过反射遍历到动态添加的属性。正好适合于JSPatch打补丁的情况

之所以不通过调用class_addIvar来添加实例变量,是因为它会改变一个已有类的内存布局,一般是通过objc_allocateClassPair动态创建一个class,才能调用class_addIvar创建Ivar,最后通过objc_registerClassPair注册class。一般情况下打补丁没有这个需求。

方法1 使用 objc_allocateClassPair

运行时规定,只能在objc_allocateClassPair与objc_registerClassPair两个函数之间为类添加变量

//额外空间 未知,通常设置为 0 Class clazz = objc_allocateClassPair(父类class,类名,额外空间);

  //以NSString*为例  //变量size sizeof(NSString)  //对齐    指针类型的为log2(sizeof(NSString*))  //类型    @encode(NSString*)  BOOL flag = class_addIvar(clazz,变量名,变量size,对齐,类型);

  objc_registerClassPair(clazz);


方法2 使用class_addIvar

BOOL class_addProperty(Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount) 

示例:

Class People = objc_allocateClassPair([NSObject class], "People", 0);

  objc_registerClassPair(People);

  //T@  objc_property_attribute_t attribute1;

  attribute1.name = "T";

  attribute1.value=@encode(NSString*);

  //Noatomic  objc_property_attribute_t attribute2 = {"N",""};//value无意义时通常设置为空  //Copy  objc_property_attribute_t attribute3 = {"C",""};

  //V_属性名  objc_property_attribute_t attribute4 = {"V","_name"};

//特性数组  objc_property_attribute_t attributes[] ={attribute1,attribute2,attribute3,attribute4};

  //向People类中添加名为name的属性,属性的4个特性包含在attributes中  class_addProperty(People, "name", attributes, 4);

  //获取类中的属性列表  unsigned int propertyCount;

  objc_property_t * properties = class_copyPropertyList(People, &propertyCount);

for (int i = 0; i< propertyCount,i++){

      NSLog(@"属性的名称为 : %s",property_getName(properties[i]));

      NSLog(@"属性的特性字符串为: %s",property_getAttributes(properties[i]));

  }

  //释放属性列表数组  free(properties);

1.2 添加对象的方法 

OBJC_EXPORT BOOL class_addMethod(Class cls, SEL name, IMP imp,

                                constchar*types)

    __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0);

Class cls  cls 参数表示需要添加新方法的类。

SEL name  name 参数表示 selector 的方法名称,可以根据喜好自己进行命名。

IMP imp  imp 即 implementation ,表示由编译器生成的、指向实现方法的指针。也就是说,这个指针指向的方法就是我们要添加的方法。

const char *types  最后一个参数 *types 表示我们要添加的方法的返回值和参数   这些表示方法都是定义好的(Type Encodings),关于Type Encodings的其他类型定义请参考官方文档   示例如果types参数为"i@:@“,按顺序分别表示:

i:返回值类型int,若是v则表示void

@:参数id 对象

::SEL(_cmd)

@:id(str)

代码示例 :

-(void)answer{

给类添加一个guess的方法,guessAnswer为guess方法的指针,指向guessAnswe实现的方法.  第四个参数是函数的返回值以及参数内容    v代表无返回值void,如果是i则代表int;@代表 id sel;

    class_addMethod(Class cls, @selector(guess), (IMP)guessAnswer, "v@:"); 

   if ([Class cls respondsToSelector:@selector(guess)]) {

        //Method method = class_getInstanceMethod(Class cls, @selector(guess));

//        [Class cls  performSelector:@selector(guess)];

        [ Class cls  performSelector:@selector(guess)];

   } else{

        NSLog(@"Sorry,I don't know");

    }

}

void guessAnswer(id self,SEL _cmd){

   NSLog(@"He is from erha");

}

2. 动态交换方法的实现 

用class_getInstanceMethod来取类方法,用class_getClassMethod来取实例方法时 

Classclass= [selfclass];

 Method originalMethod = class_getInstanceMethod(class, originalSelector);

 Method newMethod = class_getInstanceMethod(class, newSelector);

BOOLdidAddMethod = class_addMethod(class, originalSelector, method_getImplementation(newMethod), method_getTypeEncoding(newMethod)); 

给老方法天加新方法的实现(也可以说重写老方法), 如果新方法的实现在类里面存在,则添加方法失败,直接交换方法即可. 如果不存在,就会用新方法的实现重写老方法.

if(didAddMethod) { 

 class_replaceMethod(class, newSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)); //class_replaceMethod 会调用class_addMethod和method_setImplementation  添加新方法,并获得老方法的实现.

 }else{ 

存在直接交换 新老方法

 method_exchangeImplementations(originalMethod, newMethod); 

 }

3. 实现分类也可以添加属性 

关联对象就是runTime界的NSMultableDictionary  主要函数:

void objc_setAssociatedObject(id object,constvoid*key, id value, objc_AssociationPolicy policy);//相当于 setValue:forKey 进行关联value对象

id objc_getAssociatedObject(id object, constvoid*key); //用来读取对象

void objc_removeAssociatedObjects(id object);//函数来移除一个关联对象,或者使用objc_setAssociatedObject函数将key指定的关联对象设置为nil。

参数讲解:

key:要保证全局唯一,key与关联的对象是一一对应关系。必须全局唯一。通常用@selector(methodName) ,静态变量&btnKey,_cmd。

value作为key:要关联的对象。

policy:关联策略。有五种关联策略。OBJC_ASSOCIATION_ASSIGN 等价于 @property(assign)。OBJC_ASSOCIATION_RETAIN_NONATOMIC等价于 @property(strong, nonatomic)。OBJC_ASSOCIATION_COPY_NONATOMIC等价于@property(copy, nonatomic)。OBJC_ASSOCIATION_RETAIN等价于@property(strong,atomic)。OBJC_ASSOCIATION_COPY等价于@property(copy, atomic)。

代码实现

定义属性 @property(nonatomic,copy) NSString *chineseName;

在.m文件里重写 set get方法 

-(void)setChineseName:(NSString *) chineseName{

   char cName;

    objc_setAssociatedObject(self, &cName, chineseName, OBJC_ASSOCIATION_COPY_NONATOMIC);

}

-(NSString *)chineseName{

    return objc_getAssociatedObject(self, &cName);

}

4. 实现NSCoding的自动归档和解档 

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

最近项目比较近还没写完,后续会继续更新

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

推荐阅读更多精彩内容