美文网首页oc基础
OC属性的修饰符

OC属性的修饰符

作者: 相思的忆 | 来源:发表于2020-07-01 17:25 被阅读0次

    属性修饰符是什么?有什么作用?

    属性修饰符,顾名思义就是对属性进行修饰的符号。不同修饰符修饰的属性会表现出不一样的属性特性,其表现形式并不是那么显而易见。
    那它的作用是什么呢?从上面的定义上面我们就能发现,被不同修饰符修饰的属性会有不同的特性,根据这些特性,我们能更好的优化自己的代码逻辑,更简单的实现效果。
    接下来我们就逐个详细分析一下。

    属性修饰符有哪些?

    这里我把常用的属性修饰符分为三大类

    • 线程安全类 nonatomic/atomic
    • 读写权限类 readwrite/readonly
    • 内存管理类 assign/copy/strong/weak

    还有其他的例如

    • class 类属性
    • setter=/getter= 自定义setter/getter方法名
    • retain MRC下内存管理

    现在我们来详细说一下常用的三大类属性修饰符。

    1、线程安全类 nonatomic/atomic

    1. nonatomic 非原子属性。它的特点是多线程并发访问性能高,但是访问不安全;与之相对的就是atomic,特点就是安全但是是以耗费系统资源为代价,所以一般在工程开发中用nonatomic的时候比较多。
    2. 系统默认的是atomic,为setter方法加锁,而nonatomic 不为setter方法加锁。
    3. 如1所述,使用nonatomic要注意多线程间通信的线程安全。

    根据上述描述,项目中我们基本上只使用nonatomic,因为他的访问性能高,对于多线程处理的时候,也建议自己去实现加锁的处理。一是因为atomic的加锁占用系统资源量大,二是因为atomic只是在setter/getter方法中进行了加锁处理,在其他操作中是没有的,这里可能会出现遗漏。

    2、读写权限类 readwrite/readonly
    这个就是访问权限的控制,决定该属性是否可读和可写,默认是readwrite,所以我们定义属性的时候,一般不需要这个修饰。只有只读属性才需要加上readonly的修饰。
    readonly来控制读写权限的方式就是只生成getter方法,不生成setter方法。

    3、内存管理类 assign/copy/strong/weak
    这一类的的修饰符是该文档中最重要的部分了,这也是我们项目编码中,经常会混淆,理解错误的地方。不知道这些修饰符都要什么时候时候使用。

    • assign
      该修饰符是给那些不需要进行内存管理的变量使用的,包括所有的基础类型变量,例如 int float double long BOOL NSInteger 等。还有的是由栈区,全局区,常量区管理的变量也可以使用assign来修饰,因为他们的内存已经被系统自动管理了,无需手动额外管理。

    • copy
      该修饰符修饰的属性在赋值的时候会多一个执行copy方法的操作,对于copy的方法实现完全按照对应的对象的copy实现,如NSString的copy是不变的。
      对于那些属性赋值需要进行浅拷贝的,也就是当前类中处理该属性的值不影响传入变量的情况下,可以使用该类。但是要谨慎使用,很容易会产生问题,例如可变类型不能使用copy来进行修饰,否则赋值的变量都会变成不可变类型的属性,与原先定义的属性类型不一致。在编写代码过程中可能会调用不属与该类的方法。

    • strong
      该修饰符相当于是MRC中的 retain 修饰符。它是一种强引用,被它修饰的属性都会进行内存管理,也就是引用计数的管理。

    • weak
      弱引用,和 strong 相对应。常用于解决循环引用问题。被修饰的属性在其他持有者都被释放之后,该属性会自动指向 nil,也就是说,作用和assign一样,但是更加安全!

    总结:可以说属性修饰符就是对setter/getter方法的一些功能性扩展,使其定义简单的同时能够满足更多的功能要求。

    栈区、常量区、全局区用 assign
    堆区持有用 strongcopy,不持有用 weak
    什么叫不持有?
    就是一个对象别其他变量持有了,那该变量就指向该对象,如果别的变量都不持有该对象了,那该变量也不需要指向该对象了,这就叫不持有。

    注意点

    接下来我们说明一下在实际使用过程中比较常见的一些误区和知识点。

    1. NSString 为什么要用 copy?为什么不能用strong
      如果是简单的就是 NSString 类型的字符串,它们都是存放在常量区里面 的,就不需要进行内存管理,assign,copy,strong 都是可以修饰的,它们实际上都不会去改变任何东西。
    @property (nonatomic, assign) NSString *str1;
    @property (nonatomic, copy) NSString *str2;
    @property (nonatomic, strong) NSString *str3;
    
        self.str1 = @"str1";
        self.str2 = @"str2";
        self.str3 = @"str3";
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"str1:%@ %p", self.str1, self.str1);
            NSLog(@"str2:%@ %p", self.str2, self.str2);
            NSLog(@"str3:%@ %p", self.str3, self.str3);
        });
    
    2020-07-01 14:37:16.465702+0800 Test_UI[76089:3507003] str1:str1 0x10c1d5150
    2020-07-01 14:37:16.465822+0800 Test_UI[76089:3507003] str2:str2 0x10c1d5170
    2020-07-01 14:37:16.465888+0800 Test_UI[76089:3507003] str3:str3 0x10c1d5190
    

    但是为什么只能用 copy 呢?因为 NSString 的子类 NSMutableString,他是存放在堆区的。子类也是可以用父类来接收的,这中情况下 assign 就不能使用了,它无法持有 NSMutableString 会导致被提前释放。不使用 strong 而使用 copy 是为了在赋值的时候去除可变的特性,优化内存管理,让存放在堆区的 NSMutableString 对象及时得到释放。

    @property (nonatomic, assign) NSString *str1;
    @property (nonatomic, copy) NSString *str2;
    @property (nonatomic, strong) NSString *str3;
    
        self.str1 = [@"str1" mutableCopy];
        self.str2 = [@"str2" mutableCopy];
        self.str3 = [@"str3" mutableCopy];
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"str1:%@ %p", self.str1, self.str1);
            NSLog(@"str2:%@ %p", self.str2, self.str2);
            NSLog(@"str3:%@ %p", self.str3, self.str3);
            NSLog(@"str class: %@", [@"str123" class]);
            NSLog(@"mutable str copy class : %@", [[[@"str123" mutableCopy] copy] class]);
        });
    
    2020-07-01 14:38:10.159028+0800 Test_UI[76124:3508299] str1:str3 0x6000012d9e00
    2020-07-01 14:38:10.159150+0800 Test_UI[76124:3508299] str2:str2 0xaffe863aa45e1a58
    2020-07-01 14:38:10.159220+0800 Test_UI[76124:3508299] str3:str3 0x6000012d9e00
    2020-07-01 14:38:10.159310+0800 Test_UI[76124:3508299] str class: __NSCFConstantString
    2020-07-01 14:38:10.159393+0800 Test_UI[76124:3508299] mutable str copy class : NSTaggedPointerString
    

    我们来分析一下上面输出的情况可以看到 str1str3 的地址变成一样的了,str1 并没有变成野指针崩溃,然后我同时也打印了一下内容,发现 str1 的内容变成了 str3的内容。这是为什么呢?
    因为我们使用 assign 修饰的 str1 ,在赋值之后没有持有导致马上就被释放掉了,而该属性指向的内容也没有了,而在之后 str3 创建的对象刚好也是从这个地址开始创建的,所以就造成了上面的那种现象。这里我们把后面 str2,str3 的代码删除之后运行会发现, str1的内容就变成空了。

    2020-07-01 15:13:42.852822+0800 Test_UI[92383:3561873] str1:<__NSMallocBlock__: 0x60000089e220> 0x60000089e220
    

    接下来我们发现 str2 的地址不是在常量区的,这是为什么呢?
    最后我们也打印了一下两种字符串的类型,发现它们是不一样的,为什么呢?同样都是不可变字符串,还都是 NSString
    这是因为 NSString 是另一个类蔟,类蔟的定义和表现我们之后再讲。__NSCFConstantString 该类型的会直接存放在常量区,这是代码编译时就能够决定的,然后直接存放在常量区。NSTaggedPointerString 这种类型的可以说是动态生成的,编译的时候无法判断,所以在他产生的时候 动态添加到栈区里面。

    1. 可变类型不能使用 copy
      可变类型的属性如果使用 copy 来修饰,在赋值的时候,该值会进行一次 copy 操作,导致属性指向的是一个不可变的对象,如果用该属性去调用可变对象的方法,会产生崩溃。

    2. Block到底使用什么来修饰?
      block有一个特性,当它访问了外部局部变量(注意:这里是外部的局部变量,全局变量不受影响),就会存放在堆区。

    @property (nonatomic, assign) void(^block1)(void);
    @property (nonatomic, copy) void(^block2)(void);
    @property (nonatomic, strong) void(^block3)(void);
    
        void(^block1)(void) = ^() {
            NSLog(@"1");
        };
        void(^block2)(void) = ^() {
            NSLog(@"2");
        };
        void(^block3)(void) = ^() {
            NSLog(@"3");
        };
        NSLog(@"block1: %p", block1);
        NSLog(@"block2: %p", block2);
        NSLog(@"block3: %p", block3);
        self.block1 = block1;
        self.block2 = block2;
        self.block3 = block3;
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"block1: %p", self.block1);
            NSLog(@"block2: %p", self.block2);
            NSLog(@"block3: %p", self.block3);
        });
    
    2020-07-01 16:39:06.271597+0800 Test_UI[24869:3706043] block1: 0x10a984108
    2020-07-01 16:39:06.271695+0800 Test_UI[24869:3706043] block2: 0x10a984128
    2020-07-01 16:39:06.271759+0800 Test_UI[24869:3706043] block3: 0x10a984148
    2020-07-01 16:39:06.294224+0800 Test_UI[24869:3706043] block1: 0x10a984108
    2020-07-01 16:39:06.294314+0800 Test_UI[24869:3706043] block2: 0x10a984128
    2020-07-01 16:39:06.294391+0800 Test_UI[24869:3706043] block3: 0x10a984148
    

    所以这样的block所有修饰符都可以修饰。

    @property (nonatomic, assign) void(^block1)(void);
    @property (nonatomic, copy) void(^block2)(void);
    @property (nonatomic, strong) void(^block3)(void);
    
        int num = 2;
    
        void(^block1)(void) = ^() {
            NSLog(@"%d", num);
        };
        void(^block2)(void) = ^() {
            NSLog(@"%d", num);
        };
        void(^block3)(void) = ^() {
            NSLog(@"%d", num);
        };
        NSLog(@"block1: %p", block1);
        NSLog(@"block2: %p", block2);
        NSLog(@"block3: %p", block3);
        self.block1 = block1;
        self.block2 = block2;
        self.block3 = block3;
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"block1: %p", self.block1);
            NSLog(@"block2: %p", self.block2);
            NSLog(@"block3: %p", self.block3);
        });
    
    2020-07-01 17:08:27.093104+0800 Test_UI[25619:3736522] block1: 0x600003fb1950
    2020-07-01 17:08:27.093214+0800 Test_UI[25619:3736522] block2: 0x600003fb12f0
    2020-07-01 17:08:27.093287+0800 Test_UI[25619:3736522] block3: 0x600003fb08d0
    2020-07-01 17:08:27.102584+0800 Test_UI[25619:3736522] block1: 0x600003fb1950
    2020-07-01 17:08:27.102696+0800 Test_UI[25619:3736522] block2: 0x600003fb12f0
    2020-07-01 17:08:27.102785+0800 Test_UI[25619:3736522] block3: 0x600003fb08d0
    

    这种block就不能使用 assign 来修饰,如果及时释放的话,访问 self.block1 的时候会产生崩溃。

    报错

    而同样的我们也可以看到,不管是 copy 还是 strong,block的地址都没有变,所以它们是等价的,而使用 strong 更加直接,性能会更好,而同样的,对已经自动管理的block类型而言,我们所有修饰符都可以使用,所以为了通用,我们在ARC下使用 strong 来修饰所有的block,当然也可以用 copy,可以说 copy 的修饰是从MRC中继承过来的。

    相关文章

      网友评论

        本文标题:OC属性的修饰符

        本文链接:https://www.haomeiwen.com/subject/dhpiahtx.html