Runtime系列(二)--Runtime的使用场景
Runtime 理解介绍的文章非常多,我只想讲讲Runtime 可以用在哪里,而我在项目里哪些地方用到了runtime。多以实际使用过程为主,来介绍runtime的使用。
* 那么runtime 怎么使用?可以用在哪些场景下呢?*
首先,使用runtime 相关API,要#import <objc/runtime.h>
运行时获取某个类的属性或函数
运行时动态获取某个类的属性或者函数等,可以用来做很多事情,如json 解析、数据库结果解析、判断某个类的子类等。
解析、转化为Model
// 获取属性列表
objc_property_t * class_copyPropertyList(Class cls, unsigned int *outCount)
// 获取属性名
const char *property_getName(objc_property_t property)
// 获取属性类型
const char *property_getAttributes(objc_property_t property)
以上方法可以用来:
* 解析json数据,转化为Model对象。
* 解析数据库查询结果,转化为Model 对象。
这里有动态获取类的属性的示例代码片段:
unsigned int outCount, i;
objc_property_t *properties = class_copyPropertyList([self class], &outCount);
for (i = 0; i < outCount; i++) {
objc_property_t property = properties[i];
//获取属性名
NSString *propertyName = [NSString stringWithCString:property_getName(property) encoding:NSUTF8StringEncoding];
//获取属性类型等参数
NSString *propertyType = [NSString stringWithCString: property_getAttributes(property) encoding:NSUTF8StringEncoding];
/*
各种符号对应类型,部分类型在新版SDK中有所变化,如long 和long long
c char C unsigned char
i int I unsigned int
l long L unsigned long
s short S unsigned short
d double D unsigned double
f float F unsigned float
q long long Q unsigned long long
B BOOL
@ 对象类型 //指针 对象类型 如NSString 是@“NSString”
propertyType,你可以打印出来,看看它是什么。
要判断某个属性的类型,只需要[propertyType hasPrefix:@"Ti"]
这代表它是int 类型。
*/
}
free(properties);
判断某个类的子类
有时候我们在程序中需要判断某个类是否是另一个类的子类。这个功能也可以利用runtime类实现,这里有示例代码:
int numClasses;
Class *classes = NULL;
numClasses = objc_getClassList(NULL,0);
if (numClasses >0 )
{
classes = (__unsafe_unretained Class *)malloc(sizeof(Class) * numClasses);
numClasses = objc_getClassList(classes, numClasses);
for (int i = 0; i < numClasses; i++) {
if (class_getSuperclass(classes[i]) == [xxxxClass class]){
id class = classes[i];
// 执行某个方法 或者 做其他事情
[class performSelector:@selector(xxxxMethod) withObject:nil];
}
}
free(classes);
}
以上两段示例代码摘自我之前写的FMDB Model 封装:JKDBModel,你可以去看更详尽的解析和使用过程。
获取某个类的实例变量
如果你还需要获取某个类的实例变量做什么操作的话,可以使用如下这几个API:
// 获取实例变量数组
Ivar * class_copyIvarList(Class cls, unsigned int *outCount)
// 获取实例变量名称
const char * ivar_getName( Ivar ivar)
// 获取实例变量类型
const char * ivar_getTypeEncoding( Ivar ivar)
这面有获取实例变量的示例代码片段:
unsigned int outCount, i;
Ivar *ivaries = class_copyIvarList([Son class], &outCount);
for (i = 0; i < outCount; i++) {
Ivar ivar = ivaries[i];
NSString *ivarName = [NSString stringWithCString:ivar_getName(ivar) encoding:NSUTF8StringEncoding];
NSString *ivarType = [NSString stringWithCString:ivar_getTypeEncoding(ivar) encoding:NSUTF8StringEncoding];
NSLog(@"名称:%@---类型:%@",ivarName,ivarType);
/*
各种符号对应类型,部分类型在新版SDK中有所变化,如long 和long long
c char C unsigned char
i int I unsigned int
l long L unsigned long
s short S unsigned short
d double D unsigned double
f float F unsigned float
q long long Q unsigned long long
B BOOL
@ 对象类型 //指针 对象类型 如NSString 是@“NSString”
*/
}
free(ivaries);
获取某个类的方法
获取某个类的方法,会包含这个类的property 的set 和get 方法,但是不包括父类的property set 和get 方法,不包括父类的方法(如果在当前类覆写,就包括)。
主要API:
// 获取方法数组
Method * class_copyMethodList(Class cls, unsigned int *outCount)
// 获取方法的 SEL
SEL method_getName( Method method)
// 获取方法名
const char* sel_getName(SEL aSelector)
获取方法数组的示例代码片段:
unsigned int outMethodCount, j;
Method *methods = class_copyMethodList([Son class], &outMethodCount);
for (j = 0; j < outMethodCount; j++) {
Method method = methods[j];
SEL selector = method_getName(method);
if (selector) {
NSString *methodName = [NSString stringWithCString:sel_getName(selector) encoding:NSUTF8StringEncoding];
NSLog(@"方法:%@",methodName);
}
}
free(methods);
运行时替换方法(Method Swizzling)
Method Swizzling 的使用需要谨慎,因为一不小心可能就会导致无法排查的Bug,毕竟它替换的是官方的API,有些API内部做了什么事情,很难完全把握。
使用场景,需要监控用户经常打开的界面,以及在某界面停留的时长。
我们可以怎么做呢?写一个UIViewController 的Category,然后在类别中,添加自定义的方法:如-xxxviewDidAppear:和-xxxviewDidDisappear:方法,然后在-load 方法中,用自定义的方法替换原来的方法。
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
// When swizzling a class method, use the following:
// Class class = object_getClass((id)self);
SEL originalSelector = @selector(viewWillAppear:);
SEL swizzledSelector = @selector(xxx_viewWillAppear:);
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
BOOL didAddMethod =
class_addMethod(class,
originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {
class_replaceMethod(class,
swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}
#pragma mark - Method Swizzling
- (void)xxx_viewWillAppear:(BOOL)animated {
[self xxx_viewWillAppear:animated];
NSLog(@"xxx_viewWillAppear: %@", self);
// 在这里,我们可以发送一个消息到服务器,或者做其他事情等。
}
以上示例代码摘自:Objective-C Runtime 运行时之四:Method Swizzling
关于Method Swizzling,他是把两个方法的实现部分互换了。
比如上面我们调用-xxx_viewWillAppear:,因为-xxx_viewWillAppear: 和-viewWillAppear:的实现部分互换后,其实执行的时候,并不会执行上面的这个实现,而是调用-viewWillAppear:的内部实现。所以上面的代码,完全不会产生循环调用。
还是写段代码说明吧:
- (void)viewWillAppear:(BOOL)animated {
NSLog(@"这是原来的方法");
}
- (void)xxx_viewWillAppear:(BOOL)animated {
NSLog(@"xxx_viewWillAppear: %@", self);
// 在这里,我们可以发送一个消息到服务器,或者做其他事情等。
}
假如上面这俩方法用method swizzling 替换后,我们调用-xxx_viewWillAppear:
会打印这是原来的方法
;而调用-viewWillAppear:
会打印xxx_viewWillAppear:
。这里需要细细体会一下。
关于Method Swizzling更多的注意点请看原文Method Swizzling
对象关联(Associated Objects)
对象关联(或称为关联引用)本来是Objective-C 运行时的一个重要特性,它能让开发者对已经存在的类在扩展中添加自定义的属性。
需要用的以下三个函数:
void objc_setAssociatedObject(id object, void *key, id value, objc_AssociationPolicy policy)
id objc_getAssociatedObject(id object, void *key)
void objc_removeAssociatedObjects(id object)
众所周知,OC 中的Category 中不能添加新的属性,但是我们通过Associated Objects
可以间接的实现往类上添加自定义的属性。
不能添加属性的根本原因是不会帮我们自动添加对象的实例变量,也不会帮我们生成set 和get方法,虽然set /get 方法可以自己实现,但是没有实例变量来存储数据。
很容易看懂官方文档对参数的描述,但是key 需要注意一下:
通常推荐的做法是添加的属性最好是 static char类型的,当然更推荐是指针型的。通常来说该属性应该是常量、唯一的、在适用范围内用getter和setter访问到,所以通常我们这样写:
static char kAssociatedObjectKey;
objc_setAssociatedObject(self, &kAssociatedObjectKey, object, OBJC_ASSOCIATION_RETAIN_NONATOMIC)
objc_getAssociatedObject(self, &kAssociatedObjectKey);
当然,对于key 还有更好的做法,那就是selector。用selector 的示例在下面。
下面用代码演示如何在Category中添加一个新的属性。
这是Son+AssociatedObject.h
#import "Son.h"
@interface Son (AssociatedObject)
/** 家庭住址 */
@property (copy, nonatomic) NSString *address;
/** 身高 */
@property (assign, nonatomic) int height;
@end
这是Son+AssociatedObject.m
#import "Son+AssociatedObject.h"
#import <objc/runtime.h>
@implementation Son (AssociatedObject)
- (void)setAddress:(NSString *)address
{
objc_setAssociatedObject(self, @selector(address), address, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)address
{
return objc_getAssociatedObject(self, @selector(addObject:));
}
- (void)setHeight:(int)height
{
NSNumber *heighNum = [NSNumber numberWithInt:height];
objc_setAssociatedObject(self, @selector(height), heighNum, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (int)height
{
NSNumber *heightNum = objc_getAssociatedObject(self, @selector(addObject:));
return heightNum.intValue;
}
@end
虽然上面有提到void objc_removeAssociatedObjects(id object)
,但是不要轻易使用这个函数,因为它会移除所有的关联对象。我们一般要移除某个关联对象,只需要用objc_setAssociatedObject
传入nil即可。
补充一个关联对象的使用场景:
你在使用AlertView 或者ActionSheet的时候,有没有很苦恼不能在点击的代理方法中方便的获取到Model对象呢?
除了在控制器中添加一个property 这种方式外;
我们也可以为AlertView 或者ActionSheet 添加一个关联对象,这样就可以在代理方法中方便的获取到Model 对象啦。
这里如果我们为AlertView 或者ActionSheet 添加Category来实现的话,代码跟上面为Son 添加类别基本一样,对象类型改为id 类型即可。
或者我们在控制器中调用的时候,添加关联对象也可以。这时候就用这种方式:
static char kAssociatedObjectKey;
objc_setAssociatedObject(self, &kAssociatedObjectKey, object, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
objc_getAssociatedObject(self, &kAssociatedObjectKey);
UIAlertController 也跟上面一样。
关于Associated Objects的使用,有连个为Category扩展功能,使得Category中也能方便的添加属性以及相应的getter 和setter 的例子。
OC 自动生成分类属性方法
一个库–DProperty
运行时动态创建一个类
我再某控制器中测试写了这么一个方法,来创建一个MyClass 类。项目中并不存在叫MyClass 的类文件。
- (void)createClass
{
Class MyClass = objc_allocateClassPair([NSObject class], "MyClass", 0);
// 1.添加一个叫name 类型为NSString的实例变量,第四个参数是对其方式,第五个参数是参数类型
if (class_addIvar(MyClass, "name", sizeof(NSString *), 0, "@")) {
NSLog(@"add name ivar success");
}
// 2.添加一个property
// 这里需要注意,添加property之前需要先添加一个与之对应的实例变量
if (class_addIvar(MyClass, "_address", sizeof(NSString *), 0, "@")) {
NSLog(@"add _address ivar success");
}
objc_property_attribute_t type = {"T", "@\"NSString\""};
objc_property_attribute_t ownership = { "C", "" };
objc_property_attribute_t backingivar = { "V", "_address"};
objc_property_attribute_t attrs[] = {type, ownership, backingivar};
class_addProperty(MyClass, "address", attrs,2);
// 3.添加函数, myclasstest是已经实现的函数,"v@:"这种写法见参数类型连接
class_addMethod(MyClass, @selector(myclasstest:), (IMP)myclasstest, "v@:");
// 4.注册这个类到runtime系统中就可以使用他了
objc_registerClassPair(MyClass);
// 5.生成了一个实例化对象
id myobj = [[MyClass alloc] init];
NSString *str = @"名字";
// 6.给刚刚添加的变量赋值
// object_setInstanceVariable(myobj, "itest", (void *)&str);在ARC下不允许使用
[myobj setValue:str forKey:@"name"];
[myobj setValue:@"这是地址" forKey:@"address"];
// 7.调用myclasstest方法,也就是给myobj这个接受者发送myclasstest这个消息
[myobj myclasstest:10];
}
//这个方法实际上没有被调用,但是必须实现否则不会调用下面的方法
- (void)myclasstest:(int)a
{
NSLog(@"啊啊啊啊啊");
}
//调用的是这个方法
static void myclasstest(id self, SEL _cmd, int a) //self和_cmd是必须的,在之后可以添加其他参数
{
Ivar v = class_getInstanceVariable([self class], "name");
//返回名为name的ivar的变量的值
id o = object_getIvar(self, v);
//成功打印出结果
NSLog(@"name is %@", o);
NSLog(@"参数 a is %d", a);
objc_property_t property = class_getProperty([self class], "address");
NSString *propertyName = [NSString stringWithUTF8String:property_getName(property)];
id value = [self valueForKey:propertyName];
NSLog(@"address is %@", value);
}
关于运行时创建一个新类,上面的注释已经写的很详细了。
Have Fun!
Runtime系列(二)--Runtime的使用场景的更多相关文章
- Zookeeper系列(二)特征及应用场景
zookeeper类似一个分布式的文件系统,每个节点可以有和它自身或它的子节点相关联的数据,此外指向节点的路劲必须使用绝对路径(不能使用相对路劲): Znode 对应目录树中的的一个节点,并拥有一 ...
- Runtime简介以及常见的使用场景(此内容非原创,为转载内容)
Runtime简称运行时,是一套比较底层的纯C语言的API, 作为OC的核心,运行时是一种面向对象的编程语言的运行环境,其中最主要的是消息机制,Objective-C 就是基于运行时的. 所谓运行时, ...
- Runtime常用的几个应用场景
Runtime常见的几个应用场景. Runtime常见应用场景 具体应用拦截系统自带的方法调用(Method Swizzling黑魔法) 实现给分类增加属性 实现字典的模型和自动转换 JSPatch替 ...
- 微信公众号开发C#系列-11、生成带参数二维码应用场景
1.概述 我们在微信公众号开发C#系列-7.消息管理-接收事件推送章节有对扫描带参数二维码事件的处理做了讲解.本篇主要讲解通过微信公众号开发平台提供的接口生成带参数的二维码及应用场景. 微信公众号平台 ...
- (转发)IOS高级开发~Runtime(二)
一些公用类: @interface ClassCustomClass :NSObject{ NSString *varTest1; NSString *varTest2; NSString *varT ...
- CUDA运行时 Runtime(二)
CUDA运行时 Runtime(二) 一. 概述 下面的代码示例是利用共享内存的矩阵乘法的实现.在这个实现中,每个线程块负责计算C的一个方子矩阵C sub,块内的每个线程负责计算Csub的一个元素.如 ...
- Docker系列之AspNetCore Runtime VS .NetCore Runtime VS .NET Core SDK(四)
前言 接下来我们就要慢慢步入在.NET Core中使用Docker的殿堂了,在开始之前如题,我们需要搞清楚一些概念,要不然看到官方提供如下一系列镜像,我们会一脸懵逼,不知道到底要使用哪一个. AspN ...
- [知识库分享系列] 二、.NET(ASP.NET)
最近时间又有了新的想法,当我用新的眼光在整理一些很老的知识库时,发现很多东西都已经过时,或者是很基础很零碎的知识点.如果分享出去大家不看倒好,更担心的是会误人子弟,但为了保证此系列的完整,还是选择分享 ...
- Newtonsoft.Json C# Json序列化和反序列化工具的使用、类型方法大全 C# 算法题系列(二) 各位相加、整数反转、回文数、罗马数字转整数 C# 算法题系列(一) 两数之和、无重复字符的最长子串 DateTime Tips c#发送邮件,可发送多个附件 MVC图片上传详解
Newtonsoft.Json C# Json序列化和反序列化工具的使用.类型方法大全 Newtonsoft.Json Newtonsoft.Json 是.Net平台操作Json的工具,他的介绍就 ...
- 探索ASP.Net Core 3.0系列二:聊聊ASP.Net Core 3.0 中的Startup.cs
原文:探索ASP.Net Core 3.0系列二:聊聊ASP.Net Core 3.0 中的Startup.cs 前言:.NET Core 3.0 SDK包含比以前版本更多的现成模板. 在本文中,我将 ...
随机推荐
- 2015 ICL, Finals, Div. 1 Ceizenpok’s formula(组合数取模,扩展lucas定理)
J. Ceizenpok’s formula time limit per test 2 seconds memory limit per test 256 megabytes input stand ...
- hdu3340 线段树+多边形
Rain in ACStar Time Limit: 4000/2000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others) T ...
- Linux input子系统 io控制字段【转】
转自:http://www.cnblogs.com/leaven/archive/2011/02/12/1952793.html http://blog.csdn.net/guoshaobei/arc ...
- PTA 邻接矩阵存储图的深度优先遍历
6-1 邻接矩阵存储图的深度优先遍历(20 分) 试实现邻接矩阵存储图的深度优先遍历. 函数接口定义: void DFS( MGraph Graph, Vertex V, void (*Visit)( ...
- IntelliJ IDEA设置统一编码utf-8
File菜单->Other Settings->Default Settings->File Encodings 全改成utf-8!
- java海量大文件数据处理方式
1. 给定a.b两个文件,各存放50亿个url,每个url各占64字节,内存限制是4G,让你找出a.b文件共同的url? 方案1:可以估计每个文件安的大小为50G×64=320G,远远大于内存限制的4 ...
- Spring boot集成swagger2
一.Swagger2是什么? Swagger 是一款RESTFUL接口的文档在线自动生成+功能测试功能软件. Swagger 是一个规范和完整的框架,用于生成.描述.调用和可视化 RESTful 风格 ...
- 设计模式之中介者模式(Mediator )
中介者模式是关于数据交互的设计模式,该模式的核心是一个中介者对象,负责协调一系列对象之间的不同的数据请求,这一系列对象成为同事类.如房产中介(简直不想提它),买房的卖房的,租房的放租的都到房产中介那里 ...
- rhel7 启动网络
我装的是rhel7 服务器版本(在virtualbox虚拟机里),安装后默认不启动网络,另外还有很多命令也不能用,比如ifconfig, yum-config-manager等. 先解决网络问题: 切 ...
- 转:window与linux互相拷贝文件
window与linux互相拷贝文件 借助 PSCP 命令可以实现文件的互拷: 1.下载pscp.exe 文件 (我的资源文件中有) 2.如果想在所有目录可以执行,请更改环境变量. w ...