第一章
1、起源
OC由Smalltalk演化而來,后者是消息型語言的鼻祖。
消息與函數(shù)調(diào)用的關(guān)鍵區(qū)別在于:消息結(jié)構(gòu)的語言,運行時所應(yīng)執(zhí)行的代碼由運行環(huán)境來決定;而使用函數(shù)調(diào)用的語言,則由編譯器決定。
OC的重要工作由“運行期組件”而非編譯器來完成,使用OC的面形對象特性所需的全部數(shù)據(jù)結(jié)構(gòu)及函數(shù)都在運行期組建里面。運行期組件本質(zhì)上就是一種與開發(fā)者所編代碼相連接的動態(tài)庫,其代碼能把開發(fā)者編寫的所有程序粘合起來。(ps:我的個人理解是,OC代碼在編寫之后依然是一個骨架,真正成為一個能跑能跳的人,還是在運行期間,通過runtime將這個人需要的“血肉”粘合起來,這個“血肉”已經(jīng)客觀存在。)
OC是c的超集,OC語言中的指針是用來指示對象的。
NSString *someString = @"The string";
someString為指向NSString的指針,指向分配在堆里的某塊指針,其中含有一個NSString對象。
NSString *someString = @"The string";
NSString *anotherString = someString;
這里是在棧上分配兩塊內(nèi)存,每塊內(nèi)存的大小都能容下一枚指針。兩塊內(nèi)存里的值都一樣,都是指向NSString實例的內(nèi)存地址。

分配在堆中的內(nèi)存必須直接管理,而分配在棧上用于保存變量的內(nèi)存則會在其棧幀彈出時自動清理。
OC將堆內(nèi)存管理抽象出來,OC運行期環(huán)境把這部分工作抽象為一套內(nèi)存管理架構(gòu),名叫“引用計數(shù)”。
第二章
對象是“基本構(gòu)造單元”,開發(fā)者通過對象來存儲并傳遞數(shù)據(jù)。對象之間傳遞數(shù)據(jù)并執(zhí)行任務(wù)的過程就叫做“消息傳遞”。
7、屬性
“屬性”是OC的一項特性,用于封裝對象中的數(shù)據(jù)。OC對象通常會把所需要的數(shù)據(jù)保存為各種實例變量,實例變量一般通過“存取方法”來訪問。

我們要討論的是訪問_firstName變量的代碼,編譯器就把其替換為offset,offset是硬編碼,表示該變量具體存放對象的內(nèi)存區(qū)域的起始地址有多遠
,當再開頭添加一個實例變量就會出現(xiàn)問題。

不討論其他語言的做法,OC的做法是,把實例變量當作一種存儲偏移量所用的“特殊變量”,交由“類對象”保管。偏移量會在運行期查找,如果累的定義變了,存儲的偏移量也就變了,保證訪問地址正確。(PS:代碼測試確實是這樣的,偏移量會改變,但不是上圖那種改變)。
由此引出了屬性的存取方法,也就是setter和getter,OC提供了點語法來進行存取,這里就不多做敘述了。說一點就是,使用property的自動合成是在編譯期執(zhí)行。synthesize可以指定實例變量的名字,dynamic代表不自動生成實例變量和存取方法。
關(guān)于關(guān)鍵字的描述之前的文章中有提到,這里就不再重復了。
如果想在其他方法里設(shè)置屬性指,那么同樣要遵守屬性定義中所宣稱的語意。也就是對外暴露一個方法時,內(nèi)部屬性的設(shè)置也要對應(yīng)到關(guān)鍵字。
.h
- (id)initWithFirstName:(NSString)firstName lastName:(NSString *)lastName;
.m
- (id)initWithFirstName:(NSString)firstName lastName:(NSString *)lastName
{
_firstName = [firstName copy];
_lastName = [lastName copy];
}
7、在對象內(nèi)部盡量直接訪問實例變量
在對象之外訪問實例變量時,總是應(yīng)該通過屬性來做,在對象內(nèi)部訪問實例變量時,作者建議讀取實例變量時采用直接訪問的形式,設(shè)置實例變量時通過屬性來做。
.h
- (NSString *)fullName;
- (void)setFullName:(NSString *)fullName;
.m
- (NSString *)fullName{
return [NSString stringWithFormat:@"%@ %@",self.firstName,self.lastName];
}
- (void)setFullName:(NSString *)fullName
{
NSArray *components = [fullName componentsSeparatedByString:@" "];
self.firstName = [components objectAtIndex:0];
self.lastName = [components objectAtIndex:1];
}
重寫實現(xiàn)方法
- (NSString *)fullName{
return [NSString stringWithFormat:@"%@ %@",_firstName,_lastName];
}
- (void)setFullName:(NSString *)fullName
{
NSArray *components = [fullName componentsSeparatedByString:@" "];
_firstName = [components objectAtIndex:0];
_lastName = [components objectAtIndex:1];
}
區(qū)別
- 不經(jīng)過OC的方法派發(fā),直接訪問實例變量的速度快,在這種情況下,編譯器生成的代碼會直接訪問保存對象實例變量的那塊內(nèi)存
- 直接訪問實例變量時,不會調(diào)用其“設(shè)置方法”,這就如熬過了相關(guān)屬性定義的“內(nèi)存管理語義”,例如copy等。
- 如果直接訪問實例變量,不會出發(fā)鍵值觀測“KVO”。
- 通過屬性訪問,有助于排查與之相關(guān)的錯誤,可以通過其存取方法中增加斷點進行調(diào)試。
這種方案,寫通過設(shè)置方法來做,讀取直接訪問。
注意一、在初始化方法中應(yīng)該如何設(shè)置屬性值,這種情況下總是應(yīng)該直接訪問實例變量,因為子類可能會“覆寫”設(shè)置方法。
注意二、惰性初始化,必須通過“獲取方法”來訪問屬性,如果直接訪問實例變量,則會看到尚未設(shè)置好的brain。
8、理解“對象等同性”這一概念
NSString *foo = @"Badger 123";
NSString *bar = [NSString stringWithFormat:@"Badger %i",123];
NSString *foo1 = @"Badger 123";
這個比較結(jié)果還是有意思的,這里不貼運行結(jié)果了。
NSObject 協(xié)議中有兩個用于判斷等同性的關(guān)鍵方法:
- (BOOL)isEqual:(id)object;
- (NSUInteger)hash;
isEqual判斷步驟,指針,所屬類,每個屬性。
這里調(diào)用的是系統(tǒng)默認的方法,與作者寫的方法有不同。即使是內(nèi)存地址不同,也可以isEqual成立。
- (BOOL)isEqual:(id)other
{
if (other == self) {
return YES;
} else if (![super isEqual:other]) {
return NO;
} else {
EOCPerson *ohterPerson = (EOCPerson *)other;
if(![_firstName isEqualToString:ohterPerson.firstName]){
return NO;
}
if(![_lastName isEqualToString:ohterPerson.lastName]){
return NO;
}
return YES;
}
}
hash方法,若兩對象相等,則其哈希碼也想等,但是哈希碼相同的對象卻并未想等。如上例中bar和foo的哈希碼相同,但是內(nèi)存地址是不同的。
重寫hash方法可以返回一個固定的值,這樣在collection中使用這種對象會產(chǎn)生性能問題,因為collection在檢索哈希表時,會用對象的哈希碼做索引。如果collection用set實現(xiàn),set可能會根據(jù)哈希碼把對象分裝在不同的數(shù)組中。在向set中添加新對象時,要根據(jù)其哈希碼找到與之相關(guān)的那個數(shù)組,依次檢查其中的各個元素,看數(shù)組中已有的對象是否和將要添加的新對象相等。如果相等,那就說明要添加的對象已經(jīng)在set里面了,由此可知,如果每個對象返回相同的哈希碼,那么在set中已有100000個對象的情況下,若是繼續(xù)向其中添加對象,則需要將這100000個對象全部掃描一遍。
特定類所具有的等同性判定方法
(PS;個人理解重寫isEqual方法)
等同性判定的執(zhí)行深度
NSArray檢測方式,對象個數(shù),逐個調(diào)用“isEqual:”方法。
通過類的某個屬性,“唯一標識符”來判斷
容器中可變類的等同性
在容器中放入可變類對象的時候,把某個對象放入collection之后,就不應(yīng)再改變其哈希碼了。
NSMutableArray *arrayA = [@[@1,@2]mutableCopy];
[set addObject:arrayA];
NSMutableArray *arrayB = [@[@1,@2]mutableCopy];
[set addObject:arrayB];
NSLog(@"%@",set);
NSMutableArray *arrayC = [@[@1]mutableCopy];
[set addObject:arrayC];
NSLog(@"%@",set);
[arrayC addObject:@2];
NSLog(@"%@",set);
NSSet *setB = [set copy];
NSLog(@"%@",setB);
打印結(jié)果(PS:手寫)
- {[1,2]}
- {[1],[1,2]}
- {[1,2],[1,2]}
- {[1,2]}
11、理解objc_msgSend的作用
void printHello() {
printf("hello");
}
void printGoodbye () {
printf("goodbye");
}
void doTheThing(int type) {
if (type == 0) {
printHello();
}else{
printGoodbye();
}
}
void printHello() {
printf("hello");
}
void printGoodbye () {
printf("goodbye");
}
void doTheThing(int type) {
void (*fun)();
if (type == 0) {
fun = printHello;
}else{
fun = printGoodbye;
}
fun();
}
第二種使用“動態(tài)綁定”,因為所要調(diào)用的函數(shù)知道運行期才能確定。編譯器在這種情況下生成的指令與剛才那個例子不同,在第一個例子中,if和else語句里都有函數(shù)調(diào)用指令。而在第二個例子中,只有一個函數(shù)調(diào)用指令,不過待調(diào)用的函數(shù)地址無法硬編碼在指令中,而是要在運行期讀取出來。
id returnValue = [someObject messageName:parameter];
someObject叫做接受不了者,messageName叫做選擇子,選擇子與參數(shù)結(jié)合起來成為“消息”。轉(zhuǎn)化為C語言函數(shù)調(diào)用
objc_msgSend(id self,SEL cmd,...)
這是個“參數(shù)個數(shù)可變的函數(shù)”,能接受兩個或兩個以上的參數(shù)。第一個代表參數(shù)接收者,第二個參數(shù)代表選擇子,后續(xù)參數(shù)就是消息中的那些參數(shù)。
id returnValue = objc_msgSend(someObject,@selector(messageName:),parameter);
objc_msgSend函數(shù)會依據(jù)接受著雨選擇子的類型來調(diào)用適當?shù)姆椒?。為了完成此菜做,該方法需要在接受者所屬的類中搜尋其“方法列表”,找不到就沿著繼承體系繼續(xù)向上查找,找到合適方法之后再跳轉(zhuǎn),找不到執(zhí)行“消息轉(zhuǎn)發(fā)”。
objc_msgSend會將匹配結(jié)果緩存在“快速映射表”里,每個類都有這樣一塊緩存。其他特殊情況由OC運行環(huán)境的另一些函數(shù)來處理:
- objc_msgSend_stret
- objc_msgSend_fpret
- objc_msgSendSuper
每個類中都有一張表,其中的指針都會指向這種函數(shù),選擇子的名稱則是查表時所用的“鍵”。objc_msgSend等函數(shù)正是通過這張表來尋找應(yīng)該執(zhí)行的方法并跳至其實現(xiàn)。請注意,原型的樣子和objc_msgSend函數(shù)很想,這不是巧合,而是利用尾調(diào)用優(yōu)化技術(shù),令“跳轉(zhuǎn)至方法實現(xiàn)”這一操作變得更簡單。
如果某函數(shù)的最后一項操作是調(diào)用另外一個函數(shù),那么久可以運用“尾調(diào)用優(yōu)化”技術(shù)。編譯器會生成調(diào)轉(zhuǎn)至另一函數(shù)所需的指令碼,而且不會向調(diào)用對戰(zhàn)中推入新的棧幀。只有當某函數(shù)的最后一個操作僅僅調(diào)用其他函數(shù)而不會將其返回值另作他用時,才執(zhí)行尾調(diào)用優(yōu)化。如果不這么做的話,每次調(diào)用OC方法錢,都需要為調(diào)用objc_msgSend函數(shù)準備棧幀,大家在棧蹤跡種可以看到這種棧幀,不優(yōu)化會過早發(fā)生棧溢出。
(PS:我的個人理解,函數(shù)調(diào)用,會把函數(shù)的指針壓入新的??臻g,尾調(diào)用就是返回時調(diào)用新的函數(shù),也就是再次壓入,如果還有調(diào)用,??臻g很容易溢出,這里OC使用了指令碼,不將尾調(diào)用的函數(shù)壓入棧,也就是理想情況下只在最開始的調(diào)用時壓入一次,節(jié)省了??臻g)
12、理解消息轉(zhuǎn)發(fā)機制
消息轉(zhuǎn)發(fā)分為兩大階段。第一階段先征詢接受者,所屬的類,能否動態(tài)添加方法,這叫做“動態(tài)方法解析”。第二階段涉及“完整的消息轉(zhuǎn)發(fā)機制”。
如果第一階段執(zhí)行失敗,運行時系統(tǒng)會請接受者看看有沒有其他對象能處理這條消息。如果沒有則啟動完整的消息轉(zhuǎn)發(fā)機制,運行時系統(tǒng)會把與消息有關(guān)的細節(jié)封裝在NSInvocation對象中,給接受者最后一次機會,令其設(shè)法解決當前還未處理的消息。
@dynamic string,number,date,opaqueObject;
- (instancetype)init
{
if(self = [super init]){
_backingStore = [NSMutableDictionary new];
}
return self;
}
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
NSString *selectorString = NSStringFromSelector(sel);
if([selectorString hasPrefix:@"set"]){
/**
1:消息接受者
2:方法選擇子
3:待添加方法函數(shù)指針
4:待添加方法的“類型編碼”
*/
class_addMethod(self, sel, (IMP)autoDictionarySetter, "v@:@");
}else{
class_addMethod(self, sel, (IMP)autoDictionaryGetter, "@@:");
}
return YES;
}
id autoDictionaryGetter(id self,SEL _cmd){
//從類中獲取backingStore對象
EOCAutoDictionary *typedSelf = (EOCAutoDictionary *)self;
NSMutableDictionary *backingStore = typedSelf.backingStore;
//key是selector的名字
NSString *key = NSStringFromSelector(_cmd);
//返回這個值
return [backingStore objectForKey:key];
}
void autoDictionarySetter(id self,SEL _cmd, id value){
//從類中獲取backingStore對象
EOCAutoDictionary *typedSelf = (EOCAutoDictionary *)self;
NSMutableDictionary *backingStore = typedSelf.backingStore;
//方法的應(yīng)該是類似于setOpaqueObject:,我們需要截掉set和:,并把首字母轉(zhuǎn)化為小寫
NSString *selectorString = NSStringFromSelector(_cmd);
NSMutableString *key = [selectorString mutableCopy];
//刪除:
[key deleteCharactersInRange:NSMakeRange(key.length-1, 1)];
//刪除set
[key deleteCharactersInRange:NSMakeRange(0, 3)];
//最小化首字母
NSString *lowercaseFirstChar = [[key substringToIndex:1]lowercaseString];
[key replaceCharactersInRange:NSMakeRange(0, 1) withString:lowercaseFirstChar];
if(value){
[backingStore setObject:value forKey:key];
}else{
[backingStore removeObjectForKey:key];
}
}
以上代碼的基本思路是,創(chuàng)建一個字典,存放屬性的數(shù)據(jù)。
使用resolveInstanceMethod:方法截獲到set和get請求。
按照一般邏輯,會先用set方法賦值,就動態(tài)添加這個方法class_addMethod(self, sel, (IMP)autoDictionarySetter, "v@:@");
在autoDictionarySetter方法中,會把set方法的方法名截取成get方法,步驟就是去除set和“:”然后首字母小寫。把方法名作為key,傳入的數(shù)據(jù)作為value,存在字典中。在調(diào)用get方法時,會根據(jù)get方法名找到字典中對應(yīng)的value。
13、用“方法調(diào)配技術(shù)”調(diào)試“黑盒方法”
為什么要出現(xiàn)這個方法呢,因為我們即不需要源代碼,也不需要通過繼承子類來覆寫方法就能改變這個類的本身功能。簡而言之就是方法替換。
類的方法列表會吧選擇子的名字映射到相關(guān)的方法實現(xiàn)上。

OC運行時系統(tǒng)提供方法可以操作這個表,如新增選擇子,改變選擇子對應(yīng)的方法實現(xiàn),交換選擇子所映射到的指針。

本條討論互換兩個方法實現(xiàn)。
交換方法實現(xiàn):
void method_exchangeImplementations(Method m1, Method m2)
上述參數(shù)的方法實現(xiàn):
Method class_getInstanceMethod(class aClass, SEL aSelector)
執(zhí)行下列代碼,即可交換前面提到的lowercaseString 與 uppercaseString方法實現(xiàn):
Method originalMethod = class_getInstanceMethod([NSString class], @selector(lowercaseString));
Method swappedMethod = class_getInstanceMethod([NSString class], @selector(uppercaseString));
method_exchangeImplementations(originalMethod, swappedMethod);
實際應(yīng)用:
Method originalMethod = class_getInstanceMethod([self class], @selector(lowercaseString));
Method swappedMethod = class_getInstanceMethod([self class], @selector(eocMyLowercaseString));
method_exchangeImplementations(originalMethod, swappedMethod);
- (NSString *)eocMyLowercaseString
{
NSString *lowercase = [self eocMyLowercaseString];
NSLog(@"%@ => %@",self,lowercase);
return lowercase;
}
NSString *string = @"Asssssss";
NSString *lowString = [string lowercaseString];
2018-05-11 11:22:09.843709+0800 EffecitveOC[45073:2901031] Asssssss => asssssss
14、理解@“類對象”的用意
每個OC對象實例都是指向某塊內(nèi)存數(shù)據(jù)的指針。所以在聲明變量時,類型后面要跟一個@“*”字符:
NSString *pointerVariable = @"Some thing";
對于通用的對象類型id,由于其本身已經(jīng)是指針了,所以我們能夠這樣寫:
id string1 = @"Some thing";
上面這種定義方式與用NSString*來定義性筆,語法意義相同,區(qū)別在于,如果聲明時制定了具體類型,那么在該類的實例上調(diào)用其所沒有的方法,會有警告。

super_class指針確立了繼承關(guān)系,而isa指針描述了實例所屬的類。
以下代碼與原書運行有出入,應(yīng)該是apple修改了規(guī)則
NSMutableDictionary *mutableDict = [NSMutableDictionary new];
BOOL boo1 = [mutableDict isMemberOfClass:[NSDictionary class]];
BOOL boo2 = [mutableDict isMemberOfClass:[NSMutableDictionary class]];
BOOL boo3 = [mutableDict isKindOfClass:[NSDictionary class]];
BOOL boo4 = [mutableDict isKindOfClass:[NSDictionary class]];
NSLog(@"%d%d%d%d",boo1,boo2,boo3,boo4);
2018-05-11 13:45:22.112139+0800 EffecitveOC[46246:2977156] 0011
以上代碼調(diào)用isMemberOfClass時,由于apple使用了類簇模式,所以mutableDict并不是NSMutableDictionary類型,而是子類型__NSDictionaryM,故都為NO。
- 每個實例都有一個指向class對象的指針,用以表示其類型,而寫著class對象則構(gòu)成了類的繼承體系
- 如果對象類型無法在編譯期確定,那么就應(yīng)該使用消息類型查詢方法來探知isMemberOfClass isKindOfClass
-盡量使用類型消息查詢方法來確定對象類型,不要直接比較類對象,因為某些對象可能實現(xiàn)消息轉(zhuǎn)發(fā)功能。
第四章
23、通過委托與數(shù)據(jù)源協(xié)議進行對象間通信
委托模式,用于對象間的通信,可將數(shù)據(jù)與業(yè)務(wù)邏輯解耦。
比如用戶界面有個顯示數(shù)據(jù)的視圖,此視圖應(yīng)包含數(shù)據(jù)所需的邏輯代碼,不應(yīng)界定要顯示何種數(shù)據(jù)。視圖對象的屬性中,可以包含負責數(shù)據(jù)與事件處理的對象。這兩種對象分別稱為“數(shù)據(jù)源(data source)”和“委托(delegate)”。
第五章
29、內(nèi)存管理
這本書有的地方和現(xiàn)在的方式有出入,我盡量總結(jié)和現(xiàn)在類似的概念或代碼。
OC語言使用引用計數(shù)來管理內(nèi)存。
- retain 增加引用計數(shù)
- release 減少引用計數(shù)
- autorelease 待稍后清理“自動釋放池”時,再遞減保留計數(shù)。
屬性存取方法中的內(nèi)存管理
- (void)setFoo:(id)foo {
[foo retain];
[_foo release];
_foo = foo;
}
先retain后release是因為 如果兩個值指向同一個對象,先執(zhí)行release可能會釋放掉對象,retain就失效,實例變量成了懸掛指針。
autorelease可以保證對象在跨越“方法調(diào)用邊界”后一定存活。
30、以ARC簡化引用計數(shù)
內(nèi)存泄漏的意思是,沒有正確釋放已經(jīng)不再使用的內(nèi)存。
ARC中引用計數(shù)還在執(zhí)行,只不過保留與釋放操作現(xiàn)在是由ARC自動為你添加。因為ARC會自動執(zhí)行retain、release、autorelease等操作,所以調(diào)用這些方法是非法的。手動調(diào)用會干擾ARC工作。ARC調(diào)用這些方法時,不采用OC的消息機制,而是直接調(diào)用底層c語言版本。
使用ARC時必須遵循的方法命名規(guī)則
第六章塊與大中樞派發(fā)
37、理解“塊”這一概念
塊的強大之處時:在聲明它的范圍內(nèi),所有變量都可以為其所捕獲。也就是說,那個范圍里的全部變量,在塊里依然可用。
void (^someBlock)() = ^{
//block implementation here
};
int (^addBlock) (int a,int b) = ^(int a, int b){
return a+b;
};
int add = addBlock(2,5);
NSLog(@"%d",add);
默認情況下,為塊所捕獲的變量,是不可以在塊里修改的,聲明變量的時候可以加上__block修飾符,就可以在塊內(nèi)修改了。
NSArray *array = @[@1,@2,@3,@4];
__block NSInteger count = 0;
[array enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
if([obj compare:@2] == NSOrderedAscending){
count ++;
}
}];
NSLog(@"%lu",count);
如果塊所捕獲的變量是對象類型,那么就會自動保留它,系統(tǒng)在釋放這個塊的時候,也會將其一并釋放。塊本身也和其他對象一樣,有引用計數(shù),當最后一個指向塊的引用移走之后,塊就會瘦了,回收時也會釋放塊所捕獲的變量,以便平衡捕獲時所執(zhí)行的保留操作。
如果讀取或?qū)懭氩僮鞑东@了實例變量,那么也會自動把self變量一并捕獲。self也是個對象,因而塊在捕獲它時也會將其保留,如果self所指代的那個對象同時也保留了塊,那么這種種情況下就會導致“保留環(huán)”。
塊本身也是對象,在存放塊對象的內(nèi)存區(qū)域中,首個變量指向class對象的指針,該指針叫做isa。其余內(nèi)存里還有塊對象正常運轉(zhuǎn)所需的各種信息。
下面這個圖就這樣吧。

全局塊、棧塊、堆塊
這一段其實解釋了為什么現(xiàn)在block要用copy修飾

if和else語句中的兩個塊都分配在棧內(nèi)存中。編譯器會給每個塊分配好棧內(nèi)存,等離開相應(yīng)范圍后,編譯器有可能吧分配給給塊的內(nèi)存覆寫掉。這樣就可能導致程序崩潰。為了解決此問題,可以給塊發(fā)送copy,將塊從棧復制到堆上,塊就是帶有引用計數(shù)的對象了,就需要arc管理了。
全局塊,這種塊不回捕捉任何狀態(tài),運行時也無需有狀態(tài)來參與,塊所使用的整個內(nèi)存區(qū)域,在編譯器已經(jīng)完全確定,因此,在全局塊可以聲明在全局內(nèi)存中,而不需要每次用的時候與棧中創(chuàng)建,另外全局塊的拷貝操作是個空操作,因為全局塊絕不可能為系統(tǒng)所回收,這種塊實際上相當于單例。
41、派發(fā)隊列
文章中提到了synchronized和NSLock,篇幅不多,我這邊也就直接用GCD。
就屬性來說,可以用原子性來修飾,即可實現(xiàn),如果使用GCD就可以這么寫
- (NSString *)name{
@synchronized(self){
return _name;
}
}
- (void)setName:(NSString *)name
{
@synchronized(self){
_name = name;
}
}
使用 @synchronized(self)會很危險,因為所有同步塊都會彼此搶奪同一個鎖,要是有很多屬性都這么寫的話,那么每個屬性的同步塊都要等其他所有同步塊執(zhí)行完畢才能執(zhí)行,我們想要的是每個屬性各自獨立的執(zhí)行。同樣,atomic也不是肯定線程安全。
gcd第一步
使用“穿行同步隊列”,將讀寫操作安排在同一個隊列里,即可保證數(shù)據(jù)同步。
_syncQueue = dispatch_queue_create("com.zhjy.larkdata.FaceEaxm", NULL);
- (NSString *)name{
__block NSString *localSomeString;
dispatch_sync(_syncQueue, ^{
localSomeString = _name;
});
return localSomeString;
}
- (void)setName:(NSString *)name
{
dispatch_sync(_syncQueue, ^{
_name = name;
});
}
此模式的思路是,把設(shè)置操作與獲取操作都安排在穿行隊列中,這樣的話,所有針對屬性的訪問操作就都同步了。
然而還可以進一步優(yōu)化。設(shè)置方法并不一定非得是同步的。設(shè)置實例變量所用的塊,并不需要想設(shè)置方法返回什么值。
- (void)setName:(NSString *)name
{
dispatch_async(_syncQueue, ^{
_name = name;
});
}
把同步派發(fā)改成了異步派發(fā),壞處:執(zhí)行異步派發(fā),需要拷貝塊,效率低。
多個獲取方法可以并發(fā)執(zhí)行,而獲取方法與設(shè)置方法之間不能并發(fā)執(zhí)行
_syncQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
- (NSString *)name{
__block NSString *localSomeString;
dispatch_sync(_syncQueue, ^{
localSomeString = _name;
});
return localSomeString;
}
- (void)setName:(NSString *)name
{
dispatch_async(_syncQueue, ^{
_name = name;
});
}
現(xiàn)在無法正確實現(xiàn)同步,所有的讀取和寫入會在同一個隊列上執(zhí)行,不過由于是并發(fā)隊列,所以讀取與寫入操作可以隨時執(zhí)行,而我們恰恰不想讓這些操作隨意執(zhí)行,可以用一個柵欄來解決。
- (NSString *)name{
__block NSString *localSomeString;
dispatch_sync(_syncQueue, ^{
localSomeString = _name;
});
return localSomeString;
}
- (void)setName:(NSString *)name
{
dispatch_barrier_sync(_syncQueue, ^{
_name = name;
});
}
對于讀取操作依然可以并發(fā)執(zhí)行,但是寫入操作就要單獨執(zhí)行了。