IOS 浅拷贝和深拷贝

对于不可变的集合类对象进行 copy 操作,只是改变了指针,其内存地址并没有发生变化;进行 mutableCopy 操作,内存地址发生了变化,但是其中的元素内存地址并没有发生变化。
对于可变集合类对象,不管是进行 copy 操作还是 mutableCopy 操作,其内存地址都发生了变化,但是其中的元素内存地址都没有发生变化,属于单层深拷贝。

深拷贝的实现

  • 第一种方法,可以通过归解档生成两份完全独立的对象,但是前提是对象必须支持 NSCoding 协议。
  • 第二种方法,自己实现了一个 BNDeepCopy 深拷贝协议,把 NSArray、NSSet、NSDictionary 分别用 category 添加一下实现。后面如果自己的某个对象如果 NSCopying 协议不能满足深拷贝的要求,只需实现 BNDeepCopy 协议即可。(对于一些 NSString、NSNumber 的内存优化,此实现中暂时不独立成两份)。
    大概实现如下:
@implementation NSArray (BNDeepCopy)

- (instancetype)BN_deepCopy {
    NSMutableArray *mutableResultArray = [[NSMutableArray alloc] initWithCapacity:[self count]];
    for (id subObject in self) {
        id deepCopySubObject = nil;
        if ([subObject respondsToSelector:@selector(BN_deepCopy)]) {
            deepCopySubObject = [subObject BN_deepCopy];
        } else if ([subObject isKindOfClass:[NSMutableArray class]] || [subObject isKindOfClass:[NSMutableSet class]] || [subObject isKindOfClass:[NSMutableDictionary class]]) {
            deepCopySubObject = [subObject mutableCopy];
        } else if ([subObject conformsToProtocol:@protocol(NSCopying)]) {
            deepCopySubObject = [subObject copy];
        } else {
            deepCopySubObject = subObject;
        }

        if (deepCopySubObject) {
            [mutableResultArray addObject:deepCopySubObject];
        } else {
            [mutableResultArray addObject:subObject];
        }
    }

    if ([self isKindOfClass:[NSMutableArray class]]) {
        return mutableResultArray;
    } else {
        return [NSArray arrayWithArray:mutableResultArray];
    }
}

浅拷贝和深拷贝都是说引用数据类型,不是说基本数据类型
浅拷贝是说在给一个变量赋值的是将另一个变量的引用(内存地址)赋值给新变量,而不是又重新造了一个新的东西来
深拷贝是将重新制造出一个变量的副本,然后将变量的副本赋值给新变量

浅拷贝:指针拷贝,不产生新的对象,源对象的引用计数器+1;

深拷贝:对象拷贝,会产生新的对象,源对象的引用计数器不变;

判断是浅拷贝和深拷贝就看一下两个变量的内存地址是否一样,一样就是浅拷贝,不一样就是深拷贝,也可以改变一个变量的其中一个属性值看两者的值都会发生变化;

copy

拷贝的结果是一个不可变(imutable)的对象, 无论源对象是可变的还是不可变的,copy之后的都是不可变的类型
不可变类型 变量名 = [不可变类型|可变类型 copy];

mutableCopy

可变拷贝的结果的数据类型是一个可变的对象,无论源对象时不可变的还是可变的,可变拷贝之后的数据类型都是可变类型
可变类型 变量名 = [不可变类型|可变类型 mutableCopy];

copy对引用计数器的影响

         拷贝一个不可变的类型的结果是新对象和源对象都指向同一个内存地址,即使指针拷贝,属于浅拷贝,所以不生产新对象,源对象的引用计数+1
         拷贝一个可变的类型,会生成一个新对象,不影响源对象的引用计数

mutableCopy对引用计数器的影响:

无论对可变类型或者对不可变类型使用mutableCopy操作,都不会影响源对象的引用计数

除了对一个不可变的类型进行拷贝操作外会对源对象的引用计数+1,其他情况(无论是对可变类型或不可变类型 进行拷贝或者可变拷贝)都会生成一个全新的对象(对象的引用计数器从0到1),都不会影响源对象的引用计数。**

copy一般不会对源对象的引用计数+1,即使对自定义的对象(实现了NSCopying协议)进行copy也不会影响源对象的引用计数,除了 [不可变类型 copy]例外,因为这种情况特殊,因为:对一个不可变类型的对象copy之后也是不可变的类型,既然不可变也没法修改,再生成一份新的对象感觉在内存上有些浪费,还不如对源对象的引用计数+1,这样既达到节约内存的目的,也不破坏内存管理规则。copy会是新对象的引用计数器值为1**

重要的事情所三遍,再一次强调一下一种特殊情况,对 不可变类型 copy操作会,是一种指针拷贝,是浅拷贝,源对象和新对象会指向同一块内存地址,copy之后会是引用计数器+1

测试代码:

#import <Foundation/Foundation.h>
#import "User.h"


// copy 不可变的对象,OC对该类型的操作进行了特殊优化
// copy 会得到一个全新的对象,新的内存地址,但是对于[NSArray copy]操作,并没有这样,这是因为系统对此进行了优化,减少内存的开销,但这样并不影响内存管理规则的使用,不用开辟新内存,只要要增加一个引用计数即可,注意系统只是对不可变类型 的 不可变复制 进行了优化,是的copy能使得引用计数+1,但并不使用与其他情况,包括自定义对象这种情况,也不能得出copy能使引用计数+1,内存法则中是说如果对一个对象copy,那么新的对象的计数将为1,是说的新对象,而不是源对象, 所以说copy并不能使源对象应用计数+1(不包括[NSArray copy]这种情况), 只能将新对象的引用计数置为1
void copyNotMutableType() {
    // 不可变数组
    NSArray *array = [NSArray arrayWithObjects:@"1", @"2", @"3", nil];
    NSLog(@"【array】 retainCount:%ld", [array retainCount]);  // 1
    
    NSArray *array2 = [array copy];
    NSLog(@"【array】 retainCount:%ld", [array retainCount]);  // 2
    
    // 难道copy也把对象的引用数量也拷贝了, retainCount也是该对象的一个实例变量,拷贝整个对象当然也会复制了该变量,所以和array 的retainCount的数一样
    NSLog(@"《array2》 retainCount:%ld", [array2 retainCount]); // 2
    
    [array2 retain];
    NSLog(@"《array2》 retainCount:%ld", [array2 retainCount]); // 3
    NSLog(@"【array】 retainCount:%ld", [array retainCount]);   // 3
    
    if (array == array2) {
        NSLog(@"同一个对象");
    }
    
    NSLog(@"【array】:%@", array);      // [1, 2, 3]
    NSLog(@"《array2》:%@", array2);    // [1, 2, 3]
    
    [array2 release];
    [array2 release];
    [array release];
}


// 对可变类型copy,不影响可变变量的引用计数
void copyMutableType() {
    NSMutableArray *mutableArray = [NSMutableArray arrayWithObjects:@"1", @"2", @"3", nil];
    NSLog(@"【mutableArray】 retainCount:%ld", [mutableArray retainCount]);   // 1
    [mutableArray retain];
    NSLog(@"【mutableArray】 retainCount:%ld", [mutableArray retainCount]);   // 2
    
    NSArray *array = [mutableArray copy];
    NSLog(@"【mutableArray】 retainCount:%ld", [mutableArray retainCount]);  // 2:对可变类型copy不会影响引用计数器
    NSLog(@"【array】 retainCount:%ld", [array retainCount]);  // 1
    
    [mutableArray addObject:@"4"];
    NSLog(@"mutableArray:%@", mutableArray);    // [1, 2, 3, 4]
    NSLog(@"array:%@", array);                  // [1, 2, 3]
}


// mutableCopy 可变拷贝一个非可变类型的对象 不会影响源对象的引用计数
void mutableCopyNotMutableType() {
    NSArray *array = [NSArray arrayWithObjects:@"1", @"2", @"3", nil];
    NSLog(@"【array】 retainCount:%ld", [array retainCount]);  // 1
    [array retain];
    NSLog(@"【array】 retainCount:%ld", [array retainCount]);  // 2
    NSMutableArray *mutableArray = [array mutableCopy];
    NSLog(@"【array】 retainCount:%ld", [array retainCount]);  // 2
    NSLog(@"《mutableArray》 retainCount:%ld", [mutableArray retainCount]);  // 1
    
    [mutableArray addObject:@"4"];
    NSLog(@"mutableArray:%@", mutableArray);    // [1, 2, 3, 4]
    NSLog(@"array:%@", array);                  // [1, 2, 3]
}

//
void mutableCopyMutableType(){
    NSMutableArray *mutableArray = [NSMutableArray arrayWithObjects:@"1", @"2", @"3", nil];
    NSLog(@"【mutableArray】 retainCount:%ld", [mutableArray retainCount]);   // 1
    [mutableArray retain];
    NSLog(@"【mutableArray】 retainCount:%ld", [mutableArray retainCount]);   // 2
    
    NSMutableArray *mutableCopyArray = [mutableArray mutableCopy];
    NSLog(@"【mutableArray】 retainCount:%ld", [mutableArray retainCount]);   // 2
    NSLog(@"《mutableCopyArray》retainCount:%ld", [mutableCopyArray retainCount]);  // 1
    
    [mutableArray addObject:@"4"];
    NSLog(@"mutableArray:%@", mutableArray);            // [1, 2, 3, 4]
    NSLog(@"mutableCopyArray:%@", mutableCopyArray);    // [1, 2, 3]
}


void test(){
    NSArray *array = [NSArray arrayWithObjects:@"1", @"2", @"3", nil];
    NSLog(@"【array】 retainCount:%ld", [array retainCount]);
    [array copy];
    NSLog(@"【array】 retainCount:%ld", [array retainCount]);
}

void testObject(){
    User *user = [[User alloc] init];
    user.username = @"zhangsan";
    user.age = 25;
    NSLog(@"【user】 retainCount:%ld", [user retainCount]);
    
    // copy 自定义对象难到引用计数不+1?
    User *copyUser = [user copy];
    NSLog(@"【[user]】 retainCount:%ld", [user retainCount]);
    
    NSLog(@"【copyUser】 retainCount:%ld", [copyUser retainCount]);
    
    copyUser.username = @"lisi";
    NSLog(@"copyUser:%@", copyUser);
    NSLog(@"user:%@", user);
    
    [copyUser release];// 遵守内存管理规则
    [user release];
}
int main(int argc, const char * argv[]) {
//    Person *xiaohong = [[Person alloc] init];
//    NSString *name = @"xiaohong";
//    [xiaohong setName:name];
//    [xiaohong setName:name];
//    [xiaohong setName:name];
//    NSLog(@"4 retainCount : %ld", [[xiaohong name] retainCount]);
    
    copyNotMutableType();
    copyMutableType();
    mutableCopyNotMutableType();
    mutableCopyMutableType();
    test();
    testObject();
    return 0;
}
#import <Foundation/Foundation.h>
@interface User : NSObject <NSCopying>

@property (copy, nonatomic) NSString *username;
@property (assign, nonatomic) int *age;

@end


#import "User.h"
@implementation User

- (id)init {
    if (self = [super init]) {
        
    }
    
    return self;
}


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

推荐阅读更多精彩内容