美文网首页iOS开发技术
iOS底层原理 - 内存管理 之 copy

iOS底层原理 - 内存管理 之 copy

作者: hazydream | 来源:发表于2019-08-20 08:55 被阅读0次

    面试题引发的思考:

    Q: 谈一谈对copymutableCopy、浅拷贝、深拷贝的理解?

    • copy:不可变拷贝,产生不可变副本;
    • mutableCopy:可变拷贝,产生可变副本;
    • 浅拷贝:指针拷贝,没有产生新的对象;
    • 深拷贝:内容拷贝,产生新的对象。

    Q: iOS项目中copy修饰词的使用?

    • copy修饰词 只能声明 不可变类型。
    • 字符串一般用copy修饰,用于UI控件显示的时候不会有问题。
    • 属性不存在mutableCopy修饰词,只有部分Foundation框架自带的一些类可以用mutableCopy修饰。

    Q: 自定义的类实现 copy 功能?

    • 遵守 NSCopying 协议;
    • 实现 copyWithZone: 方法。
    // TODO: -----------------  Person类  -----------------
    // 自定义的类实现 copy 功能需要:1. 遵守 NSCopying 协议
    @interface Person : NSObject <NSCopying>
    @property (nonatomic, assign) int age;
    @property (nonatomic, copy) NSString *name;
    @end
    
    @implementation Person
    // 自定义的类实现 copy 功能需要:2. 实现 copyWithZone: 方法
    - (nonnull id)copyWithZone:(nullable NSZone *)zone {
        Person *person = [[Person allocWithZone:zone] init];
        person.age = self.age;
        person.name = self.name;
        return person;
    }
    - (NSString *)description {
        return [NSString stringWithFormat:@"age = %d, weight = %@", self.age, self.name];
    }
    @end
    

    1. 原理介绍

    拷贝的目的:产生一个副本对象,跟源对象互不影响

    • 修改了源对象,不会影响副本对象;
    • 修改了副本对象,不会影响源对象。

    iOS提供了2个拷贝方法:

    • copy:不可变拷贝,产生不可变副本;
    • mutableCopy:可变拷贝,产生可变副本。

    浅拷贝和深拷贝:

    • 浅拷贝:指针拷贝,没有产生新的对象;
      不可变对象NSStringNSArrayNSDictionary调用copy
    • 深拷贝:内容拷贝,产生新的对象;
      深拷贝:其他情况。

    2. 案例分析

    (1) copymutableCopy分析

    // TODO: -----------------  main  -----------------
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            // insert code here...
            NSString *str1 = [NSString stringWithFormat:@"test"];
            NSString *str2 = [str1 copy]; // 返回的是NSString
            NSMutableString *str3 = [str1 mutableCopy]; // 返回的是NSMutableString
    
            [str3 appendString:@"ceshi"];
    
            NSLog(@"%@ %@ %@", str1, str2, str3);
        }
        return 0;
    }
    
    // 打印结果
    Demo[1234:567890] test test testceshi
    
    // TODO: -----------------  main  -----------------
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            // insert code here...
            NSMutableString *str1 = [NSMutableString stringWithFormat:@"test"];
            NSString *str2 = [str1 copy]; // 返回的是NSString
            NSMutableString *str3 = [str1 mutableCopy]; // 返回的是NSMutableString
    
            [str3 appendString:@"ceshi"];
    
            NSLog(@"%@ %@ %@", str1, str2, str3);
        }
        return 0;
    }
    
    // 打印结果
    Demo[1234:567890] test test testceshi
    

    由以上代码可知:

    • copy:不可变拷贝,产生不可变副本;
    • mutableCopy:可变拷贝,产生可变副本。

    (2) 浅拷贝和深拷贝分析

    将环境改为手动内存管理,我们知道:

    当调用allocnewcopymutableCopy方法返回了一个对象,在不需要这个对象时,要调用release或者autorelease来释放它。

    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            // insert code here...
            // 避免使用Tagged Pointer 技术存储数据,所以赋值长一些
            NSString *str1 = [[NSString alloc] initWithFormat:@"test123456789"];
            NSString *str2 = [str1 copy]; // 浅拷贝:指针拷贝,没有产生新的对象
            NSMutableString *str3 = [str1 mutableCopy]; // 深拷贝:内容拷贝,产生新的对象
    
            NSLog(@"%@ %@ %@", str1, str2, str3);
            NSLog(@"%p %p %p", str1, str2, str3);
    
            [str3 release];
            [str2 release];
            [str1 release];
        }
        return 0;
    }
    
    // 打印结果
    Demo[1234:567890] test123456789 test123456789 test123456789
    Demo[1234:567890] 0x1005b9b60 0x1005b9b60 0x100604a40
    

    由打印结果可知:
    str1str2的地址是一样的,说明str1str2是指向的是同一个对象。其指针分析如下:

    指针分析
    // TODO: -----------------  main  -----------------
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            // insert code here...
            NSMutableString *str1 = [[NSMutableString alloc] initWithFormat:@"test123456789"];
            NSString *str2 = [str1 copy]; // 深拷贝:内容拷贝,产生新的对象
            NSMutableString *str3 = [str1 mutableCopy]; // 深拷贝:内容拷贝,产生新的对象
    
            [str1 appendFormat:@"111"];
            [str3 appendFormat:@"333"];
    
            NSLog(@"%@ %@ %@", str1, str2, str3);
            NSLog(@"%p %p %p", str1, str2, str3);
    
            [str3 release];
            [str2 release];
            [str1 release];
        }
        return 0;
    }
    
    // 打印结果
    Demo[1234:567890] test123456789111 test123456789 test123456789333
    Demo[1234:567890] 0x10049d9b0 0x10049ded0 0x10049dfd0
    

    由打印结果可知:
    str1str2str3的地址都是不一样的,说明str1str2str3是分别指向的是不同的对象。其指针分析如下:

    指针分析

    (3) 扩展分析

    1> 对于数组
    // TODO: -----------------  main  -----------------
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            // insert code here...
            NSArray *array1 = [[NSArray alloc] initWithObjects:@"1", @"2", nil];
            NSArray *array2 = [array1 copy]; // 浅拷贝:指针拷贝,没有产生新的对象
            NSMutableArray *array3 = [array1 mutableCopy]; // 深拷贝:内容拷贝,产生新的对象
    
            NSLog(@"%p %p %p", array1, array2, array3);
    
            [array1 release];
            [array2 release];
            [array3 release];
        }
        return 0;
    }
    
    // 打印结果
    Demo[1234:567890] 0x10049eca0 0x10049eca0 0x10049ef50
    

    由打印结果可知:
    array1array2的地址是一样的,说明array1array2是指向的是同一个对象。

    // TODO: -----------------  main  -----------------
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            // insert code here...
            NSMutableArray *array1 = [[NSMutableArray alloc] initWithObjects:@"1", @"2", nil];
            NSArray *array2 = [array1 copy]; // 深拷贝:内容拷贝,产生新的对象
            NSMutableArray *array3 = [array1 mutableCopy]; // 深拷贝:内容拷贝,产生新的对象
    
            NSLog(@"%p %p %p", array1, array2, array3);
    
            [array1 release];
            [array2 release];
            [array3 release];
        }
        return 0;
    }
    
    // 打印结果
    Demo[1234:567890] 0x10313c020 0x10313c700 0x10313c720
    

    由打印结果可知:
    array1array2array3的地址都是不一样的,说明array1array2array3是分别指向的是不同的对象。

    2> 对于字典
    // TODO: -----------------  main  -----------------
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            // insert code here...
            NSDictionary *dict1 = [[NSDictionary alloc] initWithObjectsAndKeys:@"jack", @"name", nil];
            NSDictionary *dict2 = [dict1 copy]; // 浅拷贝:指针拷贝,没有产生新的对象
            NSMutableDictionary *dict3 = [dict1 mutableCopy]; // 深拷贝:内容拷贝,产生新的对象
    
            NSLog(@"%p %p %p", dict1, dict2, dict3);
    
            [dict1 release];
            [dict2 release];
            [dict3 release];
        }
        return 0;
    }
    
    // 打印结果
    Demo[1234:567890] 0x100511810 0x100511810 0x1005118f0
    

    由打印结果可知:
    dict1dict2的地址是一样的,说明dict1dict2是指向的是同一个对象。

    // TODO: -----------------  main  -----------------
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            // insert code here...
            NSMutableDictionary *dict1 = [[NSMutableDictionary alloc] initWithObjectsAndKeys:@"jack", @"name", nil];
            NSDictionary *dict2 = [dict1 copy]; // 深拷贝:内容拷贝,产生新的对象
            NSMutableDictionary *dict3 = [dict1 mutableCopy]; // 深拷贝:内容拷贝,产生新的对象
    
            NSLog(@"%p %p %p", dict1, dict2, dict3);
    
            [dict1 release];
            [dict2 release];
            [dict3 release];
        }
        return 0;
    }
    
    // 打印结果
    Demo[1234:567890] 0x103132250 0x103132850 0x103132870
    

    由打印结果可知:
    dict1dict2dict3的地址都是不一样的,说明dict1dict2dict3是分别指向的是不同的对象。


    (4) 总结

    根据以上代码及分析可总结如下:

    • copy:不可变拷贝,产生不可变副本;
    • mutableCopy:可变拷贝,产生可变副本;
    • 浅拷贝:指针拷贝,没有产生新的对象;
    • 深拷贝:内容拷贝,产生新的对象;
    • 不可变对象NSStringNSArrayNSDictionary调用copy时是浅拷贝;
    • 其他情况是深拷贝。

    总结图例如下:

    总结图例

    3. 修饰词copy的使用分析

    (1) 总结修饰词copyretain的区别

    对于retain修饰词修饰:

    // TODO: -----------------  Person类  -----------------
    @interface Person : NSObject
    @property(nonatomic, retain) NSArray *dataArray;
    @end
    
    @implementation Person
    // retain 相当于一下代码
    - (void)setDataArray:(NSArray *)dataArray {
        if (_dataArray != dataArray) {
            [_dataArray release];
            _dataArray = [dataArray retain]; // 仅此处有差别
        }
    }
    - (void)dealloc {
        self.dataArray = nil;
        [super dealloc];
    }
    @end
    

    对于copy修饰词修饰:

    // TODO: -----------------  Person类  -----------------
    @interface Person : NSObject
    @property(nonatomic, copy) NSArray *dataArray;
    @end
    
    @implementation Person
    // copy 相当于一下代码
    - (void)setDataArray:(NSArray *)dataArray {
        if (_dataArray != dataArray) {
            [_dataArray release];
            _dataArray = [dataArray copy]; // 仅此处有差别
        }
    }
    - (void)dealloc {
        self.dataArray = nil;
        [super dealloc];
    }
    @end
    

    因为用copy修饰词修饰的dataArray,会先把旧的_dataArrayrelease,然后调用copy赋值给_dataArray,那么_dataArray就是不可变类型。

    得出结论:

    copy修饰词只能声明不可变类型。


    (2) 案例分析

    1> 对于copy修饰词修饰不可变数组NSArray
    // TODO: -----------------  Person类  -----------------
    @interface Person : NSObject
    @property(nonatomic, copy) NSArray *dataArray;
    @end
    
    @implementation Person
    @end
    
    // TODO: -----------------  main  -----------------
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            // insert code here...
            Person *person = [[Person alloc] init];
    
            person.dataArray = @[@"jack", @"rose"];
            // 相当于 不可变的数组  调用 copy 返回一个 不可变的数组 给 person.dataArray
            // person.dataArray = [@[@"jack", @"rose"] copy];
    
            NSLog(@"%@", person.dataArray);
    
            [person release];
        }
        return 0;
    }
    
    // 打印结果
    Demo[1234:567890] (
        jack,
        rose
    )
    

    运行正常,没有问题。

    2> 对于copy修饰词修饰可变数组NSMutableArray
    // TODO: -----------------  Person类  -----------------
    @interface Person : NSObject
    @property(nonatomic, copy) NSMutableArray *dataArray;
    @end
    
    @implementation Person
    @end
    
    // TODO: -----------------  main  -----------------
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            // insert code here...
            Person *person = [[Person alloc] init];
    
            person.dataArray = [NSMutableArray array];
            // 相当于 可变的数组 赋值给 person.dataArray,然后调用 copy 返回一个 不可变数组
    
            [person.dataArray addObject:@"jack"];
            [person.dataArray addObject:@"rose"];
    
            NSLog(@"%@", person.dataArray);
    
            [person release];
        }
        return 0;
    }
    
    // 打印结果
    Demo[1234:567890] '-[__NSArray0 addObject:]: unrecognized selector sent to instance 0x7fff898d1060'
    

    运行崩溃,提示“方法找不到错误”。

    因为可变数组dataArraycopy修饰词修饰,语句person.dataArray = [NSMutableArray array];相当于可变的数组赋值给person.dataArray,然后调用copy返回一个不可变数组,所以不可变数组person.dataArray没有addObject:方法,运行崩溃。


    (3) iOS项目中copy修饰词的使用

    1> UI控件显示使用copy修饰

    对于UITextFieldtext属性内部代码如下:

    UITextField属性

    我们发现iOS底层代码对于字符串相关的基本上都是使用copy修饰。

    具体事例分析如下:

    - (void)viewDidLoad {
        [super viewDidLoad];
        // Do any additional setup after loading the view.
        NSMutableString *text = [NSMutableString stringWithFormat:@"123"];
    
        UITextField *textField;
        // copy 保证 textField.text 赋值的是 text 的副本,不会因为 text 的改变而改变
        textField.text = text;
    
        // 此处修改 text 不会影响 textField.text 的值
        [text appendString:@"abc"];
    }
    

    由以上分析可知:

    字符串一般用copy修饰,用于UI控件显示的时候不会有问题。

    属性不存在mutableCopy修饰词,因为只有部分Foundation框架自带的一些类可以用mutableCopy修饰,比如以下类:

    NSString, NSMutableString;
    NSArray, NSMutableArray;
    NSDictionary, NSMutableDictionary;
    NSData, NSMutableData;
    NSSet, NSMutableSet;
    等等...
    
    2> 自定义的类实现copy功能
    // TODO: -----------------  Person类  -----------------
    // 自定义的类实现 copy 功能需要:1. 遵守 NSCopying 协议
    @interface Person : NSObject <NSCopying>
    @property (nonatomic, assign) int age;
    @property (nonatomic, copy) NSString *name;
    @end
    
    @implementation Person
    // 自定义的类实现 copy 功能需要:2. 实现 copyWithZone: 方法
    - (nonnull id)copyWithZone:(nullable NSZone *)zone {
        Person *person = [[Person allocWithZone:zone] init];
        person.age = self.age;
        person.name = self.name;
        return person;
    }
    - (NSString *)description {
        return [NSString stringWithFormat:@"age = %d, weight = %@", self.age, self.name];
    }
    @end
    
    // TODO: -----------------  main  -----------------
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            Person *p1 = [[Person alloc] init];
            p1.age = 20;
            p1.name = @"Jack";
    
            // p2是一个全新的副本对象,与p1互不干涉
            Person *p2 = [p1 copy];
            p2.age = 30;
    
            NSLog(@"%@", p1);
            NSLog(@"%@", p2);
    
            [p2 release];
            [p1 release];
        }
        return 0;
    }
    
    // 打印结果
    Demo[1234:567890] age = 20, weight = Jack
    Demo[1234:567890] age = 30, weight = Jack
    

    自定义的类实现 copy 功能需要:

    • 遵守 NSCopying 协议;
    • 实现 copyWithZone: 方法。

    相关文章

      网友评论

        本文标题:iOS底层原理 - 内存管理 之 copy

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