1. 程式人生 > >Effective Object-C 2.0 觀後管《二》

Effective Object-C 2.0 觀後管《二》


1.description和debugdescription區別


2.儘量使用不可變物件,標頭檔案中暴露readonly,實現檔案中重寫成readwrite.(序列配發佇列)
3.使用清晰而協調的命名方式
4.為私有方法名加字首,真陽很容易將其與公共方法區分開。不要單用一個下劃線做私有方法的字首,因為這種做法是預留給蘋果自己用的。
5.如果想讓自己所寫的物件具有拷貝功能,需要實現NSCopying協議。
6.如果自定義的物件分為可變和不可變版本,那麼就需要同時實現NSCopying與NSMutableCopying協議。
7.

-(id)copyWithZone:(NSZone *)zone {
    EOCPerson *copy = [[[self class] alllocWithZone:zone] initWithFirstName:_firstName andLastName:_lastName];
    copy->_friends = [_friends mutableCopy];
    return copy;
}

8.使用“位段”結構體來快取委託物件是否能響應特定的選擇子。(欄位後面帶數字表示該欄位儲存位的位大小),如下圖,該結構體共佔三個位。1位元組=8位。

@interface EOCNetworkFetcher () {
    struct {
        unsigned int didReceivedData : 1,
        unsigned int didFailWithError : 1,
        unsigned int didUpdateProgressTo : 1,
    }_delegateFlags
}
@end

實現快取功能的程式碼可以寫在設定delegate的方法裡

- (void)setDelegate:(id)delegate{
    _delegate = delegate;
    _deleteFlags.didReceiveData = delegate respondsToSelector:@selector(networkFetcher:);
    _deleteFlags.didFailWithError = delegate respondsToSelector:@selector(didFailWithError:);
    _deleteFlags.didUpdateProgressTo = delegate respondsToSelector:@selector(didUpdateProgressTo:);
}

在實現delegate方法時,就不用再頻繁的去判斷是否實現了委託方法

if(_delegate.didReceiveData){
    [_delegate didReceiveData];
}

9.使用分類機制把類的實現程式碼劃分成多個小塊,便於管理。

10.a.向第三方類中新增分類時,總應該給其名稱加上專屬的字首。b.給分類中新增方法時,總應該給其新增專屬的字首名稱。

11.“懸掛”指標,retain,release,autorelease.

12.autorelease的理解:

autorelease能延長物件的生命週期,

- (NSString *)stringValue{
    NSString *str = [NSString alloc] initWithFormat:@"test"];
    return [str autorelease];
}

NSString *str = [self stringValue];
NSLog(@"The string is: %@", str);

13."孤島"事件,即迴圈引用。通常採用“弱引用”來解決此類問題。或是從外界命令迴圈中的某個物件不再保留另外一個物件。這兩種方法都能打破保留環,從而避免記憶體洩漏。

14.塊的分類:全域性塊,棧塊,堆塊。分析:全域性塊的生命週期像單例一樣。棧塊的作用域有限,需要把棧塊拷貝到堆區。

void (^block)();
if(/*some condition*/){
    block = ^{
        NSLog(@"block A");
    };
}else{
    block = ^{
        NSLog(@"block B");
    };
}
block();

定義在if及else語句中的塊都定義在棧記憶體中。編譯器會給每塊分配好棧記憶體,然而等離開了相應的的範圍之後,編譯器有可能把分配給塊的記憶體覆寫掉。於是,這兩個塊只能在if-else作用域內有效,這樣寫出來的程式碼可以編譯,但是執行起來時而正常時而錯誤。為了解決此問題,我們可以把棧塊拷貝到堆記憶體中:

void (^block)();
if(/*some condition*/){
    block = [^{
        NSLog(@"block A");
    } copy];
}else{
    block = [^{
        NSLog(@"block B");
    } copy];
}
block();

判斷是否為全域性塊,就看塊所需要的全部的資訊是否在編譯期確定,所以可以把它做成全域性塊。

15.為常用的塊型別建立typedef.

塊的語法定義: return_type (^block_name)(parameters)。

a.為了隱藏複雜的塊型別,我們可以給塊取個別名,需要用到C中的“型別定義”的特性。

typedef int (^EOCSomeBlock)(BOOL flag,int value);

EOCSomeBlock block = ^(BOOL flag,int value){
    //Implementation
        
}

b.還有一個好處就是,如果需要修改塊的傳入引數,則只需要修改塊的定義即可,不需要修改每個地方的塊。

c.用handler塊降低程式碼的分散程度。比如一些網路請求框架,都是利用塊來實現的非同步回撥。

d.建議使用一個塊來表示成功回撥和失敗回撥。好處有:更為靈活,比方說中途出現錯誤,那麼把資料和錯誤同時傳過來,並且可以把傳過來的部分資料做處理;缺點是:全部邏輯都一些在一起,所以會令塊變得比較長。

要點:

1.如果塊所捕獲的物件直接或間接地保留了塊本身,那麼就得當心保留環的問題。

2.一定要找個適當的時機解除保留環,而不能把責任推給API的呼叫者。

16.多用派發佇列,少用同步鎖。

a.派發佇列可用來表述同步語義,這種做法要比使用@synchronized塊或NSLock物件更簡單。

b.將同步與非同步派發結合起來,可以實現與普通加鎖機制一樣的同步行為,而這麼做卻不會阻塞非同步派發的執行緒。

c.使用同步佇列和柵欄塊,可以令同步行為更加高效。

17.多用GCD,少用performSelector系列方法。

a.performSelector系列方法在記憶體管理方面容易有疏失。它無法確定將要執行的選擇子具體是什麼,因為ARC編譯器就無法插入適當的記憶體管理方法。

b.performSelector系列方法所能處理的選擇子太過侷限了,選擇子的返回型別和方法引數都有收到了限制。

c.如果想要把任務放在另外一個執行緒上,最好不要使用performSelector,而應該採用GCD派發機制來實現。

18.掌握GCD及操作佇列的使用時機。

a.取消操作。如果使用操作佇列,那麼想要取消操作是很容易的。NSOperation的操作很容易取消,而GCD佇列一旦被新增到執行緒佇列中就無法被取消了。

b.指定操作間的依賴關係。

c.通過鍵值觀測機制監控NSOperation物件的屬性。NSOperation有很多屬性都可以通過KVO來監聽,比如可以通過isCancelled屬性來判斷任務是否已取消,通過觀察isFinished來監聽任務是否已完成。

d.指定操作的優先順序。操作的優先順序表示此操作和佇列中其他操作之間的優先順序。優先順序高的操作先執行,優先順序低的後執行。反觀GCD則沒有直接實現此功能的api,gcd有的是針對整個佇列的優先順序。

19.通過Dispatch Group機制

涉及函式:dispatch_group_enter,dispatch_group_leave,dispatch_group_wait(group,dispatch_time_t),dispatch_group_notify,dispatch_group_asyn,dispatch_apply(count,dispatch_queue_t,block)(迴圈呼叫函式);

a.一系列任務可歸入一個dispatch group之中。開發者可以在這組任務執行完畢時獲得通知。

b.通過dispatch group,可以在併發式派發佇列裡同時執行多項任務。

20.使用dispatch_once來執行只需執行一次的執行緒安全程式碼。

a.經常需要編寫“只需執行一次的執行緒安全程式碼”。通過GCD提供的dispatch_once函式,很容易就能實現此功能。

b.標記應該申明在static或global作用域中,這樣的話,這樣的話,再把只需執行一次的塊傳給dispatch_once函式時,傳進去的標記也是相同的。

21.dispatch_get_current_queue函式的行為常常與開發者所預期的不同。此函式已經廢棄,只應做除錯用。

系統框架

1.瞭解Foundation框架

2.遍歷collection有四種方式。for迴圈,NSEnumerator,for..in,基於塊的遍歷器。

3.構建快取是應該選用NSCache而非NSDictionary物件。

a.因為NSCache可以提供優雅的自動刪減功能,而且是執行緒安全的,此外,它與字典不同,它不會拷貝鍵。

b.可以給NSCache設定上限,以限制快取中的物件總數和總記憶體消耗,,而這些尺度則定義了快取刪減其中物件的時機。但是絕對不要把這些尺度當成可靠的“硬限制”,他們僅對NSCache起指導作用.

c.將NSPurgeable和NSCache搭配使用,可實現自動清除資料的功能,也就是說,該NSPurgeable物件所佔記憶體被系統所丟棄時,該物件也會從快取中移除。

d.如果快取使用得當,那麼應用程式的響應速度就能提高。只有那些“重新計算起來很費事的”的資料,才值得放入快取,比如那些需要從網路獲取或從磁碟讀取的資料。

4.精簡initialize和load的實現程式碼

a.在載入階段,如果類實現了load方法,那麼系統就會呼叫它。分類裡也可以定義此方法,類的load方法比分類的load方法要先呼叫。與其他方法不太一樣,load方法不參與覆寫機制。

b.首次使用某個類之前,系統會向其傳送initialize訊息。由於此方法會遵從普通方法的覆寫機制,所以通常應該在裡面判斷當前要初始化哪個類。

c.load與initialize方法都應該實現得精簡些。這有助於保持應用程式的響應能力,也能減少引入“依賴環”的機率。

d.無法在編譯期設定的全域性變數,可以放在initialize方法裡初始化。

5.使用“無縫橋接”技術,可以在定義於Foundation框架中的OC類和定義於CoreFoundation框架中的C資料結構之間相互轉換。

oc轉c:__bridge + 資料結構

c轉oc:__bridge_transfer + 物件型別

我們稱oc裡面的資料型別為物件型別(NSArray),稱C裡面的資料型別為資料結構(CFArrayRef)。

6.別忘了NSTimer會保留目標物件

計時器和“執行迴圈”相關聯,執行迴圈到時候會觸發任務。建立NSTimer時,可以將其“預先安排”在當前的執行迴圈中,也可以先建立好,然後由開發者自己來排程。無論採用哪種方式,只有把計時器放進執行迴圈裡,它才能正常觸發任務。

+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimerInterval)seconds
                                        target:(id)target
                                        selector:(SEL)selector
                                        userInfo:(id)userInfo
                                        repeates:(BOOL)repeats
    

一般我們可以通過上述方法建立計時器,會在指定的時間間隔之後執行任務,也可以令其反覆執行任務,直到開發者可以手動將其關閉為止。一次性計時器在執行完相關任務之後,會自動失效。但如果是設定成了重複執行模式,那麼必須自己呼叫invalidate方法,才能令其停止。

如何有效的避免“保留環”呢。這個問題可以通過“塊”來解決。雖然當前計時器並不支援直接使用塊,但是我們可以新增分類,新增方法來實現。

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface NSTimer (BlockSupport)
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti task:(void(^)())task
                                    repeats:(BOOL)yesOrNo;
@end

NS_ASSUME_NONNULL_END


#import "NSTimer+BlockSupport.h"

@implementation NSTimer (BlockSupport)
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti task:(void(^)())task
                                    repeats:(BOOL)yesOrNo
{
    NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:ti target:self selector:@selector(targetTask:) userInfo:[task copy] repeats:yesOrNo];
    return timer;
}

+ (void)targetTask:(NSTimer *)timer
{
    void (^block)(NSTimer *) = timer.userInfo;
    if (block) {
        block(timer);
    }
}
@end

然後使用的時候注意避免迴圈引用即可。

- (void)viewDidLoad {
    [super viewDidLoad];
    
    //_timer = [NSTimer scheduledTimerWithTimeInterval:3.f target:self selector:@selector(doSomething) userInfo:nil repeats:YES];
    __weak typeof(self)weakSelf = self;
    _timer = [NSTimer scheduledTimerWithTimeInterval:3.f task:^{
        __strong typeof(weakSelf)strongSelf = weakSelf;
        [strongSelf doSomething];
    } repeats:YES];
    
    dispatch_time_t afterTime = dispatch_time(DISPATCH_TIME_NOW, 6 * NSEC_PER_SEC);
    dispatch_after(afterTime, dispatch_get_main_queue(), ^{
        __strong typeof(weakSelf)strongSelf = weakSelf;
        [strongSelf->_timer invalidate];
    });
}