ObjC 基础-属性关键字(atomic、nonatomic、retain、strong、copy、assign、unsafe_unretained、weak)的区别与用法

文章目录

  • ObjC 基础 关键字-属性关键字
    • 原子操作类
      • atomic
      • nonatomic
    • 内存管理类
      • retain
      • strong
      • copy
        • 为什么经常用copy来修饰Block属性,而不用strong?
        • 为什么经常用copy来修饰NSString属性,而不用strong?
        • 为什么修饰属性没有mutableCopy关键字?
      • assign
        • 什么是野指针?
      • unsafe_unretained
      • weak
        • weak 弱指针是存放在什么位置?
        • weak 弱指针的底层实现原理是什么?怎么办到一旦指向的对象被销毁,弱引用(指针)会被置为nil?
        • strong & weak & unsafe_unretained 之间的区别?
    • 读写权限类
      • readOnly
      • readWrite

ObjC 基础 关键字-属性关键字

属性的功能:

  • 编译器会自动生成setter和getter方法的声明与实现。
  • 如果没有声明成员变量,自动声明一个_属性名的私有变量(默认的成员变量是受保护的)。

属性关键字大体分为几大类:

  • 原子操作类:atomic、nonatomic。默认是atomic,保证线程安全。
  • 内存管理类:retain、strong、copy、assign、unsafe_unretained、weak。默认是assign。
  • 读写权限类:readwrite、readonly。默认是readwrite,可读可写。
  • iOS9新增关键字:nonnull、nullable、null_resettable、null_unspecified

原子操作类

atomic

表示原子性(默认属性),用于保证属性的setter和getter方法内部都是原子性操作,相当于在setter和getter方法内部加了线程同步的锁。

  • atomic并不能保证使用属性的过程是线程安全的;
  • 属性的调用频率非常高,如果使用atomic,那太耗性能,而iOS设备的内存本来就小,在iOS开发中几乎不会使用,在mac开发中才可能会使用。
  • atom:原子,代表不可再分割的单位;
  • 底层源码实现 (objc-accessors.mm)
// 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);
}

nonatomic

表示非原子性,属性的setter和getter方法是非线程安全的,开发中经常使用。

内存管理类

想要持有某个对象,就让它的引用计数+1,进行retain操作,不想再持有某个对象,就让它的引用计数-1,进行release操作。

retain

诞生于MRC,表示持有该对象,会进行retain操作,会对该对象的引用计数加1

  • 只适用于指针类型,一般用于修饰对象类型(继承自NSObject的类)或id类型。
  • ARC环境下 ,strong取代了retain。
  • 底层实现伪代码:
- (void)setCar:(FSLCar *)car
{
    if (_car != car) {
        // 原先的car会先释放
        [_car release];// 引用计数 -1
        // 新的car会后持有
        _car = [car retain];//引用计数 +1
    }
}
- (FSLCar *)car
{
    return _car;
}

strong

诞生于ARC,是强引用(强指针),表示持有该对象,会进行retain操作,会对该对象的引用计数加1

  • 只适用于指针类型,经常用于修饰对象类型(继承自NSObject的类)或id类型。
  • strong类似于MRC环境下的retain。

copy

诞生于MRC,是不可变拷贝,表示产生一个引用计数为1的不可变副本对象

  • 只适用于指针类型,适用于那些遵循NSCopying协议的对象类型(继承自NSObject的子类)或id类型,经常用于修饰字符串类型或Block。
  • Foundation框架下具备区分可变/不可变的能力的对象且该对象是可变类型,不要用copy修饰,如:可变字符串、可变数组、可变字典等,不要用copy修饰。
  • 底层实现伪代码:
- (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;
}

什么是浅拷贝、深拷贝

为什么经常用copy来修饰Block属性,而不用strong?

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。

为什么经常用copy来修饰NSString属性,而不用strong?

能保证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:@"我要修改内容啦"];

为什么修饰属性没有mutableCopy关键字?

首先,关于属性的拷贝关键字只存在cooy,不存在mutableCopy。
其次,严格来讲mutableCopy操作是只给Foundation框架自带的一些类去做事情的,只有他们才有资格产生可变或不可变的权利,具备区分可变或不可变的能力,如:NSString、NSArray、NSDictionary等;其他类是没有的,不具备区分可变或不可变的能力,如:自定义类、UIKit相关类等
最后,属性必须保证是通用的,属性可以定义任意类型的对象或变量,什么东西都有可能,如:可以是自定义的Person或Car对象,可以是某些字符串、某些数值变量等等,所以为了保证通用性,属性不能用mutableCopy修饰。

全面了解:copy与mutableCopy的区别与用法

assign

诞生于MRC,表示单纯地赋值(默认属性),没有引用计数的操作

  • 适用于修饰非指针类型,一般是用于修饰基础类型(如:OC基础类型NSInterger、BOOL等;C数据类型int、float、double、char等)。
  • 不能修饰对象,如果用于修饰对象,是非强指针,是不安全的,因不会改变此对象的引用计数,就不会持有该对象,而且当该对象销毁时也不会自动将此属性值(指针)置为nil,会形成野指针,因坏内存访问会导致崩溃。

什么是野指针?

野指针:不是NULL指针,是指向"垃圾"内存(不可用内存)的指针,指向的地址是我们不可知的,是随机的,非常危险的玩意。

使用野指针可能会产生的三种后果:

  • 指向不可访问的地址
    • 危害:触发段错误。
  • 指向一个可用的,但是没有明确意义的空间
    • 危害:程序可以正确运行,但通常这种情况下,我们就会认为我们的程序是正确的没有问题的,然而事实上就是有问题存在,说不定什么时候这块空间就被使用。
  • 指向一个可用的,而且正在被使用的空间
    • 危害:如果我们对这样一个指针进行引用,对其所指向的空间内容进行了修改,但是实际上这块空间正在被使用,那么这个时候该指针指向的内容突然被改变,当然就会对程序的运行产生影响,因为正在被使用的空间内容已经被野指针修改掉。已经不是我们所想要内容了。通常这样的程序都会崩溃,或者数据被损坏。

unsafe_unretained

诞生于MRC,表示不安全的,没有引用计数的操作

  • 适用于修饰非指针类型,一般是用于修饰基础类型。
  • 不能修饰对象,如果用于修饰对象,是非强指针,是不安全的,因不会改变此对象的引用计数,就不会持有该对象,而且当该对象销毁时也不会自动将此属性值(指针)置为nil,会形成野指针,因坏内存访问会导致崩溃。
  • 和assign类似,都可以修饰基础类型,而修饰对象时又都是不安全的。
  • MRC环境下,用来解决循环引用问题。
  • ARC环境下,解决循环引用使用weak取代。

weak

诞生于ARC,是弱引用(弱指针),没有引用计数的操作

  • 只适用于指针类型,修饰对象时,是弱引用(弱指针),表示不持有该对象,不会进行retain操作,也就不会增加该对象的引用计数,而且当该对象销毁时会自动将此属性值(指针)置为nil,因会将当前指向该对象的指针指向nil,故不会形成野指针,不会导致崩溃。
  • 多用于代理委托(delegate)。
  • 一般只称weak为弱引用(弱指针)。

weak 弱指针是存放在什么位置?

弱指针是存放在对象的SideTableweak_table_t weak_table散列表中。
引用计数可以直接存储在isa的指针中,当isa不够存储时,会存储在SideTableRefcountMap refcnts散列表中。

weak 弱指针的底层实现原理是什么?怎么办到一旦指向的对象被销毁,弱引用(指针)会被置为nil?

对象销毁,底层dealloc究竟做了什么?

当对象销毁时,也就是调用dealloc时,首先,会从SideTable取出对象中的弱引用表(weak_table),把弱引用表中存储的弱引用(弱指针)清空掉,也就是将表中弱指针的都置为nil;其次,如果SideTable中引用计数表(refcnts)还有引用计数存在,也会将引用计数表(refcnts)的数据都擦除掉,也就是引用计数清零。

ARC都帮我们做了什么?

LLVM(编译器) + Runtime,ARC环境是LLVM(编译器)与Runtime运行时系统相互协作的一个环境。
LLVM(编译器)会自动生成release和retain,自动进行内存管理。
而像弱引用这样的存在,是需要Runtime来支持的,在运行时的过程中,当对象销毁时,会动态的对该对象的弱引用进行清空操作。

strong & weak & unsafe_unretained 之间的区别?

strong修饰结果图:
ObjC 基础-属性关键字(atomic、nonatomic、retain、strong、copy、assign、unsafe_unretained、weak)的区别与用法_第1张图片

weak修饰结果图:
ObjC 基础-属性关键字(atomic、nonatomic、retain、strong、copy、assign、unsafe_unretained、weak)的区别与用法_第2张图片

unsafe_unretained修饰结果图:
ObjC 基础-属性关键字(atomic、nonatomic、retain、strong、copy、assign、unsafe_unretained、weak)的区别与用法_第3张图片

虽然上面展示的是 __strong__weak__unsafe_unretained修饰局部变量的结果,但是换成strongweakunsafe_unretained修饰属性也是一样的特点,主要是关注关键字本身的特点是什么。

读写权限类

readOnly

表示只读,编译器只会自动生成getter方法,不会自动生成setter方法,即只能访问,不能赋值,
(但外部还是可以用KVC去赋值)。

readWrite

表示可读可写(默认属性),编译器会自动生成setter和getter方法。

你可能感兴趣的:(iOS开发-ObjC,基础,ios,objective-c)