我们都知道@property是用来声明属性的,可以保存类的状态或信息;而与其相关的内容,诸如copy、weak、深拷贝等,经常会在面试的过程中出现;

接下来深入下这些模糊&熟悉的内容,理理顺;

 

内容概要:

[email protected]的本质;

2.自动合成 和 动态合成;

3.原子性修饰符atomic 和 nonatomic;

4.atomic修饰的属性是线程安全的吗?

5.weak 和 strong;

6.__weak __strong __block;

7.assing 和 weak的使用;

    7.1 基础数据类型使用weak修饰的问题;

    7.2 对象类型使用assign修饰的问题;

    7.3 堆 和 栈;

    7.4 为什么基础数据类型可以使用assign修饰;

8.栈是线程安全的吗?

    8.1 线程和进程的区别;

9.copy 和 strong的使用;

    9.1 不可变对象使用strong的问题;

    9.2 可变对象使用copy的问题;

10.copy和mutableCopy;

    10.1深拷贝和浅拷贝;

    10.2 自定义对象如何支持copy方法;

 


 

@property = instance ivar + setter + getter:

面试经典-不被忽略的@property

 


 

@property修饰符:

面试经典-不被忽略的@property

面试经典-不被忽略的@property


 

atomic非线程安全的解释说明:

    atomic和nonatomic的区别在于,系统自动生成的getter和setter方法不一样;

        如果我们实现自己的getter和setter,那么内存管理和原子性相关的@property修饰符仅仅是提示,写不写都一样;

        atomic修饰的属性,系统生成的getter和setter会保证get/set操作的完整性,不受其他线程的影响;

            一个属性的多个set/get在不同的线程是线程安全,但操作属性的其他方法不一定;

            离开set/get方法之后的其他执行语句更是如此,也就是说atomic并不能保证所在线程的安全;

        atomic让CPU能在别的线程来访这个属性之前,先执行完当前流程;

        nonatomic的话,如果两个线程访问同一个属性,会出现无法预料的结果;

 

    在如下示例的log中:

        我们看到每次打印时getter获取的值都是有效的,也就是说本身get值,并没有受到其他线程的影响而取不出来;

        但一定要注意,原子性只是保证了set和get方法的操作完整性;

        对所在的线程中其他执行语句并没有约束力,所以不是线程安全的;

        (假设存在这种约束力,那么线程a:——a1之后的log,应该是线程b:-b1-a1)

面试经典-不被忽略的@property

 

上述是atomic非线程安全的一种解释,还可以这样理解:

    假设有一个atomic属性,线程1、2调用setter方法,线程3调用getter方法,在不同的线程中,这三个方法终将以某个顺序一次执行;

    也就是说,一个线程在执行setter/getter方法时,其他线程只能等待;

    但是,如果线程4中调用了release方法,就会有问题,因为release不受setter/getter方法的约束;

    也就是说atomic修饰的属性,读(get)写(set)是安全的,但不是线程安全的;

    因为别的线程还能进行读写之外的操作,这些操作都不会被约束,线程安全需要开发者自己保证;

 


 

weak和strong:

    弱引用和强引用;

        weak修饰的属性,赋值时,不会对赋值的对象retain,引用计数不会+1,所引用的对象=nil时,该属性还会自动置为nil;

        strong修饰的属性,赋值时,会对赋值的对象retain,引用计数会+1,strong是默认的缺省修饰符;

    二者的根本区别是在set方法中,虽然来两者的set方法都是对旧对象release释放,对新对象retain保留,但

        weak的set方法在retain之后会进行一次autorelease,而strong不会,最终效果就是strong引用计数会+1,weak则不会;

    主要用于解决循环引用;

面试经典-不被忽略的@property

 

__weak 和 __strong关联的变量修饰符(引用类型):

    在调用block的时候是采用copy方式,这是因为一般生成的block都是在栈上,通过copy之后的block会在堆上,相应的引用计数也会增加;

    copy到堆上时,block的外部变量也会被copy,对象类型的引用变量的引用计数会+1,如果这个对象对block持有,block中又使用了(捕获)这个对象,就会造成循环引用,这会导致对象无法释放;

    此时只需要在block外部重新定义一个采用__weak修饰的变量,这个变量指向object,然后在block中使用此变量即可;

 

    虽然使用了__weak解决了循环引用的问题,但是这个变量也有可能随时被释放掉,为了能够使用该对象,可以在block内部一开始的地方定义一个__strong的变量,这个__strong变量指向外部定义的__weak变量;

 

使用方式:

    这保证了整个block中对象一直被强引用而不被释放;

__weak typeof(self) wself = self;

>block{

    __strong typeof(wself) sself = wself;

}

 

__block存储类型修饰符(值类型):

    block会捕获用到的外部变量,并copy到堆上;

    因此如果需要在block中修改外部的变量值,这个变量可以是静态变量、静态全局变量,全局变量;

        原因是:全局变量 是存储在全局区,静态变量传递给Block的内存地址;

    还有一种方式是对自动变量的:

        自动变量的值,被copy进了Block,不带__block的自动变量只能在里边被访问,并不能改变值;

        带__block的自动变量和静态变量就是直接地址访问,所以Block里可以直接改变便来能的值;

    带有__block修饰符的变量会被捕获到Block内部持有;

 


 

我们介绍了使用assign修饰的属性,在对象释放之后,变成野指针,带来崩溃,那为什么基本数据类型可以使用assign修饰呢?

 

我们深入堆栈来看下:

面试经典-不被忽略的@property

 

使用assign修饰的基本数据类型之所以没有野指针,是因为:

    基本数据类型是分配在栈上,栈空间的分配和回收是由系统管理的,也就不会产生野指针的问题了;

 


 

栈是线程安全的吗?

我们先来回顾一下进程和线程;

 

进程和线程的关系:

    线程是进程的实体,是CPU调度和分派的基本单位;

        一个进程可以有多个线程,线程本身只有很少的,运行时必要的资源,不分配系统资源;

    线程与同一进程的其他线程共享进程资源;

        一个进程中的所有线程共享该进程的地址空间,但是每个线程有自己独立的栈,堆则是进程独有的,为进程中其他线程共享;

 

所以栈 是 线程安全的:

    栈是每个线程独有,保存线程的运行状态和局部变量;会在线程开始的时候分配;

    堆是多个线程所共有的空间,操作系统在对进程进行初始化的时候,对堆进行分配;

 


 

@property修饰符的进一步说明:copy、string、mutableCopy

 

copy和strong:

    @property (nonatomic , copy) NSString *sex;

    @property (nonatomic , strong) NSMutableArray * books;

通常:

    不可变对象属性修饰符使用copy;

    可变对象属性修饰符使用strong;

 

可变对象和不可变对象:

    不可变对象,如NSArray,NSString等;(改变变量的值,指针的地址会重新分配赋值)

    可变对象,如NSMutableArray,NSMutableString等;(改变变量的值,指针的地址不变)

 

不可变对象使用strong的问题:

    我们使用了strong修饰了strongStr属性,这是一个NSString类型,很明显我们需要的是一个不可变对象;

    依照如下示例,我们会发现,虽然打印的两个变量的地址一致,但是地址在没有改变的情况下,不可变对象属性的值被篡改了;

面试经典-不被忽略的@property

 

可变对象使用copy的问题:

    我们使用copy修饰了mutiStr属性,这是一个NSMutableString类型,很明显我们需要的是一个可变类型的对象;

    依照如下示例,我们发现,程序报错,原因在于mutiStr属性实际是NSString类型,没有appendString方法;

面试经典-不被忽略的@property

 

我们来分析下copy修饰的可变对象属性赋值时到底发生了什么:

    self.mutiStr = [NSMutableString stringWithString:tempStr];

    <=>

    NSMutableString tempString = [NSMutableString stringWithString: tempStr];

    self.mutiStr = [tempString copy];

    这里要注意,通过copy方法得到的对象是一个不可变的对象(使用mutableCopy方法返回的才是可变对象),自然没有可变对象的方法;

 


 

copy和mutableCopy:

    二者的差异主要和深拷贝和浅拷贝有关;

 

深拷贝和浅拷贝:

    浅拷贝:引用计数+1,并没有申请新的内存区域,只是另一个指针指向了该区域;

    深拷贝:申请新的内存区域,与原内存区域中的内容是一样的,原内存区域的引用计数不变;

面试经典-不被忽略的@property

 

可变对象的copy和mutableCopy:

    可变对象的copy和mutableCopy都是深拷贝,只不过方法返回的对象类型不同;

    

不可变对象的copy和mutableCopy:

    不可变对象的copy是浅拷贝;

    mutableCopy是深拷贝;    

 


 

自定义对象如何支持copy方法:

    自定义对象支持copy方法,只需要支持NSCopying协议,且实现copyWithZone方法;

    对于当前对象的可变对象属性,需要使用mutableCopyWithZone操作;

面试经典-不被忽略的@property

 


 

相关文章:

  • 2021-12-04
  • 2021-09-10
  • 2021-09-24
  • 2022-12-23
  • 2022-12-23
  • 2021-09-20
  • 2022-01-12
  • 2021-10-06
猜你喜欢
  • 2022-02-07
  • 2021-07-07
  • 2022-12-23
  • 2021-05-29
  • 2022-01-01
  • 2022-12-23
  • 2022-12-23
相关资源
相似解决方案