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 Classes、Encapsulating Data、Declared Properties、Type Encodings、Objective-C Runtime
网友评论