属性的功能:
属性关键字大体分为几大类:
表示原子性(默认属性),用于保证属性的setter和getter方法内部都是原子性操作,相当于在setter和getter方法内部加了线程同步的锁。
// setter
static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
if (!atomic) {
oldValue = *slot;
*slot = newValue;
} else {
spinlock_t& slotlock = PropertyLocks[slot];
slotlock.lock();
oldValue = *slot;
*slot = newValue;
slotlock.unlock();
}
objc_release(oldValue);
}
// getter
id objc_getProperty_non_gc(id self, SEL _cmd, ptrdiff_t offset, BOOL atomic) {
// Retain release world
id *slot = (id*) ((char*)self + offset);
if (!atomic) return *slot;
// Atomic retain release world
spinlock_t& slotlock = PropertyLocks[slot];
slotlock.lock();
id value = objc_retain(*slot);
slotlock.unlock();
return objc_autoreleaseReturnValue(value);
}
表示非原子性,属性的setter和getter方法是非线程安全的,开发中经常使用。
想要持有某个对象,就让它的引用计数+1,进行retain操作,不想再持有某个对象,就让它的引用计数-1,进行release操作。
诞生于MRC,表示持有该对象,会进行retain操作,会对该对象的引用计数加1。
- (void)setCar:(FSLCar *)car
{
if (_car != car) {
// 原先的car会先释放
[_car release];// 引用计数 -1
// 新的car会后持有
_car = [car retain];//引用计数 +1
}
}
- (FSLCar *)car
{
return _car;
}
诞生于ARC,是强引用(强指针),表示持有该对象,会进行retain操作,会对该对象的引用计数加1。
诞生于MRC,是不可变拷贝,表示产生一个引用计数为1的不可变副本对象。
- (void)setName:(NSString *)name
{
if (_name != name) {
// 原先的_name会先释放
[_name release];// 引用计数 -1
/*
新的name会后持有
注意:
浅拷贝时,不会产生副本对象,只是指针拷贝,[name copy]相当于[name retain]引用计数+1
深拷贝时,会产生引用计数为1的副本对象,存在另一块内存中,[name copy]相当于alloc,
*
/
_name = [name copy];
}
}
- (NSString *)name
{
return _name;
}
什么是浅拷贝、深拷贝
Block是对象,也有isa指针的,有三种类型:__NSGlobalBlock __、__NSStackBlock __、__NSMallocBlock __ 。
不同类型Block调用copy方法,也就是 [xxBlcok copy] 后的结果如下图:
block | 环境 | 原先内存区域 | 调用copy后的内存区域 |
---|---|---|---|
_NSGlobalBlock_ | 没有访问外部auto变量 | 在数据区 | 什么也不做,还在数据区 |
_NSStackBlock_ | 访问外部auto变量 | 在栈区 | 从栈拷贝到堆区 |
_NSMallocBlock_ | 通过copy修饰或调用copy方法 | 在堆区 | 还在堆区,引用计数增加 |
对上图进行解释:
Block块内没有访问外部局部变量时存放在全局区(ARC和MRC下均是),不常用。
Block块内访问外部局部变量,也是开发者常用的方式:MRC环境下:Block的内存地址显示在栈区,栈区的特点就是创建的对象随时可能被销毁,一旦被销毁后续再次调用空对象就可能会造成程序崩溃,在对Block进行copy后,Block存放在堆区,所以在使用Block属性时使用copy修饰。
ARC环境下:Block只要被赋值给强指针,编译器会自动将栈区Block拷贝到堆区的,不用开发者进行copy操作。实际开发中,用copy或strong修饰Block都是可以的,只是习惯了用copy。
能保证NSString字符串属性肯定是不可变的。iOS开发时对UIKit的使用是很频繁的,将后台数据显示到UI的那一刻,从安全性与维护性考虑,保证数据内容与UI显示的内容是不影响的,数据显示完的那一刻开始,数据内容发生什么改变都不可以影响到已经UI显示的内容,要改可以再重新赋值,再刷新UI。
举个UILabel例子:
// UIKit-UILbel 的属性
@property(nullable, nonatomic,copy) NSString *text;
// 假设这是其中的一条后台可变数据
NSMutableString *dataText = [[NSMutableString alloc] initWithFormat: @"我的内容是可以变化的哦"];
UILabel *lab = [[UILabel alloc] init];
// UILabel的属性text是copy修饰的,代表内容是不可修改的属性
lab.text = dataText;
/*
dataText是可变的,如果lab.text不用copy而用strong修饰,那就只是指针赋值而已,意味着lab.text 与 dataText 指向的是同一块内存,那dataText指向的内容改变了,lab.text的内容也会跟着改变。
上述情况,在开发中是不是不好维护?
当然不好维护啦,谁知道除了lab,如果其他的控件是否有用到了这个dataText,并且做了修改,那lab不就吃屁了,多不安全。
综上所述:直接用copy修饰,简单又安全还易于维护,不用操心外部数据怎么改变,数据拿到手,你就赶快走。
*/
[dataText appendFormat:@"我要修改内容啦"];
首先,关于属性的拷贝关键字只存在cooy,不存在mutableCopy。
其次,严格来讲mutableCopy操作是只给Foundation框架自带的一些类去做事情的,只有他们才有资格产生可变或不可变的权利,具备区分可变或不可变的能力,如:NSString、NSArray、NSDictionary等;其他类是没有的,不具备区分可变或不可变的能力,如:自定义类、UIKit相关类等
最后,属性必须保证是通用的,属性可以定义任意类型的对象或变量,什么东西都有可能,如:可以是自定义的Person或Car对象,可以是某些字符串、某些数值变量等等,所以为了保证通用性,属性不能用mutableCopy修饰。
全面了解:copy与mutableCopy的区别与用法
诞生于MRC,表示单纯地赋值(默认属性),没有引用计数的操作。
野指针:不是NULL指针,是指向"垃圾"内存(不可用内存)的指针,指向的地址是我们不可知的,是随机的,非常危险的玩意。
使用野指针可能会产生的三种后果:
诞生于MRC,表示不安全的,没有引用计数的操作。
诞生于ARC,是弱引用(弱指针),没有引用计数的操作。
弱指针是存放在对象的SideTable 的 weak_table_t weak_table散列表中。
引用计数可以直接存储在isa的指针中,当isa不够存储时,会存储在SideTable的RefcountMap refcnts散列表中。
对象销毁,底层dealloc究竟做了什么?
当对象销毁时,也就是调用dealloc时,首先,会从SideTable取出对象中的弱引用表(weak_table),把弱引用表中存储的弱引用(弱指针)清空掉,也就是将表中弱指针的都置为nil;其次,如果SideTable中引用计数表(refcnts)还有引用计数存在,也会将引用计数表(refcnts)的数据都擦除掉,也就是引用计数清零。
ARC都帮我们做了什么?
LLVM(编译器) + Runtime,ARC环境是LLVM(编译器)与Runtime运行时系统相互协作的一个环境。
LLVM(编译器)会自动生成release和retain,自动进行内存管理。
而像弱引用这样的存在,是需要Runtime来支持的,在运行时的过程中,当对象销毁时,会动态的对该对象的弱引用进行清空操作。
虽然上面展示的是 __strong、__weak、__unsafe_unretained修饰局部变量的结果,但是换成strong、weak、unsafe_unretained修饰属性也是一样的特点,主要是关注关键字本身的特点是什么。
表示只读,编译器只会自动生成getter方法,不会自动生成setter方法,即只能访问,不能赋值,
(但外部还是可以用KVC去赋值)。
表示可读可写(默认属性),编译器会自动生成setter和getter方法。