聲明:這個筆記的系列是我每天早上打開電腦第一件做的事情,當然使用的時間也不是很多因為還有其他的事情去做,雖然吧自己買了紙質的書但是做筆記和看的時候基本都是看的電子版本,一共52個Tip每一個Tip的要點我是完全謄寫下來的,害怕自己說的不明白所以就謄寫也算是加強記憶,我會持續修改把自己未來遇到的所有相關的點都加進去,最后希望讀者尊重原著,購買正版書籍。PS:不要打賞要喜歡~
GitHub代碼網址,大大們給個鼓勵Star啊。
整個系列筆記目錄
《Effective Objective-C 2.0》第一份讀書筆記
《Effective Objective-C 2.0》第二份讀書筆記
《Effective Objective-C 2.0》第三份讀書筆記
第四章 協議與分類
23.通過委托與數據源協議進行對象間通信
我舉一個~ 獲取網絡數據的類含有一個“委托對象”,在獲取完數據之后,它會回調這個委托對象。
EOCDataModel對象就是EOCNetworkFetcher的委托對象。EOCDataModel請求EOCNetworkFetcher“以異步方式執行一項任務”,而EOCNetworkFetcher在執行完這項任務之后,就會通知其委托對象,也就是EOCDataModel。
這里面代理要用weak修飾。通常情況下,因為代理delegate要對比TableView做相應的操作,所以代理delegate要持有TableView這個對象,而如果我們用Strong修飾TableView的delegate屬性,就會引入保留環(retain cycle)。
如果要在委托對象上調用可選方法,那么必須提前使用類型信息查詢方法判斷這個委托對象是否響應相關選擇子。
NSData * data ;
if([_delegate respondsToSelector:@selector(networkFetcher: didReceiveData:)]){
[_delegate networkFetcher: didReceiveData:];
}
委托模式: 對象把應該對某個行為的責任委托給另一個類。
以TableView為例子
委托模式是信息從類流向受委托者也就是讓這個責任流向了受委托者。
數據源模式(Data Source Pattern)是數據流向TableView,決定TableView的布局。
寫一個現實中的例子吧。(通過協議代理哈,我知道這個方案更好的方案,只不過想寫出來下協議代理的步驟)。(詳細見第二十三條Demo)。
加入我想通過一個類開啟定時器,然后另一個類來監控這個類的定時器,當這個定時器開啟5S之后,相應的做一些事情,首先是被監視定時器的那個類:
//.h
#import <Foundation/Foundation.h>
@class Thirtyeight;
//首先是協議代理方式
typedef void(^iSuCompletionHandle)(int five);
@protocol iSuNetworkFetcherDelegate <NSObject>
- (void)netwrokFecher:(Thirtyeight *)networkFetcher didFinishWithData:(int)five;
@end
@interface Thirtyeight : NSObject
@property (nonatomic,weak) id<iSuNetworkFetcherDelegate> delegate;
@property (nonatomic,strong) NSTimer * iSuTimer;
@property (nonatomic,assign) int iSuNumber;
@property (nonatomic,copy) iSuCompletionHandle iSuCompletion;
- (void)TimerTest;
@end
//.m
@implementation Thirtyeight
- (void)TimerTest{
self.iSuNumber = 0;
//這個Block我寫著玩的,實際作用不大,只不過下面有一個關于block的問題。
__weak typeof(self) weakSelf = self;
self.iSuCompletion = ^(int five) {
__strong typeof(weakSelf) strongSelf = weakSelf;
strongSelf.iSuNumber = five;
NSLog(@"賦值給self.iSuNumber為%i",strongSelf.iSuNumber);
};
__block int num = 0;
_iSuTimer =[NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
__strong typeof(weakSelf) strongSelf = weakSelf;
num++;
NSLog(@"定時器里面的變化%d",num);
/*
這邊就是最重要的代碼了,也就是傳遞給監聽對象數據。
*/
[strongSelf.delegate netwrokFecher:strongSelf didFinishWithData:num];
if (num == 5) {
strongSelf.iSuNumber = num;
NSLog(@"現在的數值為:%d",strongSelf.iSuNumber);
//這邊調用block的時候,是不允許我們使用strong.iSuCompletion這樣的操作。
_iSuCompletion(num);
[_iSuTimer invalidate];
};
}];
}
主動監聽類:
//.m
@implementation ThirtyeightViewController
interface ThirtyeightViewController ()<iSuNetworkFetcherDelegate>
- (void)netwrokFecher:(Thirtyeight *)networkFetcher didFinishWithData:(int)five{
//這樣就可以監聽了。
NSLog(@"現在行走的時間是多少:%d",five);
}
@end
要點:
- 委托模式為對象提供一套接口,使其可由此將相關事件告知其他對象。
- 將委托對象應該支持的接口定義為協議,在協議中把可能需要處理的事件定義成方法。
- 當某對象需要從另外一個對象中獲取數據時,可以使用委托模式。這種情況下,該模式也成為“數據源協議(data source protocal)”
- 若有必要,可實現含有位段的結構體,將委托對象是否響應相關協議方法這一信息緩存到其中。
24.將類的實現代碼分散到便于管理的數個分類之中
分類功能是對類對相應功能的整理,使得整個類的條理更加清晰,防止更多不需要的方法在頭文件中導入,影響系統性能。
例如:
//.h
#import <Foundation/Foundation.h>
@interface EOCBengi : NSObject
@property (nonatomic, copy, readonly) NSString * firstName;
@property (nonatomic, copy, readonly) NSString * lastName;
@property (nonatomic, strong, readonly) NSArray * friends;
- (id)initWithFirstName:(NSString *)firstName andLastName:(NSString *)lastName;
@end
@interface EOCBengi(Friendship)
- (void)addFriend:(EOCBengi *)person;
- (void)removeFirend:(EOCBengi *)person;
- (BOOL)isFriendWith:(EOCBengi *)person;
@end
@interface EOCBengi(Work)
- (void)performDaysWork;
- (void)takeVacationFromWork;
@end
@interface EOCBengi(Play)
- (void)goToTheCinema;
- (void)goToSportsGame;
@end
//.m
#import "EOCBengi.h"
@implementation EOCBengi
@end
@implementation EOCBengi(Friendship)
@end
@implementation EOCBengi(Work)
@end
@implementation EOCBengi(Play)
@end
要點:
- 使用分類機制把類的實現代碼劃為易于管理的小塊。
- 將應該視為“私有”的方法歸為Private的分類中,來隱藏實現細節。
25.總是為第三方類的分類名稱加前綴
分類機制通常用于向無源碼的既有類中新增功能。
要點:
- 向第三方類中添加分類時,總應給其名稱加上你專用的前綴。
- 向第三方磊中添加分類時,總應給其中的方法加上你專用的前綴。
26.勿在分類中聲明屬性
盡管在技術上講,分類中也是可以聲明屬性的,但這種做法還是要盡量避免的。原因在于,除了"class-continuation分類"之外,其他分類都無法向類中新增實例變量,因此,它們無法把實現屬性所需的實例變量合成出來。
如果在分類中聲明了屬性,我們可以通過運行期關聯對象的方式get set數值,但是這樣容易引起內存問題。因為我們在為屬性實現存取方法時,經常會忘記遵守從內存管理語義,那么有可能在不經意之間就造成了內存出現錯誤。(當然這個錯誤是自己可以解決的)。
#import <objc/runtime.h>
static const char * kFriendsPropertyKey = “kFriendPropertyKey”
@implemetation EOCPerson(Friendship)
(NSArray*)friends(
return objc_getAssociatedObject(self, kFriendsPropertyKey);
)
(void)setFriends:(NSArray*)friends{
objc_setAssociatedObject(self , kFriendsPropertyKey , friends,OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
要點
- 把封裝數據所用的全部屬性都定義在主接口里。
- 在”class - continuation分類” 之外的其他分類中,可以定義存取方法,但盡量不要定義屬性。
27.使用“class- continuation分類“ 隱藏實現細節
編寫的準則是
@interface EOCPerson ()
//Methods here
@end
這個就是正常的我們創建的.m上面的東西,就是class- continuation
類的延續。
要點:
- 通過“ class-continuation分類”向類中新增實例變量。
- 如果某屬性在主接口聲明為“只讀”,而類的內部又要設置方法修改此屬性,那么就在“calss-continuation分類’中將其擴展為“可讀寫”。
- 把私有方法的原型聲明在“calss-continuation分類”里面。
- 若要使類所遵循的協議不被人知道,則可用“class-continuation分類”中聲明。
28.通過協議提供匿名對象
匿名對象:以內聯形式所創建出來的無名類。
這個匿名對象具體表象就是:
@property (nonatomic ,weak) id <EOCDelegate> delegate;
由于該屬性類型是id<EOCDelegate>,所以實際上任何類的對象都能充當這一屬性,即使這個類不集成與NSObject也是可以的,只要遵守協議<EOCDelegate>就好了。相同例子還有字典的存儲,我們知道字典對于key是copy而對于值為保留的:
- (void)setObject:(id)object forKey:(id<NSCopying>)key;
要點:
- 協議可在某個程度上提供匿名類型。具體的對象類型可以淡化成遵從某協議的id類型,協議里規定了對象所實現的方法。
- 使用匿名對象來隱藏類型名稱(或類名)
- 如果具體類型不重要,重要的是對象能夠響應(定義在協議里的)特定方法,那么可使用匿名對象來表示。
第五章 內存管理
29.理解引用計數
在引用計數的架構下,每個對象都一個計數器,NSObject協議聲明了下面三個方法用于操作計數器,以遞增或遞減其值。
Retain 遞增保留計數
release 遞減保留計數
autorelease 待稍后清理“自動釋放池(autorelease pool)”時,再遞減保留計數。
NSMutableArray * array = [NSMutableArray alloc] init];
NSNumber * number =[NSNumber alloc] initWithInt:1223];
[array addObject:number];
[number release];
[array release];
如果我們不走[array release]方法,那么我們知道number對象還是會存在的,因為數組還在持有他,但是絕對不應該假設對象一定存在,也就是說,不要這樣寫代碼:
NSNumber * number = [NSNumber alloc] initWithInt:1337];
[array addObject:number];
[number release];
NSLog(@“ number = %@”,number);
如果我們調用了release之后,基于某些原因,其保留計數可能降至0.
為了避免在不經意間使用了無效對象,一般調用玩realse之后都會清空指針。這就保證了不會出現可能指向無效對象的指針。這種指針通常稱為懸掛指針
。比如:
NSNumber * number = [NSNumber alloc] initWithInt:1337];
[array addObject:number];
[number release];
number = nil;
不管是數組,其他的對象也可以保留別的對象,這一般都是用過“屬性”來實現。會用到相關實例變量的獲取方法及設置方法。若屬性為“strong關系(string relationship)”,那么設置的屬性就會保留,比方說,有一個屬性為foo
- (void)setFoo:(id ) foo {
[foo retain];
[_foo release];
_foo = foo;
}
此方法將保留新值并釋放舊值,然后更新實例變量,令其指向新值。順序很重要。加入還未保留新值就先將舊值釋放了,而且兩個值又指向同一個對象,那么,先執行的release操作就可能導致喜用將對象永久回收。而后續的retain操作則無法令這個已經徹底回收的對象復生,于是實例變量就變成了懸掛指針。
自動釋放池
調用release會立刻遞減對象的保留計數,然而有些時候不能可以不調用release,改調用autorelease,此方法會在稍后遞減計數,通常是在下一次“事件循環(event loop)時遞減,不過也可能執行的更早些”。
此特性很有用,尤其在方法中返回對象時更應該用它,在這種情況下:
(NSString*)stringValue{
NSString * str =[NSString alloc] initWithFormat:@“I am this:%@,self”];
return str;
}
這個時候返回的str對象比期望的要多1 因為調用 alloc 會加1,但是不會有對應的釋放操作,但是不能在方法內部釋放 str,否則沒等方法返回系統就把該方法回收了。這里應該用autorelease,它會稍后釋放對象,從而給調用者留下足夠長的時間,使其可以在需要時先保留返回數值,換句話就是保證對象在跨越“方法調用邊界(method call boundary)”后一定存活。
(NSString *)stringValue{
NSString * str =[NSString alloc] initWithFormat:@“I am this %@”,self];
return [str autorelease];
}
保留環(retain cycle)
就是相互持有
要點:
- 引用計數機制通過可以遞增遞減的計數器來管理內存,對象創建好之后,其保留計數至少為1。若保留計數為正,則對象繼續存活。當保留計數降為0的時,對象就被銷毀了。
- 在對象聲明周期中,其余對象通過引用來保留或釋放此對象,保留與釋放操作分別會遞增或者遞減保留計數。
30. 以ARC簡化引用計數
Clang編譯器自帶一個“靜態分析器(static analyzer)” 。用于指明程序里引用計數出問題的地方。
由于ARC會自動執行retain,release,autorelease等操作,所以不能直接調用retain,release,autorelease,dealloc 。
將內存管理語義在方法命中表示出來早就成了OC的慣例,而ARC則將之確立為硬性規定。這些規則簡單的體現在方法名上。若方法名以下列語句開頭,則其返回的對象鬼調用者所有
alloc ,new ,copy ,mutableCopy
對于ARC和MRC的轉換:
ARC:
_myPerson = [EOCPerson personWithName:@“Bob Smith”];
MRC:
EOCPerson * tmp = [EOCPerson personWithName:@“Bob Smith”];
_myPerson = [tmp retain];
ARC可以在運行期檢測到這一對多余的操作,也就是autorelease及緊跟其后的retain。為了優化代碼,在方法中返回自動釋放的對象時,要執行一個特殊函數。此時不直接調用對象的autorelease方法,而是改用調用objc_autoreleaseReturnValue。此函數會檢視當前方法之后即將要執行的那段代碼。若發現那段代碼要在返回的對象上執行retain操作,則設置全局數據結構中的一個標志位,而不執行autorelease操作。
如果返回一個自動釋放的對象,而調用方法的代碼中保留此對象,那么此時不執行retain,而改成objc_retainAutoreleasedReturnValue函數。此函數要檢測剛才提到的那個標志位,若已經執行retain操作。設置并檢測標志位。要比調用autorelease和retain更快。
要點:
- 有ARC之后,程序員就無須擔心內存管理問題。使用ARC來變成,可省去類中很多的“樣板代碼”。
- ARC管理對象生命期的辦法基本上就是:在合適的地方插入”保留“及”釋放“操作。在ARC環境下,變量的內存管理語義可以通過修飾符指明,而原來則需要手動執行“保留”及“釋放”操作。
- 由方法所返回的對象,其內存管理語義總是通過方法名來體現。ARC將此確定為開發者必須遵守的規則。
- ARC只負責管理Object-C對象的內存。尤其要注意:CoreFoundation對象不歸ARC管理,開發者必須適時調用CFRetain/CFRelease。
31.在dealloc方法中釋放引用并解除監聽
如果手動管理引用計數的話 dealloc中需要調用 [super dealloc] 而ARC中則不需要調用[super dealloc]。
要點:
- 在dealloc方法里,應該做的事情就是釋放指向其他對象的引用,并取消原來訂閱的“鍵值觀察“(KVO)或NSNotificationCenter等通知 ,不要做其他的事情。
- 如果對象持有文件描述符等系統資源,那么應該專門編寫一個方法來釋放此種資源。這樣的類要和其使用者約定:用完資源后必須調用close方法。
- 執行異步任務的方法不應在dealloc里調用;只能在正常狀態下執行的那些方法也不應在dealloc里調用,因此此時對象已處于正在回收的狀態下。
32.編寫“異常安全代碼”時留意內存管理問題
OC的錯誤模型表示,異常只有在發生了嚴重錯誤(21條詳解)的時候才會被拋出。
這個Tip里面我們研究MRC情況下怎么實現異常處理內存,就像C++那樣的。
我們使用try塊實現這個功能:
@finally的作用在于無論是否拋出異常都會走這一步,因為我們不清楚程序會不會在dosomeThing中拋出異常,如果拋出異??赡軙绊懴旅娴膭幼?。
@try {
ThirtyTwo * object =[[ThirtyTwo alloc]init];
[object dosomeThing];
// [object release];
} @catch (NSException *exception) {
NSLog(@"拋出異常");
} @finally {
// [object release];
}
要點:
- 捕捉異常時,一定要注意try塊內創立的對象清理干凈。
- 在默認情況下,ARC不生成安全處理異常所需的清理代碼。開啟編譯器標志后,可生成這種代碼,不會導致應用程序變大,而且降低運行效率。
33.以弱引用避免保留環
強引用:
#import <Foundation/Foundation.h>
@class EOCClassA;
@class EOCClassB;
@interface EOCClassA :NSObject
@property (nonatomic, strong) EOCClassB * other;
@end
@interface EOCClassB :NSObject
@porperty (nonatomic, strong) EOCClassA * other;
@end
避免保留環的最佳方式就是弱引用,用weak或者是unsafe_unretained即可。
要點:
- 將某些引用設置為weak,可避免出現“保留環”。
- weak引用可以自動清空,也可以不自動清空。自動清空是隨著ARC而引入的新特性,由運行期系統來實現。在具備自動清空的弱引用上,可以隨意讀取其數據,因為這種引用不會指向已經回收過的對象。
34.以“自動釋放池塊”降低內存峰值
創建自動釋放池的方法如下:
@autoreleasepool {
}
當數據量過大的時候我們可以通過嵌套自動釋放池的方法來降低內存峰值。
NSArray * databaseRecorde = /******/;
NSMutableArray * people = [NSMutableArray new];
for (NSDictionary * record in databaseRecords){
@autoreleasepool {
EOCPerson * person = [[EOCPerson alloc] initWithRecord:record];
[person addObject: person];
}
}
自動釋放池機制就像“棧(stack)”一樣。系統創建好自動釋放池之后,就將其推入棧中,而清空自動釋放池,則相當于將其從棧中彈出。在對象上執行自動釋放操作,就等于將其放入棧頂的那個池里。
之前MRC的時候有一個創建釋放池的類NSAutoreleasePool 此對象更加重量級(heavyweight) 通常用來創建偶爾需要清空的池,比方說:
NSArray * databasRecode = /****/
NSMutableArray * people = [NSMutableArray new];
int i = 0 ;
NSAutoreleasePool * pool = [NSAutoreleasePool alloc] init ];
for (NSDictionary * record in databaseRecode ){
EOCPerson * person = [EOCPerson alloc] initWithRecord:record];
[people addobject:person];
if (++i == 10){
[pool drain];
i = 0;
}
}
[pool drain];
是否應該用池來優化效率,完全取決于具體的應用程序。首先得監控內存用量,判斷其中有沒有需要解決的問題,如果沒完成這一步,那就別急著優化。盡管自動釋放池塊的開銷不太大,但畢竟還是有的,所以盡量不要建立額外的自動釋放池。
要點:
- 自動釋放池排布在棧中,對象受到autorelease消息后,系統將其放入最頂端的池里。
- 合理運用自動釋放池,可降低應用程序的內存峰值。
- @autoreleasepool 這種新式寫法能創建出更為輕便的自動釋放池。
35.用“僵尸對象”調用內存管理問題
僵尸對象:啟動這項調試成功之后,運行期系統會把所有已經回收的實例轉化為特殊的“僵尸對象(Zombie Object)”,而不會真正回收他們。這樣就使得我們能夠及時定位到錯誤位置。
打開全局斷點和僵尸對象調試功能有兩種方法:
第一種方法:
點擊"+"選擇Exception Breakpoint。
第二種方法:
點擊上方調試開關右邊的程序管理臺,選擇Edit Scheme。
然后選擇Run - > Diagnostics - > Zombie Objects。
要點:
- 系統在回收對象時,可以不將其真的回收,而是把它轉化為僵尸對象。通過環境變量NSZombieEnabled可開啟此功能。
- 系統會修改對象的isa指針,令其指向特殊的僵尸類,從而使該對象變成僵尸對象。僵尸類能夠響應所有的選擇子,響應方式為:打印一條包含消息內容及其接收者的消息,然后終止應用程序。
36.不要使用retainCount
要點:
- 對象的保留計數看似有用,實則不然,因為任何給定時間點上的“絕對保留計數(absolute retain count)”都無法反應對象生命期的全貌。
- 引入ARC之后,retainCount方法就正式廢止,在ARC下調用該方法會導致編譯器報錯。
結尾
自己寫的筆記首先是用Pages寫的,寫完之后放到簡書里面以為也就剩下個排版了,結果發現基本上每一個點的總結都不讓自己滿意,但是又想早點放上去,總感覺自己被什么追趕著,哈哈,本來寫完筆記的時候是2W字的,結果到第二次發表的時候發現就成了2.5W了,需要改進的東西還是太多,希望朋友們有什么改進的提議都可以告訴我,我會一直補充這個筆記,然后抓緊改GitHub上的代碼~