天天看點

iOS Copy與MutableCopy 和 Copy與Strong 深度解析

開發中,資料處理是整個項目的重中之重,清晰的資料結構,安全高效的處理流程,能大大提高開發效率和系統的穩定性。資料是事物狀态和變化的記錄,具有可修改性和拷貝性,當多處使用,并有可能改變時,為了保障原資料的不變,我們需要拷貝一份新的資料,改變新的資料,而不改變原資料。資料進行中的,操作權限控制,資料的傳遞,資料的深、淺拷貝等。今天主要深度分析下,Copy與MutableCopy 和 Copy與Strong 差別及使用。

CSDN的排版有點糟心,抱歉。言歸正傳 》》》》》》》》》》

1 深淺拷貝的差別(Copy與MutableCopy)

 淺拷貝:指針拷貝,不産生新的對象,源對象的引用計數器+1;

 深拷貝:對象拷貝,會産生新的對象,源對象的引用計數器不變;

       判斷是淺拷貝和深拷貝就看一下兩個變量的記憶體位址是否一樣,一樣就是淺拷貝,不一樣就是深拷貝,也可以改變一個變量的其中一個屬性值看兩者的值都會發生變化;

1.1 系統原生的對象深淺拷貝差別

NSObject類提供了copy和mutableCopy方法,通過這兩個方法即可拷貝已有對象的副本,主要的系統原生對象有:NSString和NSMutableString、NSArray和NSMutableArray、NSDictionary和NSMutableDictionary、NSSet和NSMutableSet。 NSValue和NSNumber 隻遵守的NSCopying協定。

注意:基本資料類型(assign修飾),沒有對應的指針,是直接指派操作,沒有,也無需copy 操作。

這裡以NSString 和 NSMutableString為例示範說明。

NSString —— copy/mutableCopy

NSString *string = @"copyTest";
    NSString *copyString = [string copy];
    NSString *mutableCopyString = [string mutableCopy];
    NSMutableString *copyMutableString = [string copy];
    NSMutableString *mutableCopyMutableString = [string mutableCopy];
    NSLog(@"\n string = %p \n copystring = %p \n mutablecopystring = %p "
           "\n copyMutableString = %p \n mutableCopyMutableString = %p \n",
          string, copyString, mutableCopyString, copyMutableString, mutableCopyMutableString);
           

列印結果:

string = 0x10dc8c260 
 copystring = 0x10dc8c260 
 mutablecopystring = 0x61000007de40 
 copyMutableString = 0x10dc8c260 
 mutableCopyMutableString = 0x61000007dac0 
           

小結論:在字元串是直接指派的,是否生成新對象是和 = 右邊有關的,如果 = 右邊是mutableCopy才會生成新對象。

NSMutableString —— copy/mutableCopy

NSMutableString *string = [NSMutableString stringWithString:@"學習研究"];
    NSString *copyString = [string copy];
    NSString *mutableCopyString = [string mutableCopy];
    NSMutableString *copyMutableString = [string copy];
    NSMutableString *mutableCopyMutableString = [string mutableCopy];
    NSLog(@"\n string = %p \n copystring = %p \n mutablecopystring = %p "
           "\n copyMutableString = %p \n mutableCopyMutableString = %p \n",
          string, copyString, mutableCopyString, copyMutableString, mutableCopyMutableString);
           

列印結果:

string = 0x600000268e40 
 copystring = 0x600000221340 
 mutablecopystring = 0x600000268f40 
 copyMutableString = 0x600000221260 
 mutableCopyMutableString = 0x600000268fc0 
           

小結論:隻要=右邊從建立到指派,至少包含一個NSMutable便會重新生成一個對象。如果對一個不可變對象拷貝,copy是指針拷貝(淺拷貝)和mutableCopy就是對象拷貝(深拷貝)。但是,無論原對象是否含有NSMutable,copy傳回的對象都是不可變的。

注意:其他對象NSArray、NSMutableArray 、NSDictionary、NSMutableDictionary、NSSet、NSMutableSet一樣适用。

iOS Copy與MutableCopy 和 Copy與Strong 深度解析

1.2 自定對象的深淺拷貝

實際開發中,所使用的資料模型基本都是自定義對象,并常見自定義對象的嵌套使用,我們如何實作自定義對象的深度拷貝呢?

1.2.1 前期準備

建立兩個對象  PersonItem、DogItem,.m檔案中暫時不做處理,.h檔案參考如下:

DogItem.h

#import <Foundation/Foundation.h>

@interface DogItem : NSObject 

@property (nonatomic, copy) NSString *dogName;

@end
           

PersonItem.h

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

@interface PersonItem : NSObject

@property (nonatomic, copy) NSString *name;

@property (nonatomic, assign) NSUInteger age;

@property (nonatomic, strong) DogItem *dog;

@end
           

參考本文1.1章節中的使用

PersonItem *person = [[PersonItem alloc] init];
    person.name = @"person";
    person.age = 100;
    DogItem *dog = [[DogItem alloc] init];
    dog.dogName = @"dog";
    person.dog = dog;
    //拷貝對象
    PersonItem *copyPerson = [person copy];
    NSLog(@"\n person: %p name = %p dog = %p", person, person.name, person.dog);
    NSLog(@"\n copyPerson: %p name = %p dog = %p", copyPerson, copyPerson.name,copyPerson.dog);
    // 更新資料
    copyPerson.name = @"copy";
    copyPerson.dog.dogName = @"xiaohuang";
    NSLog(@"\n person.name: %@ copyPerson.name = %@", person.name, copyPerson.name);
    NSLog(@"\n person.dog.dogName: %@ copyPerson.dog.dogName = %@", person.dog.dogName, copyPerson.dog.dogName);
    
    NSLog(@"\n person: %p name = %p dog = %p", person, person.name, person.dog);
    NSLog(@"\n copyPerson: %p name = %p dog = %p", copyPerson, copyPerson.name,copyPerson.dog);
           

來Run 下,竟然崩潰了。。。未找到PersonItem類的 對象方法copyWithZone:

-[PersonItem copyWithZone:]: unrecognized selector sent to instance 0x60000003d3e0
           

我們需要實作

- (id)copyWithZone:(NSZone *)zone
{
}
//或
- (id)mutableCopyWithZone:(NSZone *)zone
{
}
           

注意:細心的你是否發現,我明明調用的是copy 方法 ,卻要實作copyWithZone:而不copy? 我們需要通過copyWithZone: 或 mutableCopyWithZone: 來開辟存儲空間,拷貝對象及其執行個體變量。無需在.h檔案中聲明這兩個方法,這是因為系統方法的 copy(mutableCopy)中實作了對 copyWithZone:(mutableCopyWithZone: )的調用。 為了編碼規範化,你需要自定義對象遵守 NSCopying 或NSMutableCopying 協定,其實此處不遵守該協定,隻要正确實作了上述的兩個方法,不會報錯能也正常使用,請養成規範編碼的好習慣。

1.2.2 關鍵步驟: 實作copyWithZone: 方法

在自定義對象的.m檔案中實作copyWithZone: 方法,主要實作形式有一下四種。

方法一:僞拷貝不可用哦 
- (id)copyWithZone:(NSZone *)zone
{
    return self;
}
           

列印結果:

person: 0x61800002cd80 name = 0x1054e5118 dog = 0x618000006370
 copyPerson: 0x61800002cd80 name = 0x1054e5118 dog = 0x618000006370
 person.name: copy copyPerson.name = copy
 person.dog.dogName: xiaohuang copyPerson.dog.dogName = xiaohuang
 person: 0x61800002cd80 name = 0x1054e5198 dog = 0x618000006370
 copyPerson: 0x61800002cd80 name = 0x1054e5198 dog = 0x618000006370
           

小結論:僞拷貝,指針拷貝,沒有開辟記憶體空間,原對象引用計數器加1,新對象及其執行個體變量的指針位址都與原對象一樣。

方法二:淺拷貝(單層拷貝)
- (id)copyWithZone:(NSZone *)zone
{
    PersonItem *personCopy = [[PersonItem allocWithZone:zone] init];
    personCopy.name = self.name;
    personCopy.age = self.age;
    personCopy.dog = self.dog;
    
    return personCopy;
}
           

列印結果:

person: 0x60000003d480 name = 0x10b455118 dog = 0x60000001de80
 copyPerson: 0x60000003c2e0 name = 0x10b455118 dog = 0x60000001de80
 person.name: person copyPerson.name = copy
 person.dog.dogName: xiaohuang copyPerson.dog.dogName = xiaohuang
 person: 0x60000003d480 name = 0x10b455118 dog = 0x60000001de80 
 copyPerson: 0x60000003c2e0 name = 0x10b455198 dog = 0x60000001de80
           

小結論:淺拷貝,開辟了記憶體空間,原對象引用計數器不變,對象的指針位址與原對象不同,但新對象的執行個體變量初始化的指針位址與原對象的執行個體變量指針位址一樣,當新對象的該執行個體變量再次指派後,該執行個體變量的指針位址變更。對象嵌套時,不能拷貝dog對象的執行個體變量的内容。

方法三:深拷貝(完全拷貝)
- (id)copyWithZone:(NSZone *)zone
{
    PersonItem *personMutableCopy = [[PersonItem allocWithZone:zone] init];
    personMutableCopy.name = [self.name mutableCopy];
    // assign 修飾的基本資料類型,沒有對應的指針,可以直接指派操作,沒有也無需copy操作。
    personMutableCopy.age = self.age;
    personMutableCopy.dog = [self.dog copy];
    
    return personMutableCopy;
}
           

列印結果:

person: 0x600000024aa0 name = 0x102a9d118 dog = 0x600000002100
 copyPerson: 0x600000024ba0 name = 0x60000006ac80 dog = 0x6000000020f0
 person.name: person copyPerson.name = copy
 person.dog.dogName: dog copyPerson.dog.dogName = xiaohuang
 person: 0x600000024aa0 name = 0x102a9d118 dog = 0x600000002100
 copyPerson: 0x600000024ba0 name = 0x102a9d198 dog = 0x6000000020f0
           

小結論:深拷貝,開辟了記憶體空間,新對象及其執行個體變量的指針位址都與原對象都不一樣,如果涉及到容器中包含容器,深拷貝就是容器中每一層對象都是深拷貝。

方法四:深拷貝(完全拷貝)
- (id)copyWithZone:(NSZone *)zone
{
    PersonItem *personCopy = [[PersonItem alloc] init];
    personCopy.name = self.name;
    personCopy.age = self.age;
    DogItem *dog = [[DogItem alloc] init];
    dog.dogName = self.dog.dogName;
    personCopy.dog = dog;
 
    return personCopy;
}
           

列印結果:

person: 0x61800003fe80 name = 0x1030f3118 dog = 0x61800000fb30
 copyPerson: 0x61800003fd20 name = 0x1030f3118 dog = 0x61800000fb60
 person.name: person copyPerson.name = copy
 person.dog.dogName: dog copyPerson.dog.dogName = xiaohuang
 person: 0x61800003fe80 name = 0x1030f3118 dog = 0x61800000fb30
 copyPerson: 0x61800003fd20 name = 0x1030f3198 dog = 0x61800000fb60
           

小結論:深拷貝,開辟了記憶體空間, 新對象及其執行個體變量的指針位址都與原對象都不一樣,如果涉及到容器中包含容器,深拷貝就是容器中每一層對象都是深拷貝。 綜述: 方法一是僞拷貝隻是指針拷貝,達不到拷貝對象内容的要求。 方法二是淺拷貝(單層拷貝),對象嵌套時,不能拷貝其所包含自定義對象的其執行個體,不能完全滿足要求。 方法三是深拷貝(完全拷貝),隻要所包含自定義對象實作了copy協定和相關方法實作,能完全拷貝對象及所包含自定義對象的内容。 方法四是深拷貝(完全拷貝),和方法三效果相同。方法四可以有另外的實作方式,不用遵守協定和實作copyWithZone: 方法,可以在自定義類中實作一個myCopy的對象方法,實作和方法四中一樣,聲明并顯式的調用。

1.2.3 mutableCopyWithZone: 方法

mutableCopyWithZone和copyWithZone: 一樣是個待實作的方法,關鍵的差別在于内部實作的差別。通常,copyWithZone: 做自定義對象的單層拷貝處理(有容器嵌套的化,隻copy最外一層)。mutableCopyWithZone: 做自定義對象的完全拷貝處理(有容器嵌套的化,容器中每一層對象都做拷貝處理)。

2 Copy與Strong的差別

說完深淺拷貝,現在讓我們一起梳理下property裡的copy、strong的差別。

以NSString為例說明下,首先定義以下屬性。

@property (nonatomic, strong) NSString *strongString;
@property (nonatomic, copy) NSString *copyedString;
@property (nonatomic, strong) NSMutableString *strongMutableString;
@property (nonatomic, copy) NSMutableString *copyedMutableString;
           

2.1 當外部賦給對應屬性一個不可變(非mutable)的字元串 NSString

- (void)testPropertyCopyOrStrong
{
    NSString *string = [NSString stringWithFormat:@"abc"];
    self.strongString = string;
    self.strongMutableString = string;
    self.copyedString = string;
    self.copyedMutableString = string;
    string = [string stringByReplacingOccurrencesOfString:@"c" withString:@"233"];

    NSLog(@"\n origin        string: %p, %p  %@  %@", string, &string, string, NSStringFromClass([string class]));
    NSLog(@"\n strong        string: %p, %p  %@  %@", _strongString, &_strongString, _strongString, NSStringFromClass([_strongString class]));
    NSLog(@"\n strongMutable string: %p, %p  %@  %@", _strongMutableString, &_strongMutableString, _strongMutableString, NSStringFromClass([_strongMutableString class]));
    NSLog(@"\n copy          string: %p, %p  %@  %@", _copyedString, &_copyedString, _copyedString, NSStringFromClass([_copyedString class]));
    NSLog(@"\n copyMutable   string: %p, %p  %@  %@", _copyedMutableString, &_copyedMutableString, _copyedMutableString, NSStringFromClass([_copyedMutableString class]));

}
           

列印結果:

origin        string: 0x103a74098, 0x7fff5c18ca88  ab233  __NSCFString
 strong        string: 0xa000000006362613, 0x7f84c9f056d8  abc  NSTaggedPointerString
 strongMutable string: 0xa000000006362613, 0x7f84c9f056e8  abc  NSTaggedPointerString
 copy          string: 0xa000000006362613, 0x7f84c9f056e0  abc  NSTaggedPointerString
 copyMutable   string: 0xa000000006362613, 0x7f84c9f056f0  abc  NSTaggedPointerString
           

2.2 當外部賦給對應屬性一個可變的字元串 NSMutableString

- (void)testPropertyCopyOrStrong
{
    NSMutableString *string = [NSMutableString stringWithFormat:@"abc"];
    self.strongString = string;
    self.strongMutableString = string;
    self.copyedString = string;
    self.copyedMutableString = string;
    [string appendString:@"123"];
    
    NSLog(@"\n origin        string: %p, %p  %@  %@", string, &string, string, NSStringFromClass([string class]));
    NSLog(@"\n strong        string: %p, %p  %@  %@", _strongString, &_strongString, _strongString, NSStringFromClass([_strongString class]));
    NSLog(@"\n strongMutable string: %p, %p  %@  %@", _strongMutableString, &_strongMutableString, _strongMutableString, NSStringFromClass([_strongMutableString class]));
    NSLog(@"\n copy          string: %p, %p  %@  %@", _copyedString, &_copyedString, _copyedString, NSStringFromClass([_copyedString class]));
    NSLog(@"\n copyMutable   string: %p, %p  %@  %@", _copyedMutableString, &_copyedMutableString, _copyedMutableString, NSStringFromClass([_copyedMutableString class]));

}
           

列印結果:

origin        string: 0x60000006cec0, 0x7fff5417ea88  abc123  __NSCFString
 strong        string: 0x60000006cec0, 0x7fc702508148  abc123  __NSCFString
 strongMutable string: 0x60000006cec0, 0x7fc702508158  abc123  __NSCFString
 copy          string: 0xa000000006362613, 0x7fc702508150  abc  NSTaggedPointerString
 copyMutable   string: 0xa000000006362613, 0x7fc702508160  abc  NSTaggedPointerString
           

注意:此處的 string 是局部變量,存儲在棧區,_strongString、_strongMutableString、_copyedString、_copyedMutableString 是全局變量,存儲在堆區。他們有不同存儲的空間,對應的指針位址會不同。 詳情參考: iOS開發程式中各種變量的存儲位置和程式傳回變量的問題 __NSCFString 、__NSCFConstantString 都是NSSting抽象類的具體資料處理子類。NSTaggedPointerString 擴充閱讀: 1、 NSString NSCFString NSCFConstantString isMemberOfClass 遇到的相關的問題 2、 iOS裡的TaggedPointer[NSString篇] 這裡的底層知識,我也不甚清楚,暫時隻能說到這個程度,後續補充。

2.3 原理分析

讓我們回顧下,我們定義的四個屬性:

@property (nonatomic, strong) NSString *strongString;
@property (nonatomic, copy) NSString *copyedString;
@property (nonatomic, strong) NSMutableString *strongMutableString;
@property (nonatomic, copy) NSMutableString *copyedMutableString;
           

它們的set 方法是怎實作的呢? property strong  strongString 實際上就是對 strongString 做了

- (void)setStrongString:(NSString *)strongString
{
    _strongString = strongString;
}
           

property copy  copyString 實際上就是對 copyString 做了

- (void)setCopyedString:(NSString *)copyedString
{
    _copyedString = [copyedString copy];
}
           

property strong strongMutableString  實際上就是對 strongMutableString 做了

- (void)setStrongMutableString:(NSMutableString *)strongMutableString
{
    _strongMutableString = strongMutableString;
}
           

然而,property copy copyMutableString  實際上就是對 copyMutableString 做了

- (void)setCopyedMutableString:(NSMutableString *)copyedMutableString
{
    _copyedMutableString = [copyedMutableString copy];
}
           

問題來了。。。。。。。 iOS 類型聲明的 陷阱 看似你對 copyMutableString 屬性 聲明了 NSMutableString 類型,在使用上,調用了 NSMutableString 的方法,你會發現,程式運作到這行,直接崩潰了~_~,原因: 無論原對象是否含有NSMutable,copy傳回的對象都是不可變的。 NSString 執行個體,調用 NSMutableString 的方法,找不到該方法,當然就崩潰了。 是以,在聲明名屬性時,就會有一些不成文的規範。以字元串為例,為了保障資料的安全,以免被随意修改,盡量使用NSString(非mutable)類型,若不希望該屬性内容的随着外部變化而影響初始值,應該用copy修飾,甚至用readonly 加強修飾。若希望該屬性的内容随時變化并存儲,可以用strong修飾,NSMutable* 類,都是繼承自NS*類,是以,NS* 可以接收NSMuatble* 或NS * 類型的值,調用NS* 相關屬性或方法,是正常的操作。或聲明成NSMutableString類型,切記一定要用strong修飾。  推薦的屬性聲明方式:

@property (nonatomic, copy) NSString *copyedString;
@property (nonatomic, strong) NSMutableString *strongMutableString;
           

根據需要選擇,NSArray、NSMutableArray; NSDictionary、NSMutableDictionary; NSSet、NSMutableSet;類似。 不足之處,歡迎交流!

繼續閱讀