对runtime的总结:让你会用Runtime
导语
Runtime,简称运行时,就是系统在运行的时候的一些机制,其中最主要的是消息机制,是一套底层的纯C语言的API,我们平时所编写的OC代码,在程序的运行过程中都转成了runtime的代码,平时调方法都是转成了objc_msgSend函数。大家应该都听过或者用过一个叼库IQKeyboard,它只需导入进工程,不需要写一行代码就能实现功能,其实就是用的runtime机制实现的,俗称黑魔法。
runtime的作用:
Objective-C 的 Runtime 铸就了它动态语言的特性,这些深层次的知识虽然平时写代码用的少一些,但是却是每个 Objc 程序员需要了解的。
- 在程序运行过程中,动态的创建一个类(KVO实现原理)以及动态的为每个类添加或修改属性、方法
- 遍历一个类中所有的成员变量以及方法(NSCoding的自动解档和归档、MJExtension的实现)
- 交换两个方法的实现(Swizzle黑魔法)
- ...
KVO实现原理:
当你第一次观察某个对象时,runtime会创建一个新的类NSKVONOtifying_class,该类继承自原先的class。在这个新的派生类中,它重写了所有被观察属性的setter方法,然后将对象的isa指针指向新创建的NSKVONOtifying_class(这个指针告诉Objective-C的runtime某个对象到底是哪种类型的对象)。所以该对象神奇地变成了新的子类的实例。当对象的属性发生改变时,会触发setter方法,但这个方法已经被重写了,并且在方法的内部添加了发送通知机制,实现了自动触发通知机制,这就是KVO实现的原理。
交换两个方法的实现
先看下面例子:
创建一个Student类,实现两个类方法:studyJava和studyC
使用runtime交换两个方法的实现:
#import<objc/runtime.h>//别忘加运行时头文件
//获取class中的类方法
Method studyJava = class_getClassMethod([Student class], @selector(studyJava));
Method studyC = class_getClassMethod([Student class], @selector(studyC));
//交换方法的实现
method_exchangeImplementations(studyJava, studyC);
[Student studyC];
[Student studyJava];
运行后会发现:让他先学C再学Java就是不听,结果是先学Java。
看到这你会觉得并没有什么卵用,是的,这样写意义不大,但我们换个角度想,如果交换的是系统的方法呢?假如我们要统一监听控制器何时被销毁,用runtime可以让我们只写一次代码,看例子:
首先新建一个UIViewController的分类,然后在里面写上代码:
#import "UIViewController+Extension.h"
#import <objc/runtime.h>
@implementation UIViewController (Extension)
//将交换方法写在load方法中,使得在该类被加载如内存中就调用该方法以交换方法
+ (void)load {
//class_getInstanceMethod :获取实例方法
Method dealloc = class_getInstanceMethod(self, NSSelectorFromString(@"dealloc"));
Method jyh_dealloc = class_getInstanceMethod(self, @selector(jyh_dealloc));
method_exchangeImplementations(dealloc, jyh_dealloc);
}
//自己实现的dealloc方法,监听销毁
- (void)jyh_dealloc {
NSLog(@"%@ is dealloc", self);
[self jyh_dealloc];//调用原来的dealloc方法
}
@end
这样便实现了所用控制器销毁的监听。以上的例子只是为了说明runtime的作用,它可以有很多扩展,比如:将[UIImage imageNamed:]换成自己的方法,在方法中加入对图片名称的操作,就可以实现换肤或适配等功能;可以替换viewDidLoad方法实现用户进入页面的统计,不用每个控制器都复制粘贴同样的代码(或许你会想我们可以使用 OOP 的特性之一,继承的方式来解决这个问题。创建一个基类,在这个基类中添加统计方法,其他类都继承自这个基类。然而,这种方式修改还是很大,而且定制性很差。以后有新人加入之后,都要嘱咐其继承自这个基类,所以这种方式并不可取。)IQKeyboard库不需要写一行代码就能实现功能,就是通过这一原理实现的。
遍历类中的所有成员变量
有时候我们要对一些信息进行归档,如用户信息类UserInfo,这将需要重写initWithCoder和encodeWithCoder方法,并对每个属性进行encode和decode操作。那么问题来了:当属性只有几个的时候可以轻松写完,如果有几十个属性呢?我们可以用runtime提供的函数遍历Model自身所有属性,并对属性进行encode和decode操作。
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
if(self = [super init]) {
unsigned int outCount;
//获得所传入类的成员变量
Ivar *ivars = class_copyIvarList([self class], &outCount);
for (int i = 0; i < outCount; i++) {
Ivar ivar = ivars[i];
NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)];
[self setValue:[aDecoder decodeObjectForKey:key] forKey:key];
}
free(ivars);//别忘记释放
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder {
unsigned int outCount;
Ivar *ivars = class_copyIvarList([self class], &outCount);
for (int i = 0; i < outCount; i++) {
Ivar ivar = ivars[i];
NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)];
[aCoder encodeObject:[self valueForKey:key] forKey:key];
}
free(ivars);
}
访问苹果私有属性
利用runtime获取属性名
objc_property_t *properties = class_copyPropertyList([UITextField class], &count);
这样就可以获取到UITextField中的所有属性,包括在头文件中苹果不想让你看到的。
这有什么用呢?假设我们有一个需求,想改变输入框中占位文字的颜色,用runtime的这个作用可以让我们很方便的实现:
先将输入框所包含的属性打印出来:
+ (void)getProperties
{
unsigned int count = 0;
objc_property_t *properties = class_copyPropertyList([UITextField class], &count);
for (int i = 0; i<count; i++) {
// 取出属性
objc_property_t property = properties[i];
// 打印属性名字
JYHLog(@"%s <----> %s", property_getName(property), property_getAttributes(property));
}
free(properties);
}
我们可以看到这样一个属性:
顾名思义,这就是占位的Label,我们只要利用KVC就可以将其改成我们想要的样子:
[self setValue:[UIColor whiteColor] forKeyPath:@"_placeholderLabel.textColor"];
字典转模型和动态添加属性用NSObject+runtime的扩展类实现了
NSObject+runtime.h
#import <Foundation/Foundation.h>
@interface NSObject (Runtime)
/// 给定一个字典,创建 self 类对应的对象
/// @param dict 字典
/// @return 对象
+ (instancetype)cz_objWithDict:(NSDictionary *)dict;
/// 获取类的属性列表数组
/// @return 类的属性列表数组
+ (NSArray *)cz_objProperties;
@end
NSObject+runtime.m
#import "NSObject+Runtime.h"
#import <objc/runtime.h>
@implementation NSObject (Runtime)
// 所有字典转模型框架,核心算法!
+ (instancetype)cz_objWithDict:(NSDictionary *)dict {
// 实例化对象
id object = [[self alloc] init];
// 使用字典,设置对象信息
// 1> 获得 self 的属性列表
NSArray *proList = [self cz_objProperties];
// 2> 遍历字典
[dict enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) {
NSLog(@"key %@ --- value %@", key, obj);
// 3> 判断 key 是否在 proList 中
if ([proList containsObject:key]) {
// 说明属性存在,可以使用 `KVC` 设置数值
[object setValue:obj forKey:key];
}
}];
return object;
}
const char * kPropertiesListKey = "CZPropertiesListKey";
+ (NSArray *)cz_objProperties {
// --- 1. 从`关联对象`中获取对象属性,如果有,直接返回!
/**
获取关联对象 - 动态添加的属性
参数:
1. 对象 self
2. 动态属性的 key
返回值
动态添加的`属性值`
*/
NSArray *ptyList = objc_getAssociatedObject(self, kPropertiesListKey);
if (ptyList != nil) {
return ptyList;
}
// 调用运行时方法,取得类的属性列表
// Ivar 成员变量
// Method 方法
// Property 属性
// Protocol 协议
/**
参数
1. 要获取的类
2. 类属性的个数指针
返回值
所有属性的`数组`,C 语言中,数组的名字,就是指向第一个元素的地址
retain/create/copy 需要 release,最好 option + click
*/
unsigned int count = 0;
objc_property_t *proList = class_copyPropertyList([self class], &count);
NSLog(@"属性的数量 %d", count);
// 创建数组
NSMutableArray *arrayM = [NSMutableArray array];
// 遍历所有的属性
for (unsigned int i = 0; i < count; i++) {
// 1. 从数组中取得属性
/**
C 语言的结构体指针,通常不需要 `*`
*/
objc_property_t pty = proList[i];
// 2. 从 pty 中获得属性的名称
const char *cName = property_getName(pty);
NSString *name = [NSString stringWithCString:cName encoding:NSUTF8StringEncoding];
// NSLog(@"%@", name);
// 3. 属性名称添加到数组
[arrayM addObject:name];
}
// 释放数组
free(proList);
// --- 2. 到此为止,对象的属性数组已经获取完毕,利用关联对象,动态添加属性
/**
参数
1. 对象 self [OC 中 class 也是一个特殊的对象]
2. 动态添加属性的 key,获取值的时候使用
3. 动态添加的属性值
4. 对象的引用关系
*/
objc_setAssociatedObject(self, kPropertiesListKey, arrayM.copy, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
return arrayM.copy;
}
@end
对runtime的总结:让你会用Runtime的更多相关文章
- Objective-C runtime初识
Objective-C Runtime Describes the macOS Objective-C runtime library support functions and data struc ...
- Objective-C runtime的常见应用
用Objective-C等面向对象语言编程时,"对象"(object)就是"基本构造单元"(building block).开发者可以通过对象来存储并传递数据. ...
- iOS开发之使用Runtime给Model类赋值
本篇博客算是给网络缓存打个基础吧,本篇博客先给出简单也是最容易使用的把字典转成实体类的方法,然后在给出如何使用Runtime来给Model实体类赋值.本篇博客会介绍一部分,主要是字典的key与Mode ...
- iOS开发——高级特性&Runtime运行时特性详解
Runtime运行时特性详解 本文详细整理了 Cocoa 的 Runtime 系统的知识,它使得 Objective-C 如虎添翼,具备了灵活的动态特性,使这门古老的语言焕发生机.主要内容如下: 引言 ...
- 2.Java基础之Runtime对象
毕向东老师Java基础学习笔记——Runtime对象 今天学习Java中的Runtime对象后,感觉这个对象对我们主要有以下几点用处. 1.使用java代码打开本地可执行文件,比如打开一个计算器. 2 ...
- 深入研究java.lang.Runtime类
一.概述 Runtime类封装了运行时的环境.每个 Java 应用程序都有一个 Runtime 类实例,使应用程序能够与其运行的环境相连接. 一般不能实例化一个Runtime对象, ...
- 浅析Java.lang.Runtime类
一.概述 Runtime类封装了运行时的环境.每个 Java 应用程序都有一个 Runtime 类实例,使应用程序能够与其运行的环境相连接. 一般不能实例化一个Runtime对象, ...
- ProcessBuilder 、Runtime和Process 的区别
1.版本原因 ProcessBuilder是从java1.5加进来的,而exec系列方法是从1.0开始就有的,后续版本不断的重载这个方法,到了1.5已经有6个之多. 2.ProcessBuilder. ...
- 【引】runtime全解析,P1:Programming Guide
h2.Overview Objective-C language defers as many decisions as it can from compile time and link time ...
- Runtime的几个小例子(含Demo)
一.什么是runtime(也就是所谓的“运行时”,因为是在运行时实现的.) 1.runtime是一套底层的c语言API(包括很多强大实用的c语言类型,c语言函数); [runti ...
随机推荐
- 多校hdu5754(博弈)
©此题中在N×M的棋盘中从(1,1)走到(N,M)B先走G后走,谁先到(N,M)谁赢,走法分为4中分别是国际象棋中的国王,车,马,王后的发,在四种走法下谁能赢: 我们依次分析每一种棋子. ①王. 首先 ...
- 【P1947】笨笨当粉刷匠(DP+前缀和)
这个题乍一看觉得挺简单的,事实上却完全不是.首先,这个题看上去无脑直接刷就可以然而因为刷的次数远远大于木板的个数所以不行,然后开始考虑DP,自己一开始是这么想的,如果用f[t][i][j]表示刷t次时 ...
- hadoop 伪分布模式环境搭建
一 安装JDK 下载JDK jdk-8u112-linux-i586.tar.gz 解压JDK hadoop@ubuntu:/soft$ tar -zxvf jdk-8u112-li ...
- 先有Class还是先有Object?
先有Class还是先有Object? Java的对象模型中: 所有的类都是Class类的实例,Object是类,那么Object也是Class类的一个实例. 所有的类都最终继承自Object类,Cla ...
- PHP 学习(一)——课程介绍
一.课程路线介绍 教程的学习路线按照:初级——>中级——>高级——>项目实做 初级: 中级: 高级: 项目实做: 整体: Php体系了解:
- 批量测试邮箱登录python脚本
---恢复内容开始--- #!/usr/bin/env python #-*- coding:utf-8 -*- import smtplib import sys import time impor ...
- SQL server 2008 T-sql 总结
数据库的实现 1.添加数据:insert [into] 表名 (字段1,字段2,···) values (值1,值2,····) 其中,into可选. 2.修改数据:update 表名 set ...
- Mine_目标
1. hibernate 有简单的方式 可以用于 DDL操作吗? “ DDL(data definition language)是数据定义语言:DDL比DML要多,主要的命令有CREATE.ALTER ...
- Python快速学习-获取对象信息
1type() 获取对象的基本类型,判断两个对象类型. 2types 判断对象是否是函数,使用前要引入import types 3isinstance() 判断class类型,判断一个变量是否是某种类 ...
- 【WCF】利用WCF实现上传下载文件服务
引言 前段时间,用WCF做了一个小项目,其中涉及到文件的上传下载.出于复习巩固的目的,今天简单梳理了一下,整理出来,下面展示如何一步步实现一个上传下载的WCF服务. 服务端 1.首先新建一个名 ...