OC runtime常见的使用场景和具体实现

简介

runtime 是OC一个很重要的机制,运行时机制,我们平时写的OC代码都会被转成runtime代码, runtime是一套比较底层的纯C语言的API。 runtime最主要的应该就是消息机制了,OC调用函数为消息发送,属于动态调用,编译的时候不能决定真正调用的哪个函数,这个和C,C++不同,C,C++在一个函数没有实现的时候编译也是通过不了的,而OC则不会。

查看runtime代码

查看runtime代码需要在终端中,cd 到想要查看文件的指定路径,输入clang -rewrite-objc xxx.m,但是记不清从Xcode那个版本之后,输入这个命令会报
UIKit/UIKit.h' file not found 错误,后来经过查找资料,编译之前,需要一些编译环境 和库的参数配置,或者是三方库头文件,需要借助 xcrun 命令。

xcrun -sdk iphonesimulator clang -rewrite-objc xxx.m

这样,编译成功的话,在文件中就会生成一个.cpp的文件,打开就能看到指定文件的OC代码转换成的runtime代码 (C++)。

runtime使用场景

使用runtime需要 #import <objc/runtime.h>
1.给分类添加属性

我们应该都试过,直接在分类中是不能添加属性的,运行会crash,但是我们可以利用runtime给分类添加属性。

#例如给项目中的控制器添加一个别名 -- 关键代码

static const char aliasKey;
- (void)setName:(NSString *)alias {
    //将值和对象关联起来
    objc_setAssociatedObject(self, &aliasKey, alias, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

- (NSString *)alias {
    return objc_getAssociatedObject(self, &aliasKey);
}
2.交换两个方法的实现

比较常用的是拦截系统自带方法调用(Swizzling),如果我们想统一给项目中的控制器自定义返回按钮样式,则可以在UIViewController的分类中Swizzling 控制器的ViewDidLoad方法,在新的方法中处理导航栏。
:统一设置项目中的返回按钮,常用的方法还有自定义导航控制器,重写导航控制器的push方法统一设置。这里主要是介绍OC的黑魔法 runtime Swizzling使用。

//当程序装载到内存中的时候调用
+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Method originalMethod = class_getInstanceMethod([UIViewController class], @selector(viewDidLoad));
        Method swizzledMethod = class_getInstanceMethod([UIViewController class], @selector(swizzling_viewDidLoad));
        
        method_exchangeImplementations(originalMethod, swizzledMethod);
    });
}

- (void)swizzling_viewDidLoad {
    if (self.navigationController) {
        UIImage *buttonNormal = [[UIImage imageNamed:@"nav_back_normal"] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
        [self.navigationController.navigationBar setBackIndicatorImage:buttonNormal];
        [self.navigationController.navigationBar setBackIndicatorTransitionMaskImage:buttonNormal];
        UIBarButtonItem *backItem = [[UIBarButtonItem alloc] initWithTitle:@"" style:UIBarButtonItemStylePlain target:nil action:nil];
        self.navigationItem.backBarButtonItem = backItem;
    }
    [self swizzling_viewDidLoad];
}

3.获取某个类的所有成员变量和方法

具体的使用,放到runtime动态创建类和后面实现归档的例子中体现。

  • 获得所有成员变量
Ivar *class_copyIvarList(Class cls, unsigned int *outCount)
  • 获得所有方法
Method *class_copyMethodList(Class cls, unsigned int *outCount)
  • 获得成员变量名
 const char *ivar_getName(Ivar v)
  • 获得成员变量类型
const char *ivar_getTypeEncoding(Ivar v)
4.动态生成一个类

在运行时的机制中,我们可以动态生成一个类,例如 KVO的底层实现,在这里不介绍KVO是如何通过动态创建一个类来实现监听对象的改变,感兴趣的可以去网上了解一下,相关资料很多。

  • 动态创建类
    参数:
    1.superclass = 要动态创建类的父类
    2.name = 动态创建的类名
    3.extraBytes = 值通常为0
Class objc_allocateClassPair(Class superclass, const char *name, 
                                         size_t extraBytes)
  • 添加成员变量
    参数:
    1.cls = 为哪个类添加成员变量
    2.name = 成员变量的名字
    3.size = 成员变量的字节数
    4.alignment = 对其方式
    5.type = 成员变量的类型
BOOL class_addIvar(Class cls, const char *name, size_t size, 
                               uint8_t alignment, const char *types)
  • 添加方法
    参数:
    1.cls = 为哪个类添加方法
    2.name = 方法的名字
    3.imp = 对应的方法
    4.types = 描述方法参数类型的字符数组
BOOL class_addMethod(Class cls, SEL name, IMP imp, 
                                 const char *types)
  • 注册到运行时环境
    必须将创建的类注册到运行时环境才能生效。
void objc_registerClassPair(Class cls)

下面的例子是在Animal类中,动态创建了它的子类Dog类,并打印出了Dog类及其父类的所有成员变量。

- (void)printDogIvars {
    unsigned int varCount = 0;
    Class c = NSClassFromString(@"Dog");
    while (c && c != [NSObject class]) {
        Ivar *vars = class_copyIvarList(c, &varCount);
        // class_copyPropertyList 获得是当前类的所有属性,不包括成员变量hhh
        
        for (int i = 0; i < varCount; i ++) {
            Ivar var = vars[i];
            //获得成员变量的名字
            const char *varName = ivar_getName(var); //“_type”,"hhh"
            //获得成员变量的类型
            const char *varType = ivar_getTypeEncoding(var);//"NSString"
            NSLog(@"varName = %s,varType = %s", varName, varType);
        }
        c = [c superclass];
        free(vars);
    }
}

- (void)dymicCreateDogClass {
    const char *className = "Dog";
    
    Class Dog = object_getClass(NSClassFromString([NSString stringWithUTF8String:className]));
    if (!Dog) {
        //动态创建一个继承自Animal的Dog类
        Dog = objc_allocateClassPair([Animal class], className, 0);
        //给创建的Dog类添加成员变量
        if (class_addIvar(Dog, "header", sizeof(NSString *), 0, "@")) {
            NSLog(@"success");
        }
        class_addMethod(Dog, @selector(printDogIvarsValue), (IMP)printDogIvarsValue, "");
        //注册到运行时环境
        objc_registerClassPair(Dog);
    }
    
    id smallDog = [[Dog alloc] init];
//    //给变量赋值
    [smallDog setValue:@"smallHeader" forKey:@"header"];
    
    [smallDog printDogIvarsValue];
}

//这个方法实际上没有调用,但是必须实现这个方法,才能调用下面的方法
- (void)printDogIvarsValue {
    
}

//调用这个方法,self和_cmd 参数是必须的,在之后也可以随便添加参数
static void printDogIvarsValue(id self, SEL _cmd) {
    Ivar var = class_getInstanceVariable([self class], "header");
    id value = object_getIvar(self, var);
    
    NSLog(@"动态创建的Dog类的成员变量header的值==%@", value);
}
5.利用runtime实现对象的归档

利用runtime实现对象的归档是很常见的,我们平时在实现对象归档的时候通常的做饭是,实现归档和解档的两个方法 encodeWithCoder:initWithCoder:,在里面对要归档和解档的属性进行重复的decodeObjectForKey:encodeObject:,如果属性很多的话就要写很多重复的代码,这样并不好。现在可以利用runtime获取全部的成员变量来进行归档操作。

//不需要进行归解档的属性
- (NSArray *)ignoredNames {
    return @[@"_one",@"_tow",@"_three"];
}

//解档
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
    if (self = [super init]) {
        unsigned int varCount = 0;
        Ivar *vars = class_copyIvarList([self class], &varCount);
        for (int i = 0; i < varCount; i ++) {
            Ivar var = vars[i];
            const char *varName = ivar_getName(var);
            NSString *key = [NSString stringWithUTF8String:varName];
            // 忽略不需要解档的属性
            if ([[self ignoredNames] containsObject:key]) {
                continue;
            }
            id value = [aDecoder decodeObjectForKey:key];
            [self setValue:value forKey:key];
        }
        free(vars);
    }
    return self;
}

//归档
- (void)encodeWithCoder:(NSCoder *)aCoder {
    unsigned int varCount = 0;
    Ivar *vars = class_copyIvarList([self class], &varCount);
    for (int i = 0; i < varCount; i ++) {
        Ivar var = vars[i];
        const char *varName = ivar_getName(var);
        NSString *key = [NSString stringWithUTF8String:varName];
        // 忽略不需要归档的属性
        if ([[self ignoredNames] containsObject:key]) {
            continue;
        }
        id value = [self valueForKey:key];
        [aCoder encodeObject:value forKey:key];
    }
    free(vars);
}

另外还有一种处理方式是给NSObject添加一个分类,提供归档和解档的方法,将上面归解档操作放到相应的方法里,如果多个类都需要归解档时,就可以直接调用分类中的方法,实现了对代码的封装,简化了代码。

- (instancetype)initWithCoder:(NSCoder *)aDecoder {
    if (self = [super init]) {
        [self decode:aDecoder];
    }
    return self;
}

- (void)encodeWithCoder:(NSCoder *)aCoder {
    [self encode:aCoder];
}

归解档的操作,建议去看一下MJExtension的实现,代码封装的很好,很值得学习,这里只是讲解runtime的使用,没有花时间去封装代码,还请见谅!

最后

感谢您的阅读,希望我的这篇文章能帮助到您!

代码在这里

在这个时代根本就没有怀才不遇, 关键是你真的必须具备才华,所以请你一定要强大自己!

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

推荐阅读更多精彩内容

  • 转至元数据结尾创建: 董潇伟,最新修改于: 十二月 23, 2016 转至元数据起始第一章:isa和Class一....
    40c0490e5268阅读 1,678评论 0 9
  • 我们常常会听说 Objective-C 是一门动态语言,那么这个「动态」表现在哪呢?我想最主要的表现就是 Obje...
    Ethan_Struggle阅读 2,162评论 0 7
  • 原文出处:南峰子的技术博客 Objective-C语言是一门动态语言,它将很多静态语言在编译和链接时期做的事放到了...
    _烩面_阅读 1,212评论 1 5
  • Objective-C语言是一门动态语言,它将很多静态语言在编译和链接时期做的事放到了运行时来处理。这种动态语言的...
    有一种再见叫青春阅读 572评论 0 3
  • 浓浓秋意,总难免夹杂些许的思念,凉凉的夜,思绪纷飞于风中。再也控制不了脚步,走出小区去感受这份惬意!心里突然乐滋滋...
    落梅阅读 311评论 0 1