property and ivar 详解

property and ivar 详解

简述

  • ivar 全名叫做 instance variable, 即实例变量
  • property 定义 getter、setter 方法 和ivar

例如:

@interface UserModel: NSObject

@property(copy, nonatomic) NSString* userName;

@end


@implementation UserModel

@end

等价于:


@interface UserModel: NSObject

@end

@interface UserModel() {
    NSString *_userName;
}


@end

@implementation UserModel

- (NSString *)userName {
    return _userName;
}

- (void)setuserName:(NSString *)userName {
    _userName = userName.copy;
}


@end


使用如下代码验证:

    unsigned int methodCount;
    Method *methodList = class_copyMethodList(UserModel.class, &methodCount);
    for (int i = 0; i < methodCount; i++) {
        NSLog(@"%s", sel_getName(method_getName(methodList[i])));
    }
    
    unsigned int ivarCount;
    Ivar *ivarList = class_copyIvarList(UserModel.class, &ivarCount);
    for (int i = 0; i < ivarCount; i++) {
        NSLog(@"%s", ivar_getName(ivarList[i]));
    }
2019-01-22 10:33:49.442539+0800 propertyDemo[6497:699127] .cxx_destruct
2019-01-22 10:33:49.442679+0800 propertyDemo[6497:699127] setuserName:
2019-01-22 10:33:49.443246+0800 propertyDemo[6497:699127] userName
2019-01-22 10:33:49.443470+0800 propertyDemo[6497:699127] _userName


但是如果自定义getter,setter方法又会怎样呢?还会不会生成 ivar

@interface UserModel: NSObject

@property(copy, nonatomic) NSString* userName;

@end

@interface UserModel() {
     NSString *__userName;
}


@end

@implementation UserModel

- (NSString *)userName {
    return __userName;
}

- (void)setuserName:(NSString *)userName {
    __userName = userName.copy;
}


@end

2019-01-22 10:35:29.125579+0800 propertyDemo[6540:703976] .cxx_destruct
2019-01-22 10:35:29.125725+0800 propertyDemo[6540:703976] setuserName:
2019-01-22 10:35:29.125805+0800 propertyDemo[6540:703976] userName
2019-01-22 10:35:29.125883+0800 propertyDemo[6540:703976] __userName

可以看到并没有生成_userName.如果这个时候访问_userName,则会引起编译错误:
Use of undeclared identifier '_userName'

access property

    objc_property_t objc_property_t_UserModel_userName = class_getProperty(UserModel.class, "userName");
    NSLog(@"%s", property_getAttributes(objc_property_t_UserModel_userName));


log

2019-01-22 10:53:20.907896+0800 propertyDemo[6718:737183] T@"NSString",C,N,V_userName

这个字符串看起来有点迷惑其意思是: copy, nonatomic, value = _userName

相关链接 Property Introspection

获取 ivar 和 getter setter 方法

@property(copy, nonatomic, getter=c_userName, setter=c_setteruserName:) NSString* userName;

################################################

    NSLog(@"%s", property_copyAttributeValue(objc_property_t_UserModel_userName, "V"));
    
    NSLog(@"%s", property_copyAttributeValue(objc_property_t_UserModel_userName, "G"));
    
    NSLog(@"%s", property_copyAttributeValue(objc_property_t_UserModel_userName, "S"));
2019-01-22 11:24:55.508512+0800 propertyDemo[7112:801195] _userName
2019-01-22 11:24:55.508596+0800 propertyDemo[7112:801195] c_userName
2019-01-22 11:24:55.508704+0800 propertyDemo[7112:801195] c_setteruserName:

可以通过property_copyAttributeValue 获取property 属性, 这里我自定义了getter setter, 如果没有自定义则打印出来的就是null

dynamic add property

动态添加property存在一下三种方案:

  1. 动态创建类, 添加property, ivar

void setUserName(id self, SEL _cmd, NSString *name) {
    
    Ivar ivar = class_getInstanceVariable([self class], "_userName");
    
    object_setIvar(self, ivar, name);
    
}

NSString* userName(id self, SEL _cmd) {
    
    // 获取类中指定名称实例成员变量的信息
    Ivar ivar = class_getInstanceVariable([self class], "_userName");
    
    return object_getIvar(self, ivar);
    
}


void registerUserModelClass() {
    
    
    Class MyClass = objc_allocateClassPair([NSObject class], kUserModelClassName , 0);
    
    class_addIvar(MyClass, "_userName" , sizeof(NSString *), log2(sizeof(NSString *)), "@") ;
   
    objc_property_attribute_t type = { "T", "@\"NSString\"" };
    objc_property_attribute_t ownership = { "C", "" }; // C = copy
    objc_property_attribute_t backingivar  = { "V", "_userName" };
    objc_property_attribute_t attrs[] = { type, ownership, backingivar };
    
    
    class_addMethod(MyClass, @selector(setUserName:), (IMP)setUserName, "V@:@");
    class_addMethod(MyClass, @selector(userName), (IMP)userName, "@@:");

    //注册这个类到runtime系统中就可以使用他了
    objc_registerClassPair(MyClass);
    
    if (!class_addProperty(MyClass, "ad_id", attrs, 3)) {
        
        NSLog(@"add property failure");
        
    }
    
}

验证代码:


    registerUserModelClass();
    [self logClass:objc_getClass(kUserModelClassName)];
    
    id userModel = [[objc_getClass(kUserModelClassName) class] new];
    NSLog(@"%@", userModel);
    [userModel performSelector:@selector(setUserName:) withObject:@"*******"];

    NSLog([userModel performSelector:@selector(userName)]);

  1. override load method and add property
@implementation XFADModel {
    
    NSString *_ad_id;
}

+ (void)load {
    
    objc_property_attribute_t type = { "T", "@\"NSString\"" };
    objc_property_attribute_t ownership = { "C", "" }; // C = copy
    objc_property_attribute_t backingivar  = { "V", "_ad_id" };
    objc_property_attribute_t attrs[] = { type, ownership, backingivar };
    
    // 以下代码会failiure  因为类一旦完成注册就无法添加 ivar
    /*
    BOOL isSucess = class_addIvar(self, "ad_id", sizeof(NSString *), 64, "@");
    if (!isSucess) {
        NSLog(@"add ivar failure");
    }
     */
    
    if (!class_addProperty(self, "ad_id", attrs, 3)) {
        
        NSLog(@"add property failure");

    }
    
    
}

- (void)setAd_id:(NSString *)ad_id {
    
    Ivar  ivar_ad_id = class_getInstanceVariable([self class], "_ad_id");
    object_setIvar(self, ivar_ad_id, [ad_id copy]);
    
}

- (NSString *)ad_id {
    
    // ivar
    Ivar  ivar_ad_id = class_getInstanceVariable([self class], "_ad_id");
    return  object_getIvar(self, ivar_ad_id);
    
    // kvc
    return [self valueForKey:@"ad_id"];
    
}

  • 类在完成注册之后无法添加ivar ,所以必须在编译时声明属性_ad_id, 所以这里没有动态添加ivar,不过你可以在注册类之前做add ivar这个操作
  • 这里getter方法和setter方法,没有动态添加,这是因为该问题都可以归为如何动态为类添加实例方法问题 ,这里不做阐述

方案三: 声明一个NSMutableDictionary用于存储数据

关键代码:


@interface UserModel() {
    NSMutableDictionary *_dictCustomerProperty;
}


    //添加get和set方法
    class_addMethod([target class], NSSelectorFromString(propertyName), (IMP)getter, "@@:");
     class_addMethod([target class], NSSelectorFromString([NSString stringWithFormat:@"set%@:",[propertyName capitalizedString]]), (IMP)setter, "v@:@");
        
    //赋值
    [target setValue:value forKey:propertyName];
    

 // 需要从 SEL 中解析出key
id getter(id self1, SEL _cmd1) {
    NSString *key = NSStringFromSelector(_cmd1);
    Ivar ivar = class_getInstanceVariable([self1 class], "_dictCustomerProperty");  //basicsViewController里面有个_dictCustomerProperty属性
    NSMutableDictionary *dictCustomerProperty = object_getIvar(self1, ivar);
    return [dictCustomerProperty objectForKey:key];
}
 
void setter(id self1, SEL _cmd1, id newValue) {
    //移除set
    NSString *key = [NSStringFromSelector(_cmd1) stringByReplacingCharactersInRange:NSMakeRange(0, 3) withString:@""];
    //首字母小写
    NSString *head = [key substringWithRange:NSMakeRange(0, 1)];
    head = [head lowercaseString];
    key = [key stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:head];
    //移除后缀 ":"
    key = [key stringByReplacingCharactersInRange:NSMakeRange(key.length - 1, 1) withString:@""];
    
    Ivar ivar = class_getInstanceVariable([self1 class], "_dictCustomerProperty");  //basicsViewController里面有个_dictCustomerProperty属性
    NSMutableDictionary *dictCustomerProperty = object_getIvar(self1, ivar);
    if (!dictCustomerProperty) {
        dictCustomerProperty = [NSMutableDictionary dictionary];
        object_setIvar(self1, ivar, dictCustomerProperty);
    }
    [dictCustomerProperty setObject:newValue forKey:key];
}
 
+ (id)getPropertyValueWithTarget:(id)target withPropertyName:(NSString *)propertyName {
    //先判断有没有这个属性,没有就添加,有就直接赋值
    Ivar ivar = class_getInstanceVariable([target class], [[NSString stringWithFormat:@"_%@", propertyName] UTF8String]);
    if (ivar) {
        return object_getIvar(target, ivar);
    }
    
    ivar = class_getInstanceVariable([target class], "_dictCustomerProperty");  //basicsViewController里面有个_dictCustomerProperty属性
    NSMutableDictionary *dict = object_getIvar(target, ivar);
    if (dict && [dict objectForKey:propertyName]) {
        return [dict objectForKey:propertyName];
    } else {
        return nil;
    }


相关资料

KVC 和 ivar

有时我们会使用KVC设置和访问属性,那么KVC和 ivar, getter, setter方法之间有什么联系?
通过查阅- (void)setValue:(nullable id)value forKey:(NSString *)key; 文档发现其会做三件事情:

  1. 匹配 **-setKey **
  2. 根据accessInstanceVariablesDirectly 返回值决定是否直接访问ivar
  3. 执行 **setValue:forUndefinedKey: **,该方法默认会抛NSUndefinedKeyException

简单验证一下:

@interface UserModel: NSObject

// 自定义setter 方法名
@property(copy, nonatomic, getter=c_userName, setter=c_setteruserName:) NSString* userName;

@end

@implementation UserModel

// 不允许直接访问ivar
+ (BOOL)accessInstanceVariablesDirectly {
    return false;
}

@end


UserModel *model = [UserModel new];

// 该行代码会引起crash, 成功绕过 setter方法, 绕过直接获取ivar 方法
[model setValue:@"wolf" forKey:@"userName"];

ivar,method, property,protocol 对 instance Size的影响

ivar: 首先通过 class_getInstanceSize 获取class的 Instance Size,
加减ivar数量,观察InstanceSize 变化, 很容易发现ivar 最终会影响InstanceSize 大小

property: 前面说过 在类完成注册才能添加ivar, 在之后就无法添加ivar了,因此严格来说property 不会对InstanceSize 造成影响。

method: 对size 没有影响, 所有实例对象共享methodList

protocol: 同样对size没有影响, 所有实例对象共享protocolList

所有实例对象共享methodList 和 protocolList, 各自拥有独立ivar, 因此只有ivar会影响实例对象的大小。

你可能感兴趣的:(property and ivar 详解)