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];
});
}