runtime 运行机制

//

//  HKPerson.h

//  runtimeDemo1

//

//  Created by 123 on 16/5/23.

//  Copyright © 2016年 123. All rights reserved.

//

#import <Foundation/Foundation.h>

@interface HKPerson : NSObject

@property (nonatomic,strong) NSString * name;

@property (nonatomic,strong) NSString * age;

@property (nonatomic,strong) NSString * sex;

@end

//

//  HKPerson.m

//  runtimeDemo1

//

//  Created by 123 on 16/5/23.

//  Copyright © 2016年 123. All rights reserved.

//

#import "HKPerson.h"

#import <objc/runtime.h>

#import <objc/message.h>

@implementation HKPerson

void static printM(){

}

+(void)printMessage{

NSLog(@"print MEssage");

}

-(void)printProperty{

unsigned  int count = 0;

Ivar * var= class_copyIvarList(self.class, &count);

for (int i = 0; i < count; i++) {

const char * name=ivar_getName(var[i]);

NSLog(@"%s\n",name);

}

free(var);

}

@end

//

//  ViewController.m

//  runtimeDemo1

//

//  Created by 123 on 16/5/23.

//  Copyright © 2016年 123. All rights reserved.

//

#import "ViewController.h"

#import "HKPerson.h"

#import <objc/runtime.h>

#import <objc/message.h>

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {

[super viewDidLoad];

HKPerson * p1 = [HKPerson new];

unsigned int count = 0;

// 只能获得对象的方法   不能获得类方法以及 静态的方法

Method * method = class_copyMethodList(p1.class, &count);

[p1 performSelector:@selector(printProperty)];

// 获取一个对象的所有方法  重复崩溃

//  NSLog(@"%@",[NSString stringWithUTF8String:name]);

// Do any additional setup after loading the view, typically from a nib.

}

- (void)didReceiveMemoryWarning {

[super didReceiveMemoryWarning];

// Dispose of any resources that can be recreated.

}

2.函数的定义

对对象进行操作的方法一般以object_开头

对类进行操作的方法一般以class_开头

对类或对象的方法进行操作的方法一般以method_开头

对成员变量进行操作的方法一般以ivar_开头

对属性进行操作的方法一般以property_开头

对协议进行操作的方法一般以protocol_开头

根据以上的函数的前缀 可以大致了解到层级关系。对于以objc_开头的方法,则是runtime最终的管家,可以获取内存中类的加载信息,类的列表,关联对象和关联属性等操作。

例如:使用runtime对当前的应用中加载的类进行打印,别被吓一跳。

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {

unsigned int count = 0;

Class *classes = objc_copyClassList(&count);

for (int i = 0; i < count; i++) {

const char *cname = class_getName(classes[i]);

printf("%s\n", cname);

}

}

@end

3_1.获取属性\成员变量列表

回到顶部

对于获取成员变量的列表可以使用class_copyIvarList函数,如果想要获取属性列表可以使用class_copyPropertyList函数,使用的示例如下:

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {

Class classPerson = NSClassFromString(@"Person"); // 与下面一句效果一样,可以不用导入头文件

//    Class clazz = Person.class;

unsigned int count = 0;

Ivar *ivarList = class_copyIvarList(classPerson, &count); // 获取成员变量数组

for (int i = 0; i < count; i++) {

const char *cname = ivar_getName(ivarList[i]); // 获取成员变量的名字

NSString *name = [NSString stringWithUTF8String:cname];

NSLog(@"%@", name);

}

NSLog(@"-------------------分割线------------------");

objc_property_t *propertyList = class_copyPropertyList(classPerson, &count); // 获取属性数组

for (int i = 0; i < count; i++) {

const char *cname = property_getName(propertyList[i]);

NSString *name = [NSString stringWithUTF8String:cname];

NSLog(@"%@", name);

}

}

以上代码的输出为:

2015-06-05 22:28:16.194 runtime终极[4192:195757] _height

2015-06-05 22:28:16.195 runtime终极[4192:195757] _age

2015-06-05 22:28:16.195 runtime终极[4192:195757] _name

2015-06-05 22:28:16.195 runtime终极[4192:195757] -------------------分割线------------------

2015-06-05 22:28:16.195 runtime终极[4192:195757] name

2015-06-05 22:28:16.195 runtime终极[4192:195757] age

为什么会有上面的输出结果,因为@property会做三份工作:
1.生成一个带下划线的成员变量
2.生成这个成员变量的get方法
3.生成这个成员变量的set方法

因此会输出三个成员变量_height、_age和_name。需要注意的是属性名是不带下划线的,和定义时的名字一样。因此可以说:ivarList可以获取到@property关键字定义的属性 ,而propertyList不可以获取到成员变量。也就是:使用ivarList是可以将所有的成员变量和属性都获取的。

当属性是readonly的而且重写了getter时,这种情况还是会遇见的,比如一个属性是计算型属性,需要依赖其他属性的值计算而来。此时生成的带下划线的成员变量就不在了, 通过ivarList不能获取该属性了。因此当有这种值的时候,无论使用ivarList还是使用propertyList都无法获取全部的属性或变量。

在进行下一个话题之前:先需要弄清楚另一个问题:对于一个readonly的属性,到底是didSet+set好,还是重写getter好?

大部分的readonly的属性是计算型的,依旧是依赖于其他属性,因此可以使用didSet+set,也就是在其他属性的set方法内,将本属性set。 但是didSet+set有时候完全没有必要,不符合懒加载的规则,浪费了计算能力,用重写getter的方法好一些。 因此重写getter总是会好一点。

回归正题:在KVC时,想要获取全部的成员变量和属性, 怎么办呢?

首先要了解setValue: forKeyPath:方法的底层实现:以name属性为例

1.首先先去类的方法列表去寻找有木有setName:,如果有,就直接调用[person setName:value]

2.找找有没有带下划线的成员变量_name,如果有 _name = value;

3.找有没有成员变量name,如果有 name = value;

4.如果都没有找到,就直接报错。

因此对于readonly的又重写了getter的属性而言:如果对propertyList的属性一次使用kvc,就会报错,因此为保证代码正常,不能使用propertyList的属性进行kvc;

另外:这种属性本来就是计算型的了,为什么还有为它赋值呢,因此对它进行kvc也不合情理。

当使用ivaList时,直接就无法获取到这种属性,因此是kvc的最佳方案。再者,使用propertyList无法获取成员变量(_height),无法对成员变量进行赋值。而使用ivaList是可以将该赋值的成员变量都获取的。

以上就是使用ivar还是使用property进行kvc的论证。

话题外: 很多类 有些成员变量 既没有暴露给外部调用的getter又没有setter,只是用@private声明了一下:为什么??
猜测是:是方法调用时使用的中间变量,因为是跟随对象产生,不适合使用静态static,又因为外部不会使用,所以没必要给外部提供接口,但是可能有好几个方法都需要这个量,不适合做局部变量,所以就这样定义了。

对于这种情况,要想不对这种成员变量赋值,在KVC时又可以这样改进一下,通过ivarList获取,去掉propertyList中没有的成员变量,这样就过滤掉了上面的那种成员变量了。

上一篇:二叉树 - 建立与遍历使用Java


下一篇:Multiple sequence alignment Benchmark Data set