《Effective Objective-C 2.0:編寫高質(zhì)量iOS與OS X代碼的52個(gè)有效方法》
在看這本書的時(shí)候,對(duì)每一個(gè)章節(jié)里面的例子,都有一一去驗(yàn)證,并對(duì)一些驗(yàn)證記錄在筆記棍郎。
在項(xiàng)目中很多場(chǎng)景下都有嘗試去用,效果還是比較可觀的银室。
只記錄有用的涂佃。
1 -> Objective-C的特性
Objective-C:消息結(jié)構(gòu)語言,運(yùn)行時(shí)所執(zhí)行的代碼由運(yùn)行時(shí)環(huán)境決定蜈敢;
C++:使用函數(shù)調(diào)用的語言辜荠,由編譯器決定。
小結(jié):
Objective-C為C語言添加了面向?qū)ο筇匦宰ハ粒瞧涑。籓bjective-C使用動(dòng)態(tài)綁定的消息結(jié)構(gòu),在運(yùn)行時(shí)才會(huì)檢查對(duì)象類型否过,接收到消息后午笛,執(zhí)行代碼,由運(yùn)行期的環(huán)境而非編譯器來決定苗桂。
2 -> 在類的頭文件中盡量少引用其它頭文件
#import<Foundation/Foundation.h>
#import“JDSEmployer.h”?(這樣會(huì)一并引入該文件的所有的內(nèi)容药磺,增加編譯時(shí)間)
@class JDSEmployer;?(“向前申明”):可以節(jié)省編譯時(shí)間,將引入頭文件的時(shí)間盡量延后誉察,只在確實(shí)需要時(shí)才引入与涡;降低相互依賴問題;向前申明也可以解決兩個(gè)類相互引用的問題
?#include "JDSPerson.h"
@interfaceJDSPerson :NSObject
@property(nonatomic,copy)NSString*firstName;
@property(nonatomic,copy)NSString*lastName;
@property(nonatomic,strong)JDSEmployer*employer;
@end
小結(jié):
1、必要的情況下驼卖,才引用頭文件氨肌;一般,應(yīng)在某個(gè)類的頭文件中使用向前聲明來提及別的類酌畜,并在實(shí)現(xiàn)文件中引入那些類的頭文件怎囚,來降低類之間的耦合。
2桥胞、無法適用向前聲明時(shí)恳守,比如要聲明某個(gè)類遵循的協(xié)議,比如要聲明某個(gè)類遵循一項(xiàng)協(xié)議贩虾。這種情況下催烘,盡量把“該類遵循某協(xié)議”的這條聲明移至“class-continuation分類”中。如果不行的話缎罢,就把協(xié)議單獨(dú)放在一個(gè)頭文件中伊群,然后將其引入。
3 -> 多用字面量語法策精,少用與之等價(jià)的方法
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];
?這種方法不會(huì)奔潰,一次處理到nil時(shí)就會(huì)結(jié)束
?NSLog(@"%@",arrayA);打友释唷:(1)
?NSArray*arrayB =@[obj1,obj2,obj3];? ?
這種方法會(huì)拋出異常?NSLog(@"%@",arrayB);
打油杈怼:*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[__NSPlaceholderArray initWithObjects:count:]: attempt to insert nil object from objects[1]'
小結(jié):
1、應(yīng)該使用字面量語法來創(chuàng)建字符串询刹、數(shù)值谜嫉、數(shù)組、字典范抓。與創(chuàng)建此類對(duì)象的常規(guī)方法相比骄恶,這么做更加簡(jiǎn)明扼要。
2匕垫、應(yīng)該通過取下標(biāo)操作來訪問數(shù)組下標(biāo)或字典中的鍵所對(duì)應(yīng)的元素僧鲁。
3、用字面量語法創(chuàng)建數(shù)組或字典時(shí)象泵,若值中有nil寞秃,則會(huì)拋出異常。因此偶惠,務(wù)必確保值里不含nil春寿。?
4- >多用類型常量,少用 #define 預(yù)處理指令
static const NSTimeIntervalkAnimationDuration =0.3;
試圖修改 這個(gè)常量忽孽,編譯就會(huì)報(bào)錯(cuò)
不加static:另一個(gè)文件聲明同樣的變量名绑改,編譯器會(huì)報(bào)錯(cuò):
duplicate symbol _kAnimationDuration in:???
EOCAnimatedView.o???
EOCOtherView.o
如果一個(gè)變量既聲明為static谢床,又聲明為const,那么編譯器根本不會(huì)創(chuàng)建符號(hào)厘线,而是會(huì)像#define預(yù)處理指令一樣识腿,把所有遇到的變量都替換為常值。不過還是要記自熳场:用這種方式定義的常量帶有類型信息渡讼。
.m實(shí)現(xiàn)文件中這樣去聲明一個(gè)變量: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;
小結(jié):
1耳璧、不要用#define定義常量成箫,只是代碼替換,并沒有減少代碼量旨枯,有人重新定義此常量編譯器也不會(huì)報(bào)錯(cuò)蹬昌,導(dǎo)致常量值出錯(cuò)。
2攀隔、在實(shí)現(xiàn)文件中使用static const來定義“只在編譯單元內(nèi)可見的常量”(translation-unit-specific constant)凳厢。由于此類常量不在全局符號(hào)表中,所以無須為其名稱加前綴竞慢,在頭文件中使用extern來聲明全局常量,并在相關(guān)實(shí)現(xiàn)文件中定義其值治泥。這種常量要出現(xiàn)在全局符號(hào)表中筹煮,所以其名稱應(yīng)加以區(qū)隔,通常用與之相關(guān)的類名做前綴居夹。
?3败潦、const 不可變?cè)瓌t :其右邊的總不能被修改,能提高代碼的安全性准脂,降低程序員的溝通成本劫扒。
5 -> 用枚舉表示選項(xiàng)、狀態(tài)碼
typedefNS_ENUM(NSUInteger, JSConnectionState) {
??? JSConnectionStateDisconnected,
??? JSConnectionStatingConnecting,
??? JSDisConnectionStatedConnected,
};
用命名描述清楚狸膏,狀態(tài)值含義沟饥;
用枚舉作為switch?參數(shù);可以不用寫default分支:如上圖所示湾戳,在枚舉中添加的新的值而又未被使用就會(huì)發(fā)出警告
小結(jié):
1贤旷、應(yīng)該用枚舉來表示狀態(tài)機(jī)的狀態(tài)、傳遞給方法的選項(xiàng)以及狀態(tài)碼等值砾脑,給這些值起一個(gè)易懂的名字幼驶。
2、多個(gè)枚舉狀態(tài)可以同時(shí)使用韧衣,應(yīng)該將選項(xiàng)值定義為2的冥盅藻,以便通過通過按位或操作將其組合起來
3购桑、不需要枚舉值互相組合用NS_ENUM,?如果需要互相組合用NS_OPTIONS氏淑,NS_ENUM勃蜘、NS_OPTIONS:需要指明其底層數(shù)據(jù)結(jié)構(gòu),可以確保是程序員想要的數(shù)據(jù)類型夸政,而不是編譯器所選的類型元旬。
4、處理switch放語句守问,不需要實(shí)現(xiàn)default分支匀归,加入新的枚舉值后,編譯器會(huì)報(bào)錯(cuò):switch并為處理所有枚舉耗帕。
6 -> 理解“屬性”這一概念
1穆端、原子性
atomic:原子屬性,默認(rèn)就是atomic仿便。需要消耗大量的資源体啰。
nonatomic:非原子屬性。適合內(nèi)存小的移動(dòng)設(shè)備嗽仪。
默認(rèn)情況下荒勇,由編譯器所合成(synthesize)的方法會(huì)通過鎖定機(jī)制來確保它是原子的(atomic)。如果屬性具有nonatomic特質(zhì)闻坚,則不使用同步鎖沽翔。
需要注意的是,開發(fā)iOS程序時(shí)一般都會(huì)使用nonatomic屬性窿凤,這是因?yàn)樵?a target="_blank" rel="nofollow">ios中使用同步鎖的開銷較大仅偎,這會(huì)帶來性能問題。但是在Mac OS X中雳殊,使用atomic屬性通常都不會(huì)有性能瓶頸橘沥。
一般情況下,并不要求屬性必須是原子的夯秃,因?yàn)檫@并不能保證“線程安全”座咆,若要實(shí)現(xiàn)線程安全,需要更為深層的鎖定機(jī)制才行仓洼。例如一個(gè)線程要連續(xù)多次讀取某屬性的過程中箫措,另一個(gè)線程對(duì)該屬性值進(jìn)行了修改,那么即便將屬性聲明為atomic衬潦,還是會(huì)讀到不同的屬性值斤蔓。
2、讀/寫權(quán)限
readwirte:表明屬性具有讀取和設(shè)置方法镀岛。
readonly: ?表明屬性只擁有讀取方法
若屬性由@synthesize實(shí)現(xiàn)弦牡,則編譯器才會(huì)自動(dòng)合成與其讀寫權(quán)限相關(guān)的方法友驮。
3、內(nèi)存管理語義
內(nèi)存管理語義僅會(huì)影響屬性的“設(shè)置方法”驾锰。編譯器在合成存取方法時(shí)卸留,要根據(jù)此特質(zhì)來決定所生成的代碼。如果自己來編寫存取方法椭豫,那么就必須與相關(guān)屬性所聲明的特質(zhì)相符耻瑟。
assign:只執(zhí)行針對(duì)“純量類型”(scalar type,例如CGFloat或NSInteger等)的簡(jiǎn)單賦值操作赏酥。
strong:表明該屬性定義了一種擁有關(guān)系喳整。為該屬性設(shè)置新值時(shí),會(huì)先保留新值裸扶,并釋放舊值框都,最后再設(shè)置新值。
weak:表明該屬性定義了一種非擁有關(guān)系呵晨。為該屬性設(shè)置新值時(shí)魏保,既不保留新值,也不釋放舊值摸屠。此特質(zhì)跟assign類似谓罗,然而在屬性所指對(duì)象被銷毀時(shí),該屬性也會(huì)清空(nil out)季二。
unsafe-unretained:此特質(zhì)的語義跟assign相同妥衣,但它適用于對(duì)象類型。表明該屬性定義了一種非擁有關(guān)系戒傻,在屬性所指對(duì)象被銷毀時(shí),該屬性不會(huì)自動(dòng)清空蜂筹,這點(diǎn)跟weak不同需纳。
copy:此特質(zhì)所表達(dá)的所屬關(guān)系跟strong類似。然而設(shè)置方法并不保留新值艺挪,而是將其拷貝不翩。只要實(shí)現(xiàn)屬性所用的對(duì)象是可變的(mutable),就應(yīng)該在設(shè)置新屬性值時(shí)拷貝一份麻裳。當(dāng)屬性類型為NSString*時(shí)口蝠,經(jīng)常用此特質(zhì)來保護(hù)其封裝性,因?yàn)閭鬟f給設(shè)置方法的新值有可能是NSMutableString類型的實(shí)例津坑。
4妙蔗、方法名
可通過如下特質(zhì)來指定存取方法的方法名。
getter=:指定“獲取方法”的方法名疆瑰。
例如:@property(nonatomic,getter=isOn)BOOLon;
setter=:指定“設(shè)置方法”的方法名眉反,這種用法不太常見昙啄。
小結(jié):
1、通過“特質(zhì)”來指定存儲(chǔ)數(shù)據(jù)所需的正確語義寸五。
2梳凛、開發(fā) iOS 程序時(shí)應(yīng)使用?nonatomic?屬性,因?yàn)?atomic?屬性會(huì)嚴(yán)重影響性能梳杏。
7?->?在對(duì)象內(nèi)部盡量直接訪問實(shí)例變量
?NSString*oldName1 =self.firstName; //通過屬性訪問 ?
?NSString*oldName2 =?_firstName; ?? //??直接訪問 ?
小結(jié):
1韧拒、在對(duì)象內(nèi)部讀取數(shù)據(jù)時(shí),應(yīng)該直接訪問實(shí)例變量來讀十性,寫入數(shù)據(jù)時(shí)叛溢,則應(yīng)該通過屬性來寫
2、在初始化方法和?dealloc方法中烁试,總是應(yīng)該直接通過實(shí)例變量來讀寫數(shù)據(jù)
3雇初、有時(shí)會(huì)使用惰性初始化技術(shù)配置某分?jǐn)?shù)據(jù),那么必須通過屬性來讀取數(shù)據(jù)
4减响、直接訪問實(shí)例變量靖诗,不會(huì)掉用“設(shè)置方法”,內(nèi)存管理語義就沒用了支示,相同不會(huì)觸發(fā)“鍵值對(duì)-KVO”
8?->?理解“對(duì)象等同性”這一概念
比較對(duì)象的等同性是一個(gè)非常有用的功能刊橘。不過按照==操作符比較的是比較兩個(gè)對(duì)象的指針地址,而不是其所指的對(duì)象颂鸿。應(yīng)該使用NSObject協(xié)議中的isEqual方法來判斷兩個(gè)對(duì)象的等同性促绵。NSObject類對(duì)isEqual方法的默認(rèn)實(shí)現(xiàn)是當(dāng)且僅當(dāng)兩個(gè)對(duì)象的指針值相等時(shí),才判定這兩個(gè)對(duì)象相等嘴纺,這時(shí)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;
??? }
}
計(jì)算hash值的方法可實(shí)現(xiàn)如下,這樣既能保持高效率栽渴,又能使生成的hash碼至少落在一定范圍之內(nèi)尖坤,不會(huì)頻繁重復(fù):
-(NSUInteger)hash{
???NSIntegerfistNameHash = [_firstNamehash];
???NSIntegerlastNameHash = [_lastNamehash];
???NSIntegerageHash =_age;
???returnfistNameHash^lastNameHash^ageHash;
}
小結(jié):
1、若要檢查對(duì)象的等同性闲擦,請(qǐng)?zhí)峁﹊sEqual和hash方法慢味。
2、相同的對(duì)象必須具有相同的hash碼墅冷,但擁有相同hash碼的對(duì)象卻不一定相同纯路。
3、不要盲目地逐個(gè)檢測(cè)每條屬性寞忿,而是應(yīng)該依照具體需求來制定檢測(cè)方案驰唬。
4、編寫hash方法時(shí),應(yīng)該使用計(jì)算速度快而且哈希碼碰撞幾率低的算法定嗓。
9?->?以“類族模式”蜕琴,隱藏實(shí)現(xiàn)細(xì)節(jié)
在Cocoa中,許多類實(shí)際上是以類簇的方式實(shí)現(xiàn)的宵溅,即它們是一群隱藏在通用接口之下的與實(shí)現(xiàn)相關(guān)的類凌简。例如創(chuàng)建NSString對(duì)象時(shí),實(shí)際上獲得的可能是NSLiteralString恃逻、NSCFString雏搂、NSSimpleCString、NSBallOfString或者其他未寫入文檔的與實(shí)現(xiàn)相關(guān)的對(duì)象寇损。
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
類簇的實(shí)體子類的實(shí)現(xiàn)示例:
@interfaceEOCEmployeeDeveloper : EOCEmployee
@end
@implementationEOCEmployeeDeveloper
- (void)doADaysWork {
??? [selfwriteCode];
}
@end
判斷某對(duì)象是否位于類簇中凸郑,不要直接檢測(cè)兩個(gè)“類對(duì)象”(class)是否相等,而應(yīng)該使用類型信息查詢方法:
idmaybeAnArray =/* ... */;
if([maybeAnArray isKindOfClass:[NSArray class]]) {
???// Will be hit
}
小結(jié):
1矛市、類簇模式可以把實(shí)現(xiàn)細(xì)節(jié)隱藏在一套簡(jiǎn)單的公共接口后面芙沥。
2、系統(tǒng)框架中經(jīng)常使用類簇浊吏。
3而昨、從類簇的公共抽象基類中繼承子類時(shí)要當(dāng)心,若有開發(fā)文檔找田,則應(yīng)首先閱讀歌憨。
10?->?在既有類中使用關(guān)聯(lián)對(duì)象存放自定義數(shù)據(jù)
管理關(guān)聯(lián)對(duì)象的方法有:
? ? // 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);
}
這樣的好處是可以把實(shí)現(xiàn)方法跟調(diào)用的位置放在一起,代碼看起來方便!!货抄!
小結(jié):
1、可以通過“關(guān)聯(lián)對(duì)象”(associated objects)機(jī)制將兩個(gè)對(duì)象連起來心铃。
2、定義關(guān)聯(lián)對(duì)象時(shí)可指定內(nèi)存管理語義(OBJC_ASSOCIATION_COPY)挫剑,用以模仿定義屬性時(shí)所采用的“擁有關(guān)系”與“非擁有關(guān)系”
3去扣、只有在其它做法不可行時(shí)才應(yīng)選用關(guān)聯(lián)對(duì)象,因?yàn)檫@種做法通常會(huì)引入難查找的bug暮顺。
11、- > 理解obj_msgSend 的作用
C語言中有靜態(tài)綁定和動(dòng)態(tài)綁定兩種函數(shù)調(diào)用方式秀存。Objective-C作為c語言的超集捶码,向?qū)ο蟀l(fā)送消息時(shí)使用動(dòng)態(tài)綁定機(jī)制來決定需要調(diào)用的方法。在底層或链,所有方法都是普通的C語言函數(shù)惫恼,然而在對(duì)象收到消息后究竟調(diào)用哪個(gè)方法則完全于運(yùn)行期決定,甚至可以在程序運(yùn)行時(shí)改變澳盐,這些特性使得Objective-C成為一門真正的動(dòng)態(tài)語言祈纯。
C語言是靜態(tài)綁定
#import
voidprintfHello(){
???printf("hello");
}
voidprintfGoodBey(){
????printf("hello");
}
voiddoTheThing(inttype){
???if(type ==0) {
???????printfHello();
??? }else{
???????printfGoodBey();
??? }
}
OC是動(dòng)態(tài)綁定
#import
voidprintfHello(){
???printf("hello");
}
voidprintfGoodBey(){
????printf("GoodBey");
}
voiddoTheThing(inttype){
???void(*fun)();
???if(type ==0) {
??????? fun =printfHello;
??? }else{
??????? fun =printfGoodBey;
??? }
??? fun();
}
objc_msgSend函數(shù)會(huì)根據(jù)接收者與選擇子的類型來調(diào)用適當(dāng)?shù)姆椒钏蕖榱送瓿纱瞬僮鳎摵瘮?shù)需要在接收者所屬的類中搜尋其“方法列表”腕窥,如果找到與選擇子名稱相符的方法粒没,就跳轉(zhuǎn)到其實(shí)現(xiàn)代碼;否則即沿著繼承體系繼續(xù)向上查找簇爆;如果仍未找到癞松,就執(zhí)行“消息轉(zhuǎn)發(fā)”(message forwarding)操作。
objc_msgSend調(diào)用方法有個(gè)優(yōu)化操作入蛆。它會(huì)將匹配結(jié)果緩存在“快速映射表”(fast map)里响蓉。每個(gè)類都有這樣一塊緩存,若是稍后還發(fā)送相同的消息哨毁,就會(huì)加快執(zhí)行效率枫甲。
前面講的這部分內(nèi)容只描述了部分消息的調(diào)用過程,其他“邊界情況”(edge case)則需要交由Objective-C運(yùn)行環(huán)境中的另外一些函數(shù)來處理:
objc_msgSend_stret 待發(fā)送的消息要返回結(jié)構(gòu)體時(shí)用
objc_msgSend_fpret 待發(fā)送的消息返回浮點(diǎn)類型時(shí)用
objc_msgSendSuper ?如果給超類發(fā)消息時(shí)用
小結(jié):
1扼褪、消息由接收者想幻、選擇子和參數(shù)構(gòu)成。給某對(duì)象“發(fā)送消息”(invoke a message)迎捺,相當(dāng)于在該對(duì)象上“調(diào)用方法”(call a method)
2举畸、發(fā)送給某對(duì)象的全部消息都要經(jīng)過“動(dòng)態(tài)消息派發(fā)機(jī)制”(dynamic message dispatch system)來處理,該系統(tǒng)會(huì)查出對(duì)應(yīng)的方法凳枝,并執(zhí)行其代碼抄沮。
12 -> 理解消息轉(zhuǎn)發(fā)機(jī)制
動(dòng)態(tài)方法解析: 向當(dāng)前對(duì)象的所屬類發(fā)送resolveInstanceMethod:(針對(duì)實(shí)例方法)或resolveClassMethod(針對(duì)類方法)消息,檢查是否動(dòng)態(tài)向該類添加了方法岖瑰。使用此方案的前提是:相關(guān)的實(shí)現(xiàn)代碼已經(jīng)寫好叛买,只等著運(yùn)行時(shí)直接插在類中。此方案常用來實(shí)現(xiàn)@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;
}
關(guān)鍵在于resolveInstanceMethod:方法的實(shí)現(xiàn)代碼:
/**
?*?如果尚未實(shí)現(xiàn)的方法是實(shí)例方法蹋订,則調(diào)用此函數(shù)
?*
?*? @param selector未處理的方法
?*
?*? @return返回布爾值率挣,表示是否能新增實(shí)例方法用以處理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;
}
/**
?*?如果尚未實(shí)現(xiàn)的方法是類方法,則調(diào)用此函數(shù)
?*
?*? @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
}
/**
?*?此方法詢問是否能將消息轉(zhuǎn)給其他接收者來處理
?*
?*? @param aSelector未處理的方法
?*
?*? @return如果當(dāng)前接收者能找到備援對(duì)象椒功,就將其返回;否則返回nil智什;
?*/
- (id)forwardingTargetForSelector:(SEL)aSelector;
使用這個(gè)方法通常是在對(duì)象內(nèi)部动漾,可能還有一系列其它對(duì)象能處理該消息,我們便可借這些對(duì)象來處理消息并返回荠锭,這樣在對(duì)象外部看來旱眯,還是由該對(duì)象親自處理了這一消息。
標(biāo)準(zhǔn)消息轉(zhuǎn)發(fā): 經(jīng)過上述兩步之后,如果還是無法處理選擇子删豺,則啟動(dòng)完整的消息轉(zhuǎn)發(fā)機(jī)制共虑。我們需要重寫methodSignatureForSelector:和forwardInvocation:實(shí)例方法。runtime發(fā)送?methodSignatureForSelector:消息獲取選擇子對(duì)應(yīng)的方法簽名呀页,即參數(shù)與返回值的類型信息妈拌。runtime則根據(jù)方法簽名創(chuàng)建描述該消息的NSInvocation,以創(chuàng)建的NSInvocation對(duì)象作為參數(shù)赔桌,向當(dāng)前對(duì)象發(fā)送forwardInvocation:消息供炎。forwardInvocation:方法定位能夠響應(yīng)封裝在此NSInvocation中的消息的對(duì)象。此NSInvocation對(duì)象將會(huì)保留調(diào)用結(jié)果疾党,運(yùn)行時(shí)系統(tǒng)會(huì)提取這一結(jié)果并將其發(fā)送到消息的原始發(fā)送者音诫。在這個(gè)方法中我們可以實(shí)現(xiàn)一些更復(fù)雜的功能,可對(duì)消息內(nèi)容進(jìn)行修改雪位,比如追回一個(gè)參數(shù)等竭钝,然后再去觸發(fā)消息。另外雹洗,若發(fā)現(xiàn)某個(gè)消息不應(yīng)由本類處理香罐,則應(yīng)調(diào)用父類的同名方法,以便繼承體系中的每個(gè)類都有機(jī)會(huì)處理此調(diào)用請(qǐng)求时肿。NSObject的forwardInvocation:方法實(shí)現(xiàn)只是簡(jiǎn)單調(diào)用了doesNotRecognizeSelector:方法庇茫,它不會(huì)轉(zhuǎn)發(fā)任何消息,只拋出異常導(dǎo)致程序退出
/**
?*?消息派發(fā)系統(tǒng)通過此方法螃成,將消息派發(fā)給目標(biāo)對(duì)象
?*
?*? @param anInvocation之前創(chuàng)建的NSInvocation實(shí)例對(duì)象旦签,用于裝載有關(guān)消息的所有內(nèi)容
?*/
- (void)forwardInvocation:(NSInvocation*)anInvocation;
這個(gè)方法可以實(shí)現(xiàn)的很簡(jiǎn)單,通過改變調(diào)用的目標(biāo)對(duì)象寸宏,使得消息在新目標(biāo)對(duì)象上得以調(diào)用即可宁炫。然而這樣實(shí)現(xiàn)的效果與 備援接收者 差不多,所以很少人會(huì)這么做氮凝。更加有用的實(shí)現(xiàn)方式為:在觸發(fā)消息前羔巢,先以某種方式改變消息內(nèi)容,比如追加另一個(gè)參數(shù)罩阵、修改 selector 等等竿秆。
小結(jié):
1、若對(duì)象無法響應(yīng)某個(gè)選擇子稿壁,則進(jìn)入消息轉(zhuǎn)發(fā)流程
2幽钢、通過運(yùn)行期的動(dòng)態(tài)方法解析功能,可以在需要用到某個(gè)地方時(shí)再將其加入類中
3常摧、對(duì)象可以將其無法解讀的某些選擇子轉(zhuǎn)交給其他對(duì)象來處理
4搅吁、經(jīng)過上述兩步后還是沒辦法處理選擇子,就啟動(dòng)完整的消息轉(zhuǎn)發(fā)機(jī)制落午。
13 -> 用“方法調(diào)試技術(shù)”調(diào)試“黑盒方法”
1谎懦、用運(yùn)行時(shí)對(duì)特性獲取兩個(gè)方法的實(shí)現(xiàn),然后進(jìn)行交換
- (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溃斋、實(shí)現(xiàn)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];
? ? ??????看起來像是進(jìn)入了無限遞歸界拦,因?yàn)榻粨Q了方法,這里實(shí)際是在執(zhí)行:dealloc 這個(gè)方法
}
- (IBAction)back:(id)sender {
??? [selfdismissViewControllerAnimated:YEScompletion:nil];
}
小結(jié):
1梗劫、在運(yùn)行期享甸,可以向類中新增或替換方法實(shí)現(xiàn)
2、例如第二個(gè)??一樣用一個(gè)新的實(shí)現(xiàn)方法替換原來的實(shí)現(xiàn)梳侨,可以在原有的實(shí)現(xiàn)中加入新的功能
3蛉威、??????不宜濫用
4、http://www.cocoachina.com/ios/20150911/13260.html?可以解決button重復(fù)點(diǎn)擊
http://www.reibang.com/p/e4f1fb537af9
14 -> 理解“類對(duì)象”的用意
“在運(yùn)行期檢視對(duì)象類型”這一操作也叫做“類型信息查詢”(introspection走哺,“內(nèi)省”)蚯嫌,這個(gè)強(qiáng)大有用的特性內(nèi)置于Foundation框架的NSObject協(xié)議里,凡是由公共根類(common root class丙躏,即NSObject與NSProxy)繼承而來的對(duì)象都遵從此協(xié)議择示。在程序中,不要直接比較對(duì)象所屬的類晒旅,明智的做法是調(diào)用“類型信息查詢方法”栅盲。
類型信息查詢方法包括isMemberOfClass:(判斷對(duì)象是否為某個(gè)特定類的實(shí)例),isKindOfClass:(判斷對(duì)象是否為某類或其派生類的實(shí)例)废恋。像這樣的類型信息查詢方法使用isa指針獲取對(duì)象所屬的類谈秫,然后通過super_class指針在繼承體系里游走。
另外一種可精確判斷出對(duì)象是否為某類實(shí)例的辦法是:
idobject =/* ... */;
if([object class] == [EOCSomeClass class]){
???// 'object' is an instance of EOCSomeClass
}
即使這樣拴签,應(yīng)盡量使用類型信息查詢方法孝常,而不應(yīng)直接比較兩個(gè)類對(duì)象是否等同,因?yàn)榍罢呖梢哉_處理那些使用了消息轉(zhuǎn)發(fā)機(jī)制的對(duì)象蚓哩。比如构灸,某對(duì)象可能會(huì)把它收到的所有選擇子都轉(zhuǎn)發(fā)給另外一個(gè)對(duì)象。這樣的對(duì)象叫做代理岸梨,此種對(duì)象均以NSProxy為根類喜颁。
isMemberOfClass: 能夠判斷出對(duì)象是否為某個(gè)特定類的實(shí)例。
isKindOfClass: 能夠判斷出對(duì)象是否為某類或其派生類的實(shí)例曹阔。
? ?NSMutableDictionary*dic = [NSMutableDictionarynew];
??? [dicisMemberOfClass:[NSDictionaryclass]];//no
??? [dicisMemberOfClass:[NSMutableDictionaryclass]];//yes
??? [dicisKindOfClass:[NSDictionaryclass]];//yes
[dic?isKindOfClass:[NSArrayclass]];//no
小結(jié):
1半开、每個(gè)實(shí)例都有一個(gè)指向Class對(duì)象的指針,用以表明其類型赃份,而這些Class對(duì)象則構(gòu)成了類的繼承體系寂拆。
2奢米、如果對(duì)象類型無法在編譯期確定,那么就應(yīng)該使用類型信息查詢方法來探知
3纠永、盡量使用類型信息查詢方法來確定對(duì)象類型鬓长,而不要直接比較類對(duì)象,因?yàn)槟承?duì)象可能實(shí)現(xiàn)了消息轉(zhuǎn)發(fā)功能尝江。
15 -> 用前綴避免命名空間沖突
Objective-C沒有其他語言哪種內(nèi)置的命名空間(namespace)機(jī)制涉波。
避免命名沖突的唯一辦法就是變相實(shí)現(xiàn)命名空間:為所有名稱都加上適當(dāng)?shù)那熬Y。
創(chuàng)建應(yīng)用程序時(shí)一定要注意:Apple宣稱其保留使用所有“兩字母前綴”的權(quán)利炭序,所以你自己選用的前綴應(yīng)該是三個(gè)字母或者更多啤覆。
這么做還有一個(gè)好處:如果此符號(hào)出現(xiàn)在棧回溯信息中惭聂,則很容易就能判明問題源自哪塊代碼窗声。
小結(jié):
1、選擇與你的公司辜纲、應(yīng)用程序或者二者皆有關(guān)聯(lián)之名作為類名的前綴嫌佑,并在所有代碼中均使用這一前綴。
2侨歉、若自己所開發(fā)的程序庫中用到了第三方庫屋摇,則應(yīng)為其中的名稱加上前綴。
16 -> “提供全能初始化方法”
我們把這種可為對(duì)象提供必要信息以便其能完成工作的初始化方法叫做“全能初始化方法”(designated initializer)幽邓。
如果創(chuàng)建類的實(shí)例的方式不止一種炮温,那么這個(gè)類就會(huì)有多個(gè)初始化方法。
這當(dāng)然會(huì)很好牵舵,不過仍然要在其中選定一個(gè)作為“全能初始化方法”柒啤,令其他初始化方法都來調(diào)用它。
于是畸颅,只有在全能初始化方法中担巩,才會(huì)存儲(chǔ)內(nèi)部數(shù)據(jù)。
這樣的話没炒,當(dāng)?shù)讓訑?shù)據(jù)存儲(chǔ)機(jī)制改變時(shí)涛癌,只需要修改此方法的代碼就好,無需改動(dòng)其他初始化方法送火。
類繼承時(shí)需要注意的一個(gè)重要問題:如果子類的全能初始化方法與超類方法的名稱不同拳话,那么總應(yīng)覆寫超類的全能初始化方法。每個(gè)子類的全能初始化方法都應(yīng)該調(diào)用其超類的對(duì)應(yīng)方法种吸,并逐層向上弃衍,然后再執(zhí)行與本類有關(guān)的任務(wù)。
如不用:就拋出異常
- (instancetype)initWithWidth:(float)wigth
{ ? ? ? ? ?
?@throw[NSException exceptionWithName:NSInternalInconsistencyException ?reason:@"Must use initWithFrame: insteat"userInfo:nil];
}
小結(jié):
1坚俗、?在類中提供一個(gè)全能初始化方法镜盯,并于文檔里指明岸裙。其他初始化方法均應(yīng)調(diào)用此方法。
2速缆、若全能初始化方法與超類不同哥桥,則需覆寫超類中的對(duì)應(yīng)方法。
3激涤、如果超類的初始化方法不適用于子類,那么應(yīng)該覆寫這個(gè)超類方法判呕,并在其中拋出異常倦踢。
17 -> 實(shí)現(xiàn)description ?方法
覆寫description方法,否則打印信息時(shí)侠草,就會(huì)調(diào)用NSObject類所實(shí)現(xiàn)的默認(rèn)方法辱挥。
- (NSString*)description
{
???return[NSStringstringWithFormat:@"<%@:%p:%@>",[selfclass],self,_lastName];
}
用LLDB?“op” 完成打印
小結(jié):
1、?實(shí)現(xiàn)description方法返回一個(gè)有意義的字符串边涕,用以描述該實(shí)例晤碘。
2、若想在調(diào)式時(shí)打印出更詳盡的對(duì)象描述信息功蜓,則應(yīng)實(shí)現(xiàn)debugDescription方法园爷。
18 -> 盡量使用不可變對(duì)象
一般情況下,對(duì)外公開的接口一般聲明為readOnly,也可以在類內(nèi)部實(shí)現(xiàn)重新聲明為readwrite式撼,這樣可以在類內(nèi)部修改參數(shù)童社,在類的實(shí)現(xiàn)代碼內(nèi)部設(shè)置這些屬性了。
寫一個(gè)類別去改寫屬性
小結(jié):
1著隆、盡量創(chuàng)建不可變的對(duì)象扰楼。
2、若某屬性僅可于對(duì)象內(nèi)部修改美浦,則在“class-continuation分類”中將其由readonly屬性擴(kuò)展為readwrite屬性弦赖。
3、不要把可變的collection作為屬性公開浦辨,而應(yīng)提供相關(guān)方法蹬竖,以此修改對(duì)象中的可變collection。
19 -> 使用清晰而協(xié)調(diào)的命名方法
主要了解命名規(guī)范流酬,OC的命名和其他語言比較起來案腺,OC方法寫起來比較長(zhǎng),但更像是一句通熟易懂的話康吵。
小結(jié):
1劈榨、起名時(shí)應(yīng)遵循標(biāo)準(zhǔn)的OC規(guī)范,這樣的接口更容易讓開發(fā)者讀懂
2晦嵌、方法名要言簡(jiǎn)意賅同辣,從左至右讀起來像日常語句一樣
3拷姿、方法名不要使用縮略后的類型名稱
4、必須保證方法名的風(fēng)格與自己的代碼或所集成的框架相符
20 -> 為私有方法名加前綴
- (void)publicMethod{
}
私有方法可以加前綴旱函,區(qū)分開來
-(void)p_privateMethod{
}
小結(jié):
1响巢、私有方法加上前綴,這樣很容將公共方法與其區(qū)分開來
2棒妨、不能使用單一的下劃線做私有方法的前綴踪古,這種做法是預(yù)留給蘋果公司的的
21 -> 理解OC的錯(cuò)誤模型
- (void)publicMethod{
???idsomerResource =/* ? */;
???if(/*check? for error*/) {
???????@throw[NSExceptionexceptionWithName:@"exceptionName"
??????????????????????????????????????reason:@"There was as error"
????????????????????????????????????userInfo:nil];
??? }
??? [somerResource doSomething];
??? [somerResourcerelease];
}
拋出異常的方式不要輕易使用,這樣會(huì)造成內(nèi)存泄漏券腔,只有發(fā)生了可使整個(gè)應(yīng)用程序崩潰的嚴(yán)重錯(cuò)誤時(shí),才應(yīng)使用異常
NSError
Error domain (錯(cuò)誤范圍伏穆,其類型為字符串)
Error code ?? (錯(cuò)誤代碼,其類型為整型)
User info ? ? ? ??????? (用戶信息纷纫,其類型為字典)
小結(jié):
1枕扫、只有發(fā)生了可使整個(gè)程序奔潰的嚴(yán)重錯(cuò)誤時(shí),才跑出異常
2辱魁、錯(cuò)誤不嚴(yán)重的時(shí)候可以指派“委托方法”(delegate)來處理錯(cuò)誤烟瞧,也可以把錯(cuò)誤信息放在NSError對(duì)象里,經(jīng)由“輸出參數(shù)”返回給調(diào)用者染簇。
22 -> 理解NSCoping協(xié)議
@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;
}
只有遵守的以上兩個(gè)協(xié)議参滴,才能進(jìn)行拷貝和對(duì)象序列化的保存
對(duì)象數(shù)組序列化:
- (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];
}
小結(jié):
1、對(duì)象需要拷貝锻弓,必須遵守NSCopying協(xié)議
2卵洗、對(duì)象分為可變和不可變版本,必須同時(shí)遵守NSCopying MutableNSCopying協(xié)議
3弥咪、一般情況下執(zhí)行淺拷貝:淺拷貝是“影子”过蹂,深拷貝是“克隆人”;
4聚至、對(duì)象需要深拷貝酷勺,可以考慮新增一個(gè)專門執(zhí)行深拷貝的方法
23 -> 通過委托與數(shù)據(jù)協(xié)議進(jìn)行對(duì)象間通信
#import
@classJDNetworkingFetcher;
@protocolJDNetworkingFetcherDelegate
@required ?//代理必須要實(shí)現(xiàn)的方法?
- (void)netWorkFetcher:(JDNetworkingFetcher*)fetcher didReceiveData:(NSData*)data;
@optional?//可供代理按需實(shí)現(xiàn)此方法 ?也叫做“可選方法”
- (void)netWorkFetcher:(JDNetworkingFetcher*)fetcher didFailWithError:(NSError*)error;
@end
@interfaceJDNetworkingFetcher :NSObject
@property(nonatomic,weak)iddelegate;
@end
如果委托者執(zhí)行可選方法,那么必須提前使用類型信息查詢方法扳躬,判斷這個(gè)代理能否選用相關(guān)選擇子;這樣做的好處是可以避免脆诉,沒有實(shí)現(xiàn)相關(guān)法方法而導(dǎo)致程序的奔潰:
#import"JDNetworkingFetcher.h"
@implementationJDNetworkingFetcher
- (void)didRequestData:(NSData*)receiveData{
???NSData*data = receiveData;
???if([_delegaterespondsToSelector:@selector(netWorkFetcher:didReceiveData:)]) {
??????? [_delegatenetWorkFetcher:selfdidReceiveData:data];
??? }
}
BTW:
@optional
//新增很多可選的方法,會(huì)頻繁執(zhí)行以上方法贷币。那么除了第一次是有效的击胜,后面其實(shí)再執(zhí)行就多余了,因此可以把某個(gè)協(xié)議的信息緩存起來役纹,優(yōu)化效率
- (void)netWorkFetcher:(JDNetworkingFetcher*)fetcher didUpDataProgress:(float)progress;
.
.
.
這就比較頻繁了 偶摔,得優(yōu)化。促脉。辰斋。
@end
定義一個(gè)結(jié)構(gòu)體
@interfaceJDNetworkingFetcher :NSObject
{
???structdata{
???????unsignedintfieldA:8;//? 8個(gè)二進(jìn)制位?可以表示0到255之間
???????unsignedintfieldB:4;//? 4個(gè)二進(jìn)制位
???????unsignedintfieldC:2;//? 2個(gè)二進(jìn)制位
???????unsignedintfieldD:1;//? 1個(gè)二進(jìn)制位?可以表示0或者1兩個(gè)值
??? };
???//C語言的特性:"位段"數(shù)據(jù)類型
}
#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:)];
}
//緩存委托者是否能響應(yīng)選擇子策州,緩存功能的實(shí)現(xiàn)下載set方法內(nèi),這樣每次調(diào)用delegate相關(guān)方法宫仗,就直接查詢結(jié)構(gòu)體的的標(biāo)志位
?if(_delegateFlags.didReceiveData) {
??????? [_delegatenetWorkFetcher:selfdidReceiveData:receiveData];
??? }
小結(jié):
1够挂、委托模式為對(duì)象提供一套接口,使其能告知其他對(duì)象
2藕夫、委托者把接口定義成協(xié)議孽糖,在協(xié)議中把可能需要處理的事件定義成方法
3、有必要時(shí)毅贮,可實(shí)現(xiàn)含有段的結(jié)構(gòu)體办悟,將是否能相關(guān)協(xié)議方法這一信息緩存至其中
24 -> 將類的實(shí)現(xiàn)代碼分散到便于管理的數(shù)個(gè)分類中
通過分類機(jī)制,把類代碼分成很多個(gè)易于管理的小模塊嫩码,需要在類的.h文件中引入分類 的頭文件,可以按照不同的功能區(qū)分類別
#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
小結(jié):
1罪既、使用分類實(shí)現(xiàn)代碼劃分成易于管理的小塊
2铸题、將應(yīng)該視為“私有”的方法歸入名叫private的分類中,隱藏實(shí)現(xiàn)細(xì)節(jié)
25 -> 總是為第三方類的分類名稱加前綴
小結(jié):
1琢感、分類命名重復(fù)丢间,后者會(huì)覆蓋前者的實(shí)現(xiàn),特別是在用到第三方代碼的時(shí)候驹针,使用前綴可以減少此錯(cuò)誤出現(xiàn)的概率
2烘挫、向第三方類添加分類時(shí),總是要給其名稱和方法加上專用的前綴
26 -> 勿在分類中聲明屬性
#import"JDSPerson.h"
@interfaceJDSPerson (Friendship)
@property(nonatomic,strong)NSArray*freinds;
@end
在分類中添加屬性會(huì)報(bào)錯(cuò)誤??柬甥,可以聲明變量為?@dynamicfreinds饮六,編譯器不會(huì)抱錯(cuò)誤??;
用運(yùn)行時(shí)關(guān)聯(lián)對(duì)象解訣不能合成實(shí)例變量的問題:
#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
小結(jié):
1苛蒲、把分裝的數(shù)據(jù)所用到的全部屬性都定義在主接口里
2卤橄、在分類中盡量不要定義屬性
27 -> 使用分類隱藏實(shí)現(xiàn)細(xì)節(jié)
#import"JDSPerson+Play.h"
通過分類,定義實(shí)例變量和方法
@interfaceJDSPerson()
@property(nonatomic,strong)NSMutableDictionary*backingstore;
@property(nonatomic,readwrite,copy)NSString*addres;
@end
小結(jié)臂外;
1窟扑、通過分類向類中新增實(shí)例變量,需要遵守的協(xié)議也可卸乳分類中
2漏健、如果屬性在主接口聲明為只讀嚎货,類的內(nèi)部又要用設(shè)置方法修改此屬性,那么可以在.m中擴(kuò)展為可讀寫
3蔫浆、把私有方法原型聲明在分類里面
28 -> 通過協(xié)議提供匿名對(duì)象
?@property(nonatomic,weak)iddelegate;
由于該屬性是id殖属,所以任何類的對(duì)象都可以充當(dāng)這個(gè)屬性,即使該類不繼承自NSObject瓦盛,只要遵循lykDelegate協(xié)議就可以忱辅。如果有需要七蜘,可以在運(yùn)行期查出此對(duì)象所屬的類型。
NSDictionary:在字典中墙懂,鍵的標(biāo)準(zhǔn)內(nèi)存語義是“設(shè)置時(shí)拷貝”橡卤,值得內(nèi)存語義是“設(shè)置時(shí)保留”。因此损搬,可變版本中碧库,設(shè)置鍵值對(duì)所用的方法是
- (void)setObject:(ObjectType)anObject forKey:(KeyType )aKey;
表示鍵的那個(gè)參數(shù)可以是任意類型,只要遵從NSCopying協(xié)議就行巧勤,這樣嵌灰,就可以向?qū)ο蟀l(fā)送拷貝消息了。
小結(jié):
1.協(xié)議可以提供匿名類型颅悉。具體對(duì)象類型可以淡化為id類型沽瞭,協(xié)議里規(guī)定了對(duì)象應(yīng)該實(shí)現(xiàn)的具體方法。
?2.使用匿名對(duì)象來隱藏類型名稱剩瓶。
?3.類型不重要驹溃,重要的是對(duì)象能夠響應(yīng)的方法,這種情況可以用匿名對(duì)象來表示延曙。
29 -> 理解引用計(jì)數(shù)
Retain:遞增保留計(jì)數(shù)
release:遞減保留計(jì)數(shù)
autorelease:帶稍后清理“自動(dòng)釋放池”時(shí)豌鹤,再遞減保留計(jì)數(shù)。
1枝缔、屬性存儲(chǔ)方法中的內(nèi)存管理:
屬性為strong 或者retain 時(shí) ?會(huì)保留新值风瘦,釋放舊值漾峡,然后更新實(shí)例變量,令其指向新值
-(void)setFirstName:(NSString*)firstName{
??? [firstNameretain];
??? [_firstName realease];
?_firstName= firstName;
}
2、自動(dòng)釋放池:
調(diào)用release會(huì)立刻遞減該對(duì)象的保留計(jì)數(shù)呢诬,autorelease在稍后遞減計(jì)數(shù)伟恶,通常是在下一次“事件循環(huán)”時(shí)遞減晌梨。
autorelease能延長(zhǎng)對(duì)象生命周期垂攘,使其在跨越方法調(diào)用邊界后依然可以存活一段時(shí)間
3、保留環(huán):
相互引用多個(gè)對(duì)象赊舶,不能釋放睁搭,會(huì)產(chǎn)生內(nèi)存泄漏,通常采用弱引用解決笼平。
小結(jié):
1园骆、引用計(jì)數(shù)機(jī)制通過可以遞增遞減的計(jì)數(shù)器來管理內(nèi)存,對(duì)象創(chuàng)建好后寓调,計(jì)數(shù)至少為1锌唾,降為0時(shí)對(duì)象被銷毀
2、對(duì)象生命周期中,其余對(duì)象通過引用來保留或者釋放對(duì)象晌涕。
30 -> 以ARC簡(jiǎn)化引用計(jì)數(shù)
在應(yīng)用程序中滋捶,可用下列修飾符來改變局部變量與實(shí)例變量的語義:
__strong:默認(rèn)語義,保留此值余黎。
__unsafe_unretained:不保留此值重窟,這么做可能不安全,因?yàn)榈鹊皆俅问褂米兞繒r(shí)惧财,其對(duì)象可能已經(jīng)回收了巡扇。
__weak:不保留此值,但是變量可以安全使用垮衷,因?yàn)槿绻到y(tǒng)把這個(gè)對(duì)象回收了厅翔,那么變量也會(huì)自動(dòng)清空。
__autoreleasing:把對(duì)象“按引用傳遞”(pass by reference)給方法時(shí)搀突,使用這個(gè)特殊的修飾符刀闷。此值在方法返回時(shí)自動(dòng)釋放。
用了ARC之后仰迁,就不需要再編寫來釋放強(qiáng)引用的dealloc方法了甸昏。因?yàn)锳RC會(huì)借用Objective-C++的一項(xiàng)特性來生成清理例程(cleanup routine)⌒保回收Objective-C++對(duì)象時(shí)筒扒,待回收的對(duì)象會(huì)調(diào)用所有C++對(duì)象的析構(gòu)函數(shù)(destructor)怯邪。編譯器如果發(fā)現(xiàn)某個(gè)對(duì)象里含有C++對(duì)象绊寻,就會(huì)生成名為.cxx_destruce的方法。而ARC則借助此特性悬秉,在該方法中生成清理內(nèi)存所需代碼澄步。
不過如果有非Objective-C的對(duì)象,比如CoreFoundation中的對(duì)象或是由malloc()分配在堆中的內(nèi)存和泌,那么仍然需要清理村缸。然而不需要像原來馬羊調(diào)用超類的dealloc方法。ARC會(huì)自動(dòng)在.cxx_destruct方法中生成代碼并運(yùn)行此方法武氓,而在生成的代碼中會(huì)自動(dòng)調(diào)用超類的dealloc方法梯皿。ARC環(huán)境下,dealloc可以這樣寫:
- (void)dealloc
{
??? CFRelease(_coreFoundationObject);
??? free(_heapAllocatedMemeoryBlob);
}
小結(jié):
1县恕、有ARC后东羹,正確使用成員變量修飾符就好了,不用擔(dān)心內(nèi)存管理問題
2忠烛、ARC只負(fù)責(zé)Objective-C的內(nèi)存属提,?CoreFoundation對(duì)象不歸ARC管理,必須適時(shí)使用CFRetain/CFRelease
31 -> 在dealloc方法中只釋放引用并解除監(jiān)聽
?- (void)dealloc{
??? CFRelease(coreFoundationObject);
??? [[NSNotificationCenterdefaultCenter]removeObserver:self];
}
在dealloc中釋放掉所擁有的對(duì)象,dealloc中盡量不要寫其他的代碼冤议,因?yàn)槟承┣闆r下時(shí)不會(huì)執(zhí)行的dealloc的斟薇,例如:循環(huán)引用了對(duì)象,那這個(gè)類就不會(huì)執(zhí)行的dealloc
小結(jié):
1恕酸、在dealloc中堪滨,應(yīng)該釋放指向其他對(duì)象的引用,并取消原來訂閱的KVO 或著NSNotificationCenter通知尸疆,不要做其他事
2椿猎、如果對(duì)象持有文件描述符等系統(tǒng)資源,那么應(yīng)該專門寫一個(gè)方法來釋放資源寿弱,約定好:用完之后必須close掉
3犯眠、執(zhí)行異步的方法不能在dealloc中調(diào)用,因?yàn)榇藭r(shí)對(duì)象已處于正在回收的狀態(tài)了
32 -> 編寫“異常安全代碼”時(shí)留意內(nèi)存管理問題
?@try{
???????JDSPerson*newPerson = [JDSPersonnew];
??????? [newPersonsetAddres:@"sz"];
??? }@catch(NSException *exception) {
??? }@finally{
??? }
這樣的異常會(huì)出現(xiàn)內(nèi)存泄漏
在開啟ARC之后正常情況下一切和內(nèi)存有關(guān)的申請(qǐng)和釋放操作皆不用你關(guān)心了症革,ARC全全幫你包辦了筐咧。但是還有極少數(shù)的情況下,編譯器無法為你生成合適的ARC額外代碼噪矛,比如obj-c異常就是這么一個(gè)例子量蕊。
話句話說在ARC中異常可能會(huì)導(dǎo)致對(duì)象的內(nèi)存泄露艇挨。因?yàn)锳RC是顆敛信冢化對(duì)象為一個(gè)文件:即可以在obj-c文件上啟用ARC.所以我們可以選擇性的在編譯某個(gè)文件上加上-fobjc-arc-exceptions選項(xiàng),如果開啟了該選項(xiàng)缩滨,則ARC會(huì)額外為異常中的對(duì)象申請(qǐng)和釋放操作添加代碼势就,保證異常中ARC管理的對(duì)象也不會(huì)造成內(nèi)存泄露。當(dāng)然這樣一來缺點(diǎn)就是可能會(huì)生成大量平陈雎可能根本用不到的代碼苞冯。(只有發(fā)生異常才會(huì)執(zhí)行)
所以我們可以只在必要的obj-c文件上啟用-fobjc-arc-exceptions標(biāo)志,而其他文件禁用該標(biāo)志侧巨,這樣才可以做到萬無一失舅锄。
小結(jié):
1、捕獲異常司忱,一定要注意將try塊內(nèi)存所創(chuàng)立的對(duì)象清理干凈
2皇忿、默認(rèn)情況下,ARC不生成安全處理異常所需的清理代碼坦仍。開啟編譯器標(biāo)志后鳍烁,可生成這種代碼,不過會(huì)導(dǎo)致應(yīng)用程序變大桨踪,而且會(huì)降低運(yùn)行效率
33 -> 以弱引用避免保留環(huán)
#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老翘,避免了循環(huán)引用;?unsafe_unretained跟?assign特質(zhì)等價(jià),assign通常用于“整體類型”(int铺峭、float結(jié)構(gòu)體等)墓怀,unsafe_unretained多用于對(duì)象類型
小結(jié):
1、將某些引用設(shè)為weak卫键,可避免出現(xiàn)“保留環(huán)”
2傀履、weak引用可以自動(dòng)清空,也可以不自動(dòng)清空莉炉。自動(dòng)清空是隨著ARC引入的新特性钓账,由運(yùn)行期系統(tǒng)來實(shí)現(xiàn)。在具備自動(dòng)清空的功能等弱引用上絮宁,可以隨意讀去其數(shù)據(jù)梆暮,因?yàn)檫@種引用不會(huì)指向已經(jīng)會(huì)收過 的對(duì)象
34 -> 以“自動(dòng)釋放池塊”降低內(nèi)存峰值
? ? NSMutableArray *peoples= [[NSMutableArrayalloc]init];
? ? ?for(inti=0; i
???????@autoreleasepool{
???????????JDSPerson*person = [JDSPersonnew];
??????????? [peoples addObject:person];
??????? }
??? }
循環(huán)時(shí),降低內(nèi)存峰值绍昂,不會(huì)在執(zhí)行循環(huán)的時(shí)候啦粹,內(nèi)存暴漲。
小結(jié):
1窘游、自動(dòng)釋放池排布在棧中唠椭,對(duì)象受到autoreleasepool ?消息后,系統(tǒng)將其放入最頂端端池里忍饰;
2贪嫂、合理運(yùn)用自動(dòng)釋放池,可降低內(nèi)存峰值艾蓝;@autoreleasepool可以創(chuàng)建更輕便的自動(dòng)釋放池
35 -> 用“僵尸對(duì)象”調(diào)試內(nèi)存管理問題
設(shè)置 zombie objects 力崇,遇到僵尸對(duì)象會(huì)拋出異常,控制臺(tái)會(huì)打印改對(duì)象
小結(jié):
1饶深、系統(tǒng)在回收對(duì)象時(shí)餐曹,可以不將其真大回收逛拱,而是把它轉(zhuǎn)化為僵尸對(duì)象敌厘,通過環(huán)境變量 NSZombieEnable可以開啟此功能
2、系統(tǒng)會(huì)修改對(duì)象的isa指針朽合,令其指向特殊的僵尸類俱两,使其變?yōu)榻┦瑢?duì)象。僵尸類能響應(yīng)所有的選擇子曹步,響應(yīng)方式為:打印一條包含消息內(nèi)容及其接受者的消息宪彩,然后終止程序。
37 -> 理解“塊”這一概念
塊與函數(shù)類似讲婚,只不過是定義在另一個(gè)函數(shù)里的尿孔,和定義它的那個(gè)函數(shù)共享同一范圍類的東西
塊的基本用法:
???__blockintresult;
???void(^block)(int,int) = ^(inta,intb){
??????? result = a+b;
??? };
block(6,9);
全局塊、棧塊、堆塊:
定義塊的時(shí)候活合,內(nèi)存區(qū)域是分布在棧中的雏婶,塊只在定義的范圍內(nèi)有效,下面的寫法白指,等離開了相應(yīng)的范圍之后留晚,編譯器可能會(huì)把分配給塊的內(nèi)存覆寫掉,if 和 else 只有一個(gè)是有效的告嘲,可能會(huì)導(dǎo)致奔潰
?void(^block)();
???if(arr.count>8) {
??????? block = ^{
???????????NSLog(@"blockA");
??????? };
??? }else{
??????? block = ^{
????????????NSLog(@"blockA");
??????? };
??? }
??? block();
解決方法:copy到堆區(qū)
void(^block)();
???if(arr.count>8) {
??????? block = [^{
???????????NSLog(@"blockA");
??????? }copy];
??? }else{
??????? block = [^{
????????????NSLog(@"blockA");
??????? }copy];
??? }
??? block();
全局block:不會(huì)被系統(tǒng)所回收错维,實(shí)際相當(dāng)于單利
?void(^block)() = ^{
???????NSLog(@"global block");
??? };
小結(jié):
1、塊是C橄唬、C++赋焕、Objective-C 中的詞法閉包。
2仰楚、塊可接受參數(shù)宏邮,也可返回值
3、塊可分配在椄籽或者堆上蜜氨,也可以是全局的。分配在棧上的塊可以拷貝到堆里捎泻,這樣就和標(biāo)準(zhǔn)的Objective-C對(duì)象一樣飒炎,具備引用計(jì)數(shù)了。
38 -> 為常用的塊類型創(chuàng)建typedef
typedef int(^Completion)(BOOL,NSError*);
- (void)requestwithCompletionHandle:(void(^)(BOOL,NSError*))completion;
- (void)requestwithCompletionHandle:(Completion)completion;
使用typedef“類型定義”關(guān)鍵字給塊定義一個(gè)易讀懂名字笆豁,使代碼讀起來更順暢郎汪;
塊中參數(shù)需要更改時(shí)也可以先修改“類型定義”內(nèi)的參數(shù),后面再編譯就會(huì)報(bào)錯(cuò)闯狱,這樣就可以根據(jù)報(bào)錯(cuò)一個(gè)不差的全部修改到位
typedefvoid(^ACAccountStoreSaveCompletionHandler)(BOOLsuccess,NSError*error);
typedefvoid(^ACAccountStoreRequsetAccessCompletionHandler)(BOOLsuccess,NSError*error);
使用同一個(gè)簽名
小結(jié):
1煞赢、用typedef重新定義塊類型,可讓塊變量用起來更加簡(jiǎn)單
2哄孤、定義新類型時(shí)應(yīng)遵從現(xiàn)有的命名習(xí)慣照筑,勿使其名稱與別的類型相沖突
3、可以為同一個(gè)塊簽名定義多個(gè)類型別名瘦陈,如果重構(gòu)代碼凝危,使用了塊類型的某個(gè)別名,只需要修改相應(yīng)typedef中的塊簽名即可
39 - >用handle塊降低代碼分散程度
//成功和失敗一起處理:
缺點(diǎn): ?? 全部邏輯寫在了一起晨逝,塊的代碼會(huì)很長(zhǎng)且比較復(fù)雜
優(yōu)點(diǎn):1蛾默、數(shù)據(jù)請(qǐng)求斷開時(shí),可以處理請(qǐng)求到的數(shù)據(jù)捉貌,可以根據(jù)數(shù)據(jù)判斷問題做適當(dāng)處理支鸡。
? ? ? ? ? ?2冬念、調(diào)用API的代碼可能會(huì)在處理成功響應(yīng)的過程中發(fā)現(xiàn)錯(cuò)誤,例如數(shù)據(jù)太短牧挣、某些數(shù)據(jù)為空
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
??? }];
小結(jié):
1、在創(chuàng)建對(duì)象時(shí)浸踩,可以使用內(nèi)聯(lián)的handle塊將相關(guān)業(yè)務(wù)邏輯一并申明
2叔汁、在有很多個(gè)實(shí)例需要監(jiān)控時(shí),如果采用委托模式检碗,那么經(jīng)常需要根據(jù)傳入的對(duì)象來切換据块,而若改用handle塊來實(shí)現(xiàn),可直接將快與相關(guān)對(duì)象放在一起
3折剃、設(shè)計(jì)API用到塊時(shí)另假,可以增加一個(gè)參數(shù),使調(diào)用者可以通過此參數(shù)來決定應(yīng)該把塊安排在哪個(gè)隊(duì)列上執(zhí)行(自己貌似很少用到)怕犁。
40 - >用塊引用其所屬對(duì)象時(shí)不要出現(xiàn)保留環(huán)
?JDNetworkingFetcher*fetcher = [[JDNetworkingFetcheralloc]initWithURL:url];
??? [fetcherstartWithCompletionHandlerSuucess:^(NSData*data) {
?//handle success
???????///!!!:保留環(huán)
?self.data= data;
???????//FIXME:解決保留環(huán)
???????self.fetcher=nil;
??? }failureHandle:^(NSError*error) {
???????//handle failure
??? }];
另一種保留環(huán)边篮,下載完畢,保留環(huán)接觸奏甫,下載對(duì)象也會(huì)被回收
?JDNetworkingFetcher*fetcher = [[JDNetworkingFetcheralloc]initWithURL:url];
??? [fetcherstartWithCompletionHandlerSuucess:^(NSData*data) {
???????//handle success
???????///!!!:保留環(huán)
???????self.data= data;
??? }failureHandle:^(NSError*error) {
???????//handle failure
??? }];
- (void)p_requsetCompetion{
???if(self.completionHandle) {
???????self.completionHandle(self.downloadData);
??? }
???self.completionHandle=nil;
}
小結(jié):
1戈轿、如果塊所捕獲的對(duì)象直接或間接地保留了塊本身,那么就得當(dāng)心保留環(huán)問題
2阵子、一定要找個(gè)適當(dāng)?shù)臅r(shí)機(jī)解除保留環(huán)思杯,而不能把責(zé)任推給API的調(diào)用者。
41 -> 多用派發(fā)隊(duì)列少用同步鎖
加同步鎖挠进,確保線程安全
?-(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;
??? });
}
并發(fā)隊(duì)列色乾,但是這樣是隨意執(zhí)行的,沒有順序
-(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;
??? });
}
可通過棧欄解決順序執(zhí)行的問題dispatch_barrier_async :并發(fā)隊(duì)列如果發(fā)現(xiàn)接下來要處理的塊是個(gè)棧欄塊领突,那么就要一直等到當(dāng)前所有并發(fā)塊都執(zhí)行完畢暖璧,才會(huì)單獨(dú)執(zhí)行這個(gè)棧欄塊。待棧欄塊執(zhí)行過后君旦,再按正常方式繼續(xù)向下處理澎办。
-(void)setFirstName:(NSString*)firstName{
???dispatch_barrier_async(self.syncQueue, ^{
??????? _firstName = firstName;
??? });
}
小結(jié):
1、派發(fā)隊(duì)列可用來表述同步語義于宙,這種做法比?@synchronized 塊和NSLock?對(duì)象更簡(jiǎn)單
2浮驳、將同步和異步派發(fā)結(jié)合起來悍汛,可以實(shí)現(xiàn)與普通加鎖機(jī)制一樣的同步行為捞魁,這么做不會(huì)阻塞執(zhí)行異步派發(fā)的線程
3、使用同步隊(duì)列及棧欄塊离咐,可以令同步行為更加高效
42 -> 多用GCD谱俭,少用performSelect 系列方法
?SELselector;
?if(/*?some condition*/) {
??????? selector =@selector(greenBtn:);
??? }else{
??????? selector =@selector(yellowBtn:);
??? }
編程靈活奉件,可用來簡(jiǎn)化代碼,編譯器必須到了運(yùn)行期才能確定選擇子是哪個(gè)
?SELselector;
???if(/* DISABLES CODE */(1)) {
??????? selector =@selector(newObject:);
??? }else{
??????? selector =@selector(copy);
??? }
?idret = [object performSelector:selector];
存在內(nèi)存泄漏昆著,這段代碼即使在ARC環(huán)境下編譯器也不會(huì)主動(dòng)去釋放它
小結(jié):
1县貌、?performSelector 系列方法內(nèi)存管理方面容易疏失,它無法確定將要執(zhí)行的選擇子具體是什么凑懂,ARC編譯器也就無法插入適當(dāng)?shù)膬?nèi)存管理方法
2煤痕、performSelector ?系列方法所能處理的選擇子太過局限,選擇子的返回值類型及發(fā)送給方法的參數(shù)個(gè)數(shù)都受限制
3接谨、如果想把任務(wù)放到另一個(gè)線程上摆碉,最好用GCD不要用這個(gè)。
43 -> 掌握GCD及操作隊(duì)列的使用時(shí)機(jī)
GCD是純C的API脓豪,而操作隊(duì)列是Objective-C的對(duì)象
GCD中巷帝,任務(wù)用塊來表示,而塊是個(gè)輕量級(jí)數(shù)據(jù)結(jié)構(gòu)扫夜;“操作”則是個(gè)更為重量級(jí)的Objective-C的對(duì)象楞泼;
NSBlockOperation;
[queue addOperationWithBlock:^{
?}];
這兩個(gè)結(jié)合起來使用,與GCD類似笤闯;
使用“操作”的好處:
1堕阔、取消某個(gè)操作:
GCD無法無法取消;不過颗味,已經(jīng)執(zhí)行的任務(wù)就無法取消了印蔬。
NSInvocationOperation*operation = [[NSInvocationOperationalloc]initWithTarget:selfselector:@selector(yellowBtn:)object:nil];
???NSOperationQueue*queue = [[NSOperationQueuealloc]init];
??? [queueaddOperation:operation];
[operation?cancel];
2、指定操作間的依賴關(guān)系:
一個(gè)操作可以依賴多個(gè)操作脱衙,必須在其他依賴的操作結(jié)束以后才能執(zhí)行該操作
??NSInvocationOperation*operation3 = [[NSInvocationOperationalloc]initWithTarget:selfselector:@selector(log)object:nil];
??? [operation3addDependency:operation1];
??? [operation3addDependency:operation2];
[queue?addOperation:operation3];
注意:不能循環(huán)依賴(不能A依賴于B侥猬,B又依賴于A)
3、通過鍵值觀察機(jī)制監(jiān)控?NSOperation對(duì)象的屬性 :
1)捐韩、NSOperation?很多屬性都可以通過KVO監(jiān)聽:可以通過?isCancelled屬性來判斷人物是否已經(jīng)取消退唠,通過?isFinished屬性判斷任務(wù)是否完成.
2)、?可以監(jiān)聽一個(gè)操作的執(zhí)行完畢
//創(chuàng)建對(duì)象荤胁,封裝操作
???NSBlockOperation*operation=[NSBlockOperationblockOperationWithBlock:^{
???????for(inti=0; i<10; i++) {
???????????NSLog(@"-operation-下載圖片-%@",[NSThread currentThread]);
??????? }
??? }];
???//監(jiān)聽操作的執(zhí)行完畢
??? operation.completionBlock=^{
???????//.....下載圖片后繼續(xù)進(jìn)行的操作
???????NSLog(@"--接著下載第二張圖片--");
??? };
???//創(chuàng)建隊(duì)列
???NSOperationQueue*queue=[[NSOperationQueue alloc]init];
???//把任務(wù)添加到隊(duì)列中(自動(dòng)執(zhí)行瞧预,自動(dòng)開線程)
[queue?addOperation:operation];
?說明:在上一個(gè)任務(wù)執(zhí)行完后,會(huì)執(zhí)行operation.completionBlock=^{}代碼段仅政,且是在當(dāng)前線程執(zhí)行(2)垢油。
4、并發(fā)數(shù)
(1)并發(fā)數(shù):同時(shí)執(zhí)?行的任務(wù)數(shù).比如,同時(shí)開3個(gè)線程執(zhí)行3個(gè)任務(wù),并發(fā)數(shù)就是3
(2)最大并發(fā)數(shù):同一時(shí)間最多只能執(zhí)行的任務(wù)的個(gè)數(shù)圆丹。
(3)最?并發(fā)數(shù)的相關(guān)?方法
- (NSInteger)maxConcurrentOperationCount;
- (void)setMaxConcurrentOperationCount:(NSInteger)cnt;?
說明:如果沒有設(shè)置最大并發(fā)數(shù)滩愁,那么并發(fā)的個(gè)數(shù)是由系統(tǒng)內(nèi)存和CPU決定的,可能內(nèi)存多久開多一點(diǎn)辫封,內(nèi)存少就開少一點(diǎn)硝枉。
注意:num的值并不代表線程的個(gè)數(shù)廉丽,僅僅代表線程的ID。
提示:最大并發(fā)數(shù)不要亂寫(5以內(nèi))妻味,不要開太多正压,一般以2~3為宜,因?yàn)殡m然任務(wù)是在子線程進(jìn)行處理的责球,但是cpu處理這些過多的子線程可能會(huì)影響UI焦履,讓UI變卡。
5雏逾、指定操作的優(yōu)先級(jí):
1)設(shè)置NSOperation在queue中的優(yōu)先級(jí),可以改變操作的執(zhí)?優(yōu)先級(jí)
- (NSOperationQueuePriority)queuePriority;
- (void)setQueuePriority:(NSOperationQueuePriority)p;
?(2)優(yōu)先級(jí)的取值
typedefNS_ENUM(NSInteger, NSOperationQueuePriority) {
NSOperationQueuePriorityVeryLow = -8L,
NSOperationQueuePriorityLow = -4L,
NSOperationQueuePriorityNormal =0,
NSOperationQueuePriorityHigh =4,
NSOperationQueuePriorityVeryHigh =8
};
說明:優(yōu)先級(jí)高的任務(wù)裁良,調(diào)用的幾率會(huì)更大。
小結(jié):
1校套、在解決多線程任務(wù)管理問題時(shí)价脾,GCD并非唯一方案
2、操作隊(duì)列提供了一套高層的Objective-C API 笛匙,能實(shí)現(xiàn)純GCD所具備的絕大部分功能侨把,而且還能完成一些復(fù)雜的操作,這些操作如果該用GCD來實(shí)現(xiàn)妹孙,則需要另外編寫代碼
44 -> 通過?Dispatch Group 機(jī)制秋柄,根據(jù)系統(tǒng)資源狀況來執(zhí)行任務(wù)
任務(wù)編組的兩組方式:
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 執(zhí)行完畢的兩種方式:
1》dispatch_group_wait(dispatch_group_t?_Nonnullgroup, dispatch_time_t timeout)
2》dispatch_group_notify(dispatch_group_t?_Nonnullgroup, dispatch_queue_t?_Nonnullqueue, ^{
?? })
第二種方式:開發(fā)者可以向函數(shù)傳入塊,等dispatch group執(zhí)行完畢之后蠢正,塊會(huì)在特定的線程上執(zhí)行骇笔;假如當(dāng)前線程不應(yīng)阻塞,而開發(fā)者又想在那些任務(wù)執(zhí)行完畢時(shí)得到通知嚣崭,那么久很有必要使用第二種方式了
遍歷某個(gè)集合
dispatch_apply(10,dispatch_queue_create("com.jds.queue",NULL), ^(size_ti) {
?????? [selflog];
?});
for(inti=0; i<10; i++) {
??????? [selflog];
??? }
也可以迸發(fā)執(zhí)行笨触,但是要考慮瑣死的問題
dispatch_queue_tque =dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
???dispatch_apply(10, que, ^(size_ti) {
??????? [selflog];
?});
小結(jié):
1、一些列任務(wù)可歸入一個(gè)?dispatch group 中雹舀,開發(fā)者可在這組任務(wù)執(zhí)行完時(shí)獲得通知
2芦劣、通過dispatch group,可以在并發(fā)式派發(fā)隊(duì)列里同時(shí)執(zhí)行多項(xiàng)任務(wù)说榆。此時(shí)GCD會(huì)根據(jù)系統(tǒng)資源狀況來調(diào)度這些并發(fā)執(zhí)行的任務(wù)虚吟。
45 -> 使用dispatch_once來執(zhí)行只需要運(yùn)行一次的代碼
單利可以這樣寫,可以簡(jiǎn)化代碼并且保證線程的安全签财,無需擔(dān)心同步和加鎖
+(instancetype)shareInstance{
???staticJDSEmployer*shareInstance =nil;
???staticdispatch_once_tonceToken;
???dispatch_once(&onceToken, ^{
??????? shareInstance = [[JDSEmployeralloc]init];
??? });
???returnshareInstance;
}
小結(jié):
1串慰、一次性安全代碼,通過GCD?dispatch_once很容易實(shí)現(xiàn)此功能
2唱蒸、標(biāo)記應(yīng)該申明在 static 個(gè) global作用域中邦鲫,這樣的話,在把只需要執(zhí)行一次的塊傳給?dispatch_once函數(shù)的時(shí)候油宜。穿進(jìn)去的標(biāo)記也是相同
46 -> 不要使用dispatch_get_current_queue?(注意: ?iOS 6.0 已經(jīng)棄用這個(gè)方法了)
小結(jié):
1掂碱、此方法已經(jīng)廢棄怜姿,僅可用于調(diào)試
2慎冤、由于派發(fā)隊(duì)列是按層級(jí)來組織的疼燥,所以無法單用某個(gè)隊(duì)列對(duì)象來描述“當(dāng)前隊(duì)列”這一概念
47 -> 熟悉系統(tǒng)框架
Objective-C的重要特點(diǎn)是:經(jīng)常使用底層的C語言API
在編寫新的工具類之前,最好在系統(tǒng)框架里搜索一下蚁堤,通常都有寫好的類可供直接使用
1》CFNetwork ?此框架提供了C語言級(jí)別的網(wǎng)絡(luò)通信醉者,它將“BSD 套接字” (BSD soket)抽象成易于使用的網(wǎng)絡(luò)接口
2》CoreAudio ?此框架提供的C語言API可用來操作設(shè)備上的音頻硬件,比較難用
3》AVFundation 提供 Objective-C 對(duì)象可用來回放并錄制音頻及視頻
4》CoreData 提供的接口可將對(duì)象放入數(shù)據(jù)庫披诗,便于持久保存
5》CoreText ?此框架提供的C語言接口可以高效的執(zhí)行文字排版及渲染
小結(jié):
1撬即、許多系統(tǒng)框架都可以直接使用,F(xiàn)undation 和 CoreFundation 提供了核心的功能
2呈队、很多常見任務(wù)都用框架來做剥槐,比如:音頻與視頻的處理、網(wǎng)絡(luò)通信宪摧、數(shù)據(jù)管理
3粒竖、請(qǐng)記住:用純C寫成的框架與用 Objective-C 寫成的一樣重要几于,若想成為優(yōu)秀的 Objective-C開發(fā)者蕊苗,應(yīng)該掌握C語言的核心概念
48 -> 多用枚舉塊,少用 for 循環(huán)
下面用枚舉塊的寫法比較for的寫法讀起來更順暢
數(shù)組:
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);
??? }
語法最簡(jiǎn)單效率最高的遍歷方法:
for(id obj in [enumArray reverseObjectEnumerator]) {
?NSLog(@"%@",obj);
??? }
基于塊的遍歷方式:遍歷時(shí)可以直接從塊里獲取更多信息沿彭;
數(shù)組:
?[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;
??????? }
??? }];
小結(jié):
1朽砰、遍歷集合的有四種方式:for 、枚舉喉刘、 快速瞧柔、塊最基本的方法是for,其次是NSEnumerator非遍歷法及快速遍歷睦裳,最新非剃、最先進(jìn)的方法是“塊枚舉法”
2、“塊枚舉法”本身就能通過GCD來并發(fā)執(zhí)行遍歷操作推沸,無需另行編寫代碼备绽,而采用其他遍歷方式無法輕易實(shí)現(xiàn)這一點(diǎn)
3、如提前知道待遍歷的集合含有何種對(duì)象鬓催,則應(yīng)修改塊簽名肺素,指出對(duì)象的具體類型
49 ?-> 對(duì)自定義其內(nèi)存管理語義的collection 使用無縫橋接
使用C語言框架API需要橋接一下
?NSArray*aNSArray =@[@1,@2,@3];
?CFArrayRefaCFArrayRef = (__bridgeCFArrayRef)(aNSArray);
?aNSArray = (__bridgeNSArray*)(aCFArrayRef);
__bridge:告訴ARC如果處理轉(zhuǎn)換所涉及的Objective-C對(duì)象,ARC仍然具備這個(gè)Objective-C對(duì)象的所有權(quán)
__bridge_retained:與__bridge相反,意味著ARC交出對(duì)象的所有權(quán)宇驾,用完記得釋放
__bridge_transfer:想把CFArrayRef非轉(zhuǎn)換為NSArray*法倍靡,并且想令A(yù)RC獲得所有權(quán),就可以采取這種方式
這三種方式被稱為橋式轉(zhuǎn)換课舍。
#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表示默認(rèn)
???CFEqual,
???CFHash
};
CFDictionaryValueCallBacksvaluecallBacks = {
???0,
???JSRetainCallBack,
???JSReleaseCallBack,
???NULL,
???CFEqual,
};
-(void)p_privateMethod{
???CFMutableDictionaryRefaCFDictionary =CFDictionaryCreateMutable(NULL,0, &keyCallBacks, &valuecallBacks);
???NSMutableDictionary*aNSDictionary = (__bridgeNSMutableDictionary*)(aCFDictionary);
}
小結(jié):
1塌西、通過無縫橋接技術(shù)他挎,可以在Foudation框架中的Objective-C對(duì)象雨CoreFoundation框架中的C語言數(shù)據(jù)結(jié)構(gòu)之間來回轉(zhuǎn)換
2、在CoreFoundation層面創(chuàng)建collection時(shí)捡需,可以指定許多回調(diào)函數(shù)办桨,這些函數(shù)表示此collection應(yīng)如何處理其元素,然后站辉,可用無縫橋接技術(shù)呢撞,將其轉(zhuǎn)換成具備特殊內(nèi)存管理語義的Objective-C。
50 -> 構(gòu)建緩存時(shí)選用NSCache而非NSDictionary
{
??? NSCache *_cache;
}
- (id)initWithURL:(NSURL*)url{
???if([superinit]) {
???????_url= url;
???????_cache= [NSCachenew];
???????_cache.countLimit? =100;//最大儲(chǔ)存數(shù)
???????_cache.totalCostLimit=5*1024*1024;//不能超過的數(shù)據(jù)大小5MB
??? }
???returnself;
}
- (void)downloadDataForUrl:(NSURL*)url{
???NSData*cacheData = [_cacheobjectForKey:url];
???if(cacheData) {
???????NSLog(@"緩存過的了直接使用緩存數(shù)據(jù)");
??? }else{
???????NSLog(@"沒有緩存需要下載");
??????? [selfstartWithCompletionHandler:^(NSData*data,NSError*error) {
??????????? [_cachesetObject:dataforKey:urlcost:data.length];
???????????NSLog(@"保存并且使用數(shù)據(jù)");
??????? }];
??? }
}
使用NSPurgeableData系統(tǒng)資源緊張時(shí)會(huì)把保存的緩存對(duì)象內(nèi)存丟棄
- (void)downloadDataForUrl:(NSURL*)url{
???NSPurgeableData*cacheData = [_cacheobjectForKey:url];
???if(cacheData) {
??????? [cacheDatabeginContentAccess];//停止正在清除該data的操作
???????NSLog(@"緩存過的了直接使用緩存數(shù)據(jù)");
??????? [selfuseData:cacheData];
??????? [cacheDataendContentAccess];
??? }else{
???????NSLog(@"沒有緩存需要下載");
??????? [selfstartWithCompletionHandler:^(NSData*data,NSError*error) {
???????????//??創(chuàng)建好的NSPurgeableData對(duì)象后饰剥,其“引用計(jì)數(shù)”會(huì)多1殊霞,所以無需再調(diào)用beginContentAccess,但是必須要調(diào)用endContentAccess抵消這個(gè)”1“
???????????NSPurgeableData*purgeableData = [NSPurgeableDatadataWithData:data];
??????????? [_cachesetObject:purgeableDataforKey:urlcost:data.length];
???????????NSLog(@"保存并且使用數(shù)據(jù)");
??????????? [selfuseData:data];
??????????? [purgeableDataendContentAccess];
??????? }];
??? }
}
小結(jié):
1汰蓉、實(shí)現(xiàn)緩存時(shí)喲哦難過NSCache因?yàn)樗蔷€程安全的
2绷蹲、NSCache對(duì)象設(shè)置上限,不是“硬限制”顾孽,只是起指導(dǎo)作用
3祝钢、NSPurgeableData與NSCache搭配使用,可實(shí)現(xiàn)自動(dòng)清除功能岩齿,被丟棄時(shí)太颤,該對(duì)象自身也會(huì)從緩存清除
4、一般盹沈,從網(wǎng)絡(luò)獲取或者磁盤獲取的數(shù)據(jù)草緩存龄章,會(huì)提高程序效率
51 -> 精簡(jiǎn) initialize 與 load的實(shí)現(xiàn)代碼
+ (void)load :程序啟動(dòng)的時(shí)候分類或者類,會(huì)執(zhí)行此方法
+(void)initialize :它是惰性調(diào)用的乞封,程序首次用該類之前調(diào)用做裙,且只調(diào)用一次
小結(jié):
1、在加載階段肃晚,如果類實(shí)現(xiàn)了load方法锚贱,那么系統(tǒng)就會(huì)調(diào)用它跛梗;類的load方法要比分類先調(diào)用臣樱,且不參與覆寫機(jī)制
2、首次使用某個(gè)類之前啄清,系統(tǒng)會(huì)向其發(fā)送initialize消息晋修。因?yàn)樵摲椒ㄗ駨母矊懸?guī)則吧碾,所以通常應(yīng)該在里面判斷當(dāng)前初始化的是哪個(gè)類
3、initialize 與 load盡量精簡(jiǎn)和避免使用
4墓卦、無法在編譯器設(shè)定的全局變量倦春,可以放在initialize方法里初始化
52 -> 別忘了NSTimer 會(huì)保留其目標(biāo)對(duì)象
這段代碼采用了一種很有效的寫法,它先定義了一個(gè)弱引用,令其指向 self睁本,然后使塊捕獲這個(gè)引用尿庐,而不直接去捕獲普通的 self 變量,也就是說呢堰,self 不會(huì)為計(jì)時(shí)器所保留抄瑟。當(dāng)塊開始執(zhí)行時(shí),立刻生成 strong 引用暮胧,以保證實(shí)例在執(zhí)行期間持續(xù)存活锐借。
?@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
小結(jié):
1问麸、NSTimer 對(duì)象會(huì)保留其目標(biāo)往衷,直到計(jì)時(shí)器本身失效為止,調(diào)用 invalidate 方法可令計(jì)時(shí)器失效严卖,另外席舍,一次性的計(jì)時(shí)器在觸發(fā)完任務(wù)之后也會(huì)失效。
2哮笆、反復(fù)執(zhí)行任務(wù)的計(jì)時(shí)器(repeating timer)来颤,很容易引入保留環(huán),如果這種計(jì)時(shí)器的目標(biāo)對(duì)象又保留了計(jì)時(shí)器本身稠肘,那肯定會(huì)導(dǎo)致保留環(huán)福铅。這種環(huán)狀保留關(guān)系,可能是直接發(fā)生的项阴,也可能是通過對(duì)象圖里的其他對(duì)象間接發(fā)生的滑黔。
3、可以擴(kuò)充 NSTimer 的功能环揽,用 “塊”來打破保留環(huán)略荡。不過,除非 NSTimer 將來在公共接口里提供此功能歉胶,否則必須創(chuàng)建分類汛兜,將相關(guān)實(shí)現(xiàn)代碼加入其中。