iOS - copy和mutableCopy你真的會用么?

前言

1.深淺拷貝

2.copy 和 mutableCopy 介紹和用法。

3.為什么修飾block用copy?

4.聲明NSArray 和 NSMutableArray變量時,哪個更適合用copy修飾?

5.總結

一、深淺拷貝

1.什么是淺拷貝、什么是深拷貝

深拷貝 : 拷貝出來的對象與源對象地址不一致! 這意味著我修改拷貝對象的值對源對象的值沒有任何影響.
淺拷貝 : 拷貝的是指針地址,拷貝出來的對象與源對象地址一致! 這意味著我修改拷貝對象的值會直接影響到源對象.

2.網上有一些錯誤的觀點:
copy就是淺拷貝, mutableCopy就是深拷貝
事實上copy也可以是深拷貝。可變對象進行copy,會產生新的對象地址,而不是新的指針地址。,mutableCopy也未必是深復制。我會在下面的例子中說明。

3.針對NSArray、NSDictionary、NSSet容器類型的對象,深拷貝可分為:"不完全深拷貝""完全深拷貝"。
不完全深拷貝:拷貝出來的容器是新的對象,但是容器里面的對象還是原來對象。
完全深拷貝:拷貝出來的容器是新的對象,容器里面的對象也是新對象。

二、copy 和 mutableCopy介紹和用法

1.先看官檔是怎么說明copy的

copy (是NSCopying協議的方法)

"Returns the object returned by copyWithZone:"
翻譯:返回的對象是通過調用 copyWithZone: 這個方法返回的。

看了對copy的解釋,就可以知道調用copy實際上就是調用copyWithZone:這個方法。在我沒有貼出來的官檔里也說了copy就是copyWithZone:簡寫,為了更方便調用。為了知道copy的用法,
那我們就要知道copyWithZone:的用法。這個方法的官檔如下:

copyWithZone:

"Returns a new instance that’s a copy of the receiver."
翻譯:返回一個新的實例,這個實例是接收器的副本。

再看看官方文檔對這個方法的討論
"The returned object is implicitly retained by the sender, who is responsible for releasing it. The copy returned is immutable if the consideration “immutable vs. mutable” applies to the receiving object; otherwise the exact nature of the copy is determined by the class."

大意:發送者隱式的保留這個返回的對象,同時也負責這個返回對象的釋放工作。無論接受器的對象是"可變的"或則"不可變的",使用這個方法,返回的對象都是不可變的。否則,拷貝對象的確切的特性由被拷貝的對象的類決定。

在通俗的解釋下,一般情況下,使用copy拷貝的對象都是不可變的,無論是對可變對象拷貝還是對不可變對象拷貝。最后一句話說copy的具體特性由被拷貝的對象決定,就是說有可能copy的對象是可變。下面會有栗子。

2.再看看mutableCopy的官方文檔說明

mutableCopy(是NSMutableCopying協議的方法)

"A protocol that mutable objects adopt to provide functional copies of themselves."
可變對象采用的協議,用于提供自身的功能副本。

mutableCopy和copy很相似。mutableCopy是mutableCopyWithZone:簡寫形式。所以我們再看看mutableCopyWithZone:官方文檔是怎樣描述的。

mutableCopyWithZone:

Returns a new instance that’s a mutable copy of the receiver.
返回一個新的實例,這是一個可變的接收器的副本。
再看看官方文檔對這個方法的討論
"The returned object is implicitly retained by the sender, which is responsible for releasing it. The copy returned is mutable whether the original is mutable or not."
大意:發送者隱式的保留這個返回的對象,同時也負責這個返回對象的釋放工作。無論接受器的對象是"可變的"或則"不可變的",使用這個方法,返回的對象都是可變的。

只有定義“不可變與可變”區別的類才應采用此協議(NSMutableCopying協議)也即只有定義不可變與可變區別的類,才可以使用mutableCopy。

舉例說明用法:

  1. NSString 、NSMutableString 的copy和mutableCopy
- (void)copyFunction {
    //不可變字符串
    NSString *imutableString = @"這是一個不可變字符串";
    id imutableString_copy = [imutableString copy];
    id imutableString_mutableCopy = [imutableString mutableCopy];

    //可變字符串
    NSMutableString *mutableStr = [[NSMutableString alloc] initWithString:@"這是一個可變字符串"];
    id mutableStr_copy = [mutableStr copy];
    id mutableStr_mutableCopy = [mutableStr mutableCopy];
}

控制臺系統輸出如下:

image
image

從控制臺的輸出信息,可以看出對于不可變和可變字符串的copy和mutableCopy的規律:

NSString 類型的字符串:string

[string copy]------------------------------->NSString類型的 (淺拷貝
[string mutableCopy]-------------------->NSMutableString類型 (深拷貝

NSMutableString類型的字符串:mString

[mString copy]------------------------------->NSMutableString類型的 ( 深拷貝
[mString mutableCopy]-------------------->NSMutableString類型 (深拷貝

2.容器類型以數組舉例:NSArray 、NSMutableArray 的copy和mutableCopy
先來看看數組中的元素是OC系統定義的類

- (void)copyArray
{
    NSDate *a1 = [NSDate date];
    NSDate *a2 = [NSDate date];
    //不可變數組
    NSArray *arr = [NSArray arrayWithObjects:a1,a2, nil];
    id arr_copy = [arr copy];
    id arr_mutableCopy = [arr mutableCopy];
    //可變數組
    NSMutableArray *mArr = [NSMutableArray arrayWithObjects:a1,a2, nil];
    id mArr_copy = [mArr copy];
    id mArr_mutableCopy = [mArr mutableCopy];
}

image
image

從控制臺的輸出信息,可以看出對于不可變和可變數組的copy和mutableCopy的規律:

NSArray類型的數組:arr

[arr copy]------------------------------->NSArray類型 (淺拷貝
[arr mutableCopy]-------------------->NSMutableArray類型 (深拷貝

NSMutableArray類型的數組:mArr

[mArr copy]------------------------------->NSArray類型 ( 深拷貝
[mArr mutableCopy]-------------------->NSMutableArray類型 (深拷貝
通過字符串和數組的事例,可以看出copy和mutableCopy的用法

非容器類總結
對象類型 不可變對象 可變對象
copy 淺拷貝 深拷貝
mutableCopy 深拷貝 深拷貝
容器類型總結
對象類型 不可變對象 可變對象
copy 淺拷貝 深拷貝
mutableCopy 深拷貝 深拷貝

對于容器類型的深拷貝可細分為:不完全深拷貝和完全深拷貝
這里大家一定要注意,不同對象調用copy得到結果不一樣。正如官方文檔所述那樣:"否則,拷貝對象的確切的特性由被拷貝的對象的類決定。"

總之,大家要記住一句話,copy一般情況下是淺拷貝,但是在一些情況下,copy又是深拷貝。

下面又是一個例子證明:
這次數組里面的元素是自定義類型的User對象

- (void)copyArray
{
    User *u1 = [[User alloc] init];
    u1.name = @"小明";
    u1.professional = @"教授";
    u1.age = @(32);
    u1.hobbies = @"看書";

    User *u2 = [[User alloc] init];
    u2.name = @"張三";
    u2.professional = @"歌手";
    u2.age = @(23);
    u2.hobbies = @"唱歌";
    //不可變數組
    NSArray *arr = [NSArray arrayWithObjects:u1,u1, nil];
    id arr_copy = [arr copy];
    id arr_mutableCopy = [arr mutableCopy];
    //可變數組
    NSMutableArray *mArr = [NSMutableArray arrayWithObjects:u1,u2, nil];
    id mArr_copy = [mArr copy];
    id mArr_mutableCopy = [mArr mutableCopy];
}

image
image

從上圖可以看到無論是不可變數組還是可變數組的copy,copy產生的對象都是新的對象,而且是不可變類型的,并且是完全深拷貝。數組里面的元素對象都是新的。


三、為什么block使用copy?

block是一個對象, 所以block理論上是可以retain/release的. 但是block在創建的時候它的內存是默認是分配在棧(stack)上, 而不是堆(heap)上的. 所以它的作用域僅限創建時候的當前上下文(函數, 方法...), 當你在該作用域外調用該block時, 程序就會崩潰.
其實block使用copy是MRC時代留下來的傳統。 在MRC下, 在方法中的block創建在棧區, 使用copy就能把他放到堆區, 這樣在作用域外調用該block程序就不會崩潰. 但在ARC下, 使用copy與strong其實都一樣, 因為block的retain就是用copy來實現的。之所以大家都習慣用copy就是MRC時代留下的習慣。


四.聲明NSArray 和 NSMutableArray變量時,哪個更適合用copy修飾?

NSArray和NSMutableArray用strong和copy修飾區別:

- (void)copyArray
{
    User *u1 = [[User alloc] init];
    u1.name = @"小明";
    u1.professional = @"教授";
    u1.age = @(32);
    u1.hobbies = @"看書";

    User *u2 = [[User alloc] init];
    u2.name = @"張三";
    u2.professional = @"歌手";
    u2.age = @(23);
    u2.hobbies = @"唱歌";
    //可變數組
    NSMutableArray *mArr = [NSMutableArray arrayWithObjects:u1,u2, nil];
    //一個可變數組賦值給分別用strong和copy修飾的不可變數組。
    //@property (nonatomic, strong) NSArray *arr_strong;
    //@property (nonatomic, copy) NSArray *arr_copy;
    self.arr_strong = mArr;
    self.arr_copy = mArr;
    [mArr addObject:@"嗯哼~"];

    //@property (nonatomic, strong) NSMutableArray *mArray_strong;
    //@property (nonatomic, copy) NSMutableArray *mArr_copy;
    NSMutableArray *mArr1 = [NSMutableArray arrayWithObjects:u1,u2, nil];
    self.mArray_strong = mArr1;
    self.mArr_copy = mArr1;
    [mArr1 addObject:@"天亮啦~"];
}

下面是對應的結果

(lldb) po self.arr_strong
<__NSArrayM 0x6040004449b0>(
<User: 0x604000444710>,
<User: 0x604000444ad0>,
嗯哼~
)

(lldb) po self.arr_copy
<__NSArrayI 0x60400022cc60>(
<User: 0x604000444710>,
<User: 0x604000444ad0>
)

(lldb) po self.mArray_strong
<__NSArrayM 0x6040004447d0>(
<User: 0x604000444710>,
<User: 0x604000444ad0>,
天亮啦~
)

(lldb) po self.mArr_copy
<__NSArrayI 0x60400022cd40>(
<User: 0x604000444710>,
<User: 0x604000444ad0>
)

1.沒有對比,就不知道真相。通過對比可以看出,如果使用strong來修飾NSArray類型的數組,當array的數組被賦值了可變數組對象時,當可變數組改變時,NSArray數組里的對象也會跟著改變,這是我們不想要的結果。使用copy修飾,在被賦值可變數組時,會生成一個新的不可變數組對象,這樣可變數組之后怎樣變化,都不會影響NSArray類型的數組對象。

2.再看看使用strong來修飾NSMutableArray類型的數組,當mArray的數組被賦值了可變數組對象時,當可變數組改變時,NSMutableArray數組里的對象也會跟著改變,這是符合我們預期的。當使用copy修飾后,被賦值后,會生成一個新的不可變數組對象。這樣我們還以為它是可變類型的數組,然后使用增刪改查,就會crash,也談不上可以改變數組對象了。

3.綜上,用property聲明NSArray數組時,最好使用copy。用property聲明NSMutableArray數組時,最好使用strong。如果使用copy,又self.mArr來賦值,后面增刪改查,程序肯定會crash的。


五、總結

1.copy的用法,不能一概而論。不同類型的類使用copy,結果可能都不一樣。需要自行實驗得出結論。
2. copy可能是淺拷貝,也可能是深拷貝。mutableCopy都是深拷貝。
3.用property聲明NSArray數組或者NSMutableArray數組時,注意修飾的關鍵詞,使用strong還是copy。
4.對于自定義的對象,要使用copy。需要重寫copyWithZone:這個方法。

如下栗子:

- (nonnull id)copyWithZone:(nullable NSZone *)zone {

    User *user = [[User allocWithZone:zone] init];
    user.name = self.name;
    user.professional = self.professional;
    user.age = self.age;
    user.hobbies = self.hobbies;
    return user;
}

如有不正確的地方,還請大家指出來。歡迎大家交流學習

作者:ildream
鏈接:http://www.lxweimin.com/p/8c158bab0f0c
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯系作者獲得授權并注明出處。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 前言 1.深淺拷貝 2.copy 和 mutableCopy 介紹和用法。 3.為什么修飾block用copy? ...
    ildream閱讀 10,922評論 23 35
  • Swift1> Swift和OC的區別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴謹 對...
    cosWriter閱讀 11,145評論 1 32
  • 淺拷貝與深拷貝 淺拷貝:指針拷貝,不產生新的對象,源對象的引用計數器+1 深拷貝:對象拷貝,會產生新的對象,源對象...
    SkyMing一C閱讀 780評論 0 6
  • 前言 不敢說覆蓋OC中所有copy的知識點,但最起碼是目前最全的最新的一篇關于 copy的技術文檔了。后續發現有新...
    zyydeveloper閱讀 3,422評論 4 35
  • 關于iOS中對象的深拷貝和淺拷貝的文章有很多,但是大部分都是基于打印內存地址來推導結果,這篇文章是從源碼的角度來分...
    雪山飛狐_91ae閱讀 960評論 1 5