1、什么是深拷貝什么是淺拷貝钻蔑?淺拷貝和深拷貝的區(qū)別
* 淺拷貝(shallow copy):指針拷貝窗怒,對于被拷貝對象的每一層都是指針拷貝球恤,沒有開啟新的內(nèi)存地址堪置,拷貝前后的指針指向同一塊內(nèi)存地址雁竞。淺拷貝會影響內(nèi)存地址引用計數(shù)碑诉。
* 深拷貝(one-level-deep copy):內(nèi)存塊拷貝,拷貝后的指針指向拷貝后的內(nèi)存塊侥锦。但是這里深拷貝只是深拷貝對象自身這一層进栽,是單層深拷貝,對于容器類對象恭垦,容器內(nèi)各層元素對象依然是淺拷貝快毛。
2、copy和strong的區(qū)別
這里分兩種場景番挺,首先對于不可變對象唠帝,
@property(nonatomic, strong)NSString *strongString;
?@property(nonatomic, copy)NSString *cpString;
------------------------------------------------------------------------------------------------------------
? ? NSString *originString = [[NSString alloc] initWithFormat:@"Hello World!"];
? ? self.strongString= originString;
? ? self.cpString= originString;
? ? NSLog(@"? ? ? ? ? ? ? 對象地址? ? ? ? ? 對象指針地址? ? ? 引用計數(shù)");
? ? NSLog(@"originString: %p , %p , %ld", originString, &originString,CFGetRetainCount((__bridge? CFTypeRef)(originString)));
? ? NSLog(@"strongString? : %p , %p , %ld", _strongString, &_strongString, CFGetRetainCount((__bridge? CFTypeRef)(_strongString)));
? ? NSLog(@"cpString? ? : %p , %p , %ld", _cpString, &_cpString, CFGetRetainCount((__bridge? CFTypeRef)(_cpString)));
------打印log-------------------------------------------------------------------------------------------
? ? ? ? ? ? ? ? ? ? ? ? ?對象地址? ? ? ? ? ? ? 對象指針地址? ? ? 引用計數(shù)
originString? ?: 0x600001bcb1a0 , 0x7ffee876c188 , 3
strongString? : 0x600001bcb1a0 , 0x7ff155208ab0 , 3
cpString? ? ? ? : 0x600001bcb1a0 , 0x7ff155208ab8 , 3
對于不可變對象,copy對象和strong對象的內(nèi)存地址都沒有變化玄柏,只是生成新指針指向源對象地址襟衰,引用計數(shù)加 1 , 都是淺拷貝粪摘。
當源對象是可變對象時瀑晒,使用點語法賦值:
@property(nonatomic, strong)NSArray *strongArray;
@property(nonatomic, copy)NSArray *cpArray;
--------------------------------------------------——————————————————
? ? NSLog(@"? ? ? ? ? ? ? 對象地址? ? ? ? ? 對象指針地址? ? ? 引用計數(shù)? ? ? index1元素地址");
? ? NSMutableArray *originArray = [[NSMutableArray alloc] initWithArray:@[@"北京市", @[@"昌平區(qū)", @[@"天通苑"]]]];
? ? self.strongArray= originArray;
? ? self.cpArray= originArray;
? ? NSLog(@"originArray? : %p , %p , %ld, %p", originArray, &originArray,CFGetRetainCount((__bridge? CFTypeRef)(originArray)), originArray[1]);
? ? NSLog(@"strongArray? : %p , %p , %ld, %p", _strongArray, &_strongArray, CFGetRetainCount((__bridge? CFTypeRef)(_strongArray)), _strongArray[1]);
? ? NSLog(@"cpArray? ? ? : %p , %p , %ld, %p", _cpArray, &_cpArray, CFGetRetainCount((__bridge? CFTypeRef)(_cpArray)), _cpArray[1]);
————————————————————————————————————
iOS_copy? ? ? ? 對象地址? ? ? ? ? 對象指針地址? ? ? 引用計數(shù)? ? ? index1元素地址
originArray? : 0x600002414e10 , 0x7ffee12ea180 ,? 2,? ? ? ? ?0x600002a28b80
strongArray : 0x600002414e10 , 0x7fa512307dd0 , 2,? ? ? ? 0x600002a28b80
?cpArray? ? ? : 0x600002a28ae0 , 0x7fa512307dd8 , 1,? ? ? ? ?0x600002a28b80
對可變對象copy绍坝,新指針指向新拷貝開辟的內(nèi)存塊,是深拷貝苔悦,但是內(nèi)部元素是淺拷貝轩褐。strong是新指針指向源內(nèi)存塊,引用計數(shù)加 1间坐,是淺拷貝灾挨。
當源對象是可變對象時,直接賦值:
NSLog(@"? ? ? ? ? ? ? 對象地址? ? ? ? ? 對象指針地址? ? ? 引用計數(shù)? ? ? index1元素地址");
? ? NSMutableArray *originArray = [[NSMutableArray alloc] initWithArray:@[@"北京市", @[@"昌平區(qū)", @[@"天通苑"]]]];
? ? _strongArray= originArray;
? ? _cpArray= originArray;
? ? NSLog(@"originArray? : %p , %p , %ld, %p", originArray, &originArray,CFGetRetainCount((__bridge? CFTypeRef)(originArray)), originArray[1]);
? ? NSLog(@"strongArray? : %p , %p , %ld, %p", _strongArray, &_strongArray, CFGetRetainCount((__bridge? CFTypeRef)(_strongArray)), _strongArray[1]);
? ? NSLog(@"cpArray? ? ? : %p , %p , %ld, %p", _cpArray, &_cpArray, CFGetRetainCount((__bridge? CFTypeRef)(_cpArray)), _cpArray[1]);
——————————————————————————————
? ? ? ? ? ? ? 對象地址? ? ? ? ? 對象指針地址? ? ? 引用計數(shù)? ? ? index1元素地址
originArray? : 0x6000030b91d0 , 0x7ffeed7eb180 , 3, 0x600003e82780
strongArray : 0x6000030b91d0 , 0x7facdf707360 , 3, 0x600003e82780
?cpArray? ? ? : 0x6000030b91d0 , 0x7facdf707368 , 3, 0x600003e82780
看輸出結(jié)果竹宋,直接賦值后對象地址相同劳澄,和上面點語法賦值出現(xiàn)了不同!蜈七!這是為什么呢秒拔?
用@property來聲明屬性變量時,編譯器會自動為我們生成一個以下劃線加屬性名命名的實例變量(@synthesize cpArray = _cpArray)飒硅,并且生成其對應(yīng)的getter砂缩、setter方法。
當我們用self.cpArray = cpArray即使用點語法賦值時三娩,才會調(diào)用cpArray的setter方法庵芭,而_cpArray = originArray 賦值時給_cpArray實例變量直接賦值,并不會調(diào)用cpArray的setter方法雀监,而在setter方法中有一個非常關(guān)鍵的語句:? _cpArray = [cpArray copy];
用點語法self.cpArray = originArray 賦值時双吆,調(diào)用cpArray的setter方法,setter方法對傳入的cpArray做了次深拷貝生成了一個新的對象賦值給_cpArray会前,所以_cpArray指向的地址和對象值都不再和originArray相同好乐。
3、為什么不可變對象要用copy瓦宜,改為strong可以嗎蔚万?
不可變對象使用copy修飾可以讓該對象不受傳入對象的影響。外界傳入的是一個不可變對象時和strong修飾差別不大临庇;當傳入的是一個可變對象時會進行深拷貝反璃,屬性指針指向新的地址,保證該對象持有的是一個不可變副本假夺。
使用strong修飾時淮蜈,如果這個屬性指向一個可變對象,修改可變對象時侄泽,這個屬性值也會被修改。因為在賦值時蜻韭,直接將屬性的指針指向了可變對象地址悼尾,內(nèi)存地址共用一個柿扣。當然我們可以對一個strong對象使用 strongObj = [originObj copy] 來實現(xiàn)copy修飾作用,但是這樣并不保險闺魏,還是直接用copy修飾不可變對象為好未状。
4、數(shù)組copy后里面的元素會復(fù)制一份新的嗎
數(shù)組copy后里面的元素不會復(fù)制一份新的析桥。無論是copy還是mutableCopy司草,數(shù)組內(nèi)的元素都不會不會復(fù)制一份新的,都是淺拷貝泡仗。
使用- (instancetype)initWithArray:(NSArray *)array copyItems:(BOOL)flag函數(shù)埋虹,flag為YES時可以對數(shù)組元素進行深拷貝,復(fù)制一份新的娩怎。
使用歸檔解檔的方法(NSArray *deepCpArray = [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:originArray]];)實現(xiàn)了對數(shù)組的完全深拷貝搔课,數(shù)組的每層元素都會復(fù)制一份新的。需要注意的是截亦,歸檔解檔數(shù)組內(nèi)的元素需要實現(xiàn)NSCoding協(xié)議爬泥。
5、[object copy]是淺拷貝還是深拷貝崩瓤?為什么是淺拷貝袍啡?copy是實現(xiàn)了哪個協(xié)議?
[object copy]却桶,object是不可變對象境输,是指針拷貝,新的指針指向源對象內(nèi)存肾扰,是淺拷貝畴嘶;object是可變對象,對源對象內(nèi)存拷貝集晚,新指針指向新地址窗悯,實現(xiàn)的深拷貝,但是這個深拷貝是單層深拷貝偷拔,因為object是容器類對象時蒋院,容器內(nèi)元素還是淺拷貝。
copy方法實際上是調(diào)用NSCopying協(xié)議莲绰。如果沒有實現(xiàn)copyWithZone方法會拋出異常欺旧。NSObject本身并沒有支持NSCopying協(xié)議,子類必須支持此協(xié)議并且實現(xiàn)copyWithZone方法蛤签。子類在實現(xiàn)copyWithZone方法時應(yīng)該先調(diào)用欺負類的copyWithZone方法以讓內(nèi)容復(fù)制完全辞友,除非此類直接繼承自NSObject
示例,Animal類,Dog類繼承自Animal称龙。首先需要遵守NSCopying協(xié)議留拾,下面是copyWithZone實現(xiàn)。
@implementation Animal
- (id)copyWithZone:(NSZone*)zone
{
? ? /*若該類直接繼承自NSObject鲫尊,則在copyWithZone:中使用allocWithZone:初始化*/
? ? /*使用 [self class] 而非Animal是因為此方法可能由子類super調(diào)用過來的*/
? ? Animal*animal = [[[selfclass]allocWithZone:zone]init];
? ? animal.weight=self.weight;
? ? returnanimal;
}
@implementation Dog
- (id)copyWithZone:(NSZone*)zone
{
? ? /*一個子類調(diào)用copy痴柔,實際上是調(diào)用了NSCopying協(xié)議中的copyWithZone:方法
?? ? 調(diào)用super的copyWithZone:確保父類的內(nèi)容被拷貝*/
? ? Dog*dog = [supercopyWithZone:zone];
? ? dog.name=self.name;
? ? returndog;
}
我們demo調(diào)用copy,輸出對象地址和引用計數(shù)
Dog*dog = [[Dogalloc]init];
? ? dog.name=@"Dahuang";
? ? dog.weight=@"11K";
? ? Dog*dog2 = [dogcopy];
? ? NSLog(@"%p,,,,,%p", dog, dog2);
? ? NSLog(@"%ld, %ld", CFGetRetainCount((__bridge? CFTypeRef)(dog)), CFGetRetainCount((__bridge? CFTypeRef)(dog2)));
-----------------------輸出-------log-------------------------------------
iOS_copy[50970:4411255] 0x600000a80f00,,,,,0x600000a80ec0
2020-07-09 11K2020-07-09 15:55:52.340368+0800 iOS_copy[50970:4411255] 1, 1
我們看到copy實現(xiàn)的是深拷貝疫向,當我們將Dog類中的copyWithZone中直接return self:
@implementation Dog
- (id)copyWithZone:(NSZone*)zone
{
//直接 return self咳蔚;? 實現(xiàn)的是淺拷貝
? ? return self;
? ? /*一個子類調(diào)用copy,實際上是調(diào)用了NSCopying協(xié)議中的copyWithZone:方法
?? ? 調(diào)用super的copyWithZone:確保父類的內(nèi)容被拷貝*/
? ? /*Dog*dog = [supercopyWithZone:zone];
? ? dog.name=self.name;
? ? returndog;*/
}
再次運行調(diào)用demo搔驼,輸出
2020-07-09 16:02:37.533212+0800 iOS_copy[51188:4415464] 0x6000026f1120,,,,,0x6000026f1120
2020-07-09 16:02:37.533417+0800 iOS_copy[51188:4415464] 2, 2
此時地址copy實現(xiàn)的是淺拷貝
6谈火、對可變對象進行copy是深拷貝還是淺拷貝?
對系統(tǒng)可變對象進行copy是單層深拷貝匙奴,返回一個不可變對象堆巧。當這個對象是非容器類對象時copy也可以叫做完全深拷貝。當這個對象是容器類對象時copy是單層深拷貝泼菌,這里單層是指對對象本身是深拷貝(內(nèi)存塊拷貝)谍肤,對象內(nèi)的元素是淺拷貝(指針拷貝)。
7哗伯、@property (copy) NSMutableArray *array; 這種寫法有什么問題荒揣?
1、雖然用了NSMutableArray聲明焊刹,但是copy修飾返回的是不可變數(shù)組系任,在對array的元素進行增,刪虐块,改時俩滥,程序會因為找不到對應(yīng)的方法而崩潰。
2贺奠、關(guān)于ARC下霜旧,不顯式指定屬性關(guān)鍵字時,默認關(guān)鍵字入下:
? ? ? ?1).基本數(shù)據(jù)類型:atomic readwrite assign
? ? ? ?2).普通OC對象: atomic readwrite strong
?所以這里默認使用了 atomic 屬性會嚴重影響性能儡率。
8挂据、什么是單層拷貝,怎么實現(xiàn)多層拷貝儿普?
這里可以先看一下前面一篇文章iOS--拷貝 - 簡書崎逃。
這里需要提到容器類對象,系統(tǒng)的容器類對象如NSArray眉孩,NSDictionary等个绍。
在對不可變NSArray做copy操作后勒葱,是拷貝一個新的指針指向NSArray的地址,單純的指針拷貝巴柿,是淺拷貝错森。
對可變數(shù)組NSMutableArray做copy操作后,是對源對象內(nèi)存塊拷貝篮洁,新指針指向新地址,但是對象內(nèi)的元素只是做指針拷貝的淺拷貝殃姓,此時稱為單層拷貝袁波。
NSArray有一個- (instancetype)initWithArray:(NSArray *)array copyItems:(BOOL)flag函數(shù),flag為YES時蜗侈,副本對象首層元素也做了內(nèi)存塊拷貝篷牌,但是源對象第二層元素(首層元素是容器類元素的,首層元素內(nèi)的元素)以及更深層的元素依然是淺拷貝踏幻,源對象自身加上首層原色的深拷貝枷颊,這個函數(shù)實現(xiàn)了雙層深拷貝。
使用歸檔解檔的方法(NSArray *deepCpArray = [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:originArray]];)實現(xiàn)了容器類對象和對象內(nèi)的每層元素的深拷貝该面。需要注意的是夭苗,歸檔解檔數(shù)組內(nèi)的元素需要實現(xiàn)NSCoding協(xié)議。
9隔缀、修飾block屬性時题造,為什么用copy?
對于這個問題猾瘸,得區(qū)分 MRC 環(huán)境 和 ARC 環(huán)境界赔;block使用copy是從MRC遺留下來的,在MRC中,方法內(nèi)部的block是在棧區(qū)的,對于分配在棧區(qū)的對象牵触,我們很容易會在釋放之后繼續(xù)調(diào)用淮悼,導致程序奔潰,所以我們使用的時候需要將棧區(qū)的對象移到堆區(qū)揽思,來延長該對象的生命周期袜腥。
對于 MRC 環(huán)境,使用 Copy 修飾 Block绰更,會將棧區(qū)的 Block 拷貝到堆區(qū)瞧挤。
對于 ARC 環(huán)境,使用 Strong儡湾、Copy 修飾 Block特恬,都會將棧區(qū)的 Block 拷貝到堆區(qū)。
所以徐钠,Block 不是一定要用 Copy 來修飾的癌刽,在 ARC 環(huán)境下面 Strong 和 Copy 修飾效果是一樣的。
10、有屬性聲明?@property(nonatomic, copy)NSString *obj;? ?重寫setter方法如下:
- (void)setObj:(NSString*)obj
{
? ? _obj= obj;
}? ? ? ?有什么問題嗎显拜?
有沒有問題衡奥?直接上代碼
@interface ViewController ()
@property(nonatomic, copy)NSString *obj;
@end
@implementation ViewController
- (void)viewDidLoad {
? ? [super viewDidLoad];
? ? NSMutableString *str = [[NSMutableString alloc] initWithFormat:@"Hello World!"];
? ? self.obj= str;
//將一個可變字符串 str 賦值給了 obj,然后改變 str远荠,打印 str 和 obj
? ? [str insertString:@" I am coming!" atIndex: str.length];
? ? NSLog(@"\nstr:%@\nobj:%@", str, self.obj);
}
- (void)setObj:(NSString*)obj
{
? ? _obj= obj;
}
看一下輸出:
2020-07-10 10:41:53.266417+0800 iOS_property[83857:5014465]?
str:Hello World! I am coming!
obj:Hello World! I am coming!
我們將一個可變字符串 str 賦值給了 obj矮固,然后改變 str,obj 居然 跟著 str 一樣被改變了譬淳,雖然我們聲明中使用了copy 修飾5抵贰!邻梆!
問題就在setObj中 我們使用了? _obj= obj;? 直接賦值守伸,直接賦值和strong沒有區(qū)別,對于使用copy修飾的屬性浦妄,在重寫setter方法時尼摹,應(yīng)該使用_obj= [obj copy];??賦值。
構(gòu)造方法也一樣,需要用傳入copy后的字符串賦值
如上面的例子,如果僅用 obj 來構(gòu)造控制器ViewController,且寫成這樣:
- (instancetype)initWithObj:(NSString*)obj
{
? ? if(self= [superinit]) {
? ? ? ? //_obj= [obj? copy];? ?//應(yīng)該講copy后的值 賦值給obj
? ? ? ? _obj = obj;? ?//這樣寫是有問題的
? ? }
? ? return self;
}
這樣寫也是有問題的剂娄,如果構(gòu)造的時候傳入的是個可變字符串蠢涝,且在構(gòu)造完后立馬修改它,屬性也會跟著改變阅懦。因為這里賦值 obj 屬性用的是屬性的實例變量_obj惠赫,沒有調(diào)用set方法,所以必須把賦值改為_obj= [obj? copy]; 故黑。這里如果寫成self.obj = obj走set方法來賦值屬性本身是可以避免這種情況儿咱,但不建議,構(gòu)造方法一般還是直接操作屬性的實例變量_obj合適场晶。