你好,歡迎來到IOS教程網

 Ios教程網 >> IOS編程開發 >> IOS開發綜合 >> iOS開發——深拷貝與淺拷貝詳解

iOS開發——深拷貝與淺拷貝詳解

編輯:IOS開發綜合

深拷貝和淺拷貝這個問題在面試中常常被問到,而在實際開發中,只要稍有不慎,就會在這裡出現問題。尤其對於初學者來說,我們有必要來好好研究下這個概念。我會以實際代碼來演示,相關示例代碼上傳至 這裡 。

首先通過一句話來解釋:深拷貝就是內容拷貝,淺拷貝就是指針拷貝。

深拷貝就是拷貝出和原來僅僅是值一樣,但是內存地址完全不一樣的新的對象,創建後和原對象沒有任何關系。淺拷貝就是拷貝指向原來對象的指針,使原對象的引用計數+1,可以理解為創建了一個指向原對象的新指針而已,並沒有創建一個全新的對象。

(1)非容器類對象的深拷貝、淺拷貝

 

 // 字符串是直接賦值的,右側如果是copy,那麼就是淺拷貝;右側如果是mutableCopy,那麼就是深拷貝。
    NSString *string1 = @"helloworld";
    NSString *string2 = [string1 copy]; // 淺拷貝
    NSString *string3 = [string1 mutableCopy]; // 深拷貝
    NSMutableString *string4 = [string1 copy]; // 淺拷貝
    NSMutableString *string5 = [string1 mutableCopy]; // 深拷貝

    NSLog(@"string1 = %d;string2 = %d",string1,string2);
    NSLog(@"string1 = %d;string3 = %d",string1,string3);
    NSLog(@"string1 = %d;string4 = %d",string1,string4);
    NSLog(@"string1 = %d;string5 = %d",string1,string5);

打印結果如下:

 

\

 

我這裡用%d格式化打印出字符串對象的內存地址。我這裡主要通過內存地址來判斷是深拷貝還是淺拷貝,在該案例中,無論是深拷貝還是淺拷貝,字符串對象的值都是一樣的,所以暫且就不打印值了,而只打印地址。這裡的原字符串是直接賦值的,可以發現,右側如果使用copy,那麼打印出的內存地址是一樣的,表示是淺拷貝,只拷貝出了指向原對象的指針,沒有創建出新的對象。如果右側使用mutableCopy,那麼打印出的不一樣的內存地址,表示創建了一個新的對象,是深拷貝。

簡單說明一下什麼是非容器類對象,像NSString,NSNumber這些不能包含其他對象的叫做非容器類。如NSArray和NSDictionary可以容納其他對象的叫做容器類對象。

做一個小小的總結:

-- 淺拷貝類似retain,引用計數對象+1.創建一個指針;

-- 深拷貝是真正意義上的拷貝,是創建一個新對象。copy屬性表示兩個對象內容相同,新的對象retain為1,與原對象的引用計數無關,原對象沒有改變。copy減少對象對上下文的依賴。

-- retain屬性表示兩個對象地址相同(建立一個指針,指針拷貝),內容當然相同,這個對象的retain值+1.也就是說,retain是指針拷貝,copy是內容拷貝。

 

(2)改變字符串的創建方式,使用stringWithFormat創建字符串,而不是直接賦值

 

 // 結果同上。右側如果是copy,那麼就是淺拷貝;右側如果是mutableCopy,那麼就是深拷貝。
    NSString *string1 = [NSString stringWithFormat:@"helloworld"];
    NSString *string2 = [string1 copy]; // 淺拷貝
    NSString *string3 = [string1 mutableCopy]; // 深拷貝
    NSMutableString *string4 = [string1 copy]; // 淺拷貝
    NSMutableString *string5 = [string1 mutableCopy]; // 深拷貝

    NSLog(@"string1 = %d;string2 = %d",string1,string2);
    NSLog(@"string1 = %d;string3 = %d",string1,string3);
    NSLog(@"string1 = %d;string4 = %d",string1,string4);
    NSLog(@"string1 = %d;string5 = %d",string1,string5);

打印結果如下:

 

\

 

結果同(1)。因為都是不可變字符串,創建方式並不影響拷貝方式。所以可以得出結論,對於字符串這類非容器類對象,copy是淺拷貝,mutable是深拷貝。

 

 

(3)以上測試的都是不可變字符串,這裡來嘗試下可變字符串

 

 // 如果是一個MutableString,那麼無論是copy,mutableCopy,都會創建一個新對象。
    NSMutableString *string1 = [NSMutableString stringWithString:@"helloworld"];
    NSString *string2 = [string1 copy]; // 深拷貝
    NSString *string3 = [string1 mutableCopy]; // 深拷貝
    NSMutableString *string4 = [string1 copy]; // 深拷貝
    NSMutableString *string5 = [string1 mutableCopy]; // 深拷貝

    NSLog(@"string1 = %d;string2 = %d",string1,string2);
    NSLog(@"string1 = %d;string3 = %d",string1,string3);
    NSLog(@"string1 = %d;string4 = %d",string1,string4);
    NSLog(@"string1 = %d;string5 = %d",string1,string5);

 

 

打印結果如下:

\

 

可以看到,對於MutableString,右側無論是copy還是mutableCopy,都會創建一個新對象,都是屬於深拷貝。
 

現在我們對以上代碼做一點修改:

 

 // 如果是一個MutableString,那麼無論是copy,mutableCopy,都會創建一個新對象。
    NSMutableString *string1 = [NSMutableString stringWithString:@"helloworld"];
    NSString *string2 = [string1 copy]; // 深拷貝
    NSString *string3 = [string1 mutableCopy]; // 深拷貝
    NSMutableString *string4 = [string1 copy]; // 深拷貝
    NSMutableString *string5 = [string1 mutableCopy]; // 深拷貝

    NSLog(@"string1 = %d;string2 = %d",string1,string2);
    NSLog(@"string1 = %d;string3 = %d",string1,string3);
    NSLog(@"string1 = %d;string4 = %d",string1,string4);
    NSLog(@"string1 = %d;string5 = %d",string1,string5);

    // 增加以下代碼
    [string4 appendString:@"MMMMMM"];
    [string5 appendString:@"11111"];

    NSLog(@"string4 = %@",string4);
    NSLog(@"string5 = %@",string5);

我增加了四行代碼,我想要檢驗一下對一個可變字符串執行copy和mutableCopy後,返回的字符串是否可變。同時為了檢驗在哪一行出現crash,我提前打一個異常斷點。對於NSMutableString有appendString方法,而NSString沒有appendString方法,所以這種檢驗應該是沒有問題的。運行代碼後,會在[string4 apendString:@“MMMMM”];這一行出現崩潰。經過實際測試,可得出以下結論:

 

-- 對於可變對象的復制,都是深拷貝;

-- 可變對象copy後返回的對象是不可變的,mutableCopy後返回的對象是可變的。

 

(4)我們上面提到的都是系統類,如果是我們自定義的類,復制結果又是怎樣呢?我下面定義一個Person類,實現如下:

Person.h

 

#import 

@interface Person : NSObject

@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger age;

@end


 

Person.m

 

#import "Person.h"

@interface Person()

@end

@implementation Person

// 對應copy方法
- (id)copyWithZone:(NSZone *)zone
{
    Person *person = [[Person allocWithZone:zone] init];
    person.name = self.name; // 這裡的self就是被copy的對象
    person.age = self.age;
    return person;
}

- (id)mutableCopyWithZone:(NSZone *)zone
{
    Person *person = [[Person allocWithZone:zone] init];
    person.name = self.name;
    person.age = self.age;
    return person;
}


@end

首先聲明一下為什麼要實現上述的copyWithZone和mutableCopyWithZone方法?其實在iOS中並不是所有的對象都支持copy、mutableCopy方法,只有遵守NSCopying協議的類可以發送copy消息,遵守NSMutableCopying協議的類才可以發送mutableCopy消息,否則就會崩潰。我們可以非常方便的使用系統類中的copy,mutableCopy方法,是因為這些系統類本身就實現了NSCopying,NSMutableCopy協議,大家可以進入接口文檔進行查看。

 

然後編寫測試代碼如下:

 

// Person類必須實現copyWithZone和mutableCopyWithZone方法。
    Person *person = [[Person alloc] init];
    person.name = @"Jack";
    person.age = 23;

    Person *copyPerson = [person copy]; // 深拷貝
    Person *mutableCopyPerson = [person mutableCopy]; // 深拷貝
    NSLog(@"person = %d;copyPerson = %d",person,copyPerson);
    NSLog(@"person = %d;mutableCopyPerson = %d",person,mutableCopyPerson);

打印結果如下:

 

\

可以看到無論是用copy還是mutableCopy,復制後都創建了一個新的對象。為什麼呢?因為我們本來就在copyWithZone: ,mutableCopyWithZone方法中創建了一個新對象啊,所以一點都不奇怪。

 

(5)從上述(4)中可以看到我的Person自定義對象在copy後是創建了一個全新的對象,那麼這個對象中的屬性呢?這些屬性是深拷貝還是淺拷貝呢?

 

 NSMutableString *otherName = [[NSMutableString alloc] initWithString:@"Jack"];
    Person *person = [[Person alloc] init];
    person.name = otherName;
    person.age = 23;

    [otherName appendString:@" and Mary"];
    NSLog(@"person.name = %@",person.name);

打印結果如下:

 

\

打印的結果是"Jack",而不是”Jack and Mary“. 說明在執行person.name = otherName;的時候是重新創建了一個字符串。但是請大家注意,這裡我name的屬性修飾符是copy,如果把copy改成strong會變成怎麼樣呢?

對,沒錯,name改為strong後答案就是”Jack and Mary“。所以在這裡我們可以得出以下小小的結論:

-- 因為這裡的otherName是可變的,person.name的屬性是copy,所以創建了新的字符串,屬於深拷貝;

-- 如果person.name設置為strong,那麼就是淺拷貝。因為strong會持有原來的對象,使原來的對象的引用計數+1,其實就是指針拷貝。

-- 當然,這裡用strong還是copy,取決於實際需求。

 

(6)與上面(5)類似,我現在修改代碼如下:

 

NSString *otherName = @"jack";
    Person *person = [[Person alloc] init];
    person.name = otherName;
    person.age = 23;

    NSLog(@"othername = %d;person.name = %d",otherName,person.name);

其實這裡很好理解,無論person.name是strong還是copy,都是指針拷貝。指向的都是otherName的內存地址。

 

現修改代碼如下:

 

 NSString *otherName = @"jack";
    Person *person = [[Person alloc] init];
    person.name = [otherName copy];
    person.age = 23;

    NSLog(@"othername = %d;person.name = %d",otherName,person.name);

同樣的,無論person.name是strong還是copy,都是指針拷貝。指向的都是otherName的內存地址。因為對不可變對象執行copy一般都是淺拷貝,這沒問題。

 

 

再次修改代碼如下:

 

 NSString *otherName = @"jack";
    Person *person = [[Person alloc] init];
    person.name = [otherName mutableCopy];
    person.age = 23;

    NSLog(@"othername = %d;person.name = %d",otherName,person.name);
無論person.name是strong還是copy,都是內容拷貝。創建一個全新的對象。因為對不可變對象執行mutableCopy一般都是深拷貝,也沒問題。

 

 

(7)講了這麼多非容器對象,最後我們來講講容器對象Array。先講不可變的容器對象

 

 NSArray *array01 = [NSArray arrayWithObjects:@"a",@"b",@"c", nil];
    NSArray *copyArray01 = [array01 copy];
    NSMutableArray *mutableCopyArray01 = [array01 mutableCopy];
    NSLog(@"array01 = %d,copyArray01 = %d",array01,copyArray01);
    NSLog(@"array01 = %d,mutableCopyArray01 = %d",array01,mutableCopyArray01);


    NSLog(@"array01[0] = %d,array01[1] = %d,array01[2] = %d",array01[0],array01[1],array01[2]);
    NSLog(@"copyArray01[0] = %d,copyArray01[1] = %d,copyArray01[2] = %d",copyArray01[0],copyArray01[1],copyArray01[2]);
    NSLog(@"mutableCopyArray01[0] = %d,mutableCopyArray01[1] = %d,mutableCopyArray01[2] = %d",mutableCopyArray01[0],mutableCopyArray01[1],mutableCopyArray01[2]);

打印結果如下:

 

\

 

我們可以得出以下結論:

-- copyArray01和array01指向的是同一個對象,包括裡面的元素也是指向相同的指針。

-- mutableCopyArray01和array01指向的是不同的對象,但是裡面的元素指向相同的對象,mutableCopyArray01可以修改自己的對象。

--copyArray01是對array01的指針復制(淺復制),而mutableCopyArray01是內容復制。

----------------------------------------------------------------------------------------------------------------------------------------------------------------------打個分割線

上面的是不可變容器NSArray,這裡測試下可變容器NSMutableArray:

 

NSMutableArray *array01 = [NSMutableArray arrayWithObjects:@"a",@"b",@"c", nil];
    NSArray *copyArray01 = [array01 copy];
    NSMutableArray *mutableCopyArray01 = [array01 mutableCopy];
    NSLog(@"array01 = %d,copyArray01 = %d",array01,copyArray01);
    NSLog(@"array01 = %d,mutableCopyArray01 = %d",array01,mutableCopyArray01);


    NSLog(@"array01[0] = %d,array01[1] = %d,array01[2] = %d",array01[0],array01[1],array01[2]);
    NSLog(@"copyArray01[0] = %d,copyArray01[1] = %d,copyArray01[2] = %d",copyArray01[0],copyArray01[1],copyArray01[2]);
    NSLog(@"mutableCopyArray01[0] = %d,mutableCopyArray01[1] = %d,mutableCopyArray01[2] = %d",mutableCopyArray01[0],mutableCopyArray01[1],mutableCopyArray01[2]);

打印的結果如下:

 

\

可得結論如下:

-- 容器對象和非容器對象類似,可變對象復制(copy,mutableCopy)的都是一個新對象;不可變對象copy是淺復制,mutableCopy是深復制。

-- 對於容器而言,元素對象始終是指針復制。

 

以上涉及的也只是一部分,想要對深拷貝、淺拷貝有更為深入的了解,必須首先要對內存管理較為熟悉,然後在實際開發中不斷去實踐,才會隨心所欲。

 

  1. 上一頁:
  2. 下一頁:
蘋果刷機越獄教程| IOS教程問題解答| IOS技巧綜合| IOS7技巧| IOS8教程
Copyright © Ios教程網 All Rights Reserved