Runtime 的实践和理解

做iOS 的基本上没有有几个不知道Runtime的吧;

1.通过runtime 获取成员变量列表、属性列表、方法列表~等

举例说明,我们定义一个Person类,在一个ViewController 调用,获取该类的成员变量,属性列表,方法列表

定义Person类如下

#import <Foundation/Foundation.h>

@interface Person : NSObject
@property(nonatomic,copy) NSString *name;
@property(nonatomic,copy) NSString *sex;
-(NSString *)sayName;
-(NSString *)saySex;
-(void)updatePrivateName:(NSString *)name;
@end

//--------------------------------------

#import "Person.h"

/* Person.m 的实现
这里我对Person 进行了Class Extension
为了说明,属性和成员变量是两种不同的东西 */
@interface Person ()
{
    NSString *privateName;
}
@end


@implementation Person

- (NSString *)saySex
{
    return @"i am a boy";
}
- (NSString *)sayName
{
    return @"my name is pengpeng";
}

/* 调用扩展属性必须的 -> 函数*/
-(void)updatePrivateName:(NSString *)name{
    self ->privateName = name;
    NSLog(@"NSLog privateName Value is %@",self->privateName);
}

/* 私有方法*/
-(void)private_func{
    NSLog(@"privte function");
}

@end

然后在ViewController 引入Person,并创建一个对象,展示代码如下,应该都能看得懂;
上面Person 的定义还能延伸一下 CategoryExtension的区别, 以及@Property 自动帮我们实现了Set 、Get 方法;

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.person = [Person new];
    _person.name = @"big pengpeng";
    self.person.sex = @"man";
    /* 调用方法*/
    [self getPropertyList];
    [self getMethodList];
    [self getIvarList];
    /* 延伸,扩展属性的调用*/
    [self.person updatePrivateName:@"pengpeng"];
}

1.1 获取属性列表

/* 获取属性列表*/
-(void)getPropertyList{
    
    unsigned int count;
    objc_property_t *propertyList = class_copyPropertyList([Person class], &count);
    for (unsigned int i=0; i<count; i++) {
        const char *propertyName = property_getName(propertyList[i]);
        NSLog(@"property---->%@", [NSString stringWithUTF8String:propertyName]);
    }
}
   
/* 运行结果*/   
runtime[41582:1275679] property---->name
runtime[41582:1275679] property---->sex
   

1.2 获取方法列表

/* */
-(void)getMethodList{
    unsigned int count;
    Method *methodList = class_copyMethodList([Person class], &count);
    for (unsigned int i; i<count; i++) {
        Method method = methodList[i];
        NSLog(@"method---->%@", NSStringFromSelector(method_getName(method)));
    }
}

/* 运行结果*/  
runtime[41582:1275679] method---->sayName
runtime[41582:1275679] method---->saySex
runtime[41582:1275679] method---->updatePrivateName:
runtime[41582:1275679] method---->private_func
runtime[41582:1275679] method---->.cxx_destruct
runtime[41582:1275679] method---->name
runtime[41582:1275679] method---->setName:
runtime[41582:1275679] method---->setSex:
runtime[41582:1275679] method---->sex

出现这个结果应该也没有什么问题;sayNamesaySexupdatePrivateName都是对象方法,private_func是私有方法;
属性name、sex因为是Property声明的默认实现了setget方法;
.cxx_destruct 是ARC下将所有的成员变量变成nil的方法(系统加的)

1.3 获取成员变量列表

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

/* 运行结果*/  
runtime[45997:1632709] Ivar---->privateName
runtime[45997:1632709] Ivar---->_name
runtime[45997:1632709] Ivar---->_sex

1.4 获取协议列表

/* 在该ViewController 实现一个AlertView 的代理方法*/
-(void)createAlertView{
  
    UIAlertView *alertView = [[UIAlertView alloc]initWithTitle:@"title" message:@"message" delegate:self cancelButtonTitle:@"cancel" otherButtonTitles:@"comfirm", nil];
    [alertView show];
}

-(void)getProtocolList{
    unsigned int count;
    __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]);
    }
}
/*运行结果*/
runtime[17235:552811] protocol---->UIAlertViewDelegate

1.5 动态给类添加方法

进入runtime 的头文件,可以看到runtime 提供了很多API 给我们使用,其中就有可以动态的给一个Class添加方法;
这里同样给Person类,添加一个-(void)syGoodBye{}方法

其实还有关于动态添加方法的另一种形势,链接如下 链接

/* 定义一个方法*/
- (void)sayFrom
{
    
    class_addMethod([self.person class], @selector(guess), (IMP)guessAnswer, "v@:");
    if ([self.person respondsToSelector:@selector(guess)]) {
        
        [self.person performSelector:@selector(guess)];
        
    } else{
        NSLog(@"Sorry,I don't know");
    }
    self.textview.text = @"beijing";
}

void guessAnswer(id self,SEL _cmd){
    
    NSLog(@"i am from beijing");
}


/* 调用该方法*/
- (void)viewDidLoad {
    [super viewDidLoad];
    self.person = [Person new];
    [self sayFrom];

}

/* 运行结果*/  
runtime[45997:1632709] i am from beijing

1.6 方法交换

还是以Person对象为例子,正常情况调用Person 对象的sayName方法和saySex 都没毛病,如果我们在load方法或者在viewDidLoad 方法中,将两个方法交换,那么就能实现方法交换;

- (void)viewDidLoad {
    [super viewDidLoad];
    self.person = [Person new];
    
    NSLog(@"%@",_person.sayName);
    NSLog(@"%@",_person.saySex);
    
    Method m1 = class_getInstanceMethod([self.person class], @selector(sayName));
    Method m2 = class_getInstanceMethod([self.person class], @selector(saySex));
    method_exchangeImplementations(m1, m2);
    
 }

- (IBAction)sayName:(id)sender {
     NSLog(@"方法交换后 sayName : %@",[_person sayName]);
}

- (IBAction)saySex:(id)sender {
     NSLog(@"方法交换后 saySex : %@",[_person saySex]);
}
/*运行结果*/
runtime[45997:1632709] my name is pengpeng
runtime[45997:1632709] i am a boy

//方法交换后
runtime[46307:1667781] 方法交换后 sayName : i am a boy
runtime[46307:1667781] 方法交换后 saySex : my name is pengpeng

1.7 给分类添加属性

举例,这里给UIImageview添加一个属性 URLkey
创建一个UIImageView+Extension 分类;
代码如下

#import <UIKit/UIKit.h>
#import <objc/runtime.h>

@interface UIImageView (extension)
@property (nonatomic,copy) NSString *URLkey;
@end

//---------------------------
#import "UIImageView+extension.h"

static char * UrlKey = "imageUrlKey";

@implementation UIImageView (extension)

-(NSString *)URLkey{
    return  objc_getAssociatedObject(self, UrlKey);
}
-(void)setURLkey:(NSString *)URLkey{
    objc_setAssociatedObject(self, UrlKey, URLkey, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

/* 运行 */
- (void)viewDidLoad {
    [super viewDidLoad];
    
    UIImageView *imageview = [[UIImageView alloc]init];
    imageview.URLkey = @"我是一个假的URL";
    NSLog(@" %@",imageview.URLkey);
}

/*运行结果*/
runtime[49010:1741011]  我是一个假的URL

1.8 字典转模型

利用runtime完成字典转模型的功能,其实就是给 NSObject 扩展一个字典转模型的方法,因为所有的类都是NSObject的子类,获取字典内部ivar成员变量,字典通过成员变量对应的Key取出Value,然后把value赋值给模型相映的属性;

如果属性是另一个自定义Class,则再次递归直到解析完毕;

如果属性是一个数组,则便利数组的每个对象循环解析处理成模型数组 赋值给相映的属性值;

// 字典转模型
+ (instancetype)objectWithDict:(NSDictionary *)dict
{
    // 创建对应模型对象
    id objc = [[self alloc] init];
    
    
    unsigned int count = 0;
    
    // 1.获取成员属性数组
    Ivar *ivarList = class_copyIvarList(self, &count);
    
    // 2.遍历所有的成员属性名,一个一个去字典中取出对应的value给模型属性赋值
    for (int i = 0; i < count; i++) {
        
        // 2.1 获取成员属性
        Ivar ivar = ivarList[i];
        
        // 2.2 获取成员属性名 C -> OC 字符串
        NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];
        
        // 2.3 _成员属性名 => 字典key
        NSString *key = [ivarName substringFromIndex:1];
        
        // 2.4 去字典中取出对应value给模型属性赋值
        id value = dict[key];
        
        
        // 获取成员属性类型
        NSString *ivarType = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
        
        // 二级转换,字典中还有字典,也需要把对应字典转换成模型
        //
        // 判断下value,是不是字典
        if ([value isKindOfClass:[NSDictionary class]] && ![ivarType containsString:@"NS"]) { //  是字典对象,并且属性名对应类型是自定义类型
            // user User
            
            // 处理类型字符串 @\"User\" -> User
            ivarType = [ivarType stringByReplacingOccurrencesOfString:@"@" withString:@""];
            ivarType = [ivarType stringByReplacingOccurrencesOfString:@"\"" withString:@""];
            // 自定义对象,并且值是字典
            // value:user字典 -> User模型
            // 获取模型(user)类对象
            Class modalClass = NSClassFromString(ivarType);
            
            // 字典转模型
            if (modalClass) {
                // 字典转模型 user
                value = [modalClass objectWithDict:value];
            }
        }
        
        // 三级转换:NSArray中也是字典,把数组中的字典转换成模型.
        // 判断值是否是数组
        if ([value isKindOfClass:[NSArray class]]) {
            // 判断对应类有没有实现字典数组转模型数组的协议
            if ([self respondsToSelector:@selector(arrayContainModelClass)]) {
                
                // 转换成id类型,就能调用任何对象的方法
                id idSelf = self;
                
                // 获取数组中字典对应的模型
                NSString *type =  [idSelf arrayContainModelClass][key];
                
                // 生成模型
                Class classModel = NSClassFromString(type);
                NSMutableArray *arrM = [NSMutableArray array];
                // 遍历字典数组,生成模型数组
                for (NSDictionary *dict in value) {
                    // 字典转模型
                    id model =  [classModel objectWithDict:dict];
                    [arrM addObject:model];
                }
                
                // 把模型数组赋值给value
                value = arrM;
                
            }
        }
        
        // 2.5 KVC字典转模型
        if (value) {
            [objc setValue:value forKey:key];
        }
    }
    
    // 返回对象
    return objc;
    
}

1.9 模型归档

我们直到 要实现自定义对象,归档和解档的功能,只需给类,实现 协议即可,但是每次都要给归档的对象写这些协议的实现,是不是有点费劲,那么今天我们来看一下runtime如何实现自动归档解档;

首先没有对比就没有伤害,我们来看之前是怎么实现归档解档的

/* 手动实现-没什么好说的是吧*/

@interface Movie : NSObject<NSCoding>
@property (nonatomic, copy) NSString *movieId;
@property (nonatomic, copy) NSString *movieName;
@property (nonatomic, copy) NSString *pic_url;
@end
//-----------

@implementation Movie

- (void)encodeWithCoder:(NSCoder *)encoder
{
    [encoder encodeObject:self.movieId forKey:@"movieId"];
    [encoder encodeObject:self.movieName forKey:@"movieName"];
    [encoder encodeObject:self.pic_url forKey:@"pic_url"];
}

- (id)initWithCoder:(NSCoder *)decoder
{ 
self.movieId = [aDecoder decodeObjectForKey:@"movieId"];
self.movieName = [aDecoder decodeObjectForKey:@"movieName"];
self.pic_url = [aDecoder decodeObjectForKey:@"pic_url"];
}

利用Runtime实现实现Movie对象的 自动归档和解档;

demo 中该实现方法写在Moive的 .m 文件中,实际项目中,给需要归档和解档的对象定义一个基类BaseArchiveModel实现该Runtime的 - (void)encodeWithCoder:(NSCoder *)encoder- (id)initWithCoder:(NSCoder *)decoder方法自动归档接档,项目中需要归档解档的类只需继承该BaseArchiveModel就可以了;

@implementation Movie

- (void)encodeWithCoder:(NSCoder *)encoder

{
   unsigned int count = 0;
   Ivar *ivars = class_copyIvarList([Movie class], &count);
   
   for (int i = 0; i<count; i++) {
       Ivar ivar = ivars[i];
       const char *name = ivar_getName(ivar);
       NSString *key = [NSString stringWithUTF8String:name];
       id value = [self valueForKey:key];
       [encoder encodeObject:value forKey:key];
   }
   free(ivars);
}

- (id)initWithCoder:(NSCoder *)decoder
{
   if (self = [super init]) {
       unsigned int count = 0;
       Ivar *ivars = class_copyIvarList([Movie class], &count);
       for (int i = 0; i<count; i++) {
           Ivar ivar = ivars[i];
           const char *name = ivar_getName(ivar);
           NSString *key = [NSString stringWithUTF8String:name];
           id value = [decoder decodeObjectForKey:key];
           [self setValue:value forKey:key];
           
       }
       free(ivars);
   }
   return self;
}



项目链接 https://github.com/hunter858/runtime

2.runtime 在第三方库中的应用

2.1 MJExtension

https://github.com/CoderMJLee/MJExtension

https://github.com/ibireme/YYModel

2.2 SDWebImage

https://github.com/SDWebImage/SDWebImage

AvoidCrash

https://github.com/chenfanfang/AvoidCrash

未完待续,整理中...

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

推荐阅读更多精彩内容

  • Runtime 01 问题:objc在向一个对象发送消息时,发生了什么? 解答: 根据对象的 isa 指针找到类对...
    yycache阅读 324评论 1 0
  • 前言,文章是转载的,因为之前收藏的,今天突然发现没了。不知道什么原因,简书搜索不到了,有几篇同样转载的,但是代码没...
    安静就好_阅读 2,532评论 6 42
  • Runtime的特性主要是消息(方法)传递,如果消息(方法)在对象中找不到,就进行转发,具体怎么实现的呢。我们从下...
    ios开发闻闻阅读 6,765评论 2 26
  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,034评论 1 32
  • Runtime的特性主要是消息(方法)传递,如果消息(方法)在对象中找不到,就进行转发,具体怎么实现的呢。我们从下...
    jackyshan阅读 137,258评论 79 764