oc語言特性
oc使用動態(tài)綁定的消息結構,在運行時才會檢查對象類型纲酗。接收消息后笋婿,執(zhí)行代碼疯暑,由運行環(huán)境而非編譯器來決定。
面向對象語言,"對象"就是"基本構造單元"滩报,開發(fā)者通過對象來存儲并傳遞數(shù)據(jù)锅知。在對象之間傳遞數(shù)據(jù)并執(zhí)行任務的過程就叫做"消息傳遞"。
使用消息結構的語言脓钾,其運行時所執(zhí)行的代碼由運行環(huán)境來決定售睹;而使用函數(shù)調用的語言,由編譯器決定可训。如果調用的函數(shù)是多態(tài)的昌妹,那么在運行時就按照“虛方法表”來查出到底應該執(zhí)行哪個函數(shù)實現(xiàn)。而采用消息結構的語言握截,不論是否多態(tài)飞崖,總是在運行時才會去查找所要執(zhí)行的方法。實際上谨胞,編譯器甚至不關心接收消息的對象是何種類型固歪。接收消息的對象問題也要在運行時處理,甚至過程叫做“動態(tài)綁定”胯努。
方法
1. 在類的頭文件中盡量少引入其他頭文件
- 在編譯使用PersonTest類文件時牢裳,不需要知道PersonTest類的全部細節(jié),只需知道類名就好叶沛,使用向前聲明(@class PersonTest;)就行蒲讯。
- 除非確有必要,否則不要引入頭文件恬汁。一般應在某個類的頭文件中使用向前聲明(@class 類名)來提及別的類伶椿,并在實現(xiàn)文件中引用那些類的頭文件辜伟。這樣做盡量降低類之間的耦合。
- 有時無法使用向前聲明脊另,比如要聲明某個類遵循一項協(xié)議导狡。這種情況,盡量吧“該類遵循某協(xié)議”的這條聲明移至“calss-continuation分類”中偎痛。若不行旱捧,就把協(xié)議單獨放在一個頭文件中,然后將其引入踩麦。
2. 多用字面量語法枚赡,少用與之等價的方法
- 使用字面量語法創(chuàng)建字符串、數(shù)值谓谦、數(shù)組贫橙、字典,與常規(guī)方法比更簡明扼要
- 應該通過取小標操作來訪問數(shù)組下標或字典中鍵所對應的元素
- 用字面量語法創(chuàng)建數(shù)組或字典時反粥,若值中有nil卢肃,則會拋出異常,終止程序才顿,其語法更安全
- 局限性:除字符串外莫湘,所創(chuàng)建的對象必須屬于Foundation框架才行
NSNumber *number = [NSNumber numberWithInt:1];
NSNumber *num2 = @1;
NSNumber *booln = @YES;
NSNumber *cn = @'a';
NSArray *ary = [NSArray arrayWithObjects:@"cat",@"tom",@"mouse", nil];
NSArray *ary1 = @[@"cat",@"tom"];
NSString *dog = [ary objectAtIndex:1];
NSString *dog1 = ary[1];
NSDictionary * pd = [NSDictionary dictionaryWithObjectsAndKeys:@"Tom",@"name",[NSNumber numberWithInt:26],@"age", nil];
NSDictionary *pd1 = @{@"name":@"Tom",
@"age":@26};
3. 多用類型常量,少用 #define 預處理指令
- 不要用預處理指令定義常量郑气。這樣定義出來的常量不含類性信息幅垮,編譯器只會在編譯前據(jù)此執(zhí)行查找與替換操作。即使有人重新定義了常量值尾组,編譯器也不會產(chǎn)生警告
- 在實現(xiàn)文件中使用static const 來定義"只在編譯單元內可見的常量"忙芒。由于此類常量不會在全局符號表中,所以無需為其名稱加前綴
- 在頭文件中使用extern 來聲明全局常量演怎,并在相關實現(xiàn)文件中定義其值匕争。這種常量要出現(xiàn)在全局符號表中,所以其名稱應加以區(qū)隔爷耀,通常用與之相關的類名做前綴
#define ANIMATION_DURATION 0.3
static const NSTimeInterval kAnimationDuration = 0.3;
extern const NSTimeInterval TestAnimationDuration;
const NSTimeInterval TestAnimationDuration = 0.3;
4. 用枚舉表示狀態(tài)甘桑、選項、狀態(tài)碼
- 應該使用枚舉來表示狀態(tài)機的狀態(tài)歹叮、傳遞給方法的選項以及狀態(tài)碼等值
- 如果把傳遞給某個方法的選項表示為枚舉類型跑杭,而多個選項又可同時使用,那么就將個選項值定義為2的冪咆耿,以便通過按位或操作將其組合起來
- 用NS_ENUM與NS_OPTIONS宏來定義枚舉德谅,并指明其底層數(shù)據(jù)類型。這樣做可以確保枚舉是用開發(fā)者所選的底層數(shù)據(jù)類型實現(xiàn)的萨螺,而不會采用編譯器所選的類型
- 在處理枚舉類型的switch語句中不要實現(xiàn)default分支窄做。這樣的話愧驱,加入新枚舉之后,編譯器就會提示開發(fā)者:switch語句并未處理所有枚舉
對象椭盏、消息组砚、運行期
5. 理解"屬性"這一概念
"屬性"用于封裝對象中的數(shù)據(jù)。對象通常會把其所需要的數(shù)據(jù)保存為各種實例變量掏颊。實例變量一般通過"存取方法"來訪問糟红。其中,"獲取方法"(getter)用于讀取變量值,而"設置方法"(setter)用于寫入變量值.開發(fā)者可令編譯器自動編寫與屬性相關的存取方法乌叶。此特性引入了一種新的“點語法”盆偿,使開發(fā)者可以更容易的依照類對象來訪問其中的數(shù)據(jù)。
存取方法有著嚴格的命名規(guī)范准浴,所以OC語言才能根據(jù)名稱自動創(chuàng)建存取方法事扭,@property語法等同與寫一套存取方法,@property NSString*name就是編譯器自動寫出一套存取方法乐横。
若不想令編譯器自動合成存取方法句旱,則可以自己實現(xiàn),如果你只實現(xiàn)了其中一個存取方法晰奖,那么另一個還是由編譯器來合成。使用@dynamic關鍵字腥泥,可以阻止編譯器自動合成存取方法匾南。
屬性特質: 原子性、讀/寫權限蛔外、內存管理語義蛆楞、方法名
@property(nonatomic,readwrite,copy) NSString *firstName
@property(nonatomic,readwrite,copy,getter=isOn) BOOL on
原子性
默認情況下,由編譯器所合成的方法會通過鎖定機制確保其原子性夹厌。
atomic與nonatomic區(qū)別?具備atomic特質的獲取方法會通過鎖定機制來確保其操作的原子性豹爹,就是說,如果兩個線程讀寫同一屬性矛纹,那么不論何時臂聋,總能看到有效的屬性值。若是不加鎖的話(或者使用nonatomic語義)或南,那么當其中一個線程在改寫某屬性值時孩等,另外一個線程也許會突然闖入,把尚未修改好的屬性值讀取出來采够。發(fā)生這種情況時肄方,線程讀到的屬性值可能不對。
使用nonatomic歷史原因:在iOS中使用同步鎖的開銷較大蹬癌,會帶來性能問題权她。一般情況下并不要求屬性必須是“原子的”虹茶,因為這樣并不能保證“線程安全”,若要實現(xiàn)“線程安全”的操作隅要,還需采用更深層次的鎖定機制蝴罪。如一個線程在連續(xù)多次讀取屬性值的過程中有別的線程在同時修改該值,即使用atomic拾徙,也還是會讀到不同的屬性值洲炊。
讀寫權限
readwrite(讀寫)特質屬性擁有"獲取方法"與"設置方法";
readonly(只讀)特質屬性只擁有"獲取方法";
內存管理語義
assign "設置方法"只會執(zhí)行針對"純量類型"(如CGFloat、NSInteger等)的簡單賦值操作
strong 此特質表明該屬性定義了一種"擁有關系"尼啡。為這種屬性設置新值時暂衡,設置方法會先保留新值,并釋放舊值崖瞭,然后將新值設置上去
weak 此特質表明該屬性定義了一種"非擁有關系"狂巢。 為這種屬性設置新值時,設置方法既不保留新值书聚,也不釋放舊值唧领。此特質同assign類似,然而在屬性所指的對象遭到摧毀時雌续,屬性也會清空
unsafe_undertained 此特質的語義和assign相同斩个,但是它適合用于"對象類型",該特質表達一種"非擁有關系"驯杜,當目標對象遭到摧毀時受啥,屬性值不會自動清空,這一點與weak區(qū)別
copy 此特質所表達的所屬關系與strong類似鸽心。然而設置方法并不保留新值滚局,而是將其"拷貝"。
方法名
getter =<name>
setter=<name>
多使用nonatomic屬性顽频,因為atomic屬性會影響性能
通過"特質"來指定存儲數(shù)據(jù)所需的正確語義
6. 在對象內部盡量直接訪問實例變量
不經(jīng)過OC的"方法派送"步驟藤肢,所以直接訪問實例變量的速度比較快,此情況下糯景,編譯器所生成的代碼會直接訪問保存對象實例變量的那塊內存嘁圈。
直接訪問實例變量不會觸發(fā)“鍵值觀測”通知;
直接訪問實例變量有助于排查與之相關的錯誤蟀淮,加“斷點”丑孩,監(jiān)控該屬性的調用者及訪問時機;
直接訪問實例變量不會調用其"設置方法",繞過了相關屬性所定義的"內存管理語義"灭贷。
- 在對象內部讀取數(shù)據(jù)時温学,應該直接通過實例變量來讀,而寫入數(shù)據(jù)時甚疟,則通過那個屬性來寫
- 在初始化方法及dealloc方法中仗岖,總是應該直接通過實例變量來讀寫數(shù)據(jù)
- 有時會使用惰性初始化技術配置某份數(shù)據(jù)逃延,此情況,需要通過屬性來讀取數(shù)據(jù)
7. 理解"對象等同性"概念
- 若檢測對象的等同性轧拄,提供"isEqual:"與"hash"方法
- 相同對象具有相同的哈希碼揽祥,但是兩個哈希碼相同的對象卻未必相同
- 不要盲目的逐個檢測每條屬性,而是應該依照具體需求來制定檢測方案
- 編寫hash方法時檩电,應該使用計算速度快而且哈希碼碰撞幾率低的算法
8. 以"類族模式"隱藏實現(xiàn)細節(jié)
- 類族模式可以把實現(xiàn)細節(jié)隱藏在一套簡單的公共接口后面
- 系統(tǒng)框架常使用類族
- 從類族的公共抽象類中繼承子類要當心
* 子類應該繼承自類族中的抽象基類
* 子類應該定義自己的數(shù)據(jù)存儲方式
* 子類應當覆寫超類文檔中指明需要覆寫的方法
9. 在既有類中使用關聯(lián)對象存放自定義數(shù)據(jù)
有時候類的實例可能是有某種機制所創(chuàng)建的拄丰,而開發(fā)者無法令這種機制創(chuàng)建出自己所寫的字類實例,這時候就需要關聯(lián)對象解決問題俐末。
* void objc_setAssociatedObject(id object,void *key,id value,objc_AssociationPolicy policy)
此方法以給定的鍵和策略為某對象設置關聯(lián)對象值
* id objc_getAssociatedObject(id object,void *key)
此方法根據(jù)給定的鍵從某對象中獲取相應的關聯(lián)對象值
* void objc_removeAssociatedObject(id object)
此方法移除指定對象的全部關聯(lián)對象
例子將創(chuàng)建警告視圖與處理操作結果的代碼放在一起:
#import <objc/runtime.h>
static void *EOCMyAlertViewKey = "ECOMyAlertViewKey";
- (void)askUserAQuestion{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Question" message:@"What are you doing?" delegate:self cancelButtonTitle:@"Cancel" otherButtonTitles:@"Continue", nil];
void(^block)(NSUInteger) = ^(NSUInteger buttonIndex){
if(buttonIndex == 0 ){
NSLog(@"doCancel");
}else{
NSLog(@"doContinue");
}
};
objc_setAssociatedObject(alert, EOCMyAlertViewKey, block, OBJC_ASSOCIATION_COPY);
[alert show];
}
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{
void(^block)(NSUInteger) = objc_getAssociatedObject(alertView, EOCMyAlertViewKey);
block(buttonIndex);
}
- 可以通過"關聯(lián)對象"機制來把兩個對象連起來
- 定義關聯(lián)對象時可指定內存管理語義料按,用以模仿定義屬性所采用的"擁有關系"與"非擁有關系"
- 只有在其他做法不可行時才因選用關聯(lián)對象,因為這種做法通常會引入難于查找的bug
給分類添加屬性:
//使用前記得#import <objc/runtime.h>
- (void)setName:(NSString *)name{
// 保存name
// 動態(tài)添加屬性 = 本質:讓對象的某個屬性與值產(chǎn)生關聯(lián)
/*
object:保存到那個對象中
key:用什么屬性保存 屬性名
value:保存值
policy:策略,strong,weak
*/
objc_setAssociatedObject(self, "name", name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
// _name = name;
}
- (NSString *)name{
return objc_getAssociatedObject(self, "name");
// return _name;
}
10. 理解objc_msgSend的作用
對象調用方法卓箫,oc術語叫"消息傳遞",消息有"名稱"(name)或"選者子"(selector)载矿,可以接受參數(shù),可有返回值烹卒。
void objc_msgSend(id self,SEL cmd,...)
這個是"參數(shù)個數(shù)可變的函數(shù)",能接受兩個或兩個以上的參數(shù).第一個參數(shù)代表接收者闷盔,第二個參數(shù)代表選擇子(SEL是選擇子的類型),后續(xù)參數(shù)是消息中的參數(shù)旅急,其順序不變逢勾。選擇子指的就是方法的名字。
objc_msgSend函數(shù)會根據(jù)接收者與選擇子來調用適當?shù)姆椒晁薄榱送瓿纱瞬僮髅舫粒摲椒ㄐ枰诮邮照咚鶎俚念愔兴褜て?方法列表",如果能找到與選擇子名稱相符的方法炎码,就跳至其實現(xiàn)代碼。若找不到秋泳,那就沿著繼承體系繼續(xù)向上查找潦闲,等找到合適的方法之后再跳轉。如果最終還是找不到相符的方法迫皱,那就執(zhí)行"消息轉發(fā)"操作歉闰。
- 消息由接收者、選擇子及參數(shù)構成卓起。給某對象"發(fā)送消息"也就是相當于在該對象上"調用方法"
- 發(fā)給某對象全部消息都要由"動態(tài)消息派發(fā)系統(tǒng)"來處理和敬,該系統(tǒng)會查出對應的方法,并執(zhí)行起代碼
11. 消息轉發(fā)機制
消息轉發(fā)分為兩大階段:
第一階段先征詢接收者戏阅,所屬的類昼弟,看其是否能動態(tài)添加方法,以處理這個"未知的選擇子"(unknow selector)奕筐,這叫做"動態(tài)方法分析"(先判斷這個類是否能新增一個實例方法用以處理此選擇子)舱痘。
第二個階段涉及"完整的消息轉發(fā)機制"变骡。如果運行期系統(tǒng)已經(jīng)把第一階段執(zhí)行完了,那么接收者自己無法在已動態(tài)新增方法的手段來響應包含該選擇子的消息芭逝。此時塌碌,運行期系統(tǒng)會請求接收者已其他手段來處理與消息相關的方法調用。細分兩小步1.請接收者看看有沒有其他對象能處理這條消息旬盯。若有台妆,則運行系統(tǒng)會把消息傳給那個對象,于是消息轉發(fā)結束胖翰。若沒有“備援接收者”則啟動完整的消息轉發(fā)機制接剩,運行系統(tǒng)會把與消息有關的全部細節(jié)都封裝到NSInvocation對象中,再給接收者最后一個機會泡态,令其設法解決當前還未處理的這條消息.
動態(tài)方法解析:
對象在收到無法解讀的消息后搂漠,首先將調用其所屬類的類方法:
+(BOOL)resolveInstanceMethod:(SEL)selector
該方法的參數(shù)就是那個未知的選擇子,返回類型為bool類型某弦,表示這個類是否能新增一個實例方法用以處理此選擇子桐汤。
使用這方法前提是:相關方法的實現(xiàn)代碼已經(jīng)寫好,只等著運行的時候動態(tài)插在類里面就行了靶壮。此方案常用來實現(xiàn)@dynamic屬性怔毛,比如要訪問CoreData框架中NSManagedObjects對象的屬性時就可以用,因為實現(xiàn)這些屬性所需的存取方法在編譯器就能確定。
備援接收者:
當前接收者還有第二次機會能處理未知的選擇子腾降,這一步中拣度,運行期系統(tǒng)會問他:能不能把這條消息轉發(fā)給其它接收者來處理。該步驟對應處理方法:
-(id)forwardingTargetForSelector:(SEL)selector
方法參數(shù)代表未知的選擇子螃壤,若當前選擇子能找到備援對象抗果,則將其返回,若找不到奸晴,返回nil冤馏。
完整的消息轉發(fā):
若轉發(fā)算法來到這一步,那么只能啟用完整的消息轉發(fā)機制了寄啼。
首先創(chuàng)建NSInvocation對象逮光,把與尚未處理的那條消息有關的全部細節(jié)都封裝與其中。此對象包含選擇子墩划、目標及參數(shù)涕刚。在觸發(fā)NSInvocation對象時,"消息派發(fā)系統(tǒng)"將親自出馬乙帮,把消息指派給目標對象杜漠。此步驟會調用下列方法來轉發(fā)消息:- (void)forwardInvocation:(NSInvocation*)invocation
此方法很簡單:只需改變調用目標,使其消息在新目標上得以調用即可。實現(xiàn)此方法碑幅,若發(fā)現(xiàn)某調用操作不應由本類處理戴陡,則需調用超類的同名方法。這樣沟涨,繼承體系中的每個類都有機會處理此調用方法恤批,直至NSObject。若最后調用NSObject類的方法裹赴,那么該方法還會繼續(xù)調用"doesNotRecognizeSelector:"以拋出異常喜庞,此異常表明選擇子最終未能得到處理。
- 若對象無法響應某個選擇子棋返,則進入消息轉發(fā)流程
- 通過運行期的動態(tài)方法解析功能延都,我們可以在需要用到某個方法再將其加入類中
- 對象可以把其無法解讀的某些選擇子轉交給其他對象來處理
- 經(jīng)過上述兩步之后,如果還是沒辦法處理選擇子睛竣,那就啟動完整的消息轉發(fā)機制
12. 用"方法調配技術"調試"黑盒方法"
類的方法列表會把選擇子的名稱映射到相關的方法實現(xiàn)上晰房,使得"動態(tài)信息派發(fā)系統(tǒng)"能夠據(jù)此找到應該調用的方法。這些方法均已函數(shù)指針的形式來表示射沟,此指針叫做IMP殊者。
oc運行期系統(tǒng)提供了幾種方法可以讓我們操作IMP(指針),開發(fā)者可以向其新增選擇子验夯,也可以改變某選擇子所對應的方法實現(xiàn)猖吴,還可以交換兩個選擇子所映射到的指針。
交換實現(xiàn)方法
void method_exchangeImplementations(Method m1,Method m2)
獲取實現(xiàn)方法
Method class_getInstanceMethod(Class aClass,SEL aSelector)
- 在運行期挥转,可以向類中新增或替換選擇子所對應的方法實現(xiàn)
- 使用另一份實現(xiàn)來替換原來的方法實現(xiàn)海蔽,這道工序叫做"方法調配",開發(fā)者常用此技術向原有的實現(xiàn)中添加新功能
- 一般只有調試程序的時候才需要在運行期修改方法實現(xiàn)绑谣,這種做法不宜濫用
13. 理解"類對象"的用意
"在運行期檢視對象類型"這一操作也叫做"類型信息查詢"党窜,這個強大而有用的特性內置于Foundation框架的NSObject協(xié)議里,凡是由公共根類繼承而來的對象都要遵從此協(xié)議借宵。
每個OC對象對象實例都是指向某塊內存數(shù)據(jù)的指針幌衣。所以在聲明變量時,類型后面要加上"*"字符:NSString * s = @"someString";
對象數(shù)據(jù)結構
typedef struct objc_object{
Class isa;
} *id;
每個對象數(shù)據(jù)結構的首個成員時Class類的變量暇务。該變量定義了對象所屬的類,通常稱為"is a"指針怔软。
Class對象也定義在運行期程序庫的頭文件中:
typedef struct objc_class *Class;
struct objc class{
Class isa;
Class super_class;
const char *name;
long version;
long info;
long instance_size;
struct objc_ivar_list *ivars;
struct objc_method_list **methodLists;
struct objc_cache *cache;
struct objc_protocol_list *protocols;
};
此結構體存放類的"元數(shù)據(jù)"垦细,例如類的實例實現(xiàn)了幾個方法,具備多少個實例變量等信息挡逼。此結構體的首個變量也是isa指針括改,這說明Class本身亦為OC對象。結構體里還有個變量叫做super_class,它定義了本類的超類家坎。類對象所屬的類型(也就是isa指針所指向的類型)是另外一個類嘱能,叫做"元類"吝梅,用來表述對象本身所具備的元數(shù)據(jù)。"類方法"就定義在此處惹骂,因為這些方法可以理解成類對象的實例方法苏携。每個類僅有一個"類對象",而每個"類對象"僅有一個與之相關的"元類"对粪。super_class指針確立了繼承關系右冻,而isa指針描述里實例所屬的類。
- 每個實例都有一個指向Class對象的指針著拭,用以表面其類型纱扭,而這些Class對象則構成了類的繼承體系
- 如果對象類型無法在編譯期確定,那么就應該使用類信息查詢方法來探知
- 盡量使用類型信息查詢方法來確定對象類型儡遮,而不要直接比較類對象,因為某些對象實現(xiàn)了消息轉發(fā)功能
接口與API設計
14. 用前綴避免命名空間沖突
- 選擇公司乳蛾、應用程序或二者關聯(lián)之名稱作為類名前綴
- 若自己所開發(fā)的程序庫中用到了第三方庫,則應該為其中的名稱加上前綴
15. 提供"全能初始化方法"
- 若全能初始化方法與超類不同鄙币,則需要覆寫超類中的對應方法
- 若超類的初始化方法不適應于子類肃叶,那么應該覆寫這個超類方法,并在其中拋出異常
16. 實現(xiàn)description方法
- 實現(xiàn)description方法返回一個有意義的字符串,用以描述該實例
- 若想在調試時打印出更詳盡的對象描述信息爱榔,則應實現(xiàn)debugDescription方法
17. 盡量使用不可變對象
- 若某屬性僅可與對象內部修改被环,則在"class-continuation分類"中將其由readonly屬性擴展為readwrite屬性
- 不要把可變的collection作為屬性公開,而應該提供相關方法详幽,以此修改對象中的可變collection
18. 使用清晰而協(xié)調的命名方式
- 遵從oc命名規(guī)范
19. 為私有方法名加前綴
- 給私有方法的名稱加前綴筛欢,這樣可以很容易的將其同公共方法區(qū)分開
- 不要單用一個下劃線做私有方法的前綴,因為這種做法是預留給蘋果公司用的
20. 理解oc錯誤類型
- 只有發(fā)生可使整個應用程序崩潰的嚴重錯誤時唇聘,才應使用異常
- 在錯誤不嚴重的情況下版姑,可以指派"委托方法"來處理錯誤,
也可以把錯誤信息放在NSError對象里迟郎,經(jīng)由"輸出參數(shù)"返回給調用者
21. 理解NSCopying協(xié)議
- 要想令自己所寫的對象具有拷貝功能剥险,則需實現(xiàn)NSCopying協(xié)議,該協(xié)議只有一個方法:-(id)copyWithZone:(NSZone*)zone;
- 如果自定義的對象分為可變版本和不可變版本,那么就要同時實現(xiàn)NSCopying與NSMUtableCopying協(xié)議
- 如果你寫的對象需要深拷貝宪肖,那么可以考慮新增一個專門執(zhí)行深拷貝的方法
- 深拷貝:在拷貝對象時表制,將其底層數(shù)據(jù)也一并復制過去。
淺拷貝:只拷貝容器對象本身
協(xié)議與分類
OC語言特性:“協(xié)議”控乾,與Java的"接口"類似么介。OC不支持多重繼承,因而把某個類應該實現(xiàn)的一系列方法定義在協(xié)議里面蜕衡。
OC語言特性:"分類"壤短,利用分類機制,無需繼承子類即可直接為當前類添加方法。
22. 通過委托與數(shù)據(jù)源協(xié)議進行對象間通信
委托模式:定義一套接口久脯,某對象若想接受另一個對象的委托纳胧,則需遵從此接口,以便成為其"委托對象"帘撰,而這"另一個對象"則可以給其委托對象回傳一些信息跑慕,也可以在發(fā)生相關事件時通知委托對象。
此模式可將數(shù)據(jù)與業(yè)務邏輯解耦;
- 委托模式為對象提供了一套接口骡和,使其可由此將相關事件告知其他對象
- 將委托對象應該支持的接口定義成協(xié)議相赁,在協(xié)議中把可能需要處理的時間定義成方法
- 當某對象需要從另外一個對象中獲取數(shù)據(jù)時,可以使用委托模式慰于,此情況钮科,該模式亦稱"數(shù)據(jù)源協(xié)議"
- 若有必要,可實現(xiàn)含有位段的結構體婆赠,將委托對象是否能響應相關協(xié)議方法這一信息緩存至其中
23. 將類的實現(xiàn)代碼分散到便于管理的數(shù)個分類之中
- 通過分類機制绵脯,可把類代碼分成多個容易管理的小塊,以便單獨檢視,便于調試;
- 將應該視為"私有"的方法歸入Private的分類中休里,以隱藏實現(xiàn)細節(jié);
24. 為第三方類的分類名稱加前綴
- 向第三方類中添加分類時蛆挫,給其名稱加前綴,給其中的方法加前綴
25. 勿在分類中聲明屬性
- 分類機制妙黍,應該理解為一種手段悴侵,目標在于擴展類的功能,而非封裝數(shù)據(jù);
- 把封裝數(shù)據(jù)所用的全部屬性都定義在主接口里,這樣更加清晰
*在"class-continuation分類" 之外的其它分類中盡量不要定義屬性
26. 使用"class-continuation分類"隱藏實現(xiàn)細節(jié)
"class-continuation分類"和普通的分類不同拭嫁,它必須定義在其所接續(xù)的那個類的實現(xiàn)文件里可免。中重要之處在于,這是唯一能聲明實例變量的分類做粤,而且此分類沒有特定的實現(xiàn)文件浇借,其中的方法都應該定義在類的主實現(xiàn)文件里。此分類沒有名字怕品,比如有個類叫做Person,其"class-continuation分類"寫法如下:
@interface Person(){
NSString * _anInstanceVariable; // 實例變量
}
// Methods here
@end
- 通過"class-continuation分類'向類中新增實例變量
- 把私有方法的原型聲明在"class-continuation分類"中
- 如果某屬性在主接口中聲明為"只讀"妇垢,而類的內部又要用設置方法修改此屬性,那么就在"class-continuation分類"中將其擴展為"可讀寫"
- 若想使類所遵循的協(xié)議不為人知肉康,則可于"class-continuation分類"中聲明
27. 通過協(xié)議提供匿名對象
可以用協(xié)議把自己所寫的API之中的實現(xiàn)細節(jié)隱藏起來闯估,將返回的對象設計為遵從此協(xié)議的純id類型。這樣的話吼和,想要隱藏的類名就不會出現(xiàn)在API之中了涨薪。此概念稱為"匿名對象".
- 協(xié)議可在某種程度上提供匿名類型。具體的對象類型可以淡化成遵從某種協(xié)議的id類型纹安,協(xié)議里規(guī)定了對象所應實現(xiàn)的方法
- 使用匿名對象來隱藏類型名稱(或類名)
- 如果具體類型不重要尤辱,重要的是對象能夠響應(定義在協(xié)議里)特定方法,那么可使用匿名對象來表示
內存管理
OC語言使用引用計數(shù)來管理內存厢岂,即每個對象都有個可以遞增或遞減的計數(shù)器光督。
28. 引用計數(shù)
引用計數(shù)機制通過可以遞增遞減的計數(shù)器來管理內存。
對象創(chuàng)建出來時塔粒,其保留計數(shù)至少為1.若想令其繼續(xù)存活结借,則調用retain方法。要是某部分代碼不再使用此對象卒茬,不想令其繼續(xù)存活船老,那就調用release或autorelease方法。最終當保留計數(shù)歸零時圃酵,對象就回收了柳畔,也就是說,系統(tǒng)會將其占用的內存標記為"可重用"(reuse)郭赐。此時薪韩,所有指向該對象的引用也就變得無效了。
29. ARC
ARC實際上也是一種引用計數(shù)機制,ARC幾乎把所有的內存管理事宜都交給編譯器來決定捌锭。
ARC自動執(zhí)行retain,release,autorelease等操作俘陷,ARC在調用這些方法時,并不通過普通的OC消息派發(fā)機制观谦,而是直接調用底層C語言版本拉盾。這樣做性能更好,因為保留及釋放操作需要頻繁執(zhí)行豁状,所以直接調用底層函數(shù)能節(jié)省很多CPU周期捉偏。
- ARC管理oc對象內存,注意:CoreFoundation對象不歸ARC管理替蔬,開發(fā)者需適度調用CFRetain/CFRelease
30. 在dealloc方法中只釋放引用并解除監(jiān)聽
對象在經(jīng)歷生命周期后告私,最終會被系統(tǒng)回收,當保留計數(shù)降為o的時候承桥,執(zhí)行dealloc方法驻粟。
- dealloc方法主要是釋放對象所擁有的引用,也就是把所有OC對象都釋放掉凶异。另一件事蜀撑,就是把原來配置過的觀測行為都清理掉。
- 如果對象持有文件描述符等系統(tǒng)資源剩彬,那么應該專門編寫一個方法來釋放此種資源酷麦。這樣的類要和其使用者約定:用完資源后必須調用close方法;
開銷大或系統(tǒng)內稀缺的資源不應該在dealloc中釋放,文件描述喉恋,套接字沃饶,大塊內存等都屬于這種資源母廷。不能指望dealloc方法必定會在某個特定的時機調用,因為有一些無法預料的東西可能也持有此對象糊肤。此情況琴昆,如果非要等到系統(tǒng)調用dealloc方法時才釋放,那么保留這些稀缺資源的時間就有些過長了馆揉,通常做法是:實現(xiàn)另一個方法业舍,當應用程序用完資源對象后,就調用此方法升酣。這樣舷暮,資源對象的生命周期就變得更明確了。 - 執(zhí)行異步任務等方法不應該在dealloc里調用噩茄,只能在正常狀態(tài)下執(zhí)行的那些方法也不應在dealloc里調用下面,因為此時對象已處于正在回收的狀態(tài)了。
31. 編寫"異常安全代碼"時注意內存管理問題
TestObject *object;
@try{// 測試代碼
}@catch(){
NSLog(@" there was an error");
}@finally{
[object release];
}
- 捕獲異常時绩聘,要注意將try塊內所創(chuàng)立的對象清理干凈
- 默認情況诸狭,ARC不生成安全處理異常所需要的清理代碼。開啟編譯器標志后君纫,可生成這種代碼驯遇,不過會導致應用程序變大,而且會降低運行效率
32. 以弱引用避免保留環(huán)
- 將某些引用設為weak可避免出現(xiàn)"保留環(huán)"
- weak引用可以自動清空蓄髓,也可以不自動清空叉庐。
33. 以"自動釋放池"降低內存峰值
@autoreleasepool{
// ...
}
- 自動釋放池排布在棧中,對象受到autorelease消息后会喝,系統(tǒng)將其放入最頂端的池里
- 合理利用自動釋放池陡叠,可降低應用程序的內存峰值
34. 用"僵尸對象"調試內存管理問題
Cocoa提供了"僵尸對象",啟用這項調試功能后,運行期系統(tǒng)會把所有的已經(jīng)回收的實例轉化成特殊的"僵尸對象",而不會真正回收它們肢执。這種對象所在的核心內存無法重用汪茧,因此不可能遭到覆寫绑蔫。僵尸對象收到消息后,會拋出異常,其中準確的說明了發(fā)送過來的消息惧磺,并描述了回收之前的那個對象邑遏。僵尸對象是調試內存管理問題的最佳方式括眠。
將NSZombieEnabled環(huán)境變量設置為YES绘盟,即開啟此功能。
Xcode開啟:Scheme-Diagnostics-enable Zombie Objects
僵尸對象工作原理:系統(tǒng)在即將回收對象時诗宣,如果發(fā)現(xiàn)通過環(huán)境變量啟用了僵尸對象功能膘怕,那么還將執(zhí)行一個附加步驟。這一步就是把對象轉化為僵尸對象召庞,而不徹底回收岛心。
- 系統(tǒng)在回收對象時来破,可以不將其真的回收,而是把它轉化為僵尸對象忘古。通過環(huán)境變量NSZombieEnabled可以開啟此功能
- 系統(tǒng)會修改對象的isa指針讳癌,令其指向特殊的僵尸類,從而使該對象變?yōu)榻┦瑢ο蟠嬖怼⑹穷惸軌蝽憫械倪x擇子,響應方式為:打印一條包含消息內容及其接收者的消息逢艘,然后終止應用程序
35. 不要使用retainCount
- 對象的保留計數(shù)看似有用旦袋,實則不然,因為任何給定時間點上的"絕對保留計數(shù)"都無法反映對象生命期的全貌
- 引用ARC后它改,retainCount方法就正式廢止了疤孕,在ARC下調用該方法會導致編譯器報錯
塊與大中樞派發(fā)
36. 塊
塊與函數(shù)類似,只不過是直接定義在另一個函數(shù)里的央拖,和定義它的那個函數(shù)共享同一個范圍內的東西祭阀。塊用"^"符號表示,后面跟一對花括號鲜戒,括號里面是塊的實現(xiàn)代碼专控。
^{
// 實現(xiàn)代碼
}
塊其實就是個值,而且自有其相關類型遏餐。與int,float或OC對象一樣伦腐,也可以把塊賦值給變量,然后像是用其他變量那樣使用它失都。
塊類型語法結構:return_type (^block_name)(parameters)
塊的強大之處:在聲明它的范圍里柏蘑,所有變量都可以為其所捕獲。也就是說粹庞,那個范圍里的全部變量咳焚,在塊里依然可用。
int (^addBlock)(int a,int b) = ^(int a,int b){
return a+b;
};
int add = addBlock(2,5);
聲明變量的時候加上_block修飾符庞溜,就可以在塊內修改其變量值了革半;
塊可視為對象,可有引用計數(shù),當最后一個指向塊的引用被移走之后,塊就回收了流码。
定義塊的時候督惰,其所占的內存區(qū)域是分配在棧中的,也就是說旅掂,塊只在定義它的那個范圍內有效赏胚。
- 塊是C、C++商虐、OC中的語法閉包
- 塊可以接受參數(shù)觉阅,也可返回值
- 塊可分配在椦掳蹋或堆上,也可以是全局的典勇。分配在棧上的塊可拷貝到堆里劫哼,這樣就和標準的OC對象一樣,具備引用計數(shù)了割笙。
37. 為常用的塊類型創(chuàng)建typedef
每個塊都具備其"固有類型"权烧,因而可將其賦給適當類型的變量。這個類型由塊所接受的參數(shù)及其返回值組成伤溉。
- typedef重新定義塊類型般码,可令塊變量用起來更簡單
- 不妨為同一個塊簽名定義多個類型別名。如果要重構的代碼使用了塊類型的某個別名乱顾,那么只需修改相應typedef中的塊簽名即可板祝,無需改動其他typedef。
38. 用handler塊降低代碼的分散程度
- 在創(chuàng)建對象時走净,可使用內聯(lián)的handler塊將其相關業(yè)務邏輯一并聲明
- 由多個實例需要監(jiān)控時券时,如果采用委托模式,那么經(jīng)常需要根據(jù)傳入的對象來切換伏伯,而若該用handler塊來實現(xiàn)橘洞,則可直接將塊與相關對象放在一起
- 設計API時若果用到了handler塊,那么可以增加一個參數(shù)说搅,使其調用者可以通過此參數(shù)來覺得應該把塊安排在哪個隊列上執(zhí)行
39. 用塊引用其所屬對象時不要出現(xiàn)保留環(huán)
40. 多用派發(fā)隊列震檩,少用同步鎖
- 派發(fā)隊列可用來表述同步語義,這種做法要比使用@synchronized塊或NSLock對象更簡單
- 將同步與異步派發(fā)結合起來蜓堕,可以實現(xiàn)與普通加鎖機制一樣的同步行為抛虏,而這么做卻不會阻塞執(zhí)行派發(fā)的線程
- 使用同步隊列及柵欄塊,可以令同步行為更加高效
41. 多用GCD套才,少用performSelector系列方法
- performSelector系列方法在內存管理方面容易有疏失迂猴。它無法確定將要執(zhí)行的選擇子具體是什么,因為ARC編譯器也就無法插入適當?shù)膬却婀芾矸椒?/li>
- performSelector系列方法所能處理的選擇子太過局限了背伴,選擇子的返回值類型及發(fā)送給方法的參數(shù)個數(shù)都受限制
42. 通過Dispatch Group機制沸毁,根據(jù)系統(tǒng)資源狀況來執(zhí)行任務
43. 使用dispatch_once來執(zhí)行只需運行一次的線程安全代碼
44. 不要使用dispatch_get_current_queue
- dispatch_get_current_queue此函數(shù)已經(jīng)廢棄,只應做調試用
- 由于派發(fā)隊列是按層級來組織的傻寂,所以無法單用某個隊列對象來描述"當前隊列"這一概念
系統(tǒng)框架
45. 多用塊枚舉息尺,少用for循環(huán)
46. 對自定義其內存管理語義的collection使用無縫橋接
47. 構建緩存時選用NSCache而非NSDictionary
- NSCache可以提供優(yōu)雅的自動刪減功能,而且是"線程安全的"疾掰,此外搂誉,它與字典不同,并不會拷貝鍵
- 將NSPurgeableData與NSCache搭配使用静檬,可實現(xiàn)自動清除數(shù)據(jù)的功能
- 可給NSCache對象設置上限炭懊,用以限制緩存中的對象總個數(shù)及"總成本"并级,而這些尺度則定義了緩存刪減其中對象的時機
48. 精簡initialize與load的實現(xiàn)代碼
- 在加載階段,如果類實現(xiàn)了load方法侮腹,那么系統(tǒng)就會調用它嘲碧。分類里也可以定義此方法,類的load方法要比分類中的先調用父阻。與其他方法不同,load方法不參與覆寫機制
- 首次使用某類之前愈涩,系統(tǒng)會向其發(fā)送initialize消息。由于此方法遵從普通的覆寫規(guī)則加矛,所以通常應在里面判斷當前要初始化的是哪個類
- load與initailize方法都應該實現(xiàn)的精簡一些,這些有助于保持應用程序的響應能力履婉,也能減少引入"依賴環(huán)"的幾率
- 無法在編譯期設定的全局變量,可以放在initialize方法里初始化
49. 別忘了NSTimer會保留其目標對象
- NSTimer對象會保留其目標荒椭,直到計時器本身失效為止,調用invalidate方法可令計時器失效舰蟆,另外趣惠,一次性的計時器在觸發(fā)完任務之后也會失效
- 可以擴充NSTimer的功能,用"塊"來打破保留環(huán)
50.細節(jié)
- CGRect是C結構體身害,定義:
struct CGRect{
CGPoint origin;
CGSize size;
};
typedef struct CGRect CGRect;
整個系統(tǒng)框架都是使用這種結構體味悄,因為如果改用Objective-C對象來做的話,性能會受影響塌鸯。與重建結構體相比侍瑟,創(chuàng)建對象還需要額外開銷,例如分配及釋放堆內存等丙猬。如果只需保存int,float,double,char等"非對象類型"涨颜,那么通常使用CGRect這種結構體就可以了。
51.開發(fā)SDK
注意事項一:所有類名都應該加前綴
注意事項二:所有 category 方法加前綴
注意事項三:不要將第三方庫打包進 SDK
說明:盡量不要將第三方庫打包進 SDK茧球,如果要打包庭瑰,最好也要將該第三方庫重命名,以避免沖突抢埋。注意事項四:做基本的檢查和測試
說明:SDK 對外公布前應該進行基本的編譯檢查弹灭,不應該有編譯器警告存在。注意事項五:文檔完整并且正確
注意事項六:支持最新的 CPU 版本