《Effective Objective-C 2.0:編寫高質量iOS與OS X代碼的52個有效方法》
在看這本書的時候,對每一個章節里面的例子,都有一一去驗證,并對一些驗證記錄在筆記。
在項目中很多場景下都有嘗試去用,效果還是比較可觀的。
只記錄有用的。
1 -> Objective-C的特性
Objective-C:消息結構語言,運行時所執行的代碼由運行時環境決定;
C++:使用函數調用的語言,由編譯器決定。
小結:
Objective-C為C語言添加了面向對象特性,是其超集;Objective-C使用動態綁定的消息結構,在運行時才會檢查對象類型,接收到消息后,執行代碼,由運行期的環境而非編譯器來決定。
2 -> 在類的頭文件中盡量少引用其它頭文件
#import<Foundation/Foundation.h>
#import“JDSEmployer.h”?(這樣會一并引入該文件的所有的內容,增加編譯時間)
@class JDSEmployer;?(“向前申明”):可以節省編譯時間,將引入頭文件的時間盡量延后,只在確實需要時才引入;降低相互依賴問題;向前申明也可以解決兩個類相互引用的問題
?#include "JDSPerson.h"
@interfaceJDSPerson :NSObject
@property(nonatomic,copy)NSString*firstName;
@property(nonatomic,copy)NSString*lastName;
@property(nonatomic,strong)JDSEmployer*employer;
@end
小結:
1、必要的情況下,才引用頭文件;一般,應在某個類的頭文件中使用向前聲明來提及別的類,并在實現文件中引入那些類的頭文件,來降低類之間的耦合。
2、無法適用向前聲明時,比如要聲明某個類遵循的協議,比如要聲明某個類遵循一項協議。這種情況下,盡量把“該類遵循某協議”的這條聲明移至“class-continuation分類”中。如果不行的話,就把協議單獨放在一個頭文件中,然后將其引入。
3 -> 多用字面量語法,少用與之等價的方法
NSNumber*someNumber = [NSNumbernumberWithInt:1];
NSArray*animals = [NSArrayarrayWithObjects:@"cat",@"dog",nil];
使用下面的字面量語法代碼更整潔。
NSArray*animals =@[@"cat",@"dog"];NSNumber*someNumber =@1;???idobj1 =@"1";???idobj2 =nil;???idobj3 =@"3";?NSArray*arrayA = [NSArrayarrayWithObjects:obj1,obj2,obj3,nil];
?這種方法不會奔潰,一次處理到nil時就會結束
?NSLog(@"%@",arrayA);打印:(1)
?NSArray*arrayB =@[obj1,obj2,obj3];? ?
這種方法會拋出異常?NSLog(@"%@",arrayB);
打?。?** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[__NSPlaceholderArray initWithObjects:count:]: attempt to insert nil object from objects[1]'
小結:
1、應該使用字面量語法來創建字符串、數值、數組、字典。與創建此類對象的常規方法相比,這么做更加簡明扼要。
2、應該通過取下標操作來訪問數組下標或字典中的鍵所對應的元素。
3、用字面量語法創建數組或字典時,若值中有nil,則會拋出異常。因此,務必確保值里不含nil。?
4- >多用類型常量,少用 #define 預處理指令
static const NSTimeIntervalkAnimationDuration =0.3;
試圖修改 這個常量,編譯就會報錯
不加static:另一個文件聲明同樣的變量名,編譯器會報錯:
duplicate symbol _kAnimationDuration in:???
EOCAnimatedView.o???
EOCOtherView.o
如果一個變量既聲明為static,又聲明為const,那么編譯器根本不會創建符號,而是會像#define預處理指令一樣,把所有遇到的變量都替換為常值。不過還是要記?。河眠@種方式定義的常量帶有類型信息。
.m實現文件中這樣去聲明一個變量:NSString *const EOCStringConstant = @“VALUE”,那么需要給外部文件使用的話,這樣寫:extern NSString *const EOCStringConstant;
.h ? ??extern?NSString*constnotyfication;
.m ? NSString? *constnotyfication =@"notyfication";
上面的例子可以寫為:
// EOCAnimatedView.h
extern const NSTimeInterval EOCAnimatedViewAnimationDuration;
// EOCAnimatedView.m
const NSTimeInterval EOCAnimatedViewAnimationDuration = 0.3;
小結:
1、不要用#define定義常量,只是代碼替換,并沒有減少代碼量,有人重新定義此常量編譯器也不會報錯,導致常量值出錯。
2、在實現文件中使用static const來定義“只在編譯單元內可見的常量”(translation-unit-specific constant)。由于此類常量不在全局符號表中,所以無須為其名稱加前綴,在頭文件中使用extern來聲明全局常量,并在相關實現文件中定義其值。這種常量要出現在全局符號表中,所以其名稱應加以區隔,通常用與之相關的類名做前綴。
?3、const 不可變原則 :其右邊的總不能被修改,能提高代碼的安全性,降低程序員的溝通成本。
5 -> 用枚舉表示選項、狀態碼
typedefNS_ENUM(NSUInteger, JSConnectionState) {
??? JSConnectionStateDisconnected,
??? JSConnectionStatingConnecting,
??? JSDisConnectionStatedConnected,
};
用命名描述清楚,狀態值含義;
用枚舉作為switch?參數;可以不用寫default分支:如上圖所示,在枚舉中添加的新的值而又未被使用就會發出警告
小結:
1、應該用枚舉來表示狀態機的狀態、傳遞給方法的選項以及狀態碼等值,給這些值起一個易懂的名字。
2、多個枚舉狀態可以同時使用,應該將選項值定義為2的冥,以便通過通過按位或操作將其組合起來
3、不需要枚舉值互相組合用NS_ENUM,?如果需要互相組合用NS_OPTIONS,NS_ENUM、NS_OPTIONS:需要指明其底層數據結構,可以確保是程序員想要的數據類型,而不是編譯器所選的類型。
4、處理switch放語句,不需要實現default分支,加入新的枚舉值后,編譯器會報錯:switch并為處理所有枚舉。
6 -> 理解“屬性”這一概念
1、原子性
atomic:原子屬性,默認就是atomic。需要消耗大量的資源。
nonatomic:非原子屬性。適合內存小的移動設備。
默認情況下,由編譯器所合成(synthesize)的方法會通過鎖定機制來確保它是原子的(atomic)。如果屬性具有nonatomic特質,則不使用同步鎖。
需要注意的是,開發iOS程序時一般都會使用nonatomic屬性,這是因為在ios中使用同步鎖的開銷較大,這會帶來性能問題。但是在Mac OS X中,使用atomic屬性通常都不會有性能瓶頸。
一般情況下,并不要求屬性必須是原子的,因為這并不能保證“線程安全”,若要實現線程安全,需要更為深層的鎖定機制才行。例如一個線程要連續多次讀取某屬性的過程中,另一個線程對該屬性值進行了修改,那么即便將屬性聲明為atomic,還是會讀到不同的屬性值。
2、讀/寫權限
readwirte:表明屬性具有讀取和設置方法。
readonly: ?表明屬性只擁有讀取方法
若屬性由@synthesize實現,則編譯器才會自動合成與其讀寫權限相關的方法。
3、內存管理語義
內存管理語義僅會影響屬性的“設置方法”。編譯器在合成存取方法時,要根據此特質來決定所生成的代碼。如果自己來編寫存取方法,那么就必須與相關屬性所聲明的特質相符。
assign:只執行針對“純量類型”(scalar type,例如CGFloat或NSInteger等)的簡單賦值操作。
strong:表明該屬性定義了一種擁有關系。為該屬性設置新值時,會先保留新值,并釋放舊值,最后再設置新值。
weak:表明該屬性定義了一種非擁有關系。為該屬性設置新值時,既不保留新值,也不釋放舊值。此特質跟assign類似,然而在屬性所指對象被銷毀時,該屬性也會清空(nil out)。
unsafe-unretained:此特質的語義跟assign相同,但它適用于對象類型。表明該屬性定義了一種非擁有關系,在屬性所指對象被銷毀時,該屬性不會自動清空,這點跟weak不同。
copy:此特質所表達的所屬關系跟strong類似。然而設置方法并不保留新值,而是將其拷貝。只要實現屬性所用的對象是可變的(mutable),就應該在設置新屬性值時拷貝一份。當屬性類型為NSString*時,經常用此特質來保護其封裝性,因為傳遞給設置方法的新值有可能是NSMutableString類型的實例。
4、方法名
可通過如下特質來指定存取方法的方法名。
getter=:指定“獲取方法”的方法名。
例如:@property(nonatomic,getter=isOn)BOOLon;
setter=:指定“設置方法”的方法名,這種用法不太常見。
小結:
1、通過“特質”來指定存儲數據所需的正確語義。
2、開發 iOS 程序時應使用?nonatomic?屬性,因為?atomic?屬性會嚴重影響性能。
7?->?在對象內部盡量直接訪問實例變量
?NSString*oldName1 =self.firstName; //通過屬性訪問 ?
?NSString*oldName2 =?_firstName; ?? //??直接訪問 ?
小結:
1、在對象內部讀取數據時,應該直接訪問實例變量來讀,寫入數據時,則應該通過屬性來寫
2、在初始化方法和?dealloc方法中,總是應該直接通過實例變量來讀寫數據
3、有時會使用惰性初始化技術配置某分數據,那么必須通過屬性來讀取數據
4、直接訪問實例變量,不會掉用“設置方法”,內存管理語義就沒用了,相同不會觸發“鍵值對-KVO”
8?->?理解“對象等同性”這一概念
比較對象的等同性是一個非常有用的功能。不過按照==操作符比較的是比較兩個對象的指針地址,而不是其所指的對象。應該使用NSObject協議中的isEqual方法來判斷兩個對象的等同性。NSObject類對isEqual方法的默認實現是當且僅當兩個對象的指針值相等時,才判定這兩個對象相等,這時hash方法返回的值也必須相等。
例如:
- (BOOL)isEqual:(id)object{
???if([selfclass] == [objectclass]) {
???????return[selfisEqualToPerson:(JDSPerson*)object];
??? }else{
???????return[superisEqual:object];
??? }
???returnNO;
}
- (BOOL)isEqualToPerson:(JDSPerson*)otherPerson{
???if(self== otherPerson) {
???????returnYES;
??? }
???if([_firstNameisEqualToString:otherPerson.firstName] &&. ? ? [_lastNameisEqualToString:otherPerson.lastName] &&_age!=otherPerson.age) {
???????returnYES;
??? }else{
???????returnNO;
??? }
}
計算hash值的方法可實現如下,這樣既能保持高效率,又能使生成的hash碼至少落在一定范圍之內,不會頻繁重復:
-(NSUInteger)hash{
???NSIntegerfistNameHash = [_firstNamehash];
???NSIntegerlastNameHash = [_lastNamehash];
???NSIntegerageHash =_age;
???returnfistNameHash^lastNameHash^ageHash;
}
小結:
1、若要檢查對象的等同性,請提供isEqual和hash方法。
2、相同的對象必須具有相同的hash碼,但擁有相同hash碼的對象卻不一定相同。
3、不要盲目地逐個檢測每條屬性,而是應該依照具體需求來制定檢測方案。
4、編寫hash方法時,應該使用計算速度快而且哈希碼碰撞幾率低的算法。
9?->?以“類族模式”,隱藏實現細節
在Cocoa中,許多類實際上是以類簇的方式實現的,即它們是一群隱藏在通用接口之下的與實現相關的類。例如創建NSString對象時,實際上獲得的可能是NSLiteralString、NSCFString、NSSimpleCString、NSBallOfString或者其他未寫入文檔的與實現相關的對象。
typedef NS_ENUM(NSUInteger, EOCEmployeeType) {
??? EOCEmployeeTypeDeveloper,
??? EOCEmployeeTypeDesigner,
??? EOCEmployeeTypeFinance,
};
@interfaceEOCEmployee :NSObject
@property(copy)NSString*name;
@propertyNSUIntegersalary;
// Helper for creating Employee objects
+ (EOCEmployee*)employeeWithType:(EOCEmployeeType)type;
// Make Employees do their respective day's work
- (void)doADaysWork;
@end
@implementationEOCEmployee
+ (EOCEmployee*)employeeWithType:(EOCEmployeeType)type {
???switch(type) {
???????caseEOCEmployeeTypeDeveloper:
???????????return[EOCEmployeeDeveloper new];
???????????break;
???????caseEOCEmployeeTypeDesigner:
???????????return[EOCEmployeeDesigner new];
???????????break;
???????caseEOCEmployeeTypeFinance:
???????????return[EOCEmployeeFinance new];
???????????break;
??? }
}
- (void)doADaysWork {
???// Subclasses implement this.
}
@end
類簇的實體子類的實現示例:
@interfaceEOCEmployeeDeveloper : EOCEmployee
@end
@implementationEOCEmployeeDeveloper
- (void)doADaysWork {
??? [selfwriteCode];
}
@end
判斷某對象是否位于類簇中,不要直接檢測兩個“類對象”(class)是否相等,而應該使用類型信息查詢方法:
idmaybeAnArray =/* ... */;
if([maybeAnArray isKindOfClass:[NSArray class]]) {
???// Will be hit
}
小結:
1、類簇模式可以把實現細節隱藏在一套簡單的公共接口后面。
2、系統框架中經常使用類簇。
3、從類簇的公共抽象基類中繼承子類時要當心,若有開發文檔,則應首先閱讀。
10?->?在既有類中使用關聯對象存放自定義數據
管理關聯對象的方法有:
? ? // Sets up an association of object to value with the given key and policy.
???voidobjc_setAssociatedObject(idobject,void*key,idvalue,objc_AssociationPolicy?policy)
?// Retrieves the value for the association on object with the given key.
???idobjc_getAssociatedObject(idobject,void*key)
?// Removes all associations against object.
?voidobjc_removeAssociatedObjects(idobject)
??:
- (IBAction)button:(id)sender {
???UIAlertView*alertView = [[UIAlertViewalloc]initWithTitle:@"action"message:@"ceshieryi"delegate:selfcancelButtonTitle:@"cancel"otherButtonTitles:@"continue",nil];
???void(^block)(NSInteger)=^(NSIntegerbutSelected){
???????if(butSelected ==0) {
???????????NSLog(@"cancel");
??????? }else{
???????????NSLog(@"continue");
??????? }
??? };
???objc_setAssociatedObject(alertView,alertViewKey, block,OBJC_ASSOCIATION_COPY);
??? [alertViewshow];
}
-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{
???void(^blockclick)(NSInteger) = objc_getAssociatedObject(alertView, alertViewKey);
??? blockclick(buttonIndex);
}
這樣的好處是可以把實現方法跟調用的位置放在一起,代碼看起來方便?。?!
小結:
1、可以通過“關聯對象”(associated objects)機制將兩個對象連起來。
2、定義關聯對象時可指定內存管理語義(OBJC_ASSOCIATION_COPY),用以模仿定義屬性時所采用的“擁有關系”與“非擁有關系”
3、只有在其它做法不可行時才應選用關聯對象,因為這種做法通常會引入難查找的bug。
11、- > 理解obj_msgSend 的作用
C語言中有靜態綁定和動態綁定兩種函數調用方式。Objective-C作為c語言的超集,向對象發送消息時使用動態綁定機制來決定需要調用的方法。在底層,所有方法都是普通的C語言函數,然而在對象收到消息后究竟調用哪個方法則完全于運行期決定,甚至可以在程序運行時改變,這些特性使得Objective-C成為一門真正的動態語言。
C語言是靜態綁定
#import
voidprintfHello(){
???printf("hello");
}
voidprintfGoodBey(){
????printf("hello");
}
voiddoTheThing(inttype){
???if(type ==0) {
???????printfHello();
??? }else{
???????printfGoodBey();
??? }
}
OC是動態綁定
#import
voidprintfHello(){
???printf("hello");
}
voidprintfGoodBey(){
????printf("GoodBey");
}
voiddoTheThing(inttype){
???void(*fun)();
???if(type ==0) {
??????? fun =printfHello;
??? }else{
??????? fun =printfGoodBey;
??? }
??? fun();
}
objc_msgSend函數會根據接收者與選擇子的類型來調用適當的方法。為了完成此操作,該函數需要在接收者所屬的類中搜尋其“方法列表”,如果找到與選擇子名稱相符的方法,就跳轉到其實現代碼;否則即沿著繼承體系繼續向上查找;如果仍未找到,就執行“消息轉發”(message forwarding)操作。
objc_msgSend調用方法有個優化操作。它會將匹配結果緩存在“快速映射表”(fast map)里。每個類都有這樣一塊緩存,若是稍后還發送相同的消息,就會加快執行效率。
前面講的這部分內容只描述了部分消息的調用過程,其他“邊界情況”(edge case)則需要交由Objective-C運行環境中的另外一些函數來處理:
objc_msgSend_stret 待發送的消息要返回結構體時用
objc_msgSend_fpret 待發送的消息返回浮點類型時用
objc_msgSendSuper ?如果給超類發消息時用
小結:
1、消息由接收者、選擇子和參數構成。給某對象“發送消息”(invoke a message),相當于在該對象上“調用方法”(call a method)
2、發送給某對象的全部消息都要經過“動態消息派發機制”(dynamic message dispatch system)來處理,該系統會查出對應的方法,并執行其代碼。
12 -> 理解消息轉發機制
動態方法解析: 向當前對象的所屬類發送resolveInstanceMethod:(針對實例方法)或resolveClassMethod(針對類方法)消息,檢查是否動態向該類添加了方法。使用此方案的前提是:相關的實現代碼已經寫好,只等著運行時直接插在類中。此方案常用來實現@dynamic屬性
#import"JDSPerson.h"
#import
@interfaceJDSPerson()
@property(nonatomic,strong)NSMutableDictionary*backingstore;
@end
@implementationJDSPerson
@dynamicfirstName,lastName;
- (instancetype)init
{
???self= [superinit];
???if(self) {
???????if(!_backingstore) {
???????????_backingstore= [NSMutableDictionarynew];
??????? }
??? }
???returnself;
}
關鍵在于resolveInstanceMethod:方法的實現代碼:
/**
?*?如果尚未實現的方法是實例方法,則調用此函數
?*
?*? @param selector未處理的方法
?*
?*? @return返回布爾值,表示是否能新增實例方法用以處理selector
?*/
+(BOOL)resolveInstanceMethod:(SEL)sel{
???NSString*selectorstring =NSStringFromSelector(sel);
?if([selectorstringhasPrefix:@"set"]) {
//?添加?setter?方法
???????class_addMethod(self, sel, (IMP)autoDictionarysetter,"v@:@");
??? }else{
//?添加 getter?方法
???????class_addMethod(self, sel, (IMP)autodictionaryGetter,"@@:");
??? }
???returnYES;
}
/**
?*?如果尚未實現的方法是類方法,則調用此函數
?*
?*? @param selector未處理的方法
?*
?*? @return返回布爾值,表示是否能新增類方法用以處理selector
?*/
//+ (BOOL)resolveClassMethod:(SEL)selector;
voidautoDictionarysetter(idself,SEL_cmd,idvalue){
???JDSPerson*typeSelf = (JDSPerson*)self;
???NSMutableDictionary*backingstore = typeSelf.backingstore;
???NSString*selectorString =NSStringFromSelector(_cmd);
???NSMutableString*key = [selectorStringmutableCopy];
???//delete @":"
??? [keydeleteCharactersInRange:NSMakeRange(key.length-1,1)];
???//delete @"set"
??? [keydeleteCharactersInRange:NSMakeRange(0,3)];
???NSString*lowercasefirstchar = [[keysubstringToIndex:1]lowercaseString];
??? [keyreplaceCharactersInRange:NSMakeRange(0,1)withString:lowercasefirstchar];
???if(value) {
??????? [backingstoresetObject:valueforKey:key];
??? }else{
??????? [backingstoreremoveObjectForKey:key];
??? }
}
idautodictionaryGetter(idself,SEL_cmd){
???JDSPerson*typeSelf = (JDSPerson*)self;
???NSMutableDictionary*backingstore = typeSelf.backingstore;
???NSString*key =NSStringFromSelector(_cmd);
???NSString*value = [backingstoreobjectForKey:key];
???return[backingstoreobjectForKey:key];
}
@end
?- (void)viewDidLoad {
??? [superviewDidLoad];
???// Do any additional setup after loading the view, typically from a nib.
???JDSPerson*person = [JDSPersonnew];
??? person.firstName=@"jds";
?NSLog(@"%@",person.firstName);
? ? //輸出:jds
}
/**
?*?此方法詢問是否能將消息轉給其他接收者來處理
?*
?*? @param aSelector未處理的方法
?*
?*? @return如果當前接收者能找到備援對象,就將其返回;否則返回nil;
?*/
- (id)forwardingTargetForSelector:(SEL)aSelector;
使用這個方法通常是在對象內部,可能還有一系列其它對象能處理該消息,我們便可借這些對象來處理消息并返回,這樣在對象外部看來,還是由該對象親自處理了這一消息。
標準消息轉發: 經過上述兩步之后,如果還是無法處理選擇子,則啟動完整的消息轉發機制。我們需要重寫methodSignatureForSelector:和forwardInvocation:實例方法。runtime發送?methodSignatureForSelector:消息獲取選擇子對應的方法簽名,即參數與返回值的類型信息。runtime則根據方法簽名創建描述該消息的NSInvocation,以創建的NSInvocation對象作為參數,向當前對象發送forwardInvocation:消息。forwardInvocation:方法定位能夠響應封裝在此NSInvocation中的消息的對象。此NSInvocation對象將會保留調用結果,運行時系統會提取這一結果并將其發送到消息的原始發送者。在這個方法中我們可以實現一些更復雜的功能,可對消息內容進行修改,比如追回一個參數等,然后再去觸發消息。另外,若發現某個消息不應由本類處理,則應調用父類的同名方法,以便繼承體系中的每個類都有機會處理此調用請求。NSObject的forwardInvocation:方法實現只是簡單調用了doesNotRecognizeSelector:方法,它不會轉發任何消息,只拋出異常導致程序退出
/**
?*?消息派發系統通過此方法,將消息派發給目標對象
?*
?*? @param anInvocation之前創建的NSInvocation實例對象,用于裝載有關消息的所有內容
?*/
- (void)forwardInvocation:(NSInvocation*)anInvocation;
這個方法可以實現的很簡單,通過改變調用的目標對象,使得消息在新目標對象上得以調用即可。然而這樣實現的效果與 備援接收者 差不多,所以很少人會這么做。更加有用的實現方式為:在觸發消息前,先以某種方式改變消息內容,比如追加另一個參數、修改 selector 等等。
小結:
1、若對象無法響應某個選擇子,則進入消息轉發流程
2、通過運行期的動態方法解析功能,可以在需要用到某個地方時再將其加入類中
3、對象可以將其無法解讀的某些選擇子轉交給其他對象來處理
4、經過上述兩步后還是沒辦法處理選擇子,就啟動完整的消息轉發機制。
13 -> 用“方法調試技術”調試“黑盒方法”
1、用運行時對特性獲取兩個方法的實現,然后進行交換
- (void)viewDidLoad {
??? [superviewDidLoad];
? ??MethodoriginalMethod =class_getInstanceMethod([selfclass],@selector(greenBtn:));
?MethodswappedMethod =class_getInstanceMethod([selfclass],@selector(yellowBtn:));??
?method_exchangeImplementations(originalMethod, swappedMethod);
}
- (IBAction)greenBtn:(id)sender {
?self.view.backgroundColor= [UIColorgreenColor];
}
- (IBAction)yellowBtn:(id)sender {
?self.view.backgroundColor= [UIColoryellowColor];
}
2、實現dealloc打印
- (void)viewDidLoad {
??? [superviewDidLoad];
?MethodoriginalMethod =class_getInstanceMethod([selfclass],NSSelectorFromString(@"dealloc"));
?MethodswappedMethod =class_getInstanceMethod([selfclass],@selector(swappedDealloc));
???method_exchangeImplementations(originalMethod, swappedMethod);
}
- (void)swappedDealloc{
???NSLog(@"%@:dealloc",NSStringFromClass([selfclass]));
??? [selfswappedDealloc];
? ? ??????看起來像是進入了無限遞歸,因為交換了方法,這里實際是在執行:dealloc 這個方法
}
- (IBAction)back:(id)sender {
??? [selfdismissViewControllerAnimated:YEScompletion:nil];
}
小結:
1、在運行期,可以向類中新增或替換方法實現
2、例如第二個??一樣用一個新的實現方法替換原來的實現,可以在原有的實現中加入新的功能
3、??????不宜濫用
4、http://www.cocoachina.com/ios/20150911/13260.html?可以解決button重復點擊
http://www.lxweimin.com/p/e4f1fb537af9
14 -> 理解“類對象”的用意
“在運行期檢視對象類型”這一操作也叫做“類型信息查詢”(introspection,“內省”),這個強大有用的特性內置于Foundation框架的NSObject協議里,凡是由公共根類(common root class,即NSObject與NSProxy)繼承而來的對象都遵從此協議。在程序中,不要直接比較對象所屬的類,明智的做法是調用“類型信息查詢方法”。
類型信息查詢方法包括isMemberOfClass:(判斷對象是否為某個特定類的實例),isKindOfClass:(判斷對象是否為某類或其派生類的實例)。像這樣的類型信息查詢方法使用isa指針獲取對象所屬的類,然后通過super_class指針在繼承體系里游走。
另外一種可精確判斷出對象是否為某類實例的辦法是:
idobject =/* ... */;
if([object class] == [EOCSomeClass class]){
???// 'object' is an instance of EOCSomeClass
}
即使這樣,應盡量使用類型信息查詢方法,而不應直接比較兩個類對象是否等同,因為前者可以正確處理那些使用了消息轉發機制的對象。比如,某對象可能會把它收到的所有選擇子都轉發給另外一個對象。這樣的對象叫做代理,此種對象均以NSProxy為根類。
isMemberOfClass: 能夠判斷出對象是否為某個特定類的實例。
isKindOfClass: 能夠判斷出對象是否為某類或其派生類的實例。
? ?NSMutableDictionary*dic = [NSMutableDictionarynew];
??? [dicisMemberOfClass:[NSDictionaryclass]];//no
??? [dicisMemberOfClass:[NSMutableDictionaryclass]];//yes
??? [dicisKindOfClass:[NSDictionaryclass]];//yes
[dic?isKindOfClass:[NSArrayclass]];//no
小結:
1、每個實例都有一個指向Class對象的指針,用以表明其類型,而這些Class對象則構成了類的繼承體系。
2、如果對象類型無法在編譯期確定,那么就應該使用類型信息查詢方法來探知
3、盡量使用類型信息查詢方法來確定對象類型,而不要直接比較類對象,因為某些對象可能實現了消息轉發功能。
15 -> 用前綴避免命名空間沖突
Objective-C沒有其他語言哪種內置的命名空間(namespace)機制。
避免命名沖突的唯一辦法就是變相實現命名空間:為所有名稱都加上適當的前綴。
創建應用程序時一定要注意:Apple宣稱其保留使用所有“兩字母前綴”的權利,所以你自己選用的前綴應該是三個字母或者更多。
這么做還有一個好處:如果此符號出現在?;厮菪畔⒅?,則很容易就能判明問題源自哪塊代碼。
小結:
1、選擇與你的公司、應用程序或者二者皆有關聯之名作為類名的前綴,并在所有代碼中均使用這一前綴。
2、若自己所開發的程序庫中用到了第三方庫,則應為其中的名稱加上前綴。
16 -> “提供全能初始化方法”
我們把這種可為對象提供必要信息以便其能完成工作的初始化方法叫做“全能初始化方法”(designated initializer)。
如果創建類的實例的方式不止一種,那么這個類就會有多個初始化方法。
這當然會很好,不過仍然要在其中選定一個作為“全能初始化方法”,令其他初始化方法都來調用它。
于是,只有在全能初始化方法中,才會存儲內部數據。
這樣的話,當底層數據存儲機制改變時,只需要修改此方法的代碼就好,無需改動其他初始化方法。
類繼承時需要注意的一個重要問題:如果子類的全能初始化方法與超類方法的名稱不同,那么總應覆寫超類的全能初始化方法。每個子類的全能初始化方法都應該調用其超類的對應方法,并逐層向上,然后再執行與本類有關的任務。
如不用:就拋出異常
- (instancetype)initWithWidth:(float)wigth
{ ? ? ? ? ?
?@throw[NSException exceptionWithName:NSInternalInconsistencyException ?reason:@"Must use initWithFrame: insteat"userInfo:nil];
}
小結:
1、?在類中提供一個全能初始化方法,并于文檔里指明。其他初始化方法均應調用此方法。
2、若全能初始化方法與超類不同,則需覆寫超類中的對應方法。
3、如果超類的初始化方法不適用于子類,那么應該覆寫這個超類方法,并在其中拋出異常。
17 -> 實現description ?方法
覆寫description方法,否則打印信息時,就會調用NSObject類所實現的默認方法。
- (NSString*)description
{
???return[NSStringstringWithFormat:@"<%@:%p:%@>",[selfclass],self,_lastName];
}
用LLDB?“op” 完成打印
小結:
1、?實現description方法返回一個有意義的字符串,用以描述該實例。
2、若想在調式時打印出更詳盡的對象描述信息,則應實現debugDescription方法。
18 -> 盡量使用不可變對象
一般情況下,對外公開的接口一般聲明為readOnly,也可以在類內部實現重新聲明為readwrite,這樣可以在類內部修改參數,在類的實現代碼內部設置這些屬性了。
寫一個類別去改寫屬性
小結:
1、盡量創建不可變的對象。
2、若某屬性僅可于對象內部修改,則在“class-continuation分類”中將其由readonly屬性擴展為readwrite屬性。
3、不要把可變的collection作為屬性公開,而應提供相關方法,以此修改對象中的可變collection。
19 -> 使用清晰而協調的命名方法
主要了解命名規范,OC的命名和其他語言比較起來,OC方法寫起來比較長,但更像是一句通熟易懂的話。
小結:
1、起名時應遵循標準的OC規范,這樣的接口更容易讓開發者讀懂
2、方法名要言簡意賅,從左至右讀起來像日常語句一樣
3、方法名不要使用縮略后的類型名稱
4、必須保證方法名的風格與自己的代碼或所集成的框架相符
20 -> 為私有方法名加前綴
- (void)publicMethod{
}
私有方法可以加前綴,區分開來
-(void)p_privateMethod{
}
小結:
1、私有方法加上前綴,這樣很容將公共方法與其區分開來
2、不能使用單一的下劃線做私有方法的前綴,這種做法是預留給蘋果公司的的
21 -> 理解OC的錯誤模型
- (void)publicMethod{
???idsomerResource =/* ? */;
???if(/*check? for error*/) {
???????@throw[NSExceptionexceptionWithName:@"exceptionName"
??????????????????????????????????????reason:@"There was as error"
????????????????????????????????????userInfo:nil];
??? }
??? [somerResource doSomething];
??? [somerResourcerelease];
}
拋出異常的方式不要輕易使用,這樣會造成內存泄漏,只有發生了可使整個應用程序崩潰的嚴重錯誤時,才應使用異常
NSError
Error domain (錯誤范圍,其類型為字符串)
Error code ?? (錯誤代碼,其類型為整型)
User info ? ? ? ??????? (用戶信息,其類型為字典)
小結:
1、只有發生了可使整個程序奔潰的嚴重錯誤時,才跑出異常
2、錯誤不嚴重的時候可以指派“委托方法”(delegate)來處理錯誤,也可以把錯誤信息放在NSError對象里,經由“輸出參數”返回給調用者。
22 -> 理解NSCoping協議
@interfacePerson :NSObject
@property(nonatomic,copy)NSString*name;
@property(nonatomic,copy)NSString*image;
@property(nonatomic,copy)NSString*total;
@property(nonatomic,copy)NSString*name_enabled;
-(id)copyWithZone:(NSZone*)zone
{
???Person*person = [self.classallocWithZone:zone];
??? person.name= [self.namecopyWithZone:zone];
??? person.image= [self.imagecopyWithZone:zone];
??? person.total= [self.totalcopyWithZone:zone];
??? person.name_enabled= [self.name_enabledcopyWithZone:zone];
???returnperson;
}
只有遵守的以上兩個協議,才能進行拷貝和對象序列化的保存
對象數組序列化:
- (void)writeDataWithArray:(NSArray*)array andName:(NSString*)name{
???NSData*boadData = [NSKeyedArchiverarchivedDataWithRootObject:array];
??? [[NSUserDefaultsstandardUserDefaults]setObject:boadDataforKey:name];
??? [[NSUserDefaultsstandardUserDefaults]synchronize];
}
- (NSArray*)getDataWithIdentifier:(NSString*)name
{
???NSData*boardData = [[NSUserDefaultsstandardUserDefaults]objectForKey:name];
???return[NSKeyedUnarchiverunarchiveObjectWithData:boardData];
}
小結:
1、對象需要拷貝,必須遵守NSCopying協議
2、對象分為可變和不可變版本,必須同時遵守NSCopying MutableNSCopying協議
3、一般情況下執行淺拷貝:淺拷貝是“影子”,深拷貝是“克隆人”;
4、對象需要深拷貝,可以考慮新增一個專門執行深拷貝的方法
23 -> 通過委托與數據協議進行對象間通信
#import
@classJDNetworkingFetcher;
@protocolJDNetworkingFetcherDelegate
@required ?//代理必須要實現的方法?
- (void)netWorkFetcher:(JDNetworkingFetcher*)fetcher didReceiveData:(NSData*)data;
@optional?//可供代理按需實現此方法 ?也叫做“可選方法”
- (void)netWorkFetcher:(JDNetworkingFetcher*)fetcher didFailWithError:(NSError*)error;
@end
@interfaceJDNetworkingFetcher :NSObject
@property(nonatomic,weak)iddelegate;
@end
如果委托者執行可選方法,那么必須提前使用類型信息查詢方法,判斷這個代理能否選用相關選擇子;這樣做的好處是可以避免,沒有實現相關法方法而導致程序的奔潰:
#import"JDNetworkingFetcher.h"
@implementationJDNetworkingFetcher
- (void)didRequestData:(NSData*)receiveData{
???NSData*data = receiveData;
???if([_delegaterespondsToSelector:@selector(netWorkFetcher:didReceiveData:)]) {
??????? [_delegatenetWorkFetcher:selfdidReceiveData:data];
??? }
}
BTW:
@optional
//新增很多可選的方法,會頻繁執行以上方法。那么除了第一次是有效的,后面其實再執行就多余了,因此可以把某個協議的信息緩存起來,優化效率
- (void)netWorkFetcher:(JDNetworkingFetcher*)fetcher didUpDataProgress:(float)progress;
.
.
.
這就比較頻繁了 ,得優化。。。
@end
定義一個結構體
@interfaceJDNetworkingFetcher :NSObject
{
???structdata{
???????unsignedintfieldA:8;//? 8個二進制位?可以表示0到255之間
???????unsignedintfieldB:4;//? 4個二進制位
???????unsignedintfieldC:2;//? 2個二進制位
???????unsignedintfieldD:1;//? 1個二進制位?可以表示0或者1兩個值
??? };
???//C語言的特性:"位段"數據類型
}
#import"JDNetworkingFetcher.h"
@interfaceJDNetworkingFetcher()
{
???struct{
???????unsignedintdidReceiveData ??? :1;
???????unsignedintdidFailWithError ? :1;
???????unsignedintdidUploadProgress? :1;
??? }delegateFlags;
}
@end
@implementationJDNetworkingFetcher
-(void)setDelegate:(id)delegate{
???_delegate= delegate;
???_delegateFlags.didReceiveData= [_delegaterespondsToSelector:@selector(netWorkFetcher:didReceiveData:)];
???_delegateFlags.didFailWithError= [_delegaterespondsToSelector:@selector(netWorkFetcher:didFailWithError:)];
???_delegateFlags.didUploadProgress= [_delegaterespondsToSelector:@selector(netWorkFetcher:didUploadProgress:)];
}
//緩存委托者是否能響應選擇子,緩存功能的實現下載set方法內,這樣每次調用delegate相關方法,就直接查詢結構體的的標志位
?if(_delegateFlags.didReceiveData) {
??????? [_delegatenetWorkFetcher:selfdidReceiveData:receiveData];
??? }
小結:
1、委托模式為對象提供一套接口,使其能告知其他對象
2、委托者把接口定義成協議,在協議中把可能需要處理的事件定義成方法
3、有必要時,可實現含有段的結構體,將是否能相關協議方法這一信息緩存至其中
24 -> 將類的實現代碼分散到便于管理的數個分類中
通過分類機制,把類代碼分成很多個易于管理的小模塊,需要在類的.h文件中引入分類 的頭文件,可以按照不同的功能區分類別
#import
#import"JDSPerson+Play.h"
#import"JDSPerson+Work.h"
#import"JDSPerson+Friendship.h"
@interfaceJDSPerson :NSObject
@property(nonatomic,copy)NSString*firstName;
@property(nonatomic,copy)NSString*lastName;
- (instancetype)initWithWidth:(float)wigth;
@end
@interfaceJDSPerson (Friendship)
- (void)addFreind:(JDSPerson*)person;
@end
@interfaceJDSPerson (Work)
-(void)performDaysWork;
@end
@interfaceJDSPerson (Play)
- (void)goTosports;
@end
小結:
1、使用分類實現代碼劃分成易于管理的小塊
2、將應該視為“私有”的方法歸入名叫private的分類中,隱藏實現細節
25 -> 總是為第三方類的分類名稱加前綴
小結:
1、分類命名重復,后者會覆蓋前者的實現,特別是在用到第三方代碼的時候,使用前綴可以減少此錯誤出現的概率
2、向第三方類添加分類時,總是要給其名稱和方法加上專用的前綴
26 -> 勿在分類中聲明屬性
#import"JDSPerson.h"
@interfaceJDSPerson (Friendship)
@property(nonatomic,strong)NSArray*freinds;
@end
在分類中添加屬性會報錯誤??,可以聲明變量為?@dynamicfreinds,編譯器不會抱錯誤??;
用運行時關聯對象解訣不能合成實例變量的問題:
#import"JDSPerson+Friendship.h"
#import
staticchar? *constkfreindsKey ="kFriendsKey";
@implementationJDSPerson (Friendship)
-(NSArray*)freinds{
???returnobjc_getAssociatedObject(self,kfreindsKey);
}
-(void)setFreinds:(NSArray*)freinds{
???objc_setAssociatedObject(self,
????????????????????????????kfreindsKey,
???????????????????????????? freinds,
????????????????????????????OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end
小結:
1、把分裝的數據所用到的全部屬性都定義在主接口里
2、在分類中盡量不要定義屬性
27 -> 使用分類隱藏實現細節
#import"JDSPerson+Play.h"
通過分類,定義實例變量和方法
@interfaceJDSPerson()
@property(nonatomic,strong)NSMutableDictionary*backingstore;
@property(nonatomic,readwrite,copy)NSString*addres;
@end
小結;
1、通過分類向類中新增實例變量,需要遵守的協議也可卸乳分類中
2、如果屬性在主接口聲明為只讀,類的內部又要用設置方法修改此屬性,那么可以在.m中擴展為可讀寫
3、把私有方法原型聲明在分類里面
28 -> 通過協議提供匿名對象
?@property(nonatomic,weak)iddelegate;
由于該屬性是id,所以任何類的對象都可以充當這個屬性,即使該類不繼承自NSObject,只要遵循lykDelegate協議就可以。如果有需要,可以在運行期查出此對象所屬的類型。
NSDictionary:在字典中,鍵的標準內存語義是“設置時拷貝”,值得內存語義是“設置時保留”。因此,可變版本中,設置鍵值對所用的方法是
- (void)setObject:(ObjectType)anObject forKey:(KeyType )aKey;
表示鍵的那個參數可以是任意類型,只要遵從NSCopying協議就行,這樣,就可以向對象發送拷貝消息了。
小結:
1.協議可以提供匿名類型。具體對象類型可以淡化為id類型,協議里規定了對象應該實現的具體方法。
?2.使用匿名對象來隱藏類型名稱。
?3.類型不重要,重要的是對象能夠響應的方法,這種情況可以用匿名對象來表示。
29 -> 理解引用計數
Retain:遞增保留計數
release:遞減保留計數
autorelease:帶稍后清理“自動釋放池”時,再遞減保留計數。
1、屬性存儲方法中的內存管理:
屬性為strong 或者retain 時 ?會保留新值,釋放舊值,然后更新實例變量,令其指向新值
-(void)setFirstName:(NSString*)firstName{
??? [firstNameretain];
??? [_firstName realease];
?_firstName= firstName;
}
2、自動釋放池:
調用release會立刻遞減該對象的保留計數,autorelease在稍后遞減計數,通常是在下一次“事件循環”時遞減。
autorelease能延長對象生命周期,使其在跨越方法調用邊界后依然可以存活一段時間
3、保留環:
相互引用多個對象,不能釋放,會產生內存泄漏,通常采用弱引用解決。
小結:
1、引用計數機制通過可以遞增遞減的計數器來管理內存,對象創建好后,計數至少為1,降為0時對象被銷毀
2、對象生命周期中,其余對象通過引用來保留或者釋放對象。
30 -> 以ARC簡化引用計數
在應用程序中,可用下列修飾符來改變局部變量與實例變量的語義:
__strong:默認語義,保留此值。
__unsafe_unretained:不保留此值,這么做可能不安全,因為等到再次使用變量時,其對象可能已經回收了。
__weak:不保留此值,但是變量可以安全使用,因為如果系統把這個對象回收了,那么變量也會自動清空。
__autoreleasing:把對象“按引用傳遞”(pass by reference)給方法時,使用這個特殊的修飾符。此值在方法返回時自動釋放。
用了ARC之后,就不需要再編寫來釋放強引用的dealloc方法了。因為ARC會借用Objective-C++的一項特性來生成清理例程(cleanup routine)。回收Objective-C++對象時,待回收的對象會調用所有C++對象的析構函數(destructor)。編譯器如果發現某個對象里含有C++對象,就會生成名為.cxx_destruce的方法。而ARC則借助此特性,在該方法中生成清理內存所需代碼。
不過如果有非Objective-C的對象,比如CoreFoundation中的對象或是由malloc()分配在堆中的內存,那么仍然需要清理。然而不需要像原來馬羊調用超類的dealloc方法。ARC會自動在.cxx_destruct方法中生成代碼并運行此方法,而在生成的代碼中會自動調用超類的dealloc方法。ARC環境下,dealloc可以這樣寫:
- (void)dealloc
{
??? CFRelease(_coreFoundationObject);
??? free(_heapAllocatedMemeoryBlob);
}
小結:
1、有ARC后,正確使用成員變量修飾符就好了,不用擔心內存管理問題
2、ARC只負責Objective-C的內存,?CoreFoundation對象不歸ARC管理,必須適時使用CFRetain/CFRelease
31 -> 在dealloc方法中只釋放引用并解除監聽
?- (void)dealloc{
??? CFRelease(coreFoundationObject);
??? [[NSNotificationCenterdefaultCenter]removeObserver:self];
}
在dealloc中釋放掉所擁有的對象,dealloc中盡量不要寫其他的代碼,因為某些情況下時不會執行的dealloc的,例如:循環引用了對象,那這個類就不會執行的dealloc
小結:
1、在dealloc中,應該釋放指向其他對象的引用,并取消原來訂閱的KVO 或著NSNotificationCenter通知,不要做其他事
2、如果對象持有文件描述符等系統資源,那么應該專門寫一個方法來釋放資源,約定好:用完之后必須close掉
3、執行異步的方法不能在dealloc中調用,因為此時對象已處于正在回收的狀態了
32 -> 編寫“異常安全代碼”時留意內存管理問題
?@try{
???????JDSPerson*newPerson = [JDSPersonnew];
??????? [newPersonsetAddres:@"sz"];
??? }@catch(NSException *exception) {
??? }@finally{
??? }
這樣的異常會出現內存泄漏
在開啟ARC之后正常情況下一切和內存有關的申請和釋放操作皆不用你關心了,ARC全全幫你包辦了。但是還有極少數的情況下,編譯器無法為你生成合適的ARC額外代碼,比如obj-c異常就是這么一個例子。
話句話說在ARC中異??赡軙е聦ο蟮膬却嫘孤丁R驗锳RC是顆?;瘜ο鬄橐粋€文件:即可以在obj-c文件上啟用ARC.所以我們可以選擇性的在編譯某個文件上加上-fobjc-arc-exceptions選項,如果開啟了該選項,則ARC會額外為異常中的對象申請和釋放操作添加代碼,保證異常中ARC管理的對象也不會造成內存泄露。當然這樣一來缺點就是可能會生成大量平常可能根本用不到的代碼。(只有發生異常才會執行)
所以我們可以只在必要的obj-c文件上啟用-fobjc-arc-exceptions標志,而其他文件禁用該標志,這樣才可以做到萬無一失。
小結:
1、捕獲異常,一定要注意將try塊內存所創立的對象清理干凈
2、默認情況下,ARC不生成安全處理異常所需的清理代碼。開啟編譯器標志后,可生成這種代碼,不過會導致應用程序變大,而且會降低運行效率
33 -> 以弱引用避免保留環
#import<Foundation/Foundation.h>
@class JDSPerson;
@class JDSEmployer;
@interface JDSPerson :NSObject
@property(nonatomic,strong)JDSEmployer*other;
@end
@interface JDSEmployer :NSObject
@property(nonatomic,unsafe_unretained)JDSPerson*other;
@end
JDSEmployer不擁有JDSPerson,避免了循環引用;?unsafe_unretained跟?assign特質等價,assign通常用于“整體類型”(int、float結構體等),unsafe_unretained多用于對象類型
小結:
1、將某些引用設為weak,可避免出現“保留環”
2、weak引用可以自動清空,也可以不自動清空。自動清空是隨著ARC引入的新特性,由運行期系統來實現。在具備自動清空的功能等弱引用上,可以隨意讀去其數據,因為這種引用不會指向已經會收過 的對象
34 -> 以“自動釋放池塊”降低內存峰值
? ? NSMutableArray *peoples= [[NSMutableArrayalloc]init];
? ? ?for(inti=0; i
???????@autoreleasepool{
???????????JDSPerson*person = [JDSPersonnew];
??????????? [peoples addObject:person];
??????? }
??? }
循環時,降低內存峰值,不會在執行循環的時候,內存暴漲。
小結:
1、自動釋放池排布在棧中,對象受到autoreleasepool ?消息后,系統將其放入最頂端端池里;
2、合理運用自動釋放池,可降低內存峰值;@autoreleasepool可以創建更輕便的自動釋放池
35 -> 用“僵尸對象”調試內存管理問題
設置 zombie objects ,遇到僵尸對象會拋出異常,控制臺會打印改對象
小結:
1、系統在回收對象時,可以不將其真大回收,而是把它轉化為僵尸對象,通過環境變量 NSZombieEnable可以開啟此功能
2、系統會修改對象的isa指針,令其指向特殊的僵尸類,使其變為僵尸對象。僵尸類能響應所有的選擇子,響應方式為:打印一條包含消息內容及其接受者的消息,然后終止程序。
37 -> 理解“塊”這一概念
塊與函數類似,只不過是定義在另一個函數里的,和定義它的那個函數共享同一范圍類的東西
塊的基本用法:
???__blockintresult;
???void(^block)(int,int) = ^(inta,intb){
??????? result = a+b;
??? };
block(6,9);
全局塊、棧塊、堆塊:
定義塊的時候,內存區域是分布在棧中的,塊只在定義的范圍內有效,下面的寫法,等離開了相應的范圍之后,編譯器可能會把分配給塊的內存覆寫掉,if 和 else 只有一個是有效的,可能會導致奔潰
?void(^block)();
???if(arr.count>8) {
??????? block = ^{
???????????NSLog(@"blockA");
??????? };
??? }else{
??????? block = ^{
????????????NSLog(@"blockA");
??????? };
??? }
??? block();
解決方法:copy到堆區
void(^block)();
???if(arr.count>8) {
??????? block = [^{
???????????NSLog(@"blockA");
??????? }copy];
??? }else{
??????? block = [^{
????????????NSLog(@"blockA");
??????? }copy];
??? }
??? block();
全局block:不會被系統所回收,實際相當于單利
?void(^block)() = ^{
???????NSLog(@"global block");
??? };
小結:
1、塊是C、C++、Objective-C 中的詞法閉包。
2、塊可接受參數,也可返回值
3、塊可分配在棧或者堆上,也可以是全局的。分配在棧上的塊可以拷貝到堆里,這樣就和標準的Objective-C對象一樣,具備引用計數了。
38 -> 為常用的塊類型創建typedef
typedef int(^Completion)(BOOL,NSError*);
- (void)requestwithCompletionHandle:(void(^)(BOOL,NSError*))completion;
- (void)requestwithCompletionHandle:(Completion)completion;
使用typedef“類型定義”關鍵字給塊定義一個易讀懂名字,使代碼讀起來更順暢;
塊中參數需要更改時也可以先修改“類型定義”內的參數,后面再編譯就會報錯,這樣就可以根據報錯一個不差的全部修改到位
typedefvoid(^ACAccountStoreSaveCompletionHandler)(BOOLsuccess,NSError*error);
typedefvoid(^ACAccountStoreRequsetAccessCompletionHandler)(BOOLsuccess,NSError*error);
使用同一個簽名
小結:
1、用typedef重新定義塊類型,可讓塊變量用起來更加簡單
2、定義新類型時應遵從現有的命名習慣,勿使其名稱與別的類型相沖突
3、可以為同一個塊簽名定義多個類型別名,如果重構代碼,使用了塊類型的某個別名,只需要修改相應typedef中的塊簽名即可
39 - >用handle塊降低代碼分散程度
//成功和失敗一起處理:
缺點: ?? 全部邏輯寫在了一起,塊的代碼會很長且比較復雜
優點:1、數據請求斷開時,可以處理請求到的數據,可以根據數據判斷問題做適當處理。
? ? ? ? ? ?2、調用API的代碼可能會在處理成功響應的過程中發現錯誤,例如數據太短、某些數據為空
JDNetworkingFetcher*fetcher = [[JDNetworkingFetcheralloc]initWithURL:url];
??? [fetcherstartWithCompletionHandler:^(NSData*data,NSError*error) {
???????if(error) {
???????????//handle faile
??????? }else{
???????????//handle success
??????? }
??? }];
//成功和失敗分別處理:代碼更容易讀懂,可以把處理失敗或成功所用的代碼省略?
JDNetworkingFetcher*fetcher = [[JDNetworkingFetcheralloc]initWithURL:url];
??? [fetcherstartWithCompletionHandlerSuucess:^(NSData*data) {
???????//handle success
??? }failureHandle:^(NSError*error) {
???????//handle failure
??? }];
小結:
1、在創建對象時,可以使用內聯的handle塊將相關業務邏輯一并申明
2、在有很多個實例需要監控時,如果采用委托模式,那么經常需要根據傳入的對象來切換,而若改用handle塊來實現,可直接將快與相關對象放在一起
3、設計API用到塊時,可以增加一個參數,使調用者可以通過此參數來決定應該把塊安排在哪個隊列上執行(自己貌似很少用到)。
40 - >用塊引用其所屬對象時不要出現保留環
?JDNetworkingFetcher*fetcher = [[JDNetworkingFetcheralloc]initWithURL:url];
??? [fetcherstartWithCompletionHandlerSuucess:^(NSData*data) {
?//handle success
???????///!!!:保留環
?self.data= data;
???????//FIXME:解決保留環
???????self.fetcher=nil;
??? }failureHandle:^(NSError*error) {
???????//handle failure
??? }];
另一種保留環,下載完畢,保留環接觸,下載對象也會被回收
?JDNetworkingFetcher*fetcher = [[JDNetworkingFetcheralloc]initWithURL:url];
??? [fetcherstartWithCompletionHandlerSuucess:^(NSData*data) {
???????//handle success
???????///!!!:保留環
???????self.data= data;
??? }failureHandle:^(NSError*error) {
???????//handle failure
??? }];
- (void)p_requsetCompetion{
???if(self.completionHandle) {
???????self.completionHandle(self.downloadData);
??? }
???self.completionHandle=nil;
}
小結:
1、如果塊所捕獲的對象直接或間接地保留了塊本身,那么就得當心保留環問題
2、一定要找個適當的時機解除保留環,而不能把責任推給API的調用者。
41 -> 多用派發隊列少用同步鎖
加同步鎖,確保線程安全
?-(dispatch_queue_t)syncQueue{
???if(!_syncQueue) {
???????_syncQueue=dispatch_queue_create("com.effectiveobjectivec.sycnQueue",NULL);
??? }
???return_syncQueue;
}
-(NSString*)firstName{
???__blockNSString*firstName;
???dispatch_sync(self.syncQueue, ^{
??????? firstName =_firstName;
??? });
???returnfirstName;
}
-(void)setFirstName:(NSString*)firstName{
???dispatch_sync(self.syncQueue, ^{
??????? _firstName = firstName;
??? });
}
并發隊列,但是這樣是隨意執行的,沒有順序
-(dispatch_queue_t)syncQueue{
???if(!_syncQueue) {
???????_syncQueue=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
??? }
???return_syncQueue;
}
-(NSString*)firstName{
???__blockNSString*firstName;
???dispatch_sync(self.syncQueue, ^{
??????? firstName =_firstName;
??? });
???returnfirstName;
}
-(void)setFirstName:(NSString*)firstName{
???dispatch_async(self.syncQueue, ^{
??????? _firstName = firstName;
??? });
}
可通過棧欄解決順序執行的問題dispatch_barrier_async :并發隊列如果發現接下來要處理的塊是個棧欄塊,那么就要一直等到當前所有并發塊都執行完畢,才會單獨執行這個棧欄塊。待棧欄塊執行過后,再按正常方式繼續向下處理。
-(void)setFirstName:(NSString*)firstName{
???dispatch_barrier_async(self.syncQueue, ^{
??????? _firstName = firstName;
??? });
}
小結:
1、派發隊列可用來表述同步語義,這種做法比?@synchronized 塊和NSLock?對象更簡單
2、將同步和異步派發結合起來,可以實現與普通加鎖機制一樣的同步行為,這么做不會阻塞執行異步派發的線程
3、使用同步隊列及棧欄塊,可以令同步行為更加高效
42 -> 多用GCD,少用performSelect 系列方法
?SELselector;
?if(/*?some condition*/) {
??????? selector =@selector(greenBtn:);
??? }else{
??????? selector =@selector(yellowBtn:);
??? }
編程靈活,可用來簡化代碼,編譯器必須到了運行期才能確定選擇子是哪個
?SELselector;
???if(/* DISABLES CODE */(1)) {
??????? selector =@selector(newObject:);
??? }else{
??????? selector =@selector(copy);
??? }
?idret = [object performSelector:selector];
存在內存泄漏,這段代碼即使在ARC環境下編譯器也不會主動去釋放它
小結:
1、?performSelector 系列方法內存管理方面容易疏失,它無法確定將要執行的選擇子具體是什么,ARC編譯器也就無法插入適當的內存管理方法
2、performSelector ?系列方法所能處理的選擇子太過局限,選擇子的返回值類型及發送給方法的參數個數都受限制
3、如果想把任務放到另一個線程上,最好用GCD不要用這個。
43 -> 掌握GCD及操作隊列的使用時機
GCD是純C的API,而操作隊列是Objective-C的對象
GCD中,任務用塊來表示,而塊是個輕量級數據結構;“操作”則是個更為重量級的Objective-C的對象;
NSBlockOperation;
[queue addOperationWithBlock:^{
?}];
這兩個結合起來使用,與GCD類似;
使用“操作”的好處:
1、取消某個操作:
GCD無法無法取消;不過,已經執行的任務就無法取消了。
NSInvocationOperation*operation = [[NSInvocationOperationalloc]initWithTarget:selfselector:@selector(yellowBtn:)object:nil];
???NSOperationQueue*queue = [[NSOperationQueuealloc]init];
??? [queueaddOperation:operation];
[operation?cancel];
2、指定操作間的依賴關系:
一個操作可以依賴多個操作,必須在其他依賴的操作結束以后才能執行該操作
??NSInvocationOperation*operation3 = [[NSInvocationOperationalloc]initWithTarget:selfselector:@selector(log)object:nil];
??? [operation3addDependency:operation1];
??? [operation3addDependency:operation2];
[queue?addOperation:operation3];
注意:不能循環依賴(不能A依賴于B,B又依賴于A)
3、通過鍵值觀察機制監控?NSOperation對象的屬性 :
1)、NSOperation?很多屬性都可以通過KVO監聽:可以通過?isCancelled屬性來判斷人物是否已經取消,通過?isFinished屬性判斷任務是否完成.
2)、?可以監聽一個操作的執行完畢
//創建對象,封裝操作
???NSBlockOperation*operation=[NSBlockOperationblockOperationWithBlock:^{
???????for(inti=0; i<10; i++) {
???????????NSLog(@"-operation-下載圖片-%@",[NSThread currentThread]);
??????? }
??? }];
???//監聽操作的執行完畢
??? operation.completionBlock=^{
???????//.....下載圖片后繼續進行的操作
???????NSLog(@"--接著下載第二張圖片--");
??? };
???//創建隊列
???NSOperationQueue*queue=[[NSOperationQueue alloc]init];
???//把任務添加到隊列中(自動執行,自動開線程)
[queue?addOperation:operation];
?說明:在上一個任務執行完后,會執行operation.completionBlock=^{}代碼段,且是在當前線程執行(2)。
4、并發數
(1)并發數:同時執?行的任務數.比如,同時開3個線程執行3個任務,并發數就是3
(2)最大并發數:同一時間最多只能執行的任務的個數。
(3)最?并發數的相關?方法
- (NSInteger)maxConcurrentOperationCount;
- (void)setMaxConcurrentOperationCount:(NSInteger)cnt;?
說明:如果沒有設置最大并發數,那么并發的個數是由系統內存和CPU決定的,可能內存多久開多一點,內存少就開少一點。
注意:num的值并不代表線程的個數,僅僅代表線程的ID。
提示:最大并發數不要亂寫(5以內),不要開太多,一般以2~3為宜,因為雖然任務是在子線程進行處理的,但是cpu處理這些過多的子線程可能會影響UI,讓UI變卡。
5、指定操作的優先級:
1)設置NSOperation在queue中的優先級,可以改變操作的執?優先級
- (NSOperationQueuePriority)queuePriority;
- (void)setQueuePriority:(NSOperationQueuePriority)p;
?(2)優先級的取值
typedefNS_ENUM(NSInteger, NSOperationQueuePriority) {
NSOperationQueuePriorityVeryLow = -8L,
NSOperationQueuePriorityLow = -4L,
NSOperationQueuePriorityNormal =0,
NSOperationQueuePriorityHigh =4,
NSOperationQueuePriorityVeryHigh =8
};
說明:優先級高的任務,調用的幾率會更大。
小結:
1、在解決多線程任務管理問題時,GCD并非唯一方案
2、操作隊列提供了一套高層的Objective-C API ,能實現純GCD所具備的絕大部分功能,而且還能完成一些復雜的操作,這些操作如果該用GCD來實現,則需要另外編寫代碼
44 -> 通過?Dispatch Group 機制,根據系統資源狀況來執行任務
任務編組的兩組方式:
1》dispatch_group_async(dispatch_group_t?_Nonnullgroup, dispatch_queue_t?_Nonnullqueue, ^{? ? ?
})
?2》dispatch_group_enter(dispatch_group_t?_Nonnullgroup)
dispatch_group_leave(dispatch_group_t?_Nonnullgroup)
等待dispatch group 執行完畢的兩種方式:
1》dispatch_group_wait(dispatch_group_t?_Nonnullgroup, dispatch_time_t timeout)
2》dispatch_group_notify(dispatch_group_t?_Nonnullgroup, dispatch_queue_t?_Nonnullqueue, ^{
?? })
第二種方式:開發者可以向函數傳入塊,等dispatch group執行完畢之后,塊會在特定的線程上執行;假如當前線程不應阻塞,而開發者又想在那些任務執行完畢時得到通知,那么久很有必要使用第二種方式了
遍歷某個集合
dispatch_apply(10,dispatch_queue_create("com.jds.queue",NULL), ^(size_ti) {
?????? [selflog];
?});
for(inti=0; i<10; i++) {
??????? [selflog];
??? }
也可以迸發執行,但是要考慮瑣死的問題
dispatch_queue_tque =dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
???dispatch_apply(10, que, ^(size_ti) {
??????? [selflog];
?});
小結:
1、一些列任務可歸入一個?dispatch group 中,開發者可在這組任務執行完時獲得通知
2、通過dispatch group,可以在并發式派發隊列里同時執行多項任務。此時GCD會根據系統資源狀況來調度這些并發執行的任務。
45 -> 使用dispatch_once來執行只需要運行一次的代碼
單利可以這樣寫,可以簡化代碼并且保證線程的安全,無需擔心同步和加鎖
+(instancetype)shareInstance{
???staticJDSEmployer*shareInstance =nil;
???staticdispatch_once_tonceToken;
???dispatch_once(&onceToken, ^{
??????? shareInstance = [[JDSEmployeralloc]init];
??? });
???returnshareInstance;
}
小結:
1、一次性安全代碼,通過GCD?dispatch_once很容易實現此功能
2、標記應該申明在 static 個 global作用域中,這樣的話,在把只需要執行一次的塊傳給?dispatch_once函數的時候。穿進去的標記也是相同
46 -> 不要使用dispatch_get_current_queue?(注意: ?iOS 6.0 已經棄用這個方法了)
小結:
1、此方法已經廢棄,僅可用于調試
2、由于派發隊列是按層級來組織的,所以無法單用某個隊列對象來描述“當前隊列”這一概念
47 -> 熟悉系統框架
Objective-C的重要特點是:經常使用底層的C語言API
在編寫新的工具類之前,最好在系統框架里搜索一下,通常都有寫好的類可供直接使用
1》CFNetwork ?此框架提供了C語言級別的網絡通信,它將“BSD 套接字” (BSD soket)抽象成易于使用的網絡接口
2》CoreAudio ?此框架提供的C語言API可用來操作設備上的音頻硬件,比較難用
3》AVFundation 提供 Objective-C 對象可用來回放并錄制音頻及視頻
4》CoreData 提供的接口可將對象放入數據庫,便于持久保存
5》CoreText ?此框架提供的C語言接口可以高效的執行文字排版及渲染
小結:
1、許多系統框架都可以直接使用,Fundation 和 CoreFundation 提供了核心的功能
2、很多常見任務都用框架來做,比如:音頻與視頻的處理、網絡通信、數據管理
3、請記?。河眉僀寫成的框架與用 Objective-C 寫成的一樣重要,若想成為優秀的 Objective-C開發者,應該掌握C語言的核心概念
48 -> 多用枚舉塊,少用 for 循環
下面用枚舉塊的寫法比較for的寫法讀起來更順暢
數組:
NSArray*enumArray =@[@"1",@"2",@"33",@"23"];
???NSEnumerator*enumerator = [enumArrayobjectEnumerator];
???idobj;
???while((obj =[enumeratornextObject])!=nil) {
???????NSLog(@"%@",obj);
??? }
字典:
?NSDictionary*enumDic =@{@"11":@"11",@"22":@"22",@"33":@"33",@"44":@"44"};
???NSEnumerator*enumeratorDic = [enumDickeyEnumerator];
???idobjDic ;
???while((objDic = [enumeratorDicnextObject]) !=nil) {
???????NSLog(@"%@",objDic);
??? }
集合:
NSSet*set =? [NSSetsetWithArray:enumArray];
???NSEnumerator*enumeratorSet = [setobjectEnumerator];
???idobjSet;
???while((objSet =[enumeratorSetnextObject])!=nil) {
???????NSLog(@"%@",obj);
??? }
快速查詢:
?for(id obj in enumArray) {
???????NSLog(@"%@",obj);
??? }
語法最簡單效率最高的遍歷方法:
for(id obj in [enumArray reverseObjectEnumerator]) {
?NSLog(@"%@",obj);
??? }
基于塊的遍歷方式:遍歷時可以直接從塊里獲取更多信息;
數組:
?[enumArray enumerateObjectsUsingBlock:^(id?_Nonnullobj, NSUInteger idx,BOOL*_Nonnullstop) {
???????/*do something*/
???????if(shouldStop) {
??????????? stop =YES;
??????? }
??? }];
字典:
[enumDicenumerateKeysAndObjectsUsingBlock:^(id?_Nonnullkey,id?_Nonnullobj,BOOL*_Nonnullstop) {
???????/*do something*/
???????if(shouldStop) {
??????????? *stop=YES;
??????? }
??? }];
小結:
1、遍歷集合的有四種方式:for 、枚舉、 快速、塊最基本的方法是for,其次是NSEnumerator非遍歷法及快速遍歷,最新、最先進的方法是“塊枚舉法”
2、“塊枚舉法”本身就能通過GCD來并發執行遍歷操作,無需另行編寫代碼,而采用其他遍歷方式無法輕易實現這一點
3、如提前知道待遍歷的集合含有何種對象,則應修改塊簽名,指出對象的具體類型
49 ?-> 對自定義其內存管理語義的collection 使用無縫橋接
使用C語言框架API需要橋接一下
?NSArray*aNSArray =@[@1,@2,@3];
?CFArrayRefaCFArrayRef = (__bridgeCFArrayRef)(aNSArray);
?aNSArray = (__bridgeNSArray*)(aCFArrayRef);
__bridge:告訴ARC如果處理轉換所涉及的Objective-C對象,ARC仍然具備這個Objective-C對象的所有權
__bridge_retained:與__bridge相反,意味著ARC交出對象的所有權,用完記得釋放
__bridge_transfer:想把CFArrayRef非轉換為NSArray*法,并且想令ARC獲得所有權,就可以采取這種方式
這三種方式被稱為橋式轉換。
#import <CoreFoundation/CoreFoundation.h>
constvoid*JSRetainCallBack(CFAllocatorRefallcator,constvoid*value){
???returnCFRetain(value);
}
voidJSReleaseCallBack(CFAllocatorRefallcator,constvoid*value){
???returnCFRelease(value);
}
//CFIndex version;
//CFDictionaryRetainCallBack retain;
//CFDictionaryReleaseCallBack release;
//CFDictionaryCopyDescriptionCallBack copyDescription;
//CFDictionaryEqualCallBack equal;
//CFDictionaryHashCallBack hash;
CFDictionaryKeyCallBackskeyCallBacks = {
???0,
???JSRetainCallBack,
???JSReleaseCallBack,
???NULL,//null表示默認
???CFEqual,
???CFHash
};
CFDictionaryValueCallBacksvaluecallBacks = {
???0,
???JSRetainCallBack,
???JSReleaseCallBack,
???NULL,
???CFEqual,
};
-(void)p_privateMethod{
???CFMutableDictionaryRefaCFDictionary =CFDictionaryCreateMutable(NULL,0, &keyCallBacks, &valuecallBacks);
???NSMutableDictionary*aNSDictionary = (__bridgeNSMutableDictionary*)(aCFDictionary);
}
小結:
1、通過無縫橋接技術,可以在Foudation框架中的Objective-C對象雨CoreFoundation框架中的C語言數據結構之間來回轉換
2、在CoreFoundation層面創建collection時,可以指定許多回調函數,這些函數表示此collection應如何處理其元素,然后,可用無縫橋接技術,將其轉換成具備特殊內存管理語義的Objective-C。
50 -> 構建緩存時選用NSCache而非NSDictionary
{
??? NSCache *_cache;
}
- (id)initWithURL:(NSURL*)url{
???if([superinit]) {
???????_url= url;
???????_cache= [NSCachenew];
???????_cache.countLimit? =100;//最大儲存數
???????_cache.totalCostLimit=5*1024*1024;//不能超過的數據大小5MB
??? }
???returnself;
}
- (void)downloadDataForUrl:(NSURL*)url{
???NSData*cacheData = [_cacheobjectForKey:url];
???if(cacheData) {
???????NSLog(@"緩存過的了直接使用緩存數據");
??? }else{
???????NSLog(@"沒有緩存需要下載");
??????? [selfstartWithCompletionHandler:^(NSData*data,NSError*error) {
??????????? [_cachesetObject:dataforKey:urlcost:data.length];
???????????NSLog(@"保存并且使用數據");
??????? }];
??? }
}
使用NSPurgeableData系統資源緊張時會把保存的緩存對象內存丟棄
- (void)downloadDataForUrl:(NSURL*)url{
???NSPurgeableData*cacheData = [_cacheobjectForKey:url];
???if(cacheData) {
??????? [cacheDatabeginContentAccess];//停止正在清除該data的操作
???????NSLog(@"緩存過的了直接使用緩存數據");
??????? [selfuseData:cacheData];
??????? [cacheDataendContentAccess];
??? }else{
???????NSLog(@"沒有緩存需要下載");
??????? [selfstartWithCompletionHandler:^(NSData*data,NSError*error) {
???????????//??創建好的NSPurgeableData對象后,其“引用計數”會多1,所以無需再調用beginContentAccess,但是必須要調用endContentAccess抵消這個”1“
???????????NSPurgeableData*purgeableData = [NSPurgeableDatadataWithData:data];
??????????? [_cachesetObject:purgeableDataforKey:urlcost:data.length];
???????????NSLog(@"保存并且使用數據");
??????????? [selfuseData:data];
??????????? [purgeableDataendContentAccess];
??????? }];
??? }
}
小結:
1、實現緩存時喲哦難過NSCache因為它是線程安全的
2、NSCache對象設置上限,不是“硬限制”,只是起指導作用
3、NSPurgeableData與NSCache搭配使用,可實現自動清除功能,被丟棄時,該對象自身也會從緩存清除
4、一般,從網絡獲取或者磁盤獲取的數據草緩存,會提高程序效率
51 -> 精簡 initialize 與 load的實現代碼
+ (void)load :程序啟動的時候分類或者類,會執行此方法
+(void)initialize :它是惰性調用的,程序首次用該類之前調用,且只調用一次
小結:
1、在加載階段,如果類實現了load方法,那么系統就會調用它;類的load方法要比分類先調用,且不參與覆寫機制
2、首次使用某個類之前,系統會向其發送initialize消息。因為該方法遵從覆寫規則,所以通常應該在里面判斷當前初始化的是哪個類
3、initialize 與 load盡量精簡和避免使用
4、無法在編譯器設定的全局變量,可以放在initialize方法里初始化
52 -> 別忘了NSTimer 會保留其目標對象
這段代碼采用了一種很有效的寫法,它先定義了一個弱引用,令其指向 self,然后使塊捕獲這個引用,而不直接去捕獲普通的 self 變量,也就是說,self 不會為計時器所保留。當塊開始執行時,立刻生成 strong 引用,以保證實例在執行期間持續存活。
?@interface NSTimer (JSBlockSupport)
+ (NSTimer*)js_scheduledTimerWithTimeInterval:(NSTimeInterval)interval block:(void(^)())block repeats:(BOOL)repeats;
@end
@implementationNSTimer (JSBlockSupport)
+ (NSTimer*)js_scheduledTimerWithTimeInterval:(NSTimeInterval)interval block:(void(^)())block repeats:(BOOL)repeats {
?return[selfscheduledTimerWithTimeInterval:intervaltarget:selfselector:@selector(js_blockInvoke:)userInfo: [blockcopy]repeats:repeats];
}
+ (void)js_blockInvoke:(NSTimer*)timer {
?void(^block)() = timer.userInfo;
???if(block) {
? ? ? ? block();
??? }
}
@end
小結:
1、NSTimer 對象會保留其目標,直到計時器本身失效為止,調用 invalidate 方法可令計時器失效,另外,一次性的計時器在觸發完任務之后也會失效。
2、反復執行任務的計時器(repeating timer),很容易引入保留環,如果這種計時器的目標對象又保留了計時器本身,那肯定會導致保留環。這種環狀保留關系,可能是直接發生的,也可能是通過對象圖里的其他對象間接發生的。
3、可以擴充 NSTimer 的功能,用 “塊”來打破保留環。不過,除非 NSTimer 將來在公共接口里提供此功能,否則必須創建分類,將相關實現代碼加入其中。