OC属性的修饰符

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

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

属性修饰符有哪些?

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

  • 线程安全类 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中继承过来的。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,386评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,939评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,851评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,953评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,971评论 5 369
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,784评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,126评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,765评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,148评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,744评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,858评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,479评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,080评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,053评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,278评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,245评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,590评论 2 343