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);
}
相关推荐
GIS之路8 分钟前
GDAL 实现矢量裁剪
前端·python·信息可视化
是一个Bug12 分钟前
后端开发者视角的前端开发面试题清单(50道)
前端
Amumu1213814 分钟前
React面向组件编程
开发语言·前端·javascript
持续升级打怪中35 分钟前
Vue3 中虚拟滚动与分页加载的实现原理与实践
前端·性能优化
GIS之路39 分钟前
GDAL 实现矢量合并
前端
hxjhnct41 分钟前
React useContext的缺陷
前端·react.js·前端框架
前端 贾公子1 小时前
从入门到实践:前端 Monorepo 工程化实战(4)
前端
菩提小狗1 小时前
Sqlmap双击运行脚本,双击直接打开。
前端·笔记·安全·web安全
前端工作日常1 小时前
我学习到的AG-UI的概念
前端
韩师傅1 小时前
前端开发消亡史:AI也无法掩盖没有设计创造力的真相
前端·人工智能·后端