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)容,可以看到 copyArray 和 array 的地址是一樣的,而 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ù)制