利用runtime为系统类添加属性、成员变量.......

1️⃣runtime介绍:

runtime是一套比较底层的纯C语言API, 包含了很多底层的C语言API。在我们平时编写的OC代码中, 程序运行过程时, 其实最终都是转成了runtime的C语言代码.

比如说,下面一个创建对象的方法 :

1.[[ZSPerson alloc] init]

2.runtime :objc_msgSend(objc_msgSend(“ZSPerson” , “alloc”), “init”)

2️⃣runtime 用来干什么呢??用在那些地方呢?怎么用呢?

runtime是属于OC的底层, 可以进行一些非常底层的操作(用OC是无法现实的, 不好实现)

在程序运行过程中, 动态创建一个类(比如KVO的底层实现)

在程序运行过程中, 动态地为某个类添加属性\方法, 修改属性值\方法

遍历一个类的所有成员变量(属性)\所有方法

例如:我们需要对一个类的属性进行归档解档的时候属性特别的多,这时候,我们就会写很多对应的代码,但是如果使用了runtime就可以动态设置!

例如,ZSPerson.h的文件如下所示

@interfaceZSPerson:NSObject

@property(nonatomic,assign)intage;

@property(nonatomic,assign)intheight;

@property(nonatomic,copy)NSString*name;

@property(nonatomic,assign)intage2;

@property(nonatomic,assign)intheight2;

@property(nonatomic,assign)intage3;

@property(nonatomic,assign)intheight3;

@property(nonatomic,assign)intage4;

@property(nonatomic,assign)intheight4;

@end

而ZSPerson.m实现文件的内容如下

#import"ZSPerson.h"

@implementationZSPerson

(void)encodeWithCoder:(NSCoder )encoder 

{

unsignedintcount =0;

Ivar ivars = class_copyIvarList([ZSPerson class], &count);

for(int i =0; i<count;i++){

// 取出i位置对应的成员变量

Ivar ivar = ivars[i];

// 查看成员变量

constchar*name = ivar_getName(ivar);

// 归档

NSString*key = [NSStringstringWithUTF8String:name];

idvalue = [selfvalueForKey:key]; 

  [encoder encodeObject:value forKey:key]; 

  }

free(ivars);

    } 

  (id)initWithCoder:(NSCoder *)decoder 

  {

if(self= [super init]) {

unsignedintcount =0;

 Ivar *ivars = class_copyIvarList([ZSPerson class], &count);

for(int i =0; i<count;i++){

// 取出i位置对应的成员变量

Ivar ivar = ivars[i];

// 查看成员变量

const char*name = ivar_getName(ivar);

// 归档

NSString*key = [NSStringstringWithUTF8String:name];

id value = [decoder decodeObjectForKey:key];

// 设置到成员变量身上

[selfsetValue:value forKey:key];

free(ivars);

  }

returnself; 

  }

@end

这样我们可以看到归档和解档的案例其实是runtime写下的

学习,runtime机制首先要了解下面几个问题

1.相关的头文件和函数

a> 头文件

利用头文件,我们可以查看到runtime中的各个方法!

b> 相关应用

NSCoding(归档和解档, 利用runtime遍历模型对象的所有属性)

字典 –> 模型 (利用runtime遍历模型对象的所有属性, 根据属性名从字典中取出对应的值, 设置到模型的属性上)

KVO(利用runtime动态产生一个类)

用于封装框架(想怎么改就怎么改)

这就是我们runtime机制的只要运用方向

c> 相关函数

objc_msgSend : 给对象发送消息

class_copyMethodList : 遍历某个类所有的方法

class_copyIvarList : 遍历某个类所有的成员变量

class_…..

这是我们学习runtime必须知道的函数!

2.必备常识

a> Ivar : 成员变量

b> Method : 成员方法

从上面例子中我们看到我们定义的成员变量,如果要是动态创建方法,可以使用Method。

3️⃣接下来我们进行项目实战

首先给UITableViewCell创建一个分类RightDownPlugin

.h文件中

#import<UIKit,UIKit.h>

@interfaceUITableViewCell(RightDownPlugin)

@property(nonatomic,strong)UIImageView*statusImgV;//状态图@property(nonatomic,strong)UILabel*statusLab;//状态label

@end

.m文件

#import"UITableViewCell+RightDownPlugin.h"

#include <objc/runtime.h>

@implementationUITableViewCell(RightDownPlugin)

//定义常量 必须是C语言字符串 因为runtime是C语言API,

staticchar*statusImgKey ="statusImgKey";

staticchar*statusLabKey ="statusLabKey";

/* 

OBJC_ASSOCIATION_ASSIGN;            //assign策略

OBJC_ASSOCIATION_COPY_NONATOMIC;    //copy策略

OBJC_ASSOCIATION_RETAIN_NONATOMIC;  // retain策略

OBJC_ASSOCIATION_RETAIN;

OBJC_ASSOCIATION_COPY;

*//*

id object 给哪个对象的属性赋值

const void *key 属性对应的key

id value  设置属性值为value

objc_AssociationPolicy policy  使用的策略,是一个枚举值,和copy,retain,assign是一样的,手机开发一般都选择nonatomic

objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy);

*/// 然后就需要自定义set和get方法了,因为category默认是不能添加属性的,

- (void)setStatusImgV:(UIImageView*)statusImgV{    objc_setAssociatedObject(self,statusImgKey,statusImgV,OBJC_ASSOCIATION_RETAIN);

}

- (UIImageView*)statusImgV{

return objc_getAssociatedObject(self, statusImgKey);

}

// Lab

- (void)setStatusLab:(UILabel*)statusLab{    objc_setAssociatedObject(self,statusLabKey,statusLab,OBJC_ASSOCIATION_RETAIN);

}

- (UILabel*)statusLab{

return objc_getAssociatedObject(self, statusLabKey);

}

@end

runtime常见的用法总结

1.runtime动态添加属性

需求:给NSString类添加两个属性:name和count

NSString+String.h中

#import<Foundation/Foundation.h>

/**

*  @property在分类里添加一个属性 不会有setter getter方法 只声明了一个变量 而且 这样声明 这个属性和这个类没有什么关系 */

@interface NSString (String)

@property (copy, nonatomic, nullable) NSString *name;

@property (copy, nonatomic, nullable) NSString *count;

@end

NSString+String.m中

#import "NSString+String.h"

#import<objc/message>

/**

*  动态添加属性的本质是 指向外部已经存在的一个属性 而不是去在对象中创建一个属性

*/

@implementation NSString (String)

static NSString *_name;

//在分类里声明属性 需要自己写set方法和get方法

- (void) setName:(NSString *)name

{

_name = name;

}

- (NSString *) name

{

return _name;

}

//添加属性 应该是与对象有关

- (void) setCount:(NSString *)count

{

//set方法里设置关联

//Associated 关联 联系

//跟某个对象产生关联,添加属性

/**

* id obj 给哪个对象添加属性(产生关联)

* const void *key 属性名 (根据key获取关联的对象) void * 相当于 id 万能指针 传c或者oc的都可以

* id value 要关联的值

* objc_AssociationPolicy policy 策略 宏对应assign retain copy (因为weak没有用 外面赋值完马上就会被销毁 所以没有weak)

*/

objc_setAssociatedObject(self, @"count", count, OBJC_ASSOCIATION_ASSIGN);

}

- (NSString *) count

{

//get方法里获取关联

return [NSString stringWithFormat:@"%ld",[objc_getAssociatedObject(self, @"count") length]];

}

@end

2.runtime动态加载方法

ViewController中

#import "ViewController.h"

#import "Person.h"

@interface ViewController ()

@end

@implementation ViewController

- (void) viewDidLoad{    [super viewDidLoad]; 

  //performSelector:动态添加方法   

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

[p0 performSelector:@selector(eat)];

//动态添加方法 但是如果没有实现该方法 还是会崩溃 

[p0 performSelector:@selector(drink:) withObject:@"juice"];

//动态添加方法 但是如果没有实现该方法 还是会崩溃

}

Person.m中

#import "Person.h"

#import<objc/message.h>

//默认一个方法都有两个参数:self 和_cmd  self是方法的调用者 _cmd就是调用方法的编号(方法名) 这两个参数为隐式参数 但是如果调用的是c的函数 则需要写出来

@implementation Person

//定义函数 该函数名是啥都可以

void eat(id self,SEL _cmd)

{

//无返回值 两个参数 void(id,SEL) v@:

NSLog(@"%@调用了%@方法",[self class],NSStringFromSelector(_cmd));//SEL本身没发打印 只能打印方法名

}

void drink(id self,SEL _cmd,id param1)

{

//v void    @ 对象    : 方法编号

NSLog(@"%@调用了%@方法 传递参数:%@",[self class],NSStringFromSelector(_cmd),param1);//SEL本身没发打印 只能打印方法名

}

//1.动态添加方法 首先要实现resolveInstanceMethod:方法或resolveClassMethod:方法

//前者对应实例方法 后者对应类方法

//这两个方法的作用是要知道哪个方法没有被实现

//这两个方法是在当该类的某个方法没有实现,但是又被外界调用了的时候调用 (及:外界试用performSelector:调用了该类中某个没有实现的方法)

//sel参数为没有被实现的这个方法

+ (BOOL) resolveInstanceMethod:(SEL)sel

{

//打印该方法名

//    NSLog(@"%@",NSStringFromSelector(sel));

//动态添加方法

if ([NSStringFromSelector(sel) isEqualToString:@"eat"])//sel == @selector(eat)也可以 但是会报警

{

/**

*  Class 给哪个类添加方法

*  sel 要添加的方法编号(方法名)

*  IMP 方法的实现 ———— 函数的入口(函数的指针 函数名 是啥都可以 不一定和sel相同)

*  types 方法的类型 编码格式 (类型c语言的字符串) (函数的类型:返回值类型 参数类型 直接查文档 文档有表格)

*/

class_addMethod([self class], sel, (IMP)eat, "v@:");

//处理完了要返回YES

//        return YES;

}

else if ([NSStringFromSelector(sel) isEqualToString:@"drink:"])//要加冒号

{

class_addMethod([self class], sel, (IMP)drink, "v@:@");

//        return YES;

}

//由于不知道返回的YES还是NO 所以:

return [super resolveInstanceMethod:sel];

}

+ (BOOL) resolveClassMethod:(SEL)sel

{

return [super resolveClassMethod:sel];

}

@end

3.runtime交换方法(ios黑魔法)

ViewController.m中

#import "ViewController.h"

#import<objc/message.h>

#import "Person.h"

//#import "UIImage+image.h"//交换方法时候不用导入也可以 因为交换写在类+(void)load里

@interface ViewController ()

@end

@implementation ViewController

- (void) viewDidLoad{    [super viewDidLoad]; 

  /** 

  *  交换方法  

*///  

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

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

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

  //用imageNamed加载图片,并不知道图片是否加载成功  

//需求:在以后调用imageNamed的时候,要知道图片是否加载成功  

//交换方法的实现 (把imageNamed:方法和ov_imageNamed:方法交换 及 调用imageNamed就是调用ov_imageNamed)

}

@end

UIImage+image.m中

#import"UIImage+image.h"

#import<objc/message.h>

@implementation UIImage (image)

//加载这个分类的时候调用

+ (void) load

{

NSLog(@"%s",__func__);

//方法都定义在类里面 所以 交换对象方法也用class_开头

/**

*  class_getMethodImplementation 获取类方法的实现

*

*  Class 获取哪个类的方法

*  SEL 获取哪个方法

*  class_getMethodImplementation(<#__unsafe_unretained Class cls#>, <#SEL name#>)

*/

/**

*  class_getInstanceMethod 获取对象方法

*

*  Class 获取哪个类的方法

*  SEL 获取哪个方法

*

*  class_getInstanceMethod(<#__unsafe_unretained Class cls#>, <#SEL name#>)

*/

/**

*  class_getClassMethod 获取类方法

*

*  class_getClassMethod(<#__unsafe_unretained Class cls#>, <#SEL name#>)

*/

/**

*  method_exchangeImplementations交换方法

*

*  Method m1  要被替换的方法

*  Method m2  要替换Method m1的方法

*  method_exchangeImplementations(<#Method m1#>, <#Method m2#>)

*/

//交换方法的实现

//1.拿到两个方法

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

Method ov_imageNamedMethod = class_getClassMethod([self class], @selector(ov_imageNamed:));

//2.交换

method_exchangeImplementations(imageNamedMethod, ov_imageNamedMethod);

}

/**

*  分类没有父类 没有super

*/

//+ (nullable __kindof UIImage *) imageNamed:(nonnull NSString *)imageName

//{

//    return nil;

//}

/**

*  用其他方法做 这个方法不好的原因是 1.导入头文件太蛋疼 2.团队其他人可能不知道

*/

+ (nullable __kindof UIImage *) ov_imageNamed:(nonnull NSString *)imageName

{

//1.加载图片功能

//    UIImage *image = [UIImage imageNamed:imageName];//由于使用了方法交换 所以这里再调用该方法就会造成死循环

UIImage *image = [UIImage ov_imageNamed:imageName];//此处直接调用方法本身即可

NSLog(@"%s %d",__func__,__LINE__);

//2.判断返回是否为空功能

if (!image)

{

//NSException 为抛异常(强制崩溃)

//        NSException *e = [NSException

//                          exceptionWithName: @"异常情况"

//                          reason: @"图片为空"

//                          userInfo: nil];

//        @throw e;

}

else

{

}

return image;

}

@end

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

推荐阅读更多精彩内容

  • 转至元数据结尾创建: 董潇伟,最新修改于: 十二月 23, 2016 转至元数据起始第一章:isa和Class一....
    40c0490e5268阅读 1,636评论 0 9
  • 对于从事 iOS 开发人员来说,所有的人都会答出【runtime 是运行时】什么情况下用runtime?大部分人能...
    梦夜繁星阅读 3,674评论 7 64
  • OC最实用的runtime总结,面试、工作你看我就足够了! 前言runtime的资料网上有很多了,部分有些晦涩难懂...
    small_Sun阅读 920评论 1 12
  • 从小就是乖乖的小孩,拿到新书本要包书皮,一页漂亮的纸签怎么也舍不得写,要收藏起来。长大后物质资源呈爆炸式增长,留恋...
    迷豆酱阅读 372评论 0 0
  • 蓝蓝的天,白白的云 ,金黄又明媚的阳光,天地自有一番美好!一阵夏天的风吹过,云动了,流水唱了,树、小草也尽情摇摆,...
    清风明月秋阅读 259评论 0 0