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中没有的成员变量,这样就过滤掉了上面的那种成员变量了。

posted @ 2016-05-23 14:44  tiankongzhicheng  阅读(214)  评论(2编辑  收藏  举报