編寫高質量iOS與OS X代碼的有效方法

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 版本

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末揪垄,一起剝皮案震驚了整個濱河市穷吮,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌饥努,老刑警劉巖捡鱼,帶你破解...
    沈念sama閱讀 222,681評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異酷愧,居然都是意外死亡堰汉,警方通過查閱死者的電腦和手機辽社,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,205評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來翘鸭,“玉大人滴铅,你說我怎么就攤上這事【团遥” “怎么了汉匙?”我有些...
    開封第一講書人閱讀 169,421評論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長生蚁。 經(jīng)常有香客問我噩翠,道長,這世上最難降的妖魔是什么邦投? 我笑而不...
    開封第一講書人閱讀 60,114評論 1 300
  • 正文 為了忘掉前任伤锚,我火速辦了婚禮,結果婚禮上志衣,老公的妹妹穿的比我還像新娘屯援。我一直安慰自己,他們只是感情好念脯,可當我...
    茶點故事閱讀 69,116評論 6 398
  • 文/花漫 我一把揭開白布狞洋。 她就那樣靜靜地躺著,像睡著了一般绿店。 火紅的嫁衣襯著肌膚如雪吉懊。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,713評論 1 312
  • 那天假勿,我揣著相機與錄音借嗽,去河邊找鬼。 笑死转培,一個胖子當著我的面吹牛淹魄,可吹牛的內容都是我干的。 我是一名探鬼主播堡距,決...
    沈念sama閱讀 41,170評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼甲锡,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了羽戒?” 一聲冷哼從身側響起缤沦,我...
    開封第一講書人閱讀 40,116評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎易稠,沒想到半個月后缸废,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,651評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,714評論 3 342
  • 正文 我和宋清朗相戀三年企量,在試婚紗的時候發(fā)現(xiàn)自己被綠了测萎。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,865評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡届巩,死狀恐怖硅瞧,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情恕汇,我是刑警寧澤腕唧,帶...
    沈念sama閱讀 36,527評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站瘾英,受9級特大地震影響枣接,放射性物質發(fā)生泄漏。R本人自食惡果不足惜缺谴,卻給世界環(huán)境...
    茶點故事閱讀 42,211評論 3 336
  • 文/蒙蒙 一但惶、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧湿蛔,春花似錦膀曾、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,699評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽捌省。三九已至苫纤,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間纲缓,已是汗流浹背卷拘。 一陣腳步聲響...
    開封第一講書人閱讀 33,814評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留祝高,地道東北人栗弟。 一個月前我還...
    沈念sama閱讀 49,299評論 3 379
  • 正文 我出身青樓,卻偏偏與公主長得像工闺,于是被迫代替她去往敵國和親乍赫。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,870評論 2 361

推薦閱讀更多精彩內容