美文网首页iOS
iOSRuntime的成员与属性

iOSRuntime的成员与属性

作者: 踏云小子 | 来源:发表于2018-03-06 19:17 被阅读28次

    一、成员变量

    1.1 Ivar

    Ivar: 实例变量类型,是一个指向objc_ivar结构体的指针

    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
    } 
    
    1.2 常用方法
    // 获取所有成员变量
    class_copyIvarList
    
    // 获取所有方法
    class_copyMethodList
    
    // 获取所有属性
    ivar_getName
    
    // 获取成员变量类型编码
    ivar_getTypeEncoding
    
    // 获取指定名称的成员变量
    class_getInstanceVariable
    
    // 获取某个对象成员变量的值
    object_getIvar
    
    // 设置某个对象成员变量的值
    object_setIvar
    
    1.3 class_copyPropertyList与class_copyIvarList区别
    • class_copyPropertyList:仅仅返回属性
    • class_copyIvarList:不仅返回属性,还返回变量({}修饰的变量)
    //XYPerson.h
    #import <Foundation/Foundation.h>
    
    @interface XYPerson : NSObject
    @property (nonatomic, copy) NSString *name;
    @property (nonatomic, assign) NSUInteger age;
    @end
    
    //XYPerson.m
    #import "XYPerson.h"
    
    @interface XYPerson (){
        NSInteger sex;
    }
    
    @property (nonatomic, copy) NSString *hobby;
    
    @end
    
    @implementation XYPerson
    
    //MainController.m
    - (void)viewDidLoad {
        XYPerson *person = [XYPerson new];
        
        {
            //记录属性的个数
            unsigned int count = 0;
            
            //获取属性列表
            Ivar *members = class_copyIvarList([person class], &count);
            
            //遍历属性列表
            for (NSInteger i = 0; i < count; i++) {
                
                //取到属性
                Ivar ivar = members[i];
                
                //获取属性名
                const char *memberName = ivar_getName(ivar);
                NSString *ivarName = [NSString stringWithFormat:@"%s", memberName];
                
                NSLog(@"属性名:%@", ivarName);
                
            }
            free(members);
        }
        
        NSLog(@"----------");
      
        {
            //记录属性的个数
            unsigned int count = 0;
            objc_property_t *properties = class_copyPropertyList([person class], &count);
            //遍历属性列表
            for (NSInteger i = 0; i < count; i++) {
                
                //取到属性
                objc_property_t property = properties[i];
                
                //获取属性名
                NSString * propertyName = [NSString stringWithCString:property_getName(property) encoding:NSUTF8StringEncoding];
                
                NSLog(@"属性名:%@", propertyName);
            }
            free(properties);
        }
    }
    

    打印如下

    2018-03-07 11:26:29.693788+0800 tableView[76444:1687311] 属性名:sex
    2018-03-07 11:26:29.694058+0800 tableView[76444:1687311] 属性名:_name
    2018-03-07 11:26:29.694371+0800 tableView[76444:1687311] 属性名:_age
    2018-03-07 11:26:29.694485+0800 tableView[76444:1687311] 属性名:_hobby
    2018-03-07 11:26:29.694636+0800 tableView[76444:1687311] ----------
    2018-03-07 11:26:29.694766+0800 tableView[76444:1687311] 属性名:hobby
    2018-03-07 11:26:29.694913+0800 tableView[76444:1687311] 属性名:name
    2018-03-07 11:26:29.695080+0800 tableView[76444:1687311] 属性名:age
    

    可见class_copyIvarList还获取了一个变量sex

    二、获取类的私有属性和私有方法

    有一个XYPerson类

    //XYPerson.h
    #import <Foundation/Foundation.h>
    
    @interface XYPerson : NSObject
    @property (nonatomic, copy) NSString *name;
    @property (nonatomic, assign) NSUInteger age;
    - (void)printInfo;
    
    @end
    
    //XYPerson.m
    #import "XYPerson.h"
    
    @interface XYPerson (){
        NSInteger sex;
    }
    
    @property (nonatomic, copy) NSString *hobby;
    
    - (void)_privateMethod;
    
    @end
    
    @implementation XYPerson
    
    - (void)_privateMethod{
        NSLog(@"_privateMethod");
    }
    
    - (void)printInfo{
       NSLog(@"printInfo");
    }
    
    2.1 获取类的私有方法
    //MainController.m
    - (void)viewDidLoad {
        [super viewDidLoad];
    
        unsigned int count = 0;
        Method *methods = class_copyMethodList([XYPerson class], &count);
        for (int i = 0; i < count; i++) { // 3
            NSLog(@"%s", sel_getName(method_getName(methods[i]))); // 4
        } // 5
        free(methods); // 6
    }
    

    打印:

    2018-03-07 11:01:14.867925+0800 tableView[75802:1660458] _privateMethod
    2018-03-07 11:01:14.868151+0800 tableView[75802:1660458] hobby
    2018-03-07 11:01:14.868295+0800 tableView[75802:1660458] setHobby:
    2018-03-07 11:01:14.868550+0800 tableView[75802:1660458] printInfo
    2018-03-07 11:01:14.868742+0800 tableView[75802:1660458] .cxx_destruct
    2018-03-07 11:01:14.868884+0800 tableView[75802:1660458] name
    2018-03-07 11:01:14.869020+0800 tableView[75802:1660458] setName:
    2018-03-07 11:01:14.869117+0800 tableView[75802:1660458] setAge:
    2018-03-07 11:01:14.869215+0800 tableView[75802:1660458] age
    

    你会发现有个.cxx_destruct方法,这玩意儿大有来头

    ARC actually creates a -.cxx_destruct method to handle freeing instance variables. This method was originally created for calling C++ destructors automatically when an object was destroyed.
    When the compiler saw that an object contained C++ objects, it would generate a method called .cxx_destruct. ARC piggybacks on this method and emits the required cleanup code within it.

    可以了解到,.cxx_destruct方法原本是为了C++对象析构的,ARC借用了这个方法插入代码实现了自动内存释放的工作,详细介绍可看sunnyxx大神的

    2.2 获取类的私有属性
    • 使用KVC的valueForKey
    //MainController.h
    - (void)viewDidLoad {
        [super viewDidLoad];
        XYPerson *person = [XYPerson new];
        [person setValue:@"make love" forKey:@"hobby"];
        NSString *propertyName = [person valueForKey:@"hobby"];
        NSLog(@"属性:%@", propertyName);
    }
    

    那么问题来了,KVC是怎么实现的呢,请移步这里

    • 使用runtime的class_copyIvarList拿到所有的属性,非常的,简单粗暴!
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        XYPerson *person = [XYPerson new];
        
        //记录属性的个数
        unsigned int count = 0;
        
        //获取属性列表
        Ivar *members = class_copyIvarList([person class], &count);
        
        //遍历属性列表
        for (NSInteger i = 0; i < count; i++) {
            
            //取到属性
            Ivar ivar = members[i];
            
            //获取属性名
            const char *memberName = ivar_getName(ivar);
            NSString *ivarName = [NSString stringWithFormat:@"%s", memberName];        
            NSLog(@"属性名:%@", ivarName);
        }  
        free(members);
    }
    
    • 使用runtime的class_getInstanceVariable和object_setIvar,直捣黄龙!
    Ivar ivar = class_getInstanceVariable([XYPerson class], "_hobby");    
    XYPerson *person = [XYPerson new];
    object_setIvar(person, ivar, @"make love");
    NSString * propertyName = object_getIvar(person, ivar);
    NSLog(@"属性名:%@", propertyName);
    

    打印如下:

    2018-03-07 10:33:19.989501+0800 tableView[75120:1626828] 属性名:make love
    
    • 使用object_getInstanceVariable,不过ARC下不可用,MRC下可以用,🙄
    NSString *propertyName;
    XYPerson *person = [XYPerson new];
    object_getInstanceVariable(person, "_hobby", (void *)&propertyName);
    NSLog(@"属性名:%@", propertyName);
    

    而且object_getIvar还快一些,根据苹果的注释

    * @note \c object_getIvar is faster than \c object_getInstanceVariable if the Ivar
     *  for the instance variable is already known.
    

    三、简化initWithCoder的代码

    不使用runtime,需要一个一个的写

    //AddressModel.m
    #import "AddressModel.h"
    
    @implementation AddressModel
    
    - (id)initWithCoder:(NSCoder *)aDecoder {
        if (self = [super init]) {
            _name = [aDecoder decodeObjectForKey:@"name"];
            _selected = [aDecoder decodeBoolForKey:@"selected"];
            _lat = [aDecoder decodeDoubleForKey:@"lat"];
            _lng = [aDecoder decodeDoubleForKey:@"lng"];
             
            
        }
        return self;
    }
    
    - (void)encodeWithCoder:(NSCoder *)aCoder {    
        [aCoder encodeObject:_name forKey:@"name"];
        [aCoder encodeBool:_selected forKey:@"selected"];
        [aCoder encodeDouble:_lat forKey:@"lat"];
        [aCoder encodeDouble:_lng forKey:@"lng"];    
    }
    
    

    使用了runtime,爽歪歪

    #import "AddressModel.h"
    #import <objc/runtime.h>
    
    @implementation AddressModel
    
    - (id)initWithCoder:(NSCoder *)aDecoder {
        if (self = [super init]) {        
            unsigned int count = 0;
            Ivar *members = class_copyIvarList([self class], &count);
            for (NSInteger i = 0; i < count; i++) {
                Ivar ivar = members[i];
                const char *memberName = ivar_getName(ivar);
                NSString *key = [[NSString alloc] initWithUTF8String:memberName];
                [self setValue:[aDecoder decodeObjectForKey:key] forKey:key];
            }
            free(members);        
        }
        return self;
    }
    
    - (void)encodeWithCoder:(NSCoder *)aCoder {
        unsigned int count = 0;
        Ivar *members = class_copyIvarList([self class], &count);
        for (NSInteger i = 0; i < count; i++) {
            Ivar ivar = members[i];
            const char *memberName = ivar_getName(ivar);
            NSString *key = [[NSString alloc] initWithUTF8String:memberName];
            id value = [self valueForKey:key];        
            [aCoder encodeObject:[self valueForKey:key] forKey:key];
        }
    }
    

    这里使用initWithCoder是为了遵循NSCoding协议,以便进行持久化存储,持久化存储有NSKeyedArchiver与NSUserDefaults两种方案

    1. NSKeyedArchiver
    //
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        AddressModel *address = [AddressModel new];
        address.name = @"世贸中心";
        address.selected = NO;
        address.lat = 13.89773;
        address.lng = 31.87878;
    
        [NSKeyedArchiver archiveRootObject:address toFile:[[self class] savePath]];
        NSData *storedData = [NSKeyedUnarchiver unarchiveObjectWithFile:[[self class] savePath]];
    }
    + (NSString *)savePath{
        NSString *docPath=[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)lastObject];
        NSString *path=[docPath stringByAppendingPathComponent:@"userAddresses"];
        return path;
    }
    
    
    image.png
    1. NSUserDefaults
    
    - (void)viewDidLoad {
        NSData *theData = [NSKeyedArchiver archivedDataWithRootObject:address];
        [[NSUserDefaults standardUserDefaults] setObject:theData forKey:@"addressModel"];
        [[NSUserDefaults standardUserDefaults] synchronize];
        NSData *bufferData = [[NSUserDefaults standardUserDefaults] objectForKey:@"addressModel"];
        NSData *storedData = [NSKeyedUnarchiver unarchiveObjectWithData:bufferData];
    }
    
    image.png

    参考

    http://blog.sunnyxx.com/2014/04/02/objc_dig_arc_dealloc/
    https://www.jianshu.com/p/d361f169423b

    相关文章

      网友评论

        本文标题:iOSRuntime的成员与属性

        本文链接:https://www.haomeiwen.com/subject/qqmrfftx.html