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存在一下三种方案:
- 动态创建类, 添加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)]);
- 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;
文档发现其会做三件事情:
- 匹配 **-setKey **
- 根据accessInstanceVariablesDirectly 返回值决定是否直接访问ivar
- 执行 **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会影响实例对象的大小。