你真的理解属性修饰词吗?

今天又开始看《Effective Objective-C 2.0》,这已经是第三遍了,每一遍都有不同的收获,半夜睡不着觉,爬起来把读书笔记记录下来。

copy和strong

书中 第6条:理解“属性”这一概念 中写道,

- strong:此特质表明该属性定义了一种“拥有关系”(owning relationship)。为这种属性设置新值时,设置方法会先保留新值,并释放旧值,然后再将新值设置上去。
- copy:此特质所表达的所属关系与strong类似。然而设置方法并不保留新值,而是将其“拷贝”(copy)。当属性类型为NSString *时,经常用此特质来保护其封装特性,因为传递给设置方法的新值有可能指向一个NSMutableString类的实例。这个类是NSString的子类,表示一种可以修改其值的字符串,此时若是不拷贝字符串,那么设置完属性之后,字符串的值就可能会在对象不知情的情况下遭人更改。所以这时候就要拷贝一份“不可变”(immutable)的字符串,确保对象中的字符串值不会无意间变动。只要实现属性所用的对象是“可变的”(mutable),就应该在设置新属性值时拷贝一份。

接下来验证一下:
定义copy和strong分别修饰的NSString,

@property (copy, nonatomic) NSString *duplicateString;
@property (strong, nonatomic) NSString *strongString;

然后开始测试

- (void)test {
    NSMutableString *originalStr = @"originalStr".mutableCopy;
    self.duplicateString = originalStr;
    self.strongString = originalStr;
    NSLog(@"originalStr地址%p",originalStr);
    NSLog(@"self.duplicateString = %@,地址%p",self.duplicateString,self.duplicateString);
    NSLog(@"self.strongString = %@,地址%p",self.strongString,self.strongString);

    [originalStr appendString:@"appendString"];
    NSLog(@"originalStr地址%p",originalStr);
    NSLog(@"self.duplicateString = %@,地址%p",self.duplicateString,self.duplicateString);
    NSLog(@"self.strongString = %@,地址%p",self.strongString,self.strongString);
}

对应的打印值如下

originalStr地址0x60000024a3e0
self.duplicateString = originalStr,地址0xa0ca1d056815085b
self.strongString = originalStr,地址0x60000024a3e0
originalStr地址0x60000024a3e0
self.duplicateString = originalStr,地址0xa0ca1d056815085b
self.strongString = originalStrappendString,地址0x60000024a3e0

确实是用copy修饰的字符串的内存地址与原字符串不同,并且不随原字符串修改而改变,strong修饰的string内存地址与原字符串相同且随原字符串修改而发生了改变。

点语法 和 _ 访问

来来来,再换一种写法

- (void)test {
    NSMutableString *originalStr = @"originalStr".mutableCopy;
    _duplicateString = originalStr;
    _strongString = originalStr;
    NSLog(@"originalStr地址%p",originalStr);
    NSLog(@"self.duplicateString = %@,地址%p",_duplicateString,_duplicateString);
    NSLog(@"self.strongString = %@,地址%p",_strongString,_strongString);

    [originalStr appendString:@"appendString"];
    NSLog(@"originalStr地址%p",originalStr);
    NSLog(@"self.duplicateString = %@,地址%p",_duplicateString,_duplicateString);
    NSLog(@"self.strongString = %@,地址%p",_strongString,_strongString);
}

这次对应的打印值为

originalStr地址0x60000024db00
self.duplicateString = originalStr,地址0x60000024db00
self.strongString = originalStr,地址0x60000024db00
originalStr地址0x60000024db00
self.duplicateString = originalStrappendString,地址0x60000024db00
self.strongString = originalStrappendString,地址0x60000024db00

用copy和strong修饰的string内存地址与原字符串都相同且随原字符串修改而发生了改变,这又是为什么呢?
点语法是“通过属性访问”,而下划线是“直接访问”实例变量,造成上面的结果是:

直接访问实例变量时,不会调用其“设置方法”,这就绕过了为相关属性所定义的“内存管理语义”此时的copy并不会拷贝该属性,只会保留新值并释放旧值。

assign 和 weak

  • assign “设置方法”只会执行针对“纯量类型”(scalar type,例如CGFloat或NSInteger等)的简单赋值操作。
  • weak 此特质表明该属性定义了一种“非拥有关系”(nonowning relationship)。为这种属性设置新值时,设置方法既不保留新值,也不释放旧值。此特质与assign类似,然而在属性所指的对象遭到摧毁时,属性值也会请空(nil out)。

结合上面的strong和copy,直接上代码,在MRC下重写他们的set方法以帮助理解

@property (nonatomic,strong) NSString * name;
 - (void)setName:(NSString*)name {
        [name retain];  // 保留新值,把传进来的对象引用计数加一
        [_name  release]; // 释放旧值,把_name以前的对象release一次
        _name = name;    // 将新值设置上去,把name的对象地址给_name  这时name 和_name共同对象的引用为2
}

@property (nonatomic , copy) NSString * name;
- (void)setName: (NSString*)name {
      [_name release];   //释放旧值,把_name以前的对象release一次
      _name = [name copy]; // 把name 的对象拷贝一份给_name  这时_name 的引用计数为1   而name的引用计数不变
}

@property (nonatomic , weak) NSString *name;
- (void)setName:(NSString*)name {
    _name = name;   //不保留新值,也不释放旧值,name和_name 引用计数为1;
} 

@property (nonatomic , assign) NSInteger age;
- (void)setAge:(NSInteger)age {
   _age = age;   //不保留新值,也不释放旧值,age和_age 引用计数为1;
} 

以上总结都是特别基础的知识点,但也特别容易被忽略。以上总结如果有错误,希望各位大佬指出来,共同探讨。

担心拖延症发作,之前计划好的文章又不发了,在这儿立个flag,周末介绍一下weak的底层实现原理:(啪啪打脸了)

1. 实现weak后,为什么对象释放后会自动为nil?
2. 当weak引用指向的对象被释放时,又是如何去处理weak指针的呢?

你可能感兴趣的:(你真的理解属性修饰词吗?)