iOS中的@property关键字

引言

property 是 OC 的一项特性,用于封装对象中的数据。

OC 通常把所需要的数据保存为各种实例变量,实例变量通过存取方法来访问,即 setter 和 getter

@property 的优势

尽量多的使用属性(property)而不是实例变量(attribute)因为属性(property)相比实例变量有很多的好处:

  • 自动合成 getter 和 setter 方法。当声明一个属性(property)的时候编译器默认情况下会自动生成相关的 getter 和 setter 方法

  • 更好的声明一组方法。因为访问方法的命名约定,可以很清晰的看出 getter 和 setter 的用处。

  • 属性(property)关键词能够传递出相关行为的额外信息。属性提供了一些可能会使用的特性来进行声明,包括 assign(vs copy),weak,strong,atomic(vs nonatomic),readwrite,readonly 等

属性生成

@property 的本质是 ivar(实例变量) + getter + setter。

默认情况下 @property 是用 @synthesize (合成)修饰的(默认实现: @synthesize property = _property),自动生成 ivar + setter + getter。

ivar 是实例变量 编译器自动生成 _属性名 的实例变量

例如:

objc 复制代码
@property (nonatomic, readwrite, copy) NSString *name;

@synthesize name = _name;

@synthesize 会自动生成 setter 和 getter 方法,但是也可以省略 @synthesize 关键字,让编译器自动合成 getter 和 setter 方法。

单独重写 getter 和 setter 方法没有问题,但是如果同时重写 getter 和 setter 方法,系统部会自动生成 _property 变量,编译器会报错,所以如果需要同时重写 getter 和 setter 方法,要添加 @synthesize

objc 复制代码
// 重写 getter 和 setter 方法

- (NSString *)name {

//必须使用_name来访问属性值,使用self.name来访问值时编译器会自动转为调用该函数,会造成无限递归

return _name;

}

- (void)setName:(NSString *)name {
    //必须使用_name来赋值,使用self.name来设置值时编译器会自动转为调用该函数,会导致无限递归
    //使用_name则是直接访问底层的存储属性,不会调用该方法来赋值
    //这里使用copy是为了防止NSMutableString多态
    if (_name != name) {

    _name = [name copy];

    }
}

property 常用指示符

存取器方法 getter setter

指定获取属性对象的名字为 getterName,如果你没有使用 getter 指定 getterName ,系统默认直接使用 propertyName 访问即可。通常来说,只有所指属性需要我们指定 isPropertyName 对应的 Bool 值时,才使用指定 getterName ,一般直接用 PropertyName 即可。

setter=setterName: 则是用来指定设置属性所使用的的 setter 方法,即设置属性值时使用 setterName: 方法,此处 setterName 是一个方法名,因此要以":"结尾,具体示例如下:

objc 复制代码
// 指定getter方法名为isBlue

@property (nonatomic, readonly, getter=isBlue) BOOL blue;

// 指定setter方法名为 theNickname

@property (nonatomic, copy, setter=theNickname:) NSString *nickname;

原子性 nonatomic atomic

atomic(默认属性):原子性访问,编译器会通过默认机制(锁定机制)确保 getter 和 setter 完整性(系统给自动生成的 getter 和 setter 方法进行加锁) 。但是 atomic 并不是绝对线程安全,A 先成进行写操作后(写完成),B 线程又进行写,A 线程再读取就不一定是之前写入得值(可能是 B 的)。如果是 MRC,C 线程进行了 release 操作,会 crash,破坏了线程安全,所以要添加锁等操作来保证线程安全。所以 atomic 只是了保证了存取方法的线程安全,并不能保证整个对象是线程安全的(如 NAArray 的 objectAtIndex:就不是线程安全的,需要加锁等保证线程安全)。

nonatomic:非原子性访问,不保证 setter 和 getter 的完整性,本质来说就是去掉了 atomic 对 getter 和 setter 方法添加的锁(系统不会给自动生成的 getter 和 setter 方法进行加锁),也就是 getter 和 setter 方法不是线程安全的,比如,当 A 线程进行写操作,B 线程突然把未修改好的属性值提取出来,时候线程读到的值不一定是对,iOS 中同步锁开销过大会带来性能问题,一般情况下不要求属性是 atomic,因为其本身不能保证线程安全。

objc 复制代码
@property (nonatomic, readwrite, copy) NSString *name;

@property (atomic, readwrite, copy) NSString *atomicName;

读写权限 readwrite readonly

readwrite(编译器默认属性):可读可选权限,若该属性由@synthesize 修饰,自动生成对应的 getter 和 setter 方法。

readonly:只读权限,若该属性由@synthesize 修饰,只生成 getter 方法,不生成 setter。

objc 复制代码
@property (nonatomic, readwrite, copy) NSString *name;

@property (nonatomic, readonly, getter=isBlue) BOOL blue;

内存管理:assign strong weak copy retain unsafe_unretained

assign 是指针赋值,没有引用计数操作,对象销毁之后不会自动置为 nil

assign 对属性只是简单的赋值操作,不更改赋值新值的引用计数,即不进行 retain 操作,也不改变旧值的引用计数,常用于标量类型,NSInteget、NSUInteget、CGFloat、NSTimeInterval、Bool 等。

assign 也可以修饰对象如 NSString 等类型对象,因为不改变引用计数,所以当新值的引用计数为 0 对象被销毁时,当前属性并不知道,编译器不会将该属性置为 nil,指针仍然指向之前被销毁的内存,这个时候访问该属性会产生野指针,并 crash,所以 assign 修饰的类型一定是标量类型。例子

objc 复制代码
@property (nonatomic, assign) NSUInteger age;

@property (nonatomic, assign) NSString *assignName;

copy copy 一个新对象,引用计数+1。

当调用修饰对象的 setter 方法时会建立一个新对象,引用计数+1,即对象会在内存里拷贝一个副本,两个指针指向不同的地址。

copy 一般用来修饰有可变类型子类的对象:NSArray,NSDictionary,NSString, 为确保这些不可变对象因为可变子类对象影响,需要 copy 一份备份,如果不使用 copy 修饰,使用 strong 或 assign 等修饰则会因为多态导致属性值被修改。例子

objc 复制代码
@property (nonatomic, copy) NSArray *array;

block 也用 copy 修饰。

objc 复制代码
@property (nonatomic, copy) ABlock *block;

对于可变类型如 NSMutableString、NSMutableArray、NSDictionary 则不能用 copy 修饰,因为这些类都实现了 NSCopying 协议,使用 copy 方法返回的都是不可变对象。

即如果使用 copy 修饰符在对可变对象赋值时会获取一个不可变对象,接下来如果对这个对象进行可变对象的操作会产生异常 Crash,因为没有 mutableCopy 修饰符,对于可变对象使用 strong 修饰符来修饰。例子

objc 复制代码
@property (nonatomic, strong) NSMutableArray *mutableArray;

strong 强引用,引用计数+1

strong 表示属性对所赋值的对象强引用,是一种拥有关系,增加新值的引用计数,释放旧值减少引用计数, 修饰对象的引用计数会+1,当对象的引用计数为 0 时,对象就会在内存中释放,通常修饰对象类型、可变集合、可变字符串

objc 复制代码
@property (nonatomic, strong) NSMutableString *strongName;

retain 强引用,引用计数+1

MRC 下的强引用修饰词,跟 strong 同理,ARC 中被 strong 代替

weak 弱引用,没有引用计数操作,对象销毁之后自动置为 nil

weak 表示属性对所赋值的对象弱引用,是一种非拥有关系,所赋的值在引用计数为 0 被销毁后,weak 修饰的属性会被自动置为 nil 能够有效防止野指针错误。

weak 一般用来修饰代理 delegate,避免循环引用。weak 与 assign 不同,只能修饰对象类型

objc 复制代码
@property (nonatomnic, assign) id <AProtocol> delegate;

unsafe_unretained 弱引用,没有引用计数操作,修饰的对象销毁之后指针不会自动置为 nil,如果此时在调用该指针会产生野指针:EXC_BAD_ACCESS错误

不安全非拥有,类似 assign 非常少用 但只能修饰对象类型,不能修饰标量类型

分类 Category 中添加属性

在分类中,添加@propety 属性只会生成 setter 和 getter 方法的声明,并不会有具体的实现,以为 Category 在运行时对象的内存布局就已经确定了,如果此时添加实例变量就会破坏对象的内存布局,所以 Category 无法添加实例变量。

所以如何在 Category 实现实例变量功能呢?可以通过 Runtime 添加关联对象实现成员变量:

objc 复制代码
@property (nonatomic, assign) NSTimeInterval yz_acceptEventTime; /// 发生时间

- (NSTimeInterval)yz_acceptEventTime {
    return [objc_getAssociatedObject(self, button_acceptEventTime) doubleValue];

}
- (void)setYz_acceptEventTime:(NSTimeInterval)yz_acceptEventTime {
    objc_setAssociatedObject(self, button_acceptEventTime, @(yz_acceptEventTime), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
相关推荐
晴空万里藏片云1 小时前
elment Table多级表头固定列后,合计行错位显示问题解决
前端·javascript·vue.js
曦月合一1 小时前
html中iframe标签 隐藏滚动条
前端·html·iframe
奶球不是球1 小时前
el-button按钮的loading状态设置
前端·javascript
kidding7231 小时前
前端VUE3的面试题
前端·typescript·compositionapi·fragment·teleport·suspense
Σίσυφος19003 小时前
halcon 条形码、二维码识别、opencv识别
前端·数据库
学代码的小前端3 小时前
0基础学前端-----CSS DAY13
前端·css
css趣多多5 小时前
案例自定义tabBar
前端
姑苏洛言6 小时前
DeepSeek写微信转盘小程序需求文档,这不比产品经理强?
前端
林的快手6 小时前
CSS列表属性
前端·javascript·css·ajax·firefox·html5·safari
匹马夕阳6 小时前
ECharts极简入门
前端·信息可视化·echarts