一、属性简介
属性是OC语言中的一个机制,在OC中用@property来声明一个属性,编译器会自动为实例变量生成setter和getter方法。
所以在获取属性值NSLog(@"%@",self.name);和设置属性值(self.name = @"xiaoming")的时候其实是调用了getter和setter方法获取和设置实例值。
一般编译器生成的实例变量是“_属性名”。也可以使用@synthesize定义实例变量名。也可以自定义setter和getter方法。
#import "ViewController.h"
@interface ViewController ()
@property (nonatomic,copy)NSString *name;
@end
@implementation ViewController
//定义实例变量名
@synthesize name = _nibName;
- (void)viewDidLoad {
[super viewDidLoad];
self.name = @"xiaoming";
NSLog(@"%@",self.name);//输出xiaohong
NSLog(@"%@",_nibName);//输出xiaoli,不执行getter方法取值
}
-(void)setName:(NSString *)name{
_nibName = @"xiaoli";
}
-(NSString *)name{
return @"xiaohong";
}
@end
二、属性存储
1.Runtime属性的存储
在OC中类是objc_class的结构体指针,结构体如下:
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE;
const char * _Nonnull name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
- objc_ivar_list是类的成员变量列表
struct objc_ivar_list {
int ivar_count OBJC2_UNAVAILABLE;
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
/* variable length structure */
struct objc_ivar ivar_list[1] OBJC2_UNAVAILABLE;
}
- objc_method_list是类的方法列表
struct objc_method_list {
struct objc_method_list * _Nullable obsolete OBJC2_UNAVAILABLE;
int method_count OBJC2_UNAVAILABLE;
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
/* variable length structure */
struct objc_method method_list[1] OBJC2_UNAVAILABLE;
}
属性生成过程分为以下几步:
(1)创建属性,设置其objc_ivar,通过偏移量和内存占用就可以方便获取。
(2)生成getter和setter方法。
(3)将属性的ivar添加到类的ivar_list中,作为类的成员变量存在。
(4)将getter和setter方法加入类的method_list中。
(5)将属性描述添加到类的属性描述列表中。
2.获取成员变量和属性
(1)获取成员变量列表,返回一个指向成员变量信息的数组,数组中每个元素是指向成员变量信息的objc_ivar结构体指针。这个数组不包括在父类中声明的变量。
Ivar * class_copyIvarList ( Class cls, unsigned int *outCount );
(2)获取属性列表
objc_property_t * class_copyPropertyList ( Class cls, unsigned int *outCount );
示例:
#import <Foundation/Foundation.h>
@interface Person : NSObject{
NSString *number;
}
@property (nonatomic,strong)NSString *name;
@end
#import "ViewController.h"
#import "Person.h"
#import <objc/message.h>
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
unsigned int count = 0;
Ivar *ivarList = class_copyIvarList([Person class], &count);
for (int i = 0; i<count; i++) {
Ivar ivar = ivarList[i];
const char *ivarChar = ivar_getName(ivar);
NSString *ivarName = [NSString stringWithUTF8String:ivarChar];
NSLog(@"成员变量名 %d == %@",i,ivarName);
}
objc_property_t *propertyList = class_copyPropertyList([Person class], &count);
for (int i = 0; i<count; i++){
objc_property_t property = propertyList[i];
const char* propertyChar =property_getName(property);
NSString *propertyName = [NSString stringWithUTF8String:propertyChar];
NSLog(@"属性名 %d == %@",i,propertyName);
}
}
@end
3.属性修饰符
修饰符 | 作用 |
---|---|
readwrite | 可读可写,生成setter和getter方法,默认属性 |
readonly | 只读,只生成getter |
nonatomic | 非原子属性,提高性能但线程不安全 |
atomic | 原子性,线程安全但可能降低性能 |
assign | 简单赋值,不更改引用计数,用于修饰基础数据类型和C语言类型数据 |
strong | 强引用,持有对象,引用计数+1 (retain新值,release旧值) |
weak | 弱引用,不持有对象,不增加引用计数(在属性所指的对象遭到推毁时,属性值也会清空) |
copy | 深拷贝 |
(1)atomic和nonatomic
atomic属性是为了保证程序在多线程情况下,编译器会自动生成一些加锁代码,避免该变量的读写不同步问题。
atomic属性内部的锁是自旋锁。自旋锁是如果有其他线程加锁了,线程会以死循环的方式等待锁,一旦被访问资源被解锁,则等待资源的线程会立即执行。
- 大部分情况会使用nonatomic,提高效率,减少资源的消耗。
(2)assign和weak
assign和weak类似,在使用时,不会增加被引用对象的引用计数。weak在引用的对象被销毁后会被指向nil。而assign不会被置为nil。
assign和unsafe_unretained其实差不多,如果用assign修饰对象,当assign指向对象被释放时,assign就成了一个悬空指针,也就是会指向一块无用内存,这是给这个assign修饰的属性发送消息时会发生崩溃(也有可能不崩溃,取决于发消息时那块内存还是否有效)。
所以一般不使用assign修饰对象,因为用assign修饰指针会成为悬空指针导致错误。
为什么用assign修饰基本数据类型呢,是因为基本数据类型是被分配到栈上的,栈的内存由系统自动处理,不会造成悬空指针。
- assign用于修饰基础类型的数据(NSInteger)和C语言类型数据(int,float,double,char,bool) ,而weak只能用于修饰对象。
(3)strong和copy
使用strong和copy时都会使引用计数+1。但使用copy修饰的属性在某些情况下赋值的时候会创建对象的副本,也就是深拷贝。
- 声明NSString属性时,使用copy:
当使用NSString的时候是不希望值改变的,一般情况下使用copy,希望进行深拷贝,那么源字符串修改,就不会影响到NSString属性的值。
但当源字符串为不可变类型是,copy也只是进行浅拷贝,当源属性为可变类型时,才进行深拷贝。
所以建议在使用NSString属性时使用copy,避免可变字符串的修改导致一些非预期的问题。 - 声明NSMutableString属性时,不能使用copy:
使用copy就是深拷贝一个不可变的NSString对象,如果对这个对象进行可变操作,会产生崩溃。 - 自定义类使用copy修饰符
自定义类具有拷贝功能,需要实现NSCopying协议。如果自定义的对象分为可以变和不可变,需要同时实现NSCopying和NSMutableCopying协议。
#import <Foundation/Foundation.h>
@interface Person : NSObject
@property (nonatomic,copy)NSString *name;
@end
#import "Person.h"
@interface Person()<NSCopying>
@end
@implementation Person
- (id)copyWithZone:(nullable NSZone *)zone{
Person *model = [[Person class] allocWithZone:zone];
model.name = [_name copy];
return model;
}
@end
#import "ViewController.h"
#import "Person.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
Person *person1 = [[Person alloc] init];
person1.name = @"郑";
Person *person2 = [person1 copy];
person2.name = @"吴";
NSLog(@"person1 == %@",person1.name);//郑,深拷贝,person1不会被改变
}
@end
三、成员变量、实例变量和属性区别
1.成员变量
- 定义在{}中的变量
- 成员变量用于类内部,无需与外界接触的变量
@interface ViewController ()
{
//成员变量
Person *person;
int a;
}
@end
2.实例变量
- 如果成员变量的数据类型是一个类,则成这个变是实例变量
- 实例变量+基本数据类型变量=成员变量
@interface ViewController ()
{
//成员变量--实例变量
Person *person;
//成员变量--基本数据类型变量
int a;
}
@end
3.属性
- @ property前缀的
- 编译器会自动生成setter和getter方法和带下划线的实例变量
- 可以通过点语法访问属性,编译器会把点语法转换为对setter和getter方法的调用
- 属性变量可用于与其他对象交互的变量
- 属性变量可以用修饰符(strong、assign、weak等)修饰
@interface ViewController ()
//属性
@property(nonatomic,strong)Person *person;
@end
网友评论