Runtime--Property

Property

Objective-C中,接口的声明和实现分开在两个文件中:.h文件问声明文件,.m文件为实现文件。

//.h文件中
@interface Person : NSObject
 //在此声明接口
@end

//.m文件中
#import "Person.h"

@implementation Person
 //在此实现接口
@end

对象一般通过properties来封装数据并提供公共访问,例如

@interface Person : NSObject

@property NSString *firstName;
@property NSString *lastName;

@end

Person有两个properties可以供外界访问。Objective-C中property可以声明attributes来修饰property,比如说读写权限、原子访问、强弱引用等。格式:@property+(attributes,attributes…)+property,其中@property为关键字固定不变,(attributes)括号固定不变,修饰词多个用逗号隔开,property为访问名称。例如

@interface Person : NSObject
@property (readonly) NSString *firstName;
@property (readonly,copy) NSString *lastName;
@end

@property本质

当你使用 @property关键字来声明一个property时,编译器会自动生成器对象的访问方法:set方法和get方法。规则如下:
1、get方法名称同property一样,例如firstName对应firstName
2、set方法名称:set前缀+property名称首字母大写,例如firstName对应setFirstName:
注意:如果property声明为readonly,编译器将不生成set方法

@property自定义访问方法

如果需要自定义访问访问,可以property声明的时候,在attributes中通过setter和getter关键字来指定方法名,此时编译器值自动生成指定的方法名。例如

@property (nonatomic,assign,setter=isSetHave:,getter=isHave)BOOL have;

@property与点语法

@property声明的property编译器自动生成对应的访问方法,通过访问方法可以访问;同时通过点语法(俗称打点调用)也可以访问。例如

//People生成对象somePerson
//1、get
[somePerson firstName];
somePerson.firstName;

//2、set
[somePerson setFirstName:@"Johnny"];
somePerson.firstName = @"Johnny";

@property与Instance Variables

Instance Variables(以下称实例变量)指在一个对象生存时存在并且保存值的变量,其内存申请和释放和对象的创建 (alloc)和销毁(dealloc)同步进行。

一般情况下,可以读写的property的值被实例变量保存,该实例变量有编译器自动生成,若无特殊声明,其名称:下划线“_” + property,例如firstName对应_firstName 。

一般来说建议通过访问方法和点语法访问properties。然而在.m文件中,还可以通过property来访问,而且符合可以清晰的区别局部变量和实例变量。例如

- (void)someMethod 
  {
   //局部变量
    NSString *firstName = @"An interesting string";
    //实例变量
    _firstName = firstName;
  }

而且在initialization(初始化)、 deallocation(销毁)、 custom accessor methods(访问方法)方法中建议通过实例变量直接访问property。例如

- (instancetype)init
{
    self = [super init];
    if (self) {
        _firstName = @"zwq";
    }
    return self;
}

-(void)setFirstName:(NSString *)firstName
{
    _firstName = firstName;
}
自定义实例变量名称

如果想自定义实例变量名称,可以通过以下语法来实现

//原来_propertyName

@implementation YourClass
@synthesize propertyName = instanceVariableName;
...
@end

例如

@synthesize firstName = ivar_firstName;

其中property的名称还是firstName,set和get方法、点语法均可以正常访问,但是实例变量名称已经变成ivar_firstName,以后通过此名称直接访问;例如

@synthesize firstName = _myFirstName;


-(void)setFirstName:(NSString *)firstName
{
    _firstName = firstName;
}

改成

-(void)setFirstName:(NSString *)firstName
{
    _myFirstName = firstName;
}

@synthesize firstName;如果这么写,默认实例变量名修改为firstName不再有下划线。

其实还可以通过以下代码获取指定类的实例变量,对比前后打印结果即可验证上述结论

#import <objc/runtime.h>

/* 获取变量列表 */
unsigned int count = 0;
Ivar *list =  class_copyIvarList([Data class], &count);
for (int i = 0; i < count ; i ++)
{
   Ivar var = list[i];
   const char *name_var = ivar_getName(var);
   NSLog(@"Ivar:%@",[NSString stringWithUTF8String:name_var]);
}
free(list);
注意

以下情况编译器不会自动生成实例变量:
1、对于可读写的property,当你自己同时实现set和get方法时
2、对于只读的property,当你实现get方法时
以上两种情况,编写代码时编译器会提示你未定义变量,此时需要自己定义一个实例变量@synthesize property = _property;,以firstName为例

/****.h文件****/
@interface People : NSObject

@property (nonatomic,copy)NSString  *firstName;

@end

/****.m文件****/
@implementation People
@synthesize firstName = _firstName;//自定义实例变量名称

-(void)setFirstName:(NSString *)firstName
{
    _firstName = firstName;
}

-(NSString*)firstName
{
    return _firstName;
}
@end

@property与原子性

Objective-C 的property默认原子访问。
这就意味着访问方法在访问值期间,独立持有该property的value,即使多线程访问也是如此。
由于原子方法的同步机制的实现是私有的,所以同步自定义访问方法和编译器自动实现的方法行不通的。例如对于一个readwrite的property,自己实现set方法,编译器实现的get方法,此二者的组合实现不能实现原子性访问,而且编译器给出警告提示:

@property (copy)NSString  *firstName;

-(NSString*)firstName
{
    return _firstName;
}

//⚠️提示语,并给出修改建议
Writable atomic property 'firstName' cannot pair a synthesized setter with a user defined getter

Setter and getter must both be synthesized, or both be user defined,or the property must be nonatomic

Property declared here

原子的关键字atomic(默认实现,无需指定)和非原子性nonatomic。如果五原子性要求,使用nonatomic可以提示性能、访问速度。

@property (nonatomic)NSString  *firstName;
@property (atomic)NSString  *firstName;
原子性并不意味着线程安全

例如姓名=姓氏+名字;批量修改一系列名字
线程A:1、修改姓氏 2、修改名字
线程B:3、获取姓名

@property (copy)NSString  *firstName;//姓氏
@property (copy)NSString  *secondName;//名字
@property (copy)NSString  *fullName;//姓名

虽然姓氏和名字都是原子访问,当3在1和2直接进行的话,是不能保证获取到是同一个人的姓名。

@property与其它对象

注意循环引用

如果property是其它对象的,注意避开循环引用。经典的例子请参考UITableView的delegates的设置。

@property与copy

经典的例子是声明NSSting类型时,建议使用copy。通过下变例子代码说明

@interface People : NSObject

@property (nonatomic,strong)NSString  *firstName;

@end

NSMutableString *firstName = [[NSMutableString alloc] initWithString:@"张"];
People *data = [[People alloc] init];
data.firstName = firstName;
[firstName appendString:@"三"];
NSLog(@"%@--firstName:%p--tempStr:%p",data.firstName,data.firstName,tempStr);

//输出结果
 张三--firstName:0x7fd81a534250--tempStr:0x7fd81a534250
@interface People : NSObject

@property (nonatomic,copy)NSString  *firstName;

@end

NSMutableString *firstName = [[NSMutableString alloc] initWithString:@"张"];
People *data = [[People alloc] init];
data.firstName = firstName;
[firstName appendString:@"三"];
NSLog(@"%@--firstName:%p--tempStr:%p",data.firstName,data.firstName,tempStr);

//输出结果
 张--firstName:0x7fd4a940f200--tempStr:0x7fd4a945fab0

对比以上两段代码的输出结果不难发现strong和copy的区别:前者直接引用一份,后者拷贝一份。根据需要选择使用。

Runtime-Property

Property在Runtime中被如此定义
objc_property_t

/// An opaque type that represents an Objective-C declared property.
typedef struct objc_property *objc_property_t;

可以获取的属性有

//获取name
const char *property_getName(objc_property_t property)

//获取所有属性,以C字符串返回
const char *property_getAttributes(objc_property_t property) 

//获取所有属性,以数组返回; 数组需要free
objc_property_attribute_t *property_copyAttributeList(objc_property_t property, unsigned int *outCount)

//获取指定属性值;
char * property_copyAttributeValue(objc_property_t property, const char *attributeName);

和其相关的objc_property_attribute_t

/// Defines a property attribute
typedef struct {
    const char *name;           /**< The name of the attribute */
    const char *value;          /**< The value of the attribute (usually empty) */
} objc_property_attribute_t;

对于一个类来说,runtime对于property的操作分3种:增、改、查

//增
class_addProperty
//改
class_replaceProperty
//查
class_getProperty
class_copyPropertyList

先从查说起。首先是定义一个简单的类

@interface Data : NSObject

@property (nonatomic,copy)NSString  *firstName;

@end

获取指定的Property
//获取Data的firstName的property
objc_property_t property_firstName = class_getProperty([Data class], "firstName");
NSLog(@">>%@",[NSValue value:&property_firstName withObjCType:@encode(objc_property_t)]);
获取Property列表
//Property列表
unsigned int property_count = 0;
objc_property_t *property_list =  class_copyPropertyList([Data class], &property_count);

for (int i = 0; i < property_count ; i ++)
{
   objc_property_t property = property_list[i];
   const char *name_property = property_getName(property);//名称
   const char *name_attributes = property_getAttributes(property);//属性字符串
   NSLog(@"property:%@  attributes:%@",[NSString stringWithUTF8String:name_property],[NSString stringWithUTF8String:name_attributes]);

}

free(property_list);//必须free

//输出
property:firstName  attributes:T@"NSString",C,N,V_firstName
获取Name
const char *name_property = property_getName(property_firstName);
NSLog(@"property:%@",[NSString stringWithUTF8String:name_property]);
//输出
property:firstName
获取所有属性,以C字符串返回
const char *name_attributes = property_getAttributes(property_firstName);
NSLog(@"attributes:%@",[NSString stringWithUTF8String:name_attributes]);
//输出
attributes:T@"NSString",C,N,V_firstName
关于Attributes

例如T@”NSString”,C,N,V_firstName的解释说明,通过property_getAttributes函数获取Property所有属性,以C字符串返回,格式如下:
T + @encode type + , +…(其它属性逗号隔开)+,V+backing instance variable(Property对应的属性变量)
其中@encode type如图所示
这里写图片描述
其中Attributes类型如下
这里写图片描述

其中backing instance variable说明见第一部分。

获取所有属性,以数组返回
//属性列表
unsigned int attribute_count = 0;
objc_property_attribute_t *attribute_list = property_copyAttributeList(property_firstName, &attribute_count);
for (int x = 0; x < attribute_count; x ++)
{
   objc_property_attribute_t attribute = attribute_list[x];
   const char *name_attributes = property_copyAttributeValue(property_firstName, attribute.name);//同结构体直接取值
   NSLog(@"attributes:[%@:%@]",[NSString stringWithUTF8String:attribute.name],[NSString stringWithUTF8String:attribute.value]);

}    
free(attribute_list);//必须free

//输出
attributes:[T:@"NSString"]
attributes:[C:]
attributes:[N:]
attributes:[V:_firstName]
获取指定属性值
//获取指定属性值
const char *attribute_T = property_copyAttributeValue(property_firstName, "T");
NSLog(@"attribute_T:%@",[NSString stringWithUTF8String:attribute_T]);
const char *attribute_V = property_copyAttributeValue(property_firstName, "V");
NSLog(@"attribute_V:%@",[NSString stringWithUTF8String:attribute_V]);

//输出
attribute_T:@"NSString"
attribute_V:_firstName

/*
 * @param cls 修改的类
 * @param name 添加的名称
 * @param attributes An array of property attributes.
 * @param attributeCount Attributes数量(写少了从前向后取)
 * @return 成功YES,否则NO(例如已存在)
 * /
BOOL class_addProperty(Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount)

示例代码如下

/* property  添加:添加失败返回NO,例如属性已存在 */
objc_property_attribute_t attr_T = {"T","@\"NSString\""};//@encod
objc_property_attribute_t attr_N = {"N",""};//原子性
objc_property_attribute_t attr_C = {"C",""};//copy
objc_property_attribute_t attr_V = {"V","_secondName"};//实例变量

objc_property_attribute_t attrs[] = {attr_T,attr_N,attr_C,attr_V};
BOOL isAdd = class_addProperty([Data class], "secondName", attrs, 4);
NSLog(@"添加结果:%d",isAdd);

/* 验证是否修改成功 */
unsigned int count = 0;
objc_property_t *list =  class_copyPropertyList([Data class], &count);
for (int i = 0; i < count ; i ++)
{
   objc_property_t property = list[i];
   const char *name_property = property_getName(property);//名称
   const char *name_attributes = property_getAttributes(property);//属性字符串
   NSLog(@"property:%@  attributes:%@",[NSString stringWithUTF8String:name_property],[NSString stringWithUTF8String:name_attributes]);

}
free(list);

//输出
添加结果:1
property:secondName  attributes:T@"NSString",N,C,V_secondName
property:firstName  attributes:T@"NSString",C,N,V_firstName

/** 
 * Replace a property of a class. 
 * 
 * @param cls 修改的类.
 * @param name 修改的property名称.
 * @param attributes An array of property attributes.
 * @param attributeCount Attributes数量(写少了从前向后取)
 * @注意 如果替换property不存在则执行add操作
 */
void class_replaceProperty(Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount)

示例代码

/* property  添加:添加失败返回NO,例如属性已存在 */
objc_property_attribute_t attr_T = {"T","@\"NSString\""};//@encod
objc_property_attribute_t attr_N = {"N",""};//原子性
objc_property_attribute_t attr_C = {"C",""};//copy
objc_property_attribute_t attr_V = {"V","_secondName"};//实例变量

objc_property_attribute_t attrs[] = {attr_T,attr_N,attr_C,attr_V};
BOOL isAdd = class_addProperty([Data class], "secondName", attrs, 4);
NSLog(@"添加结果:%d",isAdd);

/* 替换*/
//若不存在,则执行add操作生成新的Property
class_replaceProperty([Data class], "number_replace", attrs, 4);

//替换:去除了原子性和copy
objc_property_attribute_t attrs_replace[] = {attr_T,attr_V};
class_replaceProperty([Data class], "secondName", attrs_replace, 2);

/* 验证是否修改成功 */
unsigned int count = 0;
objc_property_t *list =  class_copyPropertyList([Data class], &count);
for (int i = 0; i < count ; i ++)
{
   objc_property_t property = list[i];
   const char *name_property = property_getName(property);//名称
   const char *name_attributes = property_getAttributes(property);//属性字符串
   NSLog(@"property:%@  attributes:%@",[NSString stringWithUTF8String:name_property],[NSString stringWithUTF8String:name_attributes]);

}
free(list);

//输出
添加结果:1
property:number_replace attributes:T@"NSString",N,C,V_secondName
property:secondName  attributes:T@"NSString",V_secondName
property:firstName  attributes:T@"NSString",C,N,V_firstName

参考链接:Defining ClassesEncapsulating DataDeclared PropertiesType EncodingsObjective-C Runtime

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值