iOS copy關(guān)鍵字,@property,深拷貝和淺拷貝

1.什么情況使用關(guān)鍵字copy? 相比 assign ?有什么不同?

(1).在 ARC 中,在有可能出現(xiàn)循環(huán)引用的時候,往往要通過讓其中一端使用 weak 來解決,比如: delegate 代理屬性。

(2).自身已經(jīng)對它進行一次強引用,沒有必要再強引用一次,此時也會使用 weak,自定義 IBOutlet 控件屬性一般也使用 weak;當然,也可以使用strong。在下文也有論述:《IBOutlet連出來的視圖屬性為什么可以被設(shè)置成weak?》

不同點:

(1).weak 此特質(zhì)表明該屬性定義了一種“非擁有關(guān)系” (nonowning relationship)。為這種屬性設(shè)置新值時,設(shè)置方法既不保留新值,也不釋放舊值。此特質(zhì)同assign類似, 然而在屬性所指的對象遭到摧毀時,屬性值也會清空(nil out)。 而 assign 的“設(shè)置方法”只會執(zhí)行針對“純量類型” (scalar type,例如 CGFloat 或 NSlnteger 等)的簡單賦值操作。

(2).assigin 可以用非 OC 對象,而 weak 必須用于 OC 對象。

2.怎么用copy關(guān)鍵字?

用途:

(1).NSString、NSArray、NSDictionary 等等經(jīng)常使用copy關(guān)鍵字,是因為他們有對應(yīng)的可變類型:NSMutableString、NSMutableArray、NSMutableDictionary;

(2).block 也經(jīng)常使用 copy 關(guān)鍵字,具體原因見官方文檔:Objects Use Properties to Keep Track of Blocks:

block 使用 copy 是從 MRC 遺留下來的“傳統(tǒng)”,在 MRC 中,方法內(nèi)部的 block 是在棧區(qū)的,使用 copy 可以把它放到堆區(qū)。在 ARC 中寫不寫都行;對于 block 使用 copy 還是 strong 效果是一樣的,但寫上 copy 也無傷大雅,還能時刻提醒我們:編譯器自動對 block 進行了 copy 操作。如果不寫 copy ,該類的調(diào)用者有可能會忘記或者根本不知道“編譯器會自動對 block 進行了 copy 操作”,他們有可能會在調(diào)用之前自行拷貝屬性值。這種操作多余而低效。你也許會感覺我這種做法有些怪異,不需要寫依然寫。如果你這樣想,其實是你“日用而不知”,你平時開發(fā)中是經(jīng)常在用我說的這種做法的,比如下面的屬性不寫copy也行,但是你會選擇寫還是不寫呢?

@property (nonatomic, copy) NSString *userId;

- (instancetype)initWithUserId:(NSString *)userId {

self = [super init];

if (!self) {

return nil;}

_userId = [userId copy];

return self;

}

下面做下解釋: copy 此特質(zhì)所表達的所屬關(guān)系與 strong 類似。然而設(shè)置方法并不保留新值,而是將其“拷貝” (copy)。 當屬性類型為 NSString 時,經(jīng)常用此特質(zhì)來保護其封裝性,因為傳遞給設(shè)置方法的新值有可能指向一個 NSMutableString 類的實例。這個類是 NSString 的子類,表示一種可修改其值的字符串,此時若是不拷貝字符串,那么設(shè)置完屬性之后,字符串的值就可能會在對象不知情的情況下遭人更改。所以,這時就要拷貝一份“不可變” (immutable)的字符串,確保對象中的字符串值不會無意間變動。只要實現(xiàn)屬性所用的對象是“可變的” (mutable),就應(yīng)該在設(shè)置新屬性值時拷貝一份。

用 @property 聲明 NSString、NSArray、NSDictionary 經(jīng)常使用 copy 關(guān)鍵字,是因為他們有對應(yīng)的可變類型:NSMutableString、NSMutableArray、NSMutableDictionary,他們之間可能進行賦值操作,為確保對象中的字符串值不會無意間變動,應(yīng)該在設(shè)置新屬性值時拷貝一份。

3. 這個寫法會出什么問題: @property (copy) NSMutableArray *array;

兩個問題:1、添加,刪除,修改數(shù)組內(nèi)的元素的時候,程序會因為找不到對應(yīng)的方法而崩潰。因為 copy 就是復(fù)制一個不可變 NSArray 的對象;2、使用了 atomic 屬性會嚴重影響性能 ;

第1條的相關(guān)原因在下文中有論述《用@property聲明的NSString(或NSArray,NSDictionary)經(jīng)常使用 copy 關(guān)鍵字,為什么?如果改用strong關(guān)鍵字,可能造成什么問題?》 以及上文《怎么用 copy 關(guān)鍵字?》也有論述。

比如下面的代碼就會發(fā)生崩潰:

//.h

@property (nonatomic, copy) NSMutableArray *mutableArray;


//.m

NSMutableArray *array = [NSMutableArray arrayWithObjects:@1,@2,nil];

self.mutableArray = array;

[self.mutableArray removeObjectAtIndex:0];


接下來就會發(fā)生崩潰:

-[__NSArrayI removeObjectAtIndex:]: unrecognized selector sent to instance 0x7fcd1bc30460

第2條原因,如下:

該屬性使用了同步鎖,會在創(chuàng)建時生成一些額外的代碼用于幫助編寫多線程程序,這會帶來性能問題,通過聲明 nonatomic 可以節(jié)省這些雖然很小但是不必要額外開銷。

默認情況下,由編譯器所合成的方法會通過鎖定機制確保其原子性(atomicity)。如果屬性具備 nonatomic 特質(zhì),則不使用同步鎖。請注意,盡管沒有名為“atomic”的特質(zhì)(如果某屬性不具備 nonatomic 特質(zhì),那它就是“原子的”(atomic))。

在iOS開發(fā)中,你會發(fā)現(xiàn),幾乎所有屬性都聲明為 nonatomic。

一般情況下并不要求屬性必須是“原子的”,因為這并不能保證“線程安全” ( thread safety),若要實現(xiàn)“線程安全”的操作,還需采用更為深層的鎖定機制才行。例如,一個線程在連續(xù)多次讀取某屬性值的過程中有別的線程在同時改寫該值,那么即便將屬性聲明為 atomic,也還是會讀到不同的屬性值。

因此,開發(fā)iOS程序時一般都會使用 nonatomic 屬性。但是在開發(fā) Mac OS X 程序時,使用 atomic 屬性通常都不會有性能瓶頸。

4. @property 的本質(zhì)是什么?ivar、getter、setter 是如何生成并添加到這個類中的

@property 的本質(zhì)是什么?

@property = ivar + getter + setter;

下面解釋下:

“屬性” (property)有兩大概念:ivar(實例變量)、存取方法(access method = getter + setter)。

“屬性” (property)作為 Objective-C 的一項特性,主要的作用就在于封裝對象中的數(shù)據(jù)。 Objective-C 對象通常會把其所需要的數(shù)據(jù)保存為各種實例變量。實例變量一般通過“存取方法”(access method)來訪問。其中,“獲取方法” (getter)用于讀取變量值,而“設(shè)置方法” (setter)用于寫入變量值。這個概念已經(jīng)定型,并且經(jīng)由“屬性”這一特性而成為 Objective-C 2.0 的一部分。 而在正規(guī)的 Objective-C 編碼風格中,存取方法有著嚴格的命名規(guī)范。 正因為有了這種嚴格的命名規(guī)范,所以 Objective-C 這門語言才能根據(jù)名稱自動創(chuàng)建出存取方法。其實也可以把屬性當做一種關(guān)鍵字,其表示:

編譯器會自動寫出一套存取方法,用以訪問給定類型中具有給定名稱的變量。 所以你也可以這么說:

@property = getter + setter;

例如下面這個類:

@interface Person : NSObject

@property NSString *firstName;

@property NSString *lastName;

@end

上述代碼寫出來的類與下面這種寫法等效:

@interface Person : NSObject

- (NSString *)firstName;

- (void)setFirstName:(NSString *)firstName;

- (NSString *)lastName;

- (void)setLastName:(NSString *)lastName;

@end

ivar、getter、setter 是如何生成并添加到這個類中的?

“自動合成”( autosynthesis)

完成屬性定義后,編譯器會自動編寫訪問這些屬性所需的方法,此過程叫做“自動合成”(autosynthesis)。需要強調(diào)的是,這個過程由編譯 器在編譯期執(zhí)行,所以編輯器里看不到這些“合成方法”(synthesized method)的源代碼。除了生成方法代碼 getter、setter 之外,編譯器還要自動向類中添加適當類型的實例變量,并且在屬性名前面加下劃線,以此作為實例變量的名字。在前例中,會生成兩個實例變量,其名稱分別為 _firstName 與 _lastName。也可以在類的實現(xiàn)代碼里通過 @synthesize 語法來指定實例變量的名字.

@implementation Person

@synthesize firstName = _myFirstName;

@synthesize lastName = _myLastName;

@end

屬性實現(xiàn)過程中大致生成了五個東西:

OBJC_IVAR_$類名$屬性名稱 :該屬性的“偏移量” (offset),這個偏移量是“硬編碼” (hardcode),表示該變量距離存放對象的內(nèi)存區(qū)域的起始地址有多遠。

setter 與 getter 方法對應(yīng)的實現(xiàn)函數(shù)

ivar_list :成員變量列表

method_list :方法列表

prop_list :屬性列表

也就是說我們每次在增加一個屬性,系統(tǒng)都會在 ivar_list 中添加一個成員變量的描述,在 method_list 中增加 setter 與 getter 方法的描述,在屬性列表中增加一個屬性的描述,然后計算該屬性在對象中的偏移量,然后給出 setter 與 getter 方法對應(yīng)的實現(xiàn),在 setter 方法中從偏移量的位置開始賦值,在 getter 方法中從偏移量開始取值,為了能夠讀取正確字節(jié)數(shù),系統(tǒng)對象偏移量的指針類型進行了類型強轉(zhuǎn)。

5. @protocol 和 category 中如何使用 @property?

在 protocol 中使用 property 只會生成 setter 和 getter 方法聲明,我們使用屬性的目的,是希望遵守協(xié)議的對象能實現(xiàn)該屬性。

category 使用 @property 也是只會生成 setter 和 getter 方法的聲明,如果我們真的需要給 category 增加屬性的實現(xiàn),需要借助于運行時的兩個函數(shù):

objc_setAssociatedObject

objc_getAssociatedObject

6. @property中有哪些屬性關(guān)鍵字?/ @property 后面可以有哪些修飾符?

屬性可以擁有的特質(zhì)分為四類:

1.原子性--- nonatomic 特質(zhì)

在默認情況下,由編譯器合成的方法會通過鎖定機制確保其原子性(atomicity)。如果屬性具備 nonatomic 特質(zhì),則不使用同步鎖。請注意,盡管沒有名為“atomic”的特質(zhì)(如果某屬性不具備 nonatomic 特質(zhì),那它就是“原子的” ( atomic) ),但是仍然可以在屬性特質(zhì)中寫明這一點,編譯器不會報錯。若是自己定義存取方法,那么就應(yīng)該遵從與屬性特質(zhì)相符的原子性。

2.讀/寫權(quán)限---readwrite(讀寫)、readonly (只讀)

3.內(nèi)存管理語義---assign、strong、 weak、unsafe_unretained、copy

4.方法名---getter=、setter=

getter=的樣式:

@property (nonatomic, getter=isOn) BOOL on;

setter=一般用在特殊的情境下,比如:

在數(shù)據(jù)反序列化、轉(zhuǎn)模型的過程中,服務(wù)器返回的字段如果以 init 開頭,所以你需要定義一個 init 開頭的屬性,但默認生成的 setter 與 getter 方法也會以 init 開頭,而編譯器會把所有以 init 開頭的方法當成初始化方法,而初始化方法只能返回 self 類型,因此編譯器會報錯。

這時你就可以使用下面的方式來避免編譯器報錯:

@property(nonatomic, strong, getter=p_initBy, setter=setP_initBy:)NSString *initBy;

另外也可以用關(guān)鍵字進行特殊說明,來避免編譯器報錯:

@property(nonatomic, readwrite, copy, null_resettable) NSString *initBy;

- (NSString *)initBy __attribute__((objc_method_family(none)));

不常用的:non null,null_resettable,nullable

7. weak屬性需要在dealloc中置nil么?

不需要。

在ARC環(huán)境無論是強指針還是弱指針都無需在 dealloc 設(shè)置為 nil , ARC 會自動幫我們處理

我們模擬下 weak 的 setter 方法,應(yīng)該如下:

- (void)setObject:(NSObject *)object{

objc_setAssociatedObject(self, "object", object, OBJC_ASSOCIATION_ASSIGN);

[object cyl_runAtDealloc:^{

_object = nil;

}];}

所以在屬性所指的對象遭到摧毀時,屬性值也會清空(nil out)。

8. @synthesize和@dynamic分別有什么作用?

1.@property有兩個對應(yīng)的詞,一個是 @synthesize,一個是 @dynamic。如果 @synthesize和 @dynamic都沒寫,那么默認的就是@syntheszie var = _var;

@synthesize 的語義是如果你沒有手動實現(xiàn) setter 方法和 getter 方法,那么編譯器會自動為你加上這兩個方法。

@dynamic 告訴編譯器:屬性的 setter 與 getter 方法由用戶自己實現(xiàn),不自動生成。(當然對于 readonly 的屬性只需提供 getter 即可)。假如一個屬性被聲明為 @dynamic var,然后你沒有提供 @setter方法和 @getter 方法,編譯的時候沒問題,但是當程序運行到 instance.var = someVar,由于缺 setter 方法會導(dǎo)致程序崩潰;或者當運行到 someVar = var 時,由于缺 getter 方法同樣會導(dǎo)致崩潰。編譯時沒問題,運行時才執(zhí)行相應(yīng)的方法,這就是所謂的動態(tài)綁定。

9. ARC下,不顯式指定任何屬性關(guān)鍵字時,默認的關(guān)鍵字都有哪些?

1.對應(yīng)基本數(shù)據(jù)類型默認關(guān)鍵字是:

atomic , readwrite, assign

2.對于普通的 Objective-C 對象

atomic, readwrite, strong

10. 用@property聲明的NSString(或NSArray,NSDictionary)經(jīng)常使用copy關(guān)鍵字,為什么?如果改用strong關(guān)鍵字,可能造成什么問題?

1.因為父類指針可以指向子類對象,使用 copy 的目的是為了讓本對象的屬性不受外界影響,使用 copy 無論給我傳入是一個可變對象還是不可對象,我本身持有的就是一個不可變的副本。

2.如果我們使用是 strong ,那么這個屬性就有可能指向一個可變對象,如果這個可變對象在外部被修改了,那么會影響該屬性。

copy 此特質(zhì)所表達的所屬關(guān)系與 strong 類似。然而設(shè)置方法并不保留新值,而是將其“拷貝” (copy)。 當屬性類型為 NSString 時,經(jīng)常用此特質(zhì)來保護其封裝性,因為傳遞給設(shè)置方法的新值有可能指向一個 NSMutableString 類的實例。這個類是 NSString 的子類,表示一種可修改其值的字符串,此時若是不拷貝字符串,那么設(shè)置完屬性之后,字符串的值就可能會在對象不知情的情況下遭人更改。所以,這時就要拷貝一份“不可變” (immutable)的字符串,確保對象中的字符串值不會無意間變動。只要實現(xiàn)屬性所用的對象是“可變的” (mutable),就應(yīng)該在設(shè)置新屬性值時拷貝一份。

舉例說明:

定義一個以 strong 修飾的 array:

@property (nonatomic ,readwrite, strong) NSArray *array;

然后進行下面的操作:

NSMutableArray *mutableArray = [[NSMutableArray alloc] init];

NSArray *array = @[ @1, @2, @3, @4 ];

self.array = mutableArray;

[mutableArray removeAllObjects];;

NSLog(@"%@",self.array);

[mutableArray addObjectsFromArray:array];

self.array = [mutableArray copy];

[mutableArray removeAllObjects];;

NSLog(@"%@",self.array);

打印結(jié)果如下所示:

2015-09-27 19:10:32.523 CYLArrayCopyDmo[10681:713670] (

)

2015-09-27 19:10:32.524 CYLArrayCopyDmo[10681:713670] (

1,

2,

3,

4

)

為了理解這種做法,首先要知道,兩種情況:

1.對非集合類對象的 copy 與 mutableCopy 操作;

2.對集合類對象的 copy 與 mutableCopy 操作。

1. 對非集合類對象的copy操作:

在非集合類對象中:對 immutable 對象進行 copy 操作,是指針復(fù)制,mutableCopy 操作時內(nèi)容復(fù)制;對 mutable 對象進行 copy 和 mutableCopy 都是內(nèi)容復(fù)制。用代碼簡單表示如下:

1.[immutableObject copy] // 淺復(fù)制

2.[immutableObject mutableCopy] //深復(fù)制

3.[mutableObject copy] //深復(fù)制

4.[mutableObject mutableCopy] //深復(fù)制

比如以下代碼:

NSMutableString *string = [NSMutableString stringWithString:@"origin"];//copy

NSString *stringCopy = [string copy];

查看內(nèi)存,會發(fā)現(xiàn) string、stringCopy 內(nèi)存地址都不一樣,說明此時都是做內(nèi)容拷貝、深拷貝。即使你進行如下操作:

[string appendString:@"origion!"]

stringCopy 的值也不會因此改變,但是如果不使用 copy,stringCopy 的值就會被改變。 集合類對象以此類推。 所以,

用 @property 聲明 NSString、NSArray、NSDictionary 經(jīng)常使用 copy 關(guān)鍵字,是因為他們有對應(yīng)的可變類型:NSMutableString、NSMutableArray、NSMutableDictionary,他們之間可能進行賦值操作,為確保對象中的字符串值不會無意間變動,應(yīng)該在設(shè)置新屬性值時拷貝一份。

2、集合類對象的copy與mutableCopy

集合類對象是指 NSArray、NSDictionary、NSSet ... 之類的對象。下面先看集合類immutable對象使用 copy 和 mutableCopy 的一個例子:

NSArray *array = @[@[@"a", @"b"], @[@"c", @"d"]];

NSArray *copyArray = [array copy];

NSMutableArray *mCopyArray = [array mutableCopy];

查看內(nèi)容,可以看到 copyArrayarray 的地址是一樣的,而 mCopyArray 和 array 的地址是不同的。說明 copy 操作進行了指針拷貝,mutableCopy 進行了內(nèi)容拷貝。但需要強調(diào)的是:此處的內(nèi)容拷貝,僅僅是拷貝 array 這個對象,array 集合內(nèi)部的元素仍然是指針拷貝。這和上面的非集合 immutable 對象的拷貝還是挺相似的,那么mutable對象的拷貝會不會類似呢?我們繼續(xù)往下,看 mutable 對象拷貝的例子:

NSMutableArray *array = [NSMutableArray arrayWithObjects:[NSMutableString stringWithString:@"a"],@"b",@"c",nil];

NSArray *copyArray = [array copy];

NSMutableArray *mCopyArray = [array mutableCopy];

查看內(nèi)存,如我們所料,copyArray、mCopyArray和 array 的內(nèi)存地址都不一樣,說明 copyArray、mCopyArray 都對 array 進行了內(nèi)容拷貝。同樣,我們可以得出結(jié)論:

在集合類對象中,對 immutable 對象進行 copy,是指針復(fù)制, mutableCopy 是內(nèi)容復(fù)制;對 mutable 對象進行 copy 和 mutableCopy 都是內(nèi)容復(fù)制。但是:集合對象的內(nèi)容復(fù)制僅限于對象本身,對象元素仍然是指針復(fù)制。用代碼簡單表示如下:

[immutableObject copy] // 淺復(fù)制

[immutableObject mutableCopy] //單層深復(fù)制

[mutableObject copy] //單層深復(fù)制

[mutableObject mutableCopy] //單層深復(fù)制

參考鏈接:iOS集合的深復(fù)制和淺復(fù)制

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • ——讀《喜寶》有感 “幸福就是去愛,去感動,去祈求,去充滿激情地生活。”這是我在4月3日的手機晚報上看到的。 姜喜...
    sophia_青菡閱讀 873評論 0 0
  • 作者:黃漢夫 學(xué)號:16090120017 外國語學(xué)院(第一篇) 轉(zhuǎn)載自威鋒網(wǎng) 【嵌牛導(dǎo)讀】:本文將分析介紹GPU...
    超帥氣的昵稱誒閱讀 343評論 0 0
  • 我在蚊帳里掛了一只小電風扇。扇葉轉(zhuǎn)動,涼風拂面,女兒很快就睡著了。睡夢中,女兒嘴角還掛著一絲微笑。 “爸爸,...
    寧靜致遠WMJ閱讀 717評論 1 4