runtime之玩转成员变量
前言:
不铺垫那么多,单刀直入吧:runtime是一个C和汇编写的动态库,就像是一个小小的系统,将OC和C紧密关联在一次,这个系统主要做两件事情。
1,封装C语言的结构体和函数,让开发者在运行时创建,检查或者修改类,对象和方法等
2,传递消息,找出方法的最终执行代码
也就是说我们写的OC代码在运行的时候都会转为运行时代码
通过runtime的学习能够更好理解OC的这种消息发送机制,并且我也认为对runtime的学习是对深入学习iOS必不可少的坎,比如你有可能通过阅读一些第三方框架来提高自己的编程技巧,在这些第三方框架中就会有大量运行时代码。掌握了runtime我们能够简单做些什么事情呢?
1,遍历对象的所有属性
2,动态添加/修改属性,动态添加/修改/替换方法
3,动态创建类/对象
4,方法拦截使用(给方法添加一个动态实现,甚至可以讲该方法重定向或者打包给lisi)
听起来跟黑魔法一样。其实runtime就素有黑魔法之称!我们就从成员变量开始我们对runtime的学习吧。
正文
成员变量:
成员变量是我们在定义一个类中其中重要的成分,主要是想描述这个类实例化后具备了什么属性,特点,等等。就像定义了一个Person类,Person类具备了name,age,gender等各种属性来描述这个类。举了这个稍微符合的例子来辅助说明成员变量是干嘛用的,但是却还是不能说明成员变量到底本质是什么?在runtime.h文件中的成员变量是一个指向objc_ivar类型的结构体指针:
/// An opaque type that represents an instance variable.
typedef struct objc_ivar *Ivar;
在这个objc_ivar这个结构体定义如下:
struct objc_ivar {
char *ivar_name OBJC2_UNAVAILABLE;//成员变量的名字
char *ivar_type OBJC2_UNAVAILABLE;//成员变量的类型
int ivar_offset OBJC2_UNAVAILABLE;
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
}
对成员变量进行操作的主要有以下几种方式:
Ivar *class_copyIvarList(Class cls, unsigned int *outCount) //获取所有成员变量
const char *ivar_getName(Ivar v) //获取某个成员变量的名字
const char *ivar_getTypeEncoding(Ivar v) //获取某个成员变量的类型编码
Ivar class_getInstanceVariable(Class cls, const char *name) //获取某个类中指定名称的成员变量
id object_getIvar(id obj, Ivar ivar) //获取某个对象中的某个成员变量的值
void object_setIvar(id obj, Ivar ivar, id value) //设置某个对象的某个成员变量的值
下面通过建立一个Person类来理解runtime中提供的这些函数,首先我们定义一个Person类,并且重写它的description方法:
Person.h中:
@interface Person : NSObject
{
NSString *clan;//族名
}
@property(nonatomic,copy)NSString *name;
@property(nonatomic,copy)NSString *gender;
@property(nonatomic,strong)NSNumber *age;
@property(nonatomic,assign)NSInteger height;
@property(nonatomic,assign)double weight;
Person.m
-(NSString *)description
{
unsigned int outCount;
Ivar *IvarArray = class_copyIvarList([Person class], &outCount);//获取到Person中的所有成员变量
for (unsigned int i = ; i < outCount; i ++) {
Ivar *ivar = &IvarArray[i];
NSLog(@"第%d个成员变量:%s,类型是:%s",i,ivar_getName(*ivar),ivar_getTypeEncoding(*ivar));// 依次获取每个成员变量并且打印成员变量名字和类型
}
return nil;
}
在程序入口创建Person实例类并且调用description方法可以看到打印台打印:

ivar_getTypeEncoding函数获取到的是成员变量的类型编码。类型编码是苹果对数据类型对象类型规定的另一个表现形式,比如"@"代表的是对象,":"表示的是SEL指针,"v"表示的是void。具体可以看苹果官方文档对类型编码的具体规定:戳我!!!
通过runtime来给对象赋值和获取对象的值:
Person.m中实现了两个分别对实例化person对象赋值和取值方法:
+ (Person *)personWithName:(NSString *)name age:(NSNumber *)age gender:(NSString *)gender clan:(NSString *)clan
{
Person *p = [Person new];
unsigned int outCount;
Ivar *IvarArray = class_copyIvarList([Person class], &outCount);
object_setIvar(p, IvarArray[], clan);
object_setIvar(p, IvarArray[], name);
object_setIvar(p, IvarArray[], gender);
object_setIvar(p, IvarArray[], age);
return p;
} - (void)personGetPersonMessage
{
unsigned int outCount;
Ivar *IvarArray = class_copyIvarList([Person class], &outCount);
for (NSInteger i = ; i < ; i ++) {
NSLog(@"%s = %@",ivar_getName(IvarArray[i]),object_getIvar(self,IvarArray[i]));
}
}
在viewDidLoad中:
Person *person = [Person personWithName:@"张三" age:@ gender:@"man" clan:@"汉"];
[person personGetPersonMessage];
可以看到打印台打印:

成功的对Person对象进行设置值和取值操作。
属性:
属性在runtime中定义如下:
/// An opaque type that represents an Objective-C declared property.
typedef struct objc_property *objc_property_t;
/// Defines a property attribute
typedef struct {
const char *name; /**< The name of the attribute */
const char *value; /**< The value of the attribute (usually empty) */
} objc_property_attribute_t;
属性的本质是一个指向objc_property的结构体指针。跟成员变量一样,runtime中一样为属性定义了一系列对属性的操作函数:
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) //获取属性的特性描述
objc_property_attribute_t *property_copyAttributeList(objc_property_t property, unsigned int *outCount) //获取所有属性的特性
获取person实例对象中所有属性的特性描述:
Person.m中:
- (void)getAttributeOfproperty
{
unsigned int outCount;
objc_property_t *propertyList = class_copyPropertyList([Person class], &outCount);
for (NSInteger i = ; i < outCount; i ++) {
NSLog(@"属性:%s,它的特性描述:%s",property_getName(propertyList[i]),property_getAttributes(propertyList[i]));
}
}
获取属性列表只会获取有property属性声明的变量,所有当调用getAttributeOfproperty的时候打印台打印:

特性描述主要说明的是该属性的修饰符,具体的代表意义如下:
属性类型 name值:T value:变化
编码类型 name值:C(copy) &(strong) W(weak) 空(assign) 等 value:无
非/原子性 name值:空(atomic) N(Nonatomic) value:无
在运行时runtime下我们可以获取到所有的成员变量,以及类的私有变量。所有runtime的重要应用就是字典转模型,复杂归档。
应用1:复杂对象归档
复杂对象归档平常我们需要类遵循<NSCoding>协议,重写协议中编码和解码的两个方法,创建NSKeyarchive对象将类中的成员变量进行逐一编码和解码。
runtime下基本是同样的操作,但是我们可以利用runtime提供的函数获取变量的名字和所对应的成员变量,开启循环进行快速归档(要记得普通情况下我们可以要逐一的写),同样是以Person类为例;
Person.m中:
-(instancetype)initWithCoder:(NSCoder *)aDecoder
{
self = [super init];
if (self) {
unsigned int outCount;
Ivar *ivarList = class_copyIvarList([Person class], &outCount);
for (NSInteger i = ; i < outCount; i ++) {
Ivar ivar = ivarList[i];
NSString *ivarName = [NSString
stringWithUTF8String:ivar_getName(ivar)];
[self setValue:[aDecoder decodeObjectForKey:ivarName] forKey:ivarName];
}
}
return self;
} -(void)encodeWithCoder:(NSCoder *)aCoder
{
unsigned int outCount;
Ivar *ivarlist = class_copyIvarList([self class], &outCount);
for (NSInteger i = ; i < outCount; i ++) {
Ivar ivar = ivarlist[i];
NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];
[aCoder encodeObject:[self valueForKey:ivarName] forKey:ivarName];
}
}
应用2字典转模型:
另一个重要的应用便是字典转模型,将字典中的数据赋值给模型中对应的属性。大概思路是先通过class_copyPropertyList获取到所有的属性,再通过property_getName获取到变量对应的名字作为key值,通过key值查看字典中是否有对应的value,若是有的话则给属性赋值。
以上的操作都是基于对象具有的属性通过runtime获取属性的一些信息,比如名字,属性的值,属性的特性描述等。通过runtime还可以给对象动态添加变量,也就是添加关联。还记得分类和延展的区别吗?延展可以为类添加属性和方法,而分类只能为类添加方法。有个面试题:不使用继承的方式如何给系统类添加一个公共变量?我们知道在延展里面为类添加的变量是私有变量,外界无法访问的。如果对runtime有了解的人也许就知道这是想考验应聘人对runtime的了解。
runtime下提供了三个函数给我们能够进行关联对象的操作:
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) //为某个类关联某个对象
id objc_getAssociatedObject(id object, const void *key) //获取到某个类的某个关联对象
void objc_removeAssociatedObjects(id object) //移除已经关联的对象
参数说明:
/**
* 参数说明:
object:要添加成员变量的对象
key:添加成员变量对应的key值
value:要添加的成员变量
policy:添加的成员变量的修饰符
*/
我们以给NSDictionary添加一个NSString类型的公共变量funnyName为例:
在NSDictionary分类MyDict.h新增加两个属性其中一个字符串一个block中:
@property(nonatomic,copy)NSString *funnyName;
@property(nonatomic,copy)void(^dictAction)(NSString *str);
一般情况下如果我们只声明了这些变量在外面使用的时候就会报错,所有需要我们手动实现他们的set和get方法(可别以为是因为我们没有实现它们的set和方法才报错了哦,@property修饰的属性可是会自动生成get和set方法)
那应该如何实现它们的set和get方法呢:
-(void)setFunnyName:(NSString *)funnyName
{
objc_setAssociatedObject(self, @selector(funnyName), funnyName, OBJC_ASSOCIATION_COPY_NONATOMIC);
} -(NSString *)funnyName
{
return objc_getAssociatedObject(self, @selector(funnyName));
} -(void)setDictAction:(void (^)(NSString *))dictAction
{
objc_setAssociatedObject(self, @selector(dictAction), dictAction, OBJC_ASSOCIATION_COPY_NONATOMIC);
} -(void (^)(NSString *))dictAction
{
return objc_getAssociatedObject(self, @selector(dictAction));
}
在我们程序中就可以使用字典新增加的两个属性了:
NSDictionary *dict = [NSDictionary new];
dict.funnyName = @"SoFunny";
NSLog(@"dict.funnyName = %@",dict.funnyName);
void(^action)(NSString *str) = ^(NSString *str){
NSLog(@"打印了这个字符串:%@",str);
};
//设置block
dict.dictAction = action; //调用dict的action
dict.dictAction(@"新增加变量dicAction");
在打印台可以看见打印成功打印到我们想要的东西:

初尝runtime,若是有什么表述不当的地方还请指出。后续将继续更新runtime的学习。
runtime之玩转成员变量的更多相关文章
- runtime第二部分成员变量和属性
接上一篇 http://www.cnblogs.com/ddavidXu/p/5912306.html 转载来源http://www.jianshu.com/p/6b905584f536 http:/ ...
- 福利->KVC+Runtime获取类/对象的属性/成员变量/方法/协议并实现字典转模型
我们知道,KVC+Runtime可以做非常多的事情.有了这个,我们可以实现很多的效果. 这里来个福利,利用KVC+Runtime获取类/对象的所有成员变量.属性.方法及协议: 并利用它来实现字典转模型 ...
- Objective-C Runtime 运行时之二:成员变量与属性
类型编码(Type Encoding) 作为对Runtime的补充,编译器将每个方法的返回值和参数类型编码为一个字符串,并将其与方法的selector关联在一起.这种编码方案在其它情况下也是非常有用的 ...
- Objective-C Runtime 运行时之二:成员变量与属性(转载)
在前面一篇文章中,我们介绍了Runtime中与类和对象相关的内容,从这章开始,我们将讨论类实现细节相关的内容,主要包括类中成员变量,属性,方法,协议与分类的实现. 本章的主要内容将聚集在Runtime ...
- Runtime之成员变量&属性&关联对象
上篇介绍了Runtime类和对象的相关知识点,在4.5和4.6小节,也介绍了成员变量和属性的一些方法应用.本篇将讨论实现细节的相关内容. 在讨论之前,我们先来介绍一个很冷僻但又很有用的一个关键字:@e ...
- 刨根问底Objective-C Runtime(4)- 成员变量与属性
http://chun.tips/blog/2014/11/08/bao-gen-wen-di-objective[nil]c-runtime(4)[nil]-cheng-yuan-bian-lian ...
- runtime-对成员变量操作应用之归档和返归档
为了实现归档和返归档,我们要让被归档对象的类接受NSCoding协议并且实现协议里的两个方法 - (void)encodeWithCoder:(NSCoder *)aCoder; - (nullabl ...
- runtime-对成员变量和属性的操作
成员变量 首先我们来看看成员变量在runtime中是什么样的 在runtime中成员变量是一个objc_ivar类型的结构体,结构体定义如下 struct objc_ivar { char *ivar ...
- iOS 在类别里添加成员变量的方法:objc_setAssociatedObject
今天在github上查看MJPopupViewController这个项目,发现里面用到了objc_setAssociatedObject,用来为类别添加成员变量. 我百度之后,发现有人是这样说明的: ...
随机推荐
- 一图看懂host_only nat bridge拓扑结构
VMware三种模式 我们在使用VMware时经常能看到三种网络的连接模式:Bridged Host-Only Nat,它们都有什么作用,网络拓扑是怎样的?怎样才能让他们上网,他们相互间能不能相互访 ...
- 使用Nodejs+Protractor搭建测试环境
Protractor是一个end-to-end的测试框架,从网络上得到的答案是Protractor是作为Angular JS应用程序的测试框架.它的构建基于Selenium WebDriver之上,且 ...
- C# 可空值类型
一个值类型永远不可能为null,但是当数据库中的某列数据允许为空时,或者另一种语言中的数据类型(引用类型)对应C#的是值类型,当需要和另外的语言交互时,就有可能需要处理空值的问题. 所以,CLR中引用 ...
- JS弹出框插件zDialog再次封装
zDialog插件网址:http://www.jq22.com/jquery-info2426 再次封装zDialog的代码: (function ($) { $.extend({ iDialog: ...
- 为什么我会选IT【这几年是怎么过来的】
导火线 晚上跟高中同学说我近来的状况,无意中他提到:“如果当初没意外话,今年估计你就是一名老师了吧”.这让我很是怀念以前的日子,这四年来过的很快,开始想着当初是怎么过来的 : 高考 本人英语不佳,高考 ...
- Oracle sqlplus设置显示格式命令详解
/ 运行 SQL 缓冲区 ? [关键词] 对关键词提供 SQL 帮助 @[@] [文件名] [参数列表] 通过指定的参数,运行指定的命令文件 ACC[EPT] 变量 [DEF[AULT] 值] [PR ...
- 速战速决 (2) - PHP: 数据类型 bool, int, float, string, object, array
[源码下载] 速战速决 (2) - PHP: 数据类型 bool, int, float, string, object, array 作者:webabcd 介绍速战速决 之 PHP 数据类型 boo ...
- spring笔记2 spring MVC的基础知识2
2,spring MVC的注解驱动控制器,rest风格的支持 作为spring mvc的明星级别的功能,无疑是使得自己的code比较优雅的秘密武器: @RequestMapping处理用户的请求,下面 ...
- 2016弱校联盟十一专场10.3---Similarity of Subtrees(深搜+hash、映射)
题目链接 https://acm.bnu.edu.cn/v3/problem_show.php?pid=52310 problem description Define the depth of a ...
- 点我吧工作总结(技术篇) Velocity
1. 什么是velocity Velocity[vəˈlɑ:səti],名称字面翻译为:速度.速率.迅速.该项目的开源地址:http://velocity.apache.org/,它是一个基于Java ...