Effective Objective學習筆記

第一章

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)存地址。


WX20180510-102951.png

分配在堆中的內(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ù)保存為各種實例變量,實例變量一般通過“存取方法”來訪問。


WX20180510-111629.png

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


WX20180510-111951.png

不討論其他語言的做法,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)上。


WX20180511-103811.png

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


WX20180511-104033.png

本條討論互換兩個方法實現(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)用其所沒有的方法,會有警告。


WX20180511-132128.png

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)所需的各種信息。
下面這個圖就這樣吧。


WX20180515-093932.png

全局塊、棧塊、堆塊

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


WX20180515-094316.png

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í)行了。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 1.ios高性能編程 (1).內(nèi)層 最小的內(nèi)層平均值和峰值(2).耗電量 高效的算法和數(shù)據(jù)結(jié)構(gòu)(3).初始化時...
    歐辰_OSR閱讀 30,193評論 8 265
  • OC語言基礎(chǔ) 1.類與對象 類方法 OC的類方法只有2種:靜態(tài)方法和實例方法兩種 在OC中,只要方法聲明在@int...
    奇異果好補閱讀 4,516評論 0 11
  • 太陽當空照, 烏云來擋道, 太陽忍不住, 終于發(fā)問了。 為何擋道?為何擋道? 我要把大地照, 烏云伯伯道: 自然之...
    田小麥閱讀 280評論 0 2
  • 你離我那么近 像小時候那樣和我說著悄悄話 笑的依舊燦爛 笑的依舊純真 笑的像個孩子一樣 可我只能聽到自己的心跳 我...
    北辰浪兒閱讀 200評論 0 1

友情鏈接更多精彩內(nèi)容