用runtimeruntime 遍历属性性列表 修改了某个属性之后能不能上架

iOS开发之遍历Model类的属性并完善使用Runtime给Model类赋值
招聘信息:
在上篇博客中介绍了如何使用运行时在实体类的基类中添加给实体类的属性赋值的方法,这个方法的前提是字典的Key必须和实体类的Property Name相同,然后通过运行时来生成和执行Setter方法给Model类的属性赋值。通过Runtime来给Model类属性赋值的好处是多多的,它便于代码的后期维护,并且提高了开发效率。当你拿到解析后的字典时你不用一个一个的通过key去把字典的值赋值给相应的Model类的属性,本篇博客中会给出如何去遍历Model中属性的值,并且给出字典的Key和Model的属性名不一样的情况我们该如何负值。接下来会在上一个博客代码基础上在Model基类中添加通过Runtime来遍历Model类的属性值。一、获取Model的实体属性1.要想遍历Model类的属性,首先得通过Runtime来获取该Model类有哪些属性,输出Model的所有属性的值可不像遍历Dictionary和Array那样一个for循环搞定的,下面的方法是通过Runtime来获取Model类的属性字符串,并以数组的形式返回。代码如下:///通过运行时获取当前对象的所有属性的名称,以数组的形式返回
-&(NSArray&*)&allPropertyNames{
&&&&///存储所有的属性名称
&&&&NSMutableArray&*allNames&=&[[NSMutableArray&alloc]&init];
&&&&///存储属性的个数
&&&&unsigned&int&propertyCount&=&0;
&&&&///通过运行时获取当前类的属性
&&&&objc_property_t&*propertys&=&class_copyPropertyList([self&class],&&propertyCount);
&&&&//把属性放到数组中
&&&&for&(int&i&=&0;&i&<&propertyC&i&++)&{
&&&&&&&&///取出第一个属性
&&&&&&&&objc_property_t&property&=&propertys[i];
&&&&&&&&const&char&*&propertyName&=&property_getName(property);
&&&&&&&&[allNames&addObject:[NSString&stringWithUTF8String:propertyName]];
&&&&///释放
&&&&free(propertys);
&&&&return&allN
}2.获取到Model类的属性方法后需要把属性字符串生成get方法,我们可以执行get方法来获取Model属性的值,下方的方法是根据属性字符串来获取属性的getter方法,OC中属性的getter方法的名字和属性的名字是一致的,生成getter方法比较简单,具体代码如下:#pragma&mark&--&通过字符串来创建该字符串的Setter方法,并返回
-&(SEL)&creatGetterWithPropertyName:&(NSString&*)&propertyName{
&&&&//1.返回get方法:&oc中的get方法就是属性的本身
&&&&return&NSSelectorFromString(propertyName);
}二、Get方法的执行接下来要做的是通过Runtime来执行Getter方法,这一块需要通过方法的签名来执行Getter方法。在OC的运行时中要执行的方法需要传入参数或者需要接收返回值时,需要通过方法的签名来调用方法。下面的代码就是创建方法的签名,然后通过签名来获取调用的对象,在下边的方中回调用上述两个方法在通过方法的签名来获取Model属性的值,具体代码如下:-&(void)&displayCurrentModleProperty{
&&&&//获取实体类的属性名
&&&&NSArray&*array&=&[self&allPropertyNames];
&&&&//拼接参数
&&&&NSMutableString&*resultString&=&[[NSMutableString&alloc]&init];
&&&&for&(int&i&=&0;&i&<&array.&i&++)&{
&&&&&&&&//获取get方法
&&&&&&&&SEL&getSel&=&[self&creatGetterWithPropertyName:array[i]];
&&&&&&&&if&([self&respondsToSelector:getSel])&{
&&&&&&&&&&&&
&&&&&&&&&&&&//获得类和方法的签名
&&&&&&&&&&&&NSMethodSignature&*signature&=&[self&methodSignatureForSelector:getSel];
&&&&&&&&&&&&//从签名获得调用对象
&&&&&&&&&&&&NSInvocation&*invocation&=&[NSInvocation&invocationWithMethodSignature:signature];
&&&&&&&&&&&&//设置target
&&&&&&&&&&&&[invocation&setTarget:self];
&&&&&&&&&&&&
&&&&&&&&&&&&//设置selector
&&&&&&&&&&&&[invocation&setSelector:getSel];
&&&&&&&&&&&&//接收返回的值
&&&&&&&&&&&&NSObject&*__unsafe_unretained&returnValue&=&
&&&&&&&&&&&&//调用
&&&&&&&&&&&&[invocation&invoke];
&&&&&&&&&&&&//接收返回值
&&&&&&&&&&&&[invocation&getReturnValue:&returnValue];
&&&&&&&&&&&&[resultString&appendFormat:@"%@\n",&returnValue];
&&&&NSLog(@"%@",&resultString);
}执行上述方法就可以输入Model中的属性的值,下面就在main函数中对Model赋完值后调用上述方法输出一下Model的属性值,调用代码如下所示:BeautifulGirlModel&*beautifulGirl&=&[BeautifulGirlModel&modelWithDictionary:data];
&&&&&&&&&[beautifulGirl&displayCurrentModleProperty];运行结果如下,下面的输出结果是Model中属性的值。三、Dictionary的Key与Model的属性不同的处理方式有时候会遇到字典的key和Model的属性不一样的情况,那么如何去解决这个问题呢?最简单的做法是在具体的实体类中去维护一个映射关系方法,通过这个方法我们可以获取相应的的映射关系。#pragma&返回属性和字典key的映射关系
&-(NSDictionary&*)&propertyMapDic{
&&&&&return&
&}2.修改一下我们的便利初始化方法,在有映射字典的情况和没有映射字典的情况下调用的方法是不一样的,便利初始化方法的代码如下:-&(instancetype)initWithDictionary:&(NSDictionary&*)&data{
&&&&&&&&self&=&[super&init];
&&&&&&&&if&(self)&{
&&&&&&&&&&&&if&([self&propertyMapDic]&==&nil)&{
&&&&&&&&&&&&&&&&[self&assginToPropertyWithDictionary:data];
&&&&&&&&&&&&}&else&{
&&&&&&&&&&&&&&&&[self&assginToPropertyWithNoMapDictionary:data];
&&&&&&&&&&&&}
&&&&&&&&return&
}3.接下来就将实现有映射关系要调用的方法,这个方法就是通过映射关系把字典的key转换成与property的名字一样的字典,然后调用之前的赋值方法,具体代码如下:#pragma&根据映射关系来给Model的属性赋值
-(void)&assginToPropertyWithNoMapDictionary:&(NSDictionary&*)&data{
&&&&///获取字典和Model属性的映射关系
&&&&NSDictionary&*propertyMapDic&=&[self&propertyMapDic];
&&&&///转化成key和property一样的字典,然后调用assginToPropertyWithDictionary方法
&&&&NSArray&*dicKey&=&[data&allKeys];
&&&&NSMutableDictionary&*tempDic&=&[[NSMutableDictionary&alloc]&initWithCapacity:dicKey.count];
&&&&for&(int&i&=&0;&i&<&dicKey.&i&++)&{
&&&&&&&&NSString&*key&=&dicKey[i];
&&&&&&&&[tempDic&setObject:data[key]&forKey:propertyMapDic[key]];
&&&&[self&assginToPropertyWithDictionary:tempDic];
}4.创建一个BadBoyModel, 并重写propertyMapDic方法,并且在propertyMapDic方法中给出映射关系并返回该映射关系对应的字典。(1)BadBoyModel的属性如下://
//&&BadBoyModel.h
//&&BaseModelProject
//&&Created&by&Mr.LuDashi&on&15/7/24.
//&&Copyright&(c)&2015年&ludashi.&All&rights&reserved.
#import&"BaseModelObject.h"
@interface&BadBoyModel&:&BaseModelObject
@property&(nonatomic,&copy)&NSString&*boy1;
@property&(nonatomic,&copy)&NSString&*boy2;
@property&(nonatomic,&copy)&NSString&*boy3;
@property&(nonatomic,&copy)&NSString&*boy4;
@end(2)重写映射方法,映射字典的key是要转换字典的key, Value是对应Model的属性名。//
//&&BadBoyModel.m
//&&BaseModelProject
//&&Created&by&Mr.LuDashi&on&15/7/24.
//&&Copyright&(c)&2015年&ludashi.&All&rights&reserved.
#import&"BadBoyModel.h"
@implementation&BadBoyModel
#pragma&返回属性和字典key的映射关系
-(NSDictionary&*)&propertyMapDic{
&&&&return&@{@"keyBoy1":@"boy1",
&&&&&&&&&&&&&@"keyBoy2":@"boy2",
&&&&&&&&&&&&&@"keyBoy3":@"boy3",
&&&&&&&&&&&&&@"keyBoy4":@"boy4",};
@end5.在main函数中进行测试(1)生成我们的数值字典,字典的key与要赋值Model的属性不同,下面的循环就是要生成测试使用的数据://生成Dic的Key与Model的属性不一样的字典。
&&&&&&&&NSMutableDictionary&*data1&=&[[NSMutableDictionary&alloc]&init];
&&&&&&&&//创建测试适用的字典
&&&&&&&&for(int&i&=&1;&i&<=&4;&i&++){
&&&&&&&&&&&&NSString&*key&=&[NSString&stringWithFormat:@"keyBoy%d",&i];
&&&&&&&&&&&&
&&&&&&&&&&&&NSString&*value&=&[NSString&stringWithFormat:@"我是第%d个坏男孩",&i];
&&&&&&&&&&&&
&&&&&&&&&&&&[data1&setObject:value&forKey:key];
&&&&&&&&}(2) 实例化Model并输出结果,当然之前的代码也是可以使用的。BadBoyModel&*badBoyModel&=&[BadBoyModel&modelWithDictionary:data1];
[badBoyModel&displayCurrentModleProperty];运行输出结果如下:今天博客就到这,至此,Model的基类最基本的方法封装的也就差不多了,根据具体需求可以在添加新的方法作者:出处:本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。如果文中有什么错误,欢迎指出。以免更多的人被误导。
微信扫一扫
订阅每日移动开发及APP推广热点资讯公众号:CocoaChina
点击量13369点击量10506点击量9537点击量7872点击量7477点击量6526点击量5609点击量5130点击量3802
&2015 Chukong Technologies,Inc.
京公网安备89runtime简单使用之动态添加属性 - 简书
<div class="fixed-btn note-fixed-download" data-toggle="popover" data-placement="left" data-html="true" data-trigger="hover" data-content=''>
写了74329字,被161人关注,获得了184个喜欢
runtime简单使用之动态添加属性
runtime简单使用之给系统类动态添加属性
1, 给类动态添加属性,实质就是让一个属性与某个对象产生关联的关系.值得注意的一点: 使用该功能的前提是给系统的类添加属性,如果是自定义的类添加属性,就没有必要用到runtime了.
2, 背景: 给系统的NSObject类动态添加一个name属性.
1, 创建NSObject对象
#import "ViewController.h"
#import "NSObject+property.h"
@interface ViewController ()
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
NSObject *objc = [[NSObject alloc] init];
objc.name = @"WilliamAlex";
NSLog(@"%@",objc.name);
注意 : 如果直接定义一个WGPerson类,在WGPerson类中声明并实现name属性,这样添加属性直接添加就是了,没有必要使用runtime来动态添加.一般的做法是添加一个分类,在分类中声明一个name属性,然后手动写setter和getter方法,使用runtime保存name属性并与NSObject产生关联.
2, 创建一个分类
// 在分类中声明一个属性时,只会生成setter和getter方法的声明,并不能生成setter和getter方法的实现以及带下划线的成员变量.所以, 在分类中有两种方式声明一个属性
// 第一种写法
@property(nonatomic, strong) NSString *
// 第二种写法 : 原因是在分类中声明属性只会生成setter和getter的声明
@property NSString *
实现setter和getter方法
#import "NSObject+property.h"
#import &objc/message.h&
@implementation NSObject (property)
- (void)setName:(NSString *)name
// Associated : |?'s????e?t|关联的意思
产生关联,让某个对象(name)与当前对象的属性(name)产生关联
参数1: id object :表示给哪个对象添加关联
参数2: const void *key : 表示: id类型的key值(以后用这个key来获取属性) 属性名
参数3: id value : 属性值
参数4: 策略, 是个枚举(点进去,解释很详细)
objc_setAssociatedObject(self, "Alex", name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
- (NSString *)name
return objc_getAssociatedObject(self, "Alex");
// 下面是策略(是个枚举值)的类型
OBJC_ASSOCIATION_ASSIGN = 0,
& Specifies a weak reference to the associated object.
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,
& Specifies a strong reference to the associated object.
The association is not made atomically.
OBJC_ASSOCIATION_COPY_NONATOMIC = 3,
& Specifies that the associated object is copied.
The association is not made atomically
OBJC_ASSOCIATION_RETAIN = 01401,
& Specifies a strong reference to the associated object.
The association is made atomically.
OBJC_ASSOCIATION_COPY = 01403
& Specifies that the associated object is copied.
The association is made atomically.
21:58:52.232 sasass[] WilliamAlex
问题 : 为什么我们会用runtime方法来给系统的类动态添加属性? 直接在分类的.m文件中定义一个全局的NSString *_也可以打印出结果啊!为什么不能那样做呢
// 其他保持一样
#import "NSObject+property.h"
#import &objc/message.h&
NSString *_
@implementation NSObject (property)
- (void)setName:(NSString *)name
- (NSString *)name
21:58:52.232 sasass[] WilliamAlex
答 : 属性保存的地址不同,如果使用问题中所述的那样,虽然可以打印出结果,但是当objc销毁了,objc.name并不会随着它的销毁而销毁.这样就不是关联关系了.既然是添加属性,那么当objc销毁了,objc.name也需要随之销毁,如果直接在分类中实现setter和getter方法,虽然可以打印出字符串,但是并没有与之产生关联,所以这时候就需要使用东岸runtime,那么就需要将某个属性保存到条用它的对象里.给哪个对象添加属性,那么就将之保存到谁里面.
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
打开微信“扫一扫”,打开网页后点击屏幕右上角分享按钮
被以下专题收入,发现更多相似内容:
· 19人关注
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
选择支付方式:iOS运行时-使用Runtime向Category中添加属性以及运行时介绍 - 博客频道 - CSDN.NET
YUSIR 完美CODING世界
//厉害了我的哥!!!
了解OC的都应该知道,在一般情况下,我们是不能向Category中添加属性的,只能添加方法,但有些情况向,我们确实需要向Category中添加属性,而且很多系统的API也有一些在Category添加属性的情况,例如我们属性的UITableView的section和row属性,就是定义在一个名为NSIndexPath的分类里的,如下&
那这到底是怎么实现的呢?
iOS运行时机制简介
运行时机制,简单来说,就是苹果给开发这提供的一套在运行时动态创建类、添加属性/方法(不止这些,还有一些其他功能)的API,它是一套纯的API,使用相应的API就可以通过Category给一个原本存在的类添加属性。
实例:使用Runtime向Category中添加属性
&code class=&hljs objectivec has-numbering& style=&display: padding: 0 color: box-sizing: border- font-family: &Source Code Pro&,font-size: white-space: border-radius: 0 word-wrap: background:&&&span class=&hljs-preprocessor& style=&color: rgb(68, 68, 68); box-sizing: border-&&#import &span class=&hljs-title& style=&box-sizing: border-&&&Foundation/Foundation.h&&/span&&/span&
&span class=&hljs-preprocessor& style=&color: rgb(68, 68, 68); box-sizing: border-&&#import &span class=&hljs-title& style=&box-sizing: border-&&&objc/runtime.h&&/span&&/span&
&span class=&hljs-class& style=&box-sizing: border-&&&span class=&hljs-keyword& style=&color: rgb(0, 0, 136); box-sizing: border-&&@interface&/span& &span class=&hljs-title& style=&box-sizing: border- color: rgb(102, 0, 102);&&NSObject&/span& (&span class=&hljs-title& style=&box-sizing: border- color: rgb(102, 0, 102);&&CategoryWithProperty&/span&)&/span&
&span class=&hljs-comment& style=&color: rgb(136, 0, 0); box-sizing: border-&&/**
要在Category中扩展的属性
&span class=&hljs-keyword& style=&color: rgb(0, 0, 136); box-sizing: border-&&@property&/span& (&span class=&hljs-keyword& style=&color: rgb(0, 0, 136); box-sizing: border-&&nonatomic&/span&, &span class=&hljs-keyword& style=&color: rgb(0, 0, 136); box-sizing: border-&&strong&/span&) &span class=&hljs-built_in& style=&color: rgb(102, 0, 102); box-sizing: border-&&NSObject&/span& *
&span class=&hljs-keyword& style=&color: rgb(0, 0, 136); box-sizing: border-&&@end&/span&
&span class=&hljs-class& style=&box-sizing: border-&&&span class=&hljs-keyword& style=&color: rgb(0, 0, 136); box-sizing: border-&&@implementation&/span& &span class=&hljs-title& style=&box-sizing: border- color: rgb(102, 0, 102);&&NSObject&/span& (&span class=&hljs-title& style=&box-sizing: border- color: rgb(102, 0, 102);&&CategoryWithProperty&/span&)&/span&
- (&span class=&hljs-built_in& style=&color: rgb(102, 0, 102); box-sizing: border-&&NSObject&/span& *)property {
&span class=&hljs-keyword& style=&color: rgb(0, 0, 136); box-sizing: border-&&return&/span& objc_getAssociatedObject(&span class=&hljs-keyword& style=&color: rgb(0, 0, 136); box-sizing: border-&&self&/span&, &span class=&hljs-keyword& style=&color: rgb(0, 0, 136); box-sizing: border-&&@selector&/span&(property));
- (&span class=&hljs-keyword& style=&color: rgb(0, 0, 136); box-sizing: border-&&void&/span&)setProperty:(&span class=&hljs-built_in& style=&color: rgb(102, 0, 102); box-sizing: border-&&NSObject&/span& *)value {
objc_setAssociatedObject(&span class=&hljs-keyword& style=&color: rgb(0, 0, 136); box-sizing: border-&&self&/span&, &span class=&hljs-keyword& style=&color: rgb(0, 0, 136); box-sizing: border-&&@selector&/span&(property), value, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
&span class=&hljs-keyword& style=&color: rgb(0, 0, 136); box-sizing: border-&&@end&/span&&/code&&ul class=&pre-numbering& style=&box-sizing: border- position: width: 50 top: 0 left: 0 margin: 0 padding: 6px 0px 40 border-right: 1px solid rgb(221, 221, 221); list-style: text-align: background-color: rgb(238, 238, 238);&&&li style=&box-sizing: border- padding: 0px 5&&1&/li&&li style=&box-sizing: border- padding: 0px 5&&2&/li&&li style=&box-sizing: border- padding: 0px 5&&3&/li&&li style=&box-sizing: border- padding: 0px 5&&4&/li&&li style=&box-sizing: border- padding: 0px 5&&5&/li&&li style=&box-sizing: border- padding: 0px 5&&6&/li&&li style=&box-sizing: border- padding: 0px 5&&7&/li&&li style=&box-sizing: border- padding: 0px 5&&8&/li&&li style=&box-sizing: border- padding: 0px 5&&9&/li&&li style=&box-sizing: border- padding: 0px 5&&10&/li&&li style=&box-sizing: border- padding: 0px 5&&11&/li&&li style=&box-sizing: border- padding: 0px 5&&12&/li&&li style=&box-sizing: border- padding: 0px 5&&13&/li&&li style=&box-sizing: border- padding: 0px 5&&14&/li&&li style=&box-sizing: border- padding: 0px 5&&15&/li&&li style=&box-sizing: border- padding: 0px 5&&16&/li&&li style=&box-sizing: border- padding: 0px 5&&17&/li&&li style=&box-sizing: border- padding: 0px 5&&18&/li&&li style=&box-sizing: border- padding: 0px 5&&19&/li&&li style=&box-sizing: border- padding: 0px 5&&20&/li&&li style=&box-sizing: border- padding: 0px 5&&21&/li&&li style=&box-sizing: border- padding: 0px 5&&22&/li&&li style=&box-sizing: border- padding: 0px 5&&23&/li&&/ul&&ul class=&pre-numbering& style=&box-sizing: border- position: width: 50 top: 0 left: 0 margin: 0 padding: 6px 0px 40 border-right: 1px solid rgb(221, 221, 221); list-style: text-align: background-color: rgb(238, 238, 238);&&&li style=&box-sizing: border- padding: 0px 5&&1&/li&&li style=&box-sizing: border- padding: 0px 5&&2&/li&&li style=&box-sizing: border- padding: 0px 5&&3&/li&&li style=&box-sizing: border- padding: 0px 5&&4&/li&&li style=&box-sizing: border- padding: 0px 5&&5&/li&&li style=&box-sizing: border- padding: 0px 5&&6&/li&&li style=&box-sizing: border- padding: 0px 5&&7&/li&&li style=&box-sizing: border- padding: 0px 5&&8&/li&&li style=&box-sizing: border- padding: 0px 5&&9&/li&&li style=&box-sizing: border- padding: 0px 5&&10&/li&&li style=&box-sizing: border- padding: 0px 5&&11&/li&&li style=&box-sizing: border- padding: 0px 5&&12&/li&&li style=&box-sizing: border- padding: 0px 5&&13&/li&&li style=&box-sizing: border- padding: 0px 5&&14&/li&&li style=&box-sizing: border- padding: 0px 5&&15&/li&&li style=&box-sizing: border- padding: 0px 5&&16&/li&&li style=&box-sizing: border- padding: 0px 5&&17&/li&&li style=&box-sizing: border- padding: 0px 5&&18&/li&&li style=&box-sizing: border- padding: 0px 5&&19&/li&&li style=&box-sizing: border- padding: 0px 5&&20&/li&&li style=&box-sizing: border- padding: 0px 5&&21&/li&&li style=&box-sizing: border- padding: 0px 5&&22&/li&&li style=&box-sizing: border- padding: 0px 5&&23&/li&&/ul&
这样就可以在Category中添加属性了。
一、什么是运行时(Runtime)?
运行时是苹果提供的纯C语言的开发库(运行时是一种非常牛&#36924;、开发中经常用到的底层技术)
二、运行时的作用?
能获得某个类的所有成员变量能获得某个类的所有属性能获得某个类的所有方法交换方法实现能动态添加一个成员变量能动态添加一个属性能动态添加一个方法
三、案例:运行时获取成员变量名称
#import &Foundation/Foundation.h&
#import &XMGPerson.h&
#import &objc/runtime.h&
int main(int argc, const char * argv[]) {
@autoreleasepool {
unsigned int outCount = 0;
Ivar *ivars = class_copyIvarList([XMGPerson class], &outCount);
for (int i = 0; i&outC i&#43;&#43;) {
Ivar ivar = ivars[i];
NSLog(@&%s&, ivar_getName(ivar));
free(ivars);
2、获取UITextFiled成员变量的名称
unsigned int outCount = 0;
Ivar *ivars = class_copyIvarList([UITextField class], &outCount);
for (int i = 0; i&outC i&#43;&#43;) {
Ivar ivar = ivars[i];
NSLog(@&%s&, ivar_getName(ivar));
free(ivars);
四、iOS底层
1、The Runtime 简单介绍
Objective-C是一门简单的语言,95%是C。只是在语言层面上加了些关键字和语法。真正让Objective-C如此强大的是它的运行时。它很小但却很强大。它的核心是消息分发。
执行一个方法,有些语言,编译器会执行一些额外的优化和错误检查,因为调用关系很直接也很明显。但对于消息分发来说,就不那么明显了。在发消息前不必知道某个对象是否能够处理消息。你把消息发给它,它可能会处理,也可能转给其他的Object来处理。一个消息不必对应一个方法,一个对象可能实现一个方法来处理多条消息。
在Objective-C中,消息是通过objc_msgSend()这个runtime方法及相近的方法来实现的。这个方法需要一个target,selector,还有一些参数。理论上来说,编译器只是把消息分发变成objc_msgSend来执行。比如下面这两行代码是等价的。
[array insertObject:foo atIndex:5]
objc_msgSend(array, @selector(insertObject:atIndex:), foo, 5)
Objects, Classes, MetaClasses
大多数面向对象的语言里有 classes 和 objects 的概念。Objects通过Classes生成。但是在Objective-C中,classes本身也是objects,也可以处理消息,这也是为什么会有类方法和实例方法。具体来说,Objective-C中的Object是一个结构体(struct),第一个成员是isa,指向自己的class。这是在objc/objc.h中定义的。
typedef struct objc_object {
object的class保存了方法列表,还有指向父类的指针。但classes也是objects,也会有isa变量,那么它又指向哪儿呢?这里就引出了第三个类型: metaclasses。一个 metaclass被指向class,class被指向object。它保存了所有实现的方法列表,以及父类的metaclass。如果想更清楚地了解objects,classes以及metaclasses是如何一起工作地,可以阅读这篇文章。
Methods, Selectors and IMPs
我们知道了运行时会发消息给对象。我们也知道一个对象的class保存了方法列表。那么这些消息是如何映射到方法的,这些方法又是如何被执行的呢?
第一个问题的答案很简单。class的方法列表其实是一个字典,key为selectors,IMPs为value。一个IMP是指向方法在内存中的实现。很重要的一点是,selector和IMP之间的关系是在运行时才决定的,而不是编译时。这样我们就能玩出些花样。
IMP通常是指向方法的指针,第一个参数是self,类型为id,第二个参数是_cmd,类型为SEL,余下的是方法的参数。这也是self和_cmd被定义的地方。下面演示了Method和IMP
- (id)doSomethingWithInt:(int)aInt{}
id doSomethingWithInt(id self, SEL _cmd, int aInt){}
现在我们知道了objects,classes,selectors,IMPs以及消息分发,那么运行时到底能做什么呢?
运行时到底能做什么呢?
创建、修改、自省classes和objects消息分发
之前已经提过消息分发,不过这只是一小部分功能。所有的运行时方法都有特定的前缀。下面是一些有意思的方法:
class开头的方法是用来修改和自省classes。方法如:
能拿到一个class的所有内容
class_addIvar, class_addMethod, class_addProperty和class_addProtocol允许重建classes。class_copyIvarList, class_copyMethodList, class_copyProtocolList和class_copyPropertyList
返回单个内容
class_getClassMethod, class_getClassVariable, class_getInstanceMethod, class_getInstanceVariable, class_getMethodImplementation和class_getProperty
一些通用的自省方法
class_conformsToProtocol, class_respondsToSelector, class_getSuperclass
创建一个object
class_createInstance来创建一个object
这些方法能让你得到名字,内存地址和Objective-C type encoding。
这些方法主要用来自省,比如:
method_getName, method_getImplementation, method_getReturnType等等
也有一些修改的方法,包括:
method_setImplementation和method_exchangeImplementations
一旦拿到了object,你就可以对它做一些自省和修改。你可以get/set ivar, 使用object_copy和object_dispose来copy和free object的内存。不仅是拿到一个class,而是可以使用object_setClass来改变一个object的class。
属性保存了很大一部分信息。除了拿到名字,你还可以使用property_getAttributes来发现property的更多信息,如返回&#20540;、是否为atomic、getter/setter名字、是否为dynamic、背后使用的ivar名字、是否为弱引用。
Protocols有点像classes,但是精简版的,运行时的方法是一样的。你可以获取method, property, protocol列表, 检查是否实现了其他的protocol。
最后我们有一些方法可以处理 selectors,比如获取名字,注册一个selector等等。
2、运行时能干什么?(举例)
2.1 Classes And Selectors From Strings
比较基础的一个动态特性是通过String来生成Classes和Selectors。Cocoa提供了NSClassFromString和NSSelectorFromString方法,使用起来很简单:
Class stringclass = NSClassFromString(@&NSString&)
于是我们就得到了一个string class。接下来:
NSString *myString = [stringclass stringWithString:@&Hello World&];
为什么要这么做呢?直接使用Class不是更方便?通常情况下是,但有些场景下这个方法会很有用。首先,可以得知是否存在某个class,NSClassFromString 会返回nil,如果运行时不存在该class的话。
另一个使用场景是根据不同的输入返回不同的class或method。比如你在解析一些数据,每个数据项都有要解析的字符串以及自身的类型(String,Number,Array)。你可以在一个方法里搞定这些,也可以使用多个方法。其中一个方法是获取type,然后使用if来调用匹配的方法。另一种是根据type来生成一个selector,然后调用之。以下是两种实现方式:
- (void)parseObject:(id)object {
for (id data in object) {
if ([[data type] isEqualToString:@&String&]) {
[self parseString:[data value]];
} else if ([[data type] isEqualToString:@&Number&]) {
[self parseNumber:[data value]];
} else if ([[data type] isEqualToString:@&Array&]) {
[self parseArray:[data value]];
- (void)parseObjectDynamic:(id)object {
for (id data in object) {
[self performSelector:NSSelectorFromString([NSString stringWithFormat:@&parse%@:&, [data type]]) withObject:[data value]];
- (void)parseString:(NSString *)aString {}
- (void)parseNumber:(NSString *)aNumber {}
- (void)parseArray:(NSString *)aArray {}
可一看到,你可以把7行带if的代码变成1行。将来如果有新的类型,只需增加实现方法即可,而不用再去添加新的 else if。
2.2 Method Swizzling
之前我们讲过,方法由两个部分组成。Selector相当于一个方法的id;IMP是方法的实现。这样分开的一个便利之处是selector和IMP之间的对应关系可以被改变。比如一个 IMP 可以有多个 selectors 指向它。
而 Method Swizzling 可以交换两个方法的实现。或许你会问“什么情况下会需要这个呢?”。我们先来看下Objective-C中,两种扩展class的途径。首先是 subclassing。你可以重写某个方法,调用父类的实现,这也意味着你必须使用这个subclass的实例,但如果继承了某个Cocoa class,而Cocoa又返回了原先的class(比如 NSArray)。这种情况下,你会想添加一个方法到NSArray,也就是使用Category。99%的情况下这是OK的,但如果你重写了某个方法,就没有机会再调用原先的实现了。
Method Swizzling 可以搞定这个问题。你可以重写某个方法而不用继承,同时还可以调用原先的实现。通常的做法是在category中添加一个方法(当然也可以是一个全新的class)。可以通过method_exchangeImplementations这个运行时方法来交换实现。来看一个demo,这个demo演示了如何重写addObject:方法来纪录每一个新添加的对象。
&objc/runtime.h&
@interface NSMutableArray (LoggingAddObject)
- (void)logAddObject:(id)aO
@implementation NSMutableArray (LoggingAddObject)
&#43; (void)load {
Method addobject = class_getInstanceMethod(self, @selector(addObject:));
Method logAddobject = class_getInstanceMethod(self, @selector(logAddObject:));
method_exchangeImplementations(addObject, logAddObject);
- (void)logAddObject:(id)aobject {
[self logAddObject:aObject];
NSLog(@&Added object %@ to array %@&, aObject, self);
我们把方法交换放到了load中,这个方法只会被调用一次,而且是运行时载入。如果指向临时用一下,可以放到别的地方。注意到一个很明显的递归调用logAddObject:。这也是Method Swizzling容易把我们搞混的地方,因为我们已经交换了方法的实现,所以其实调用的是addObject:
动态继承、交换
我们可以在运行时创建新的class,这个特性用得不多,但其实它还是很强大的。你能通过它创建新的子类,并添加新的方法。
但这样的一个子类有什么用呢?别忘了Objective-C的一个关键点:object内部有一个叫做isa的变量指向它的class。这个变量可以被改变,而不需要重新创建。然后就可以添加新的ivar和方法了。可以通过以下命令来修改一个object的class.
object_setClass(myObject, [MySubclass class])
这可以用在Key Value Observing。当你开始observing an object时,Cocoa会创建这个object的class的subclass,然后将这个object的isa指向新创建的subclass。
动态方法处理
目前为止,我们讨论了方法交换,以及已有方法的处理。那么当你发送了一个object无法处理的消息时会发生什么呢?很明显,&it breaks&。大多数情况下确实如此,但Cocoa和runtime也提供了一些应对方法。
首先是动态方法处理。通常来说,处理一个方法,运行时寻找匹配的selector然后执行之。有时,你只想在运行时才创建某个方法,比如有些信息只有在运行时才能得到。要实现这个效果,你需要重写&#43;resolveInstanceMethod: 和/或 &#43;resolveClassMethod:。如果确实增加了一个方法,记得返回YES。
&#43; (BOOL)resolveInstanceMethod:(SEL)aSelector {
if (aSelector == @selector(myDynamicMethod)) {
class_addMethod(self, aSelector, (IMP)myDynamicIMP, &v@:&);
return YES;
return [super resolveInstanceMethod:aSelector];
那Cocoa在什么场景下会使用这些方法呢?Core Data用得很多。NSManagedObjects有许多在运行时添加的属性用来处理get/set属性和关系。那如果Model在运行时被改变了呢?
如果 resolve method 返回NO,运行时就进入下一步骤:消息转发。有两种常见用例。1) 将消息转发到另一个可以处理该消息的object。2) 将多个消息转发到同一个方法。
消息转发分两步。首先,运行时调用-forwardingTargetForSelector:,如果只是想把消息发送到另一个object,那么就使用这个方法,因为更高效。如果想要修改消息,那么就要使用-forwardInvocation:,运行时将消息打包成NSInvocation,然后返回给你处理。处理完之后,调用invokeWithTarget:。
Cocoa有几处地方用到了消息转发,主要的两个地方是代理(Proxies)和响应链(Responder Chain)。NSProxy是一个轻量级的class,它的作用就是转发消息到另一个object。如果想要惰性加载object的某个属性会很有用。NSUndoManager也有用到,不过是截取消息,之后再执行,而不是转发到其他的地方。
响应链是关于Cocoa如何处理与发送事件与行为到对应的对象。比如说,使用Cmd&#43;C执行了copy命令,会发送-copy:到响应链。首先是First Responder,通常是当前的UI。如果没有处理该消息,则转发到下一个-nextResponder。这么一直下去直到找到能够处理该消息的object,或者没有找到,报错。
使用Block作为Method IMP
iOS 4.3带来了很多新的runtime方法。除了对properties和protocols的加强,还带来一组新的以 imp 开头的方法。通常一个 IMP 是一个指向方法实现的指针,头两个参数为 object(self)和selector(_cmd)。iOS 4.0和Mac OS X 10.6 带来了block,imp_implementationWithBlock() 能让我们使用block作为 IMP,下面这个代码片段展示了如何使用block来添加新的方法。
IMP myIMP = imp_implementationWithBlock(^(id _self, NSString *string) {
NSLog(@&Hello %@&, string);
class_addMethod([MYclass class], @selector(sayHello:), myIMP, &v@:@&);
可以看到,Objective-C 表面看起来挺简单,但还是很灵活的,可以带来很多可能性。动态语言的优势在于在不扩展语言本身的情况下做很多很灵巧的事情。比如Key Value Observing,提供了优雅的API可以与已有的代码无缝结合,而不需要新增语言级别的特性。
yusirxiaer
排名:千里之外
(4)(25)(19)(12)(5)(11)(3)}

我要回帖

更多关于 runtime 获取方法列表 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信