談?wù)凮bjective-C的對(duì)象拷貝

通常我們?cè)谑褂聾property聲明屬性的時(shí)候,對(duì)于NSStringNSArrayNSDictionary經(jīng)常會(huì)使用copy,以及block的時(shí)候也會(huì)使用copy,接下來(lái)就是和所說(shuō)copy和mutableCopy。先來(lái)思考幾個(gè)問(wèn)題:

  1. copy與mutableCopy有什么區(qū)別?
  2. 使用copy/mutableCopy和直接賦值有什么區(qū)別?
  3. 深淺拷貝的區(qū)別?
  4. 自定義對(duì)象如何實(shí)現(xiàn)NSCopying協(xié)議?
  5. block為什么需要使用copy?

copy和mutableCopy

在需要復(fù)制對(duì)象的時(shí)候,會(huì)用到NSObject類提供的copy和mutableCopy方法,通過(guò)這兩個(gè)方法即可復(fù)制已有對(duì)象的副本。最常用的是賦值NSString、NSArray、NSDictionary這一類對(duì)象,那么copy和mutableCopy究竟是什么?它們有何區(qū)別?

copy

copy拷貝出來(lái)的對(duì)象類型總是不可變類型(例如, NSString, NSArray, NSDictionary等等)

mutableCopy

mutableCopy拷貝出來(lái)的對(duì)象類型總是可變類型(例如, NSMutableString, NSMutableArray, NSMutableDictionary等等)

代碼舉例:

    NSString * str = @"hello world";
    [str copy]; // 拷貝出內(nèi)容為hello world的NSString類型的字符串
    [str mutableCopy]; // 拷貝出內(nèi)容為hello world的NSMutableString類型的字符串

打印出類名:


image

同樣的,對(duì)于不可變的NSArray和可變的NSMutableArray來(lái)說(shuō),這樣的關(guān)系總是成立的:

[NSMutableArray copy] => NSArray
[NSArray mutableCopy] => NSMutableArray

使用copy/mutableCopy和直接賦值有什么區(qū)別?

先看一個(gè)例子

    NSMutableArray * arr1 = [NSMutableArray array];
    [arr1 addObject:@"A"];
    
    NSArray * arr2 = [NSArray array];
    arr2 = arr1;
    
    [arr1 addObject:@"C"];
    
    NSLog(@"arr1 = %@", arr1);
    NSLog(@"arr2 = %@", arr2);

這段代碼輸入如下:


image

arr1是可變數(shù)組,arr2是一個(gè)不可變數(shù)組,明明可變數(shù)組添加對(duì)象在賦值之后,arr2也被影響到了。

如果將arr2 = arr1修改為如下:

arr2 = [arr1 copy];

然后輸出就正常了


image

這是為什么呢?

原因其實(shí)是和OC的多態(tài)特性有關(guān),表面上arr2是一個(gè)NSArray類型的對(duì)象,實(shí)際上是指向一個(gè)NSMutableArray類型的對(duì)象,也就是arr1
我們通過(guò)打印arr1arr2兩個(gè)對(duì)象來(lái)看就知道了:

  • 在直接賦值的方式下打?。?/p>

    image
  • 在使用copy的方式下打印:

    image

一目了然,直接賦值之后,arr1arr2完全就是同一個(gè)對(duì)象,指向同一個(gè)地址,所以賦值之后再給arr1添加對(duì)象,打印出的結(jié)果肯定也是一樣的。而如果使用copy之后賦值,就是兩個(gè)完全不一樣的對(duì)象,后續(xù)的操作也不會(huì)有影響。


深拷貝(deep copy)與淺拷貝(shallow copy)的區(qū)別?

首先得清楚什么是深拷貝和淺拷貝?

深拷貝

拷貝出來(lái)的對(duì)象與原對(duì)象地址不一致,修改拷貝對(duì)象的值對(duì)源對(duì)象的值沒(méi)有任何影響。 深拷貝是直接拷貝整個(gè)對(duì)象內(nèi)容到另一塊內(nèi)存中。

淺拷貝

拷貝出來(lái)的對(duì)象與原對(duì)象地址一致,修改拷貝對(duì)象的值會(huì)直接影響源對(duì)象的值。

可以用一句話總結(jié):淺復(fù)制就是指針拷貝;深復(fù)制就是內(nèi)容拷貝

或許會(huì)聽(tīng)過(guò)這樣的說(shuō)法:copy都是淺拷貝, mutableCopy都是深拷貝
這種淺顯的理解是錯(cuò)誤的,可以看到之前使用copy的方式下打印出來(lái)的對(duì)象的地址是不一樣的,是深拷貝,這說(shuō)明用從一個(gè)可變對(duì)象copy出一個(gè)不可變對(duì)象時(shí), 是深拷貝而不是淺拷貝。

在Foundation框架中,所有的collectioon類在默認(rèn)的情況下都執(zhí)行淺拷貝,也就是說(shuō)只拷貝容器對(duì)象本身,不復(fù)制其中的數(shù)據(jù)。這樣做的目的是,容器內(nèi)的對(duì)象未必都能拷貝,而且調(diào)用者也未必想在拷貝容器時(shí)一并拷貝其中的某個(gè)對(duì)象。

不過(guò)通常情況下,執(zhí)行的都是淺拷貝,如果你所寫(xiě)的對(duì)象需要深拷貝,那么可以考慮增加一個(gè)專門執(zhí)行深拷貝的方法。


自定義對(duì)象如何實(shí)現(xiàn)NSCopying協(xié)議

雖然copy方法是在NSObject中的,如果我們自定義一個(gè)類(比如Person),向該類的對(duì)象發(fā)送copy消息,會(huì)得到如下結(jié)果:

    Person *p = [[Person alloc] init];
    Person *p2 = [p copy];
image

查看蘋(píng)果官方文檔會(huì)發(fā)現(xiàn),如果自定義的類要實(shí)現(xiàn)copy功能,需要實(shí)現(xiàn)copyWithZone方法,(如果想要區(qū)分copy和mutableCopy,那么copyWithZone:應(yīng)該返回不可變副本,而mutableCopyWithZone:應(yīng)該返回可變副本)。這個(gè)時(shí)候可以在Person類中添加如下代碼:

@implementation Person

- (instancetype)copyWithZone:(NSZone *)zone {
    Person *p = [[Person alloc] init];
    p.age = self.age;
    p.name = self.name;
    return p;
}

之后就不會(huì)報(bào)錯(cuò),能正常的使用copy了。

但是在蘋(píng)果官方文檔上還說(shuō)了一個(gè)注意事項(xiàng):

If a subclass inherits NSCopying from its superclass and declares additional instance variables, the subclass has to override copyWithZone: to properly handle its own instance variables, invoking the superclass’s implementation first.

意思是:如果你的類可以產(chǎn)生子類,那么copyWithZone:方法將被繼承,子類中也必須重寫(xiě)copyWithZone:方法,并且要先調(diào)用父類的copyWithZone:。

這個(gè)時(shí)候在demo中增加一個(gè)Person的子類,并增加一個(gè)college屬性,那么父類Person的copyWithZone:方法需要改為:

- (instancetype)copyWithZone:(NSZone *)zone {
    Person *p = [[[self class] allocWithZone:zone] init];
    p.age = self.age;
    p.name = self.name;
    return p;
}

同時(shí)在子類Student中可以這樣實(shí)現(xiàn):

@implementation Student

- (instancetype)copyWithZone:(NSZone *)zone {
    Student *stu = [super copyWithZone:zone];
    if (stu) {
        stu.college = self.college;
    }
    return stu;
}

@end

如果實(shí)現(xiàn)一個(gè)類的copyWithZone:方法,而該類的超類也實(shí)現(xiàn)了<NSCopying>協(xié)議,那么應(yīng)該先調(diào)用超類的copy方法以復(fù)制繼承來(lái)的實(shí)例變量,然后加入自己的代碼以復(fù)制想要添加到該類中的任何附加的實(shí)例變量。


block中為什么要使用copy修飾?

在使用block作為屬性的時(shí)候,通常使用的是copy

@property (copy) void (^clickBlock)(NSString * name);

使用copy修飾block其實(shí)是從MRC遺留下來(lái)的,在MRC時(shí)期,作為全局變量的block在初始化時(shí)是被存放在棧區(qū)的,這樣在使用時(shí)如果block內(nèi)有調(diào)用外部變量,那么block無(wú)法保留其內(nèi)存,如果在出了block的初始化作用域內(nèi)使用,就會(huì)引起崩潰,使用copy可以將block的內(nèi)存推入堆中,這樣讓其擁有保存調(diào)用的外部變量的內(nèi)存的能力。

在ARC下,對(duì)NSStackBLock用strong進(jìn)行強(qiáng)引用的話,好像會(huì)自動(dòng)對(duì)其進(jìn)行copy一份,變成NSMallocBLock,所以不會(huì)crash。在ARC下,其實(shí)不使用copy修飾block也是可以的。

詳細(xì)的block的實(shí)現(xiàn)可以參考唐巧關(guān)于block的講解:談Objective-C block的實(shí)現(xiàn)


blog

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 本文為轉(zhuǎn)載: 作者:zyydeveloper 鏈接:http://www.itdecent.cn/p/5f776a...
    Buddha_like閱讀 1,019評(píng)論 0 2
  • 1.ios高性能編程 (1).內(nèi)層 最小的內(nèi)層平均值和峰值(2).耗電量 高效的算法和數(shù)據(jù)結(jié)構(gòu)(3).初始化時(shí)...
    歐辰_OSR閱讀 30,235評(píng)論 8 265
  • 前言 不敢說(shuō)覆蓋OC中所有copy的知識(shí)點(diǎn),但最起碼是目前最全的最新的一篇關(guān)于 copy的技術(shù)文檔了。后續(xù)發(fā)現(xiàn)有新...
    zyydeveloper閱讀 3,714評(píng)論 4 35
  • 下午閑來(lái)無(wú)事參加教育+大數(shù)據(jù)+人工智能的主題會(huì),見(jiàn)到幾個(gè)老朋友,順帶聊了一下,獲益匪淺: 一、直達(dá)需求場(chǎng)景的根本(...
    一百八十斤大胖子閱讀 469評(píng)論 0 1
  • 一.安裝redis 下載redis安裝包 可去官網(wǎng)http://redis.io ,也可通過(guò)wget命令,wget...
    bboymonk閱讀 1,296評(píng)論 0 1

友情鏈接更多精彩內(nèi)容