iOS編寫質量代碼

這是一篇讀書筆記,快速記錄各類高效率編程的技巧和方法。這些方法是爲了提高編碼質量和效率,高質量代碼利於後期的維護和更新,畢竟不能一份代碼到永遠。nginx

因爲是記錄形式,固然不能把整篇內容都寫下來,只記錄關鍵性的內容,長期更新。算法

正文

Objective-C使用了消息機制代替調用方法。數據庫

區別:使用消息結構的語言,其運行時縮影執行的代碼由運行環境來決定。而使用函數調用的語言,則又編譯器決定。編程

頭文件中少引用其餘文件

在頭文件中使用@Class 代替直接引用其餘頭文件後端

多使用字面量語法

    NSNumber *intNumber = @1;    NSNumber *floatNumber = @2.5f;    NSNumber *doubleNumber = @3.1415926;    NSNumber *boolNumber = @YES;    NSNumber *charNumber = @'a';    
    int a = 3;    float b = 2.1;    NSNumber *c = @(a*b);    
    NSArray *animals = @[@"cat",@"dog",@"monkey"];    
    NSString *dog = animals[1];    NSDictionary *dataDict = @{ @"firstName" : @"aa",                                @"lastName" : @"bb",                                @"age" : @20 };    
    NSString *lastName = dataDict[@"lastName"];    
    NSMutableArray *mutableArray = animals.mutableCopy;

多用類型常量,少用#define預處理

若是隻在本類使用的常量,使用static const關鍵字來定義常量。緩存

若是多個類都需使用到某一常量,則需將常量定義成公開的,具體方式是在類的聲明文件中使用extern const關鍵字聲明常量,在類的實現文件中使用const關鍵字定義常量,這樣任何類只要導入了聲明常量的頭文件就能夠直接使用定義好的常量了。安全

.h文件中聲明bash

extern NSString *const XFExternalConst;

.m文件中描述服務器

NSString *const XFExternalConst = @"ko";

爲避免衝突,通常都用類名作前綴。網絡

用枚舉表示狀態、選項、狀態碼

枚舉只是一種常量命名方式,某個對象所經歷的各類狀態能夠定義爲一個枚舉集。

編譯器會爲枚舉分配一個獨有的編號,從0開始每一個遞增長1.實現枚舉所用的數據類型取決於編譯器,不過其二進制位的個數必須能徹底表示枚舉編號才行。

enum ConnectionState {
    ConnectionStateDisconnected,
    ConnectionStateConnecting,
    ConnectionStateConnected
};typedef enum ConnectionState ConnectingState;

還能夠不使用編譯器所分配的編號,手工指定某個枚舉成員所對應的值。

還有一種狀況應該使用枚舉類型,那就是定義選項的時候。若這些選項能夠彼此組合,則更應該如此。只要枚舉定義的對,各選項之間就能夠經過「按位或操做符」來組合。

凡是須要以按位或操做來組合的枚舉都應該用NS_OPTIONS宏,若是沒有組合需求,就用NS_ENUM宏。

typedef NS_ENUM(NSInteger, UIViewAnimationTransition) {
    UIViewAnimationTransitionNone,
    UIViewAnimationTransitionFlipFromLeft,
    UIViewAnimationTransitionFlipFromRight,
    UIViewAnimationTransitionCurlUp,
    UIViewAnimationTransitionCurlDown,
};typedef NS_OPTIONS(NSUInteger, UIViewAutoresizing) {
    UIViewAutoresizingNone                 = 0,
    UIViewAutoresizingFlexibleLeftMargin   = 1 << 0,
    UIViewAutoresizingFlexibleWidth        = 1 << 1,
    UIViewAutoresizingFlexibleRightMargin  = 1 << 2,
    UIViewAutoresizingFlexibleTopMargin    = 1 << 3,
    UIViewAutoresizingFlexibleHeight       = 1 << 4,
    UIViewAutoresizingFlexibleBottomMargin = 1 << 5};

枚舉在switch語句裏面的時候,不須要加default分支。

屬性的概念

基本方法就不描述了。

@dynamic 關鍵字,表示不要自動建立實現屬性全部的實例變量,也不要爲其建立存取方法。即便編譯器沒有發現定義存取方法,也不會報錯,它相信這些方法能在運行期找到。

屬性的四種特質

  • 原子性

默認狀況下,編譯器合成的方法會鎖定機制保持atomic。若是使用nonatomic,則不使用同步鎖。

  • 讀寫權限

readwrite的屬性具備gettersetter方法

readonly的屬性僅具備getter方法

  • 內存管理語義

assign只針對「純量類型」,好比CGFloat或者NSInteger

strong表示該屬性定義了一種擁有關係。爲這種屬性設置新值時,設置方法會先保留新值,並釋放舊值,而後將新值設置上去

weak表示該屬性定義另外一種非擁有關係。爲這種屬性設置新值時,設置方法既不保留新值,也不釋放舊值。和assign相似,然而在屬性所指的對象遭到摧毀時,屬性值也會清空

unsafe_unretained這個和assign相同,可是它適用於對象類型,該特質表達一種非擁有關係,當目標對象遭到摧毀時,屬性值不會自動清空,是不安全的

copy表達的所屬關係和strong類型。而後設置方法並不保留新值,而是將其拷貝。當屬性類型爲NSString *時,常常用此特質來保護其封裝性,由於傳遞給設置方法的新值可能指向一個NSMutableString類的實例。若是不是拷貝的花,那麼設置完屬性之後,字符串的值可能會在對象不知情的狀況下遭人更改。因此這個時候須要拷貝一份不可變的字符串。

  • 方法名

getter=<name> 指定getter的方法名。若是屬性是Boolean型,在方法名加上is前綴,就能夠用這個方法來指定。

setter=<name> 指定setter的方法名。這個不常見。

在對象內部儘可能直接訪問實例變量

懶加載是重寫getter方法

理解對象等同性的概念

按照==操做符比較出來的結果未必是咱們想要的,由於該操做符比較出來的是兩個指針自己,而不是指針所指的對象。應該是用NSObject協議中聲明的isEqual方法來判斷兩個對象的等同性。來辦來講兩個類型不一樣的對象老是不相等的。

NSString *oneStr = @"aaa 21";NSString *twoStr = [NSString stringWithFormat:@"aaa %d",21];BOOL equalA = (oneStr == twoStr);//NOBOOL equalB = [oneStr isEqual:twoStr];//YESBOOL equalC = [oneStr isEqualToString:twoStr];//YES

兩個用於判斷等同性的關鍵方法

  • (BOOL)isEqual:(id)object;
    @property (readonly) NSUInteger hash;

默認實現是:當且僅當其指針值徹底相等時,這兩個對象才相等。

幾個要點

  • 若想監測對象的等同性,提供isEqual:與 hash 方法

  • 相同的對象必須具備相同的哈希碼,可是兩個哈希碼相同的對象卻未必相同

  • 不要盲目逐個檢測每條屬性,而是應該依照具體需求來制定監測方案

  • 編寫 hash 方法時,應該是用計算速度快並且碰撞低的算法

以「類族模式」隱藏實現細節

核心套路就是相似UIButton,建立的時候傳入一個枚舉值,根據枚舉值來建立子類。(這裏的筆記是我看懂之後寫的,不知道的朋友先搜索一下工廠模式,其實就是那個意思)

在既有類中使用關聯對象存放自定義數據

有時候須要在對象中存放相關信息,這時候咱們一般會從對象所屬的類中繼承一個子類,而後改用這個子類對象。然而並不是全部狀況下都能這麼作,有時候類的實例可能由某種機制建立。Objective-C有一種強大機制叫關聯對象

這種機制要當心使用,由於會使代碼失控。

理解objc_msgSend的做用

原型

void objc_msgSend(id self, SEL cmd, ...)

一個例子

id returnValue = [someObject messageName:parameter];

編譯器會把它轉換爲如下函數

id returnValue = objc_msgSend(someObject,@selector(messageName:),parameter);

爲了完成調用方法,該方法須要在接受者所屬的類中搜尋其方法列表,若是能找到,就跳轉過去。若是找不到,就沿着繼承體系向上繼續查找,等找到合適的再跳轉。若是最終仍是找不到,就執行消息轉發的操做。

一些邊界狀況,則交由另外一些函數處理

  • objc_msgSend_stret 若是待發送的消息要返回結構體,可交此函數處理。

  • objc_msgSend_fpret 若是消息返回的是浮點數,可交由此函數處理。

  • ojbc_msgSendSuper 若是要給超類發送消息,例如[super message:parameter],那麼就就交由此函數處理。

理解消息轉發機制

消息轉發分爲兩大階段。

第一階段先問接受者,所屬的類,看其是否能動態添加方法,以處理當前這個unknown selector,這稱爲dynamic method resolution

第二階段涉及full forwarding mechanism。若是運行期系統已經把第一階段執行完了,那麼接受者本身就沒法再以動態新增方法的手段來響應包含該selector的消息了。此時,運行期系統會請求接受者以其餘手段來處理與消息相關的方法調用。而後又分兩部。

首先,讓接受者看看有沒有其餘對象能處理這條消息。若是有,就轉發給那個對象。

若是沒有,就會啓動完整的消息轉發機制,運行期系統會把消息有關的所有細節封裝到NSInvocation對象中,再給接受者最後一次機會,讓它設法解決當前還未處理的這條消息。

動態方法解析

對象收到沒法解讀的消息後,先調用

+ (BOOL)resolveInstanceMethod:(SEL)sel

該方法的參數就是那個未知的selector,返回Boolean類型,表示這個類是否能新增一個實例方法用來處理這個selector。在繼續走下去以前,這有個機會新增一個處理的方法。

若是還沒有實現的不是實例方法而是類方法,則調用

+ (BOOL)resolveClassMethod:(SEL)sel

使用他們的前提是,相關方法的實現代碼已經寫好,只等着運行的時候動態插入在類裏面。

這個經常用來實現@dynamic 屬性。

後備接收者

當前接收者還有第二次機會處理,能不能把消息轉發給其餘接收者

- (id)forwardingTargetForSelector:(SEL)aSelector

找獲得就返回對象,找不到就返回nil

完整的消息轉發

- (void)forwardInvocation:(NSInvocation *)anInvocation

先建立NSInvocation對象,把還沒有處理的那條消息有關的所有細節都封在其中。此對象包含selectortarget以及參數。

繼承體系中的每一個類都有機會處理此調用請求,直到NSObject。若是尚未找到,那麼該方法還會繼續調用doesNotRecognizeSelector:拋出異常,此異常表示最終未能處理。

這個機制屬於底層機制,能夠動態注入方法,甚至以前的能夠動態注入屬性,雲後端服務商能夠說基本就靠這個套路,經過KVC的樣子往類裏面添加屬性。

用方法調配技術調試黑盒方法

黑科技。

IMP指針,改方法實現,替換系統方法,能夠多添加日誌打印。

類對象

OC 是一門極其動態的語言。

每一個 OC 對象實例都是指向某塊內存數據的指針。

typedef struct objc_object {
    Class isa;
} *id;

每一個對象結構體的首個成員是Class類的變量。該變量定義了對象所屬的類,一般稱爲is a指針。

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;
};

此結構體存放類的元數據,例如類的實例實現了幾個方法,具有多少個實例變量等信息。
首個變量是isa指針,說明Class自己也是 OC 對象。

super_class定義了本類的超類。類對象所屬的類型是另外一個類,叫作超類

每一個類僅有一個類對象,而每一個類對象僅有一個與之相關的元類

class方法所返回的類表示發起代理的對象,而非接受代理的對象。

用前綴避免命名空間衝突

開發者可能會忽視另一個容易引起命名衝突的地方,那就是類的實現文件中所用的純 C 函數及全局變量。

提供全能初始化方法

全部對象均要初始化。

提供一個全能初始化方法,其餘的幾種初始化方法調用它。

若是全能初始化方法與超類不一樣,則需覆寫超類中的對應方法。

實現description方法

重寫- (NSString *)description

控制檯- (NSString *)debugDescription

儘可能使用不可變對象

儘可能把對外公佈出來的屬性設爲只讀,只在必要時候對外公佈。

有時候想修改封裝在對象內部的數據,可是卻不想讓外人所改動。這種狀況須要將readonly.m文件中從新生成readwrite。可是爲了不產生意外,須要在必要時經過dispatch queue來實現。

不要把可變的內容做爲屬性公開,而是提供相關方法,以此修改對象中的可變內容。

使用清洗而協調的命名方式

駝峯命名法

方法與變量以小寫字母開頭

類名以大寫字母開頭

不要使用str這種簡稱,而用string這樣的全稱

Boolean屬性應該加is前綴,若是某方法返回非屬性的Boolean值,應該根據功能選用has或者is當前綴

類與協議的命名

爲類與協議的名稱加上前綴,以免命名空間的衝突

委託通常使用委託的發起方名稱後面跟一個Delegate

爲私有方法名加前綴

通常可使用p_做爲前綴,表示私有方法

不要用一個單獨的下劃線做爲私有方法的前綴

理解Objective-C錯誤模型

異常NSException應該用於極其嚴重的錯誤,好比編寫了某個抽象基類,它的正確用法是先從重繼承一個子類,而後再使用這個子類。在這種狀況下,若是有人直接使用了這個抽象基類,那麼能夠考慮拋出異常。

NSError的用法很靈活,封裝了三條信息

  • Error domain 錯誤範圍,類型爲字符串
    錯誤發生的範圍,一般用一個特有的全局變量來定義。

  • Error code 錯誤碼,類型爲證書
    獨有的錯誤代碼。這種錯誤一般採用enum來定義,好比 HTTP 請求返回的狀態碼。

  • User info 用戶信息,類型爲字典
    有關此錯誤的額外信息,其中或許包含一段本地化描述,或許還包含致使該錯誤發生的另一個錯誤,經由此種信息,可將相關錯誤傳承一條chain of errors

理解NSCopying協議

使用對象時常常須要拷貝它。若是想令本身的類支持拷貝操做

- (id)copyWithZone:(NSZone *)zone;

爲何會出現NSZone,之前開發的時候,會把內存分紅不一樣的zone,而對象會建立在某個區裏面。如今不用了,每一個程序只有一個default zone

另一個NSMutableCopying協議,返回可變的副本

- (id)mutableCopyWithZone:(NSZone *)zone;

深拷貝
在拷貝對象自身時,將底層數據也一併複製過去

淺拷貝
Foundation框架中全部的容器類默認狀況下執行淺拷貝,只拷貝對象自己,不復制數據
由於不是全部對象都能拷貝,並且調用者也未必須要都一一拷貝。

經過委託與數據源協議進行對象間通訊

委託屬性要定義成weak,由於二者之間必須爲非擁有關係

- (BOOL)respondsToSelector:(SEL)aSelector;

也能夠用協議定義一套接口,令某類從該接口獲取所需的數據。委託模式的這種用法是向類提供數據,因此成爲dataSource。在這種模式中,信息從數據源流向類。而在常規的代理模式中,信息則從類流向受委託者。

將類的實現代碼分散到便於管理的數個分類之中

把一個類中的幾個不一樣模塊方法寫到別的文件中,合理使用category

不要在分類中聲明屬性

除了extension外,其餘的分類都沒法向類中新增實例變量

聲明爲@dynamic ,而後動態添加

使用extension隱藏實現細節

經過協議提供匿名對象

使用匿名對象來隱藏類型名稱

理解引用計數

retain 增計數
release 減計數
autorelease 待稍後清理autorelease pool時,再減小計數

對象建立出來時,其保留計數至少爲1

自動釋放池

循環引用

以ARC簡化引用計數

若方法名如下列詞語開頭,則返回的對象歸調用者全部

  • alloc

  • new

  • copy

  • mutableCopy

在應用程序中,可用下列修飾符來改變局部變量與實例變量的語義

__strong 默認語義,保留這個值

__unsafe_unretained 不保留這個值,這麼作可能不安全,由於等到再次使用變量時,其對象可能已經回收了

__weak 不保留這個值,可是變量能夠安全使用,由於若是系統把這個對象回收了,那麼變量也會自動清空

__autoreleasing 把對象按引用傳遞給方法時,使用這個特殊的修飾符,此值在方法返回時自動釋放

好比,想令實例變量的語義與不使用 ARC 時相同,可使用__weak__unsafe_unretained修飾符

block 塊會自動保留其所捕獲的所有對象,而若是這其中有某個對象又保留了塊自己,那麼就可能致使循環引用,能夠用__weak局部變量來打破這種循環引用

注意:CoreFoundation對象不歸 ARC 管理,開發者必須適時調用CFRetain/CFRelease

dealloc方法中只釋放引用並解除監聽

把原來配置過的觀測行爲都清除掉,若是使用NSNotificationCenter給此對象註冊過某種通知,那麼通常應該在這裏註銷

使用弱引用來避免循環引用

理解Block

若是block所捕獲的變量是對象類型,那麼就會自動保留它。系統在釋放這個塊的時候,也會將其一併釋放。這引出一個重要問題。block塊自己可視爲對象,也有引用計數。

若是將block塊定義在實例方法中,那麼除了能夠訪問類的全部實例變量以外,還可使用 self
變量,塊總能修改實例變量,因此在聲明時無需加__block。不過,若是經過讀取或者寫入操做捕獲了實例比那兩,那麼也會自動把self變量一併捕獲了,由於實例變量是與self所指代的實例關聯在一塊兒的。

全局塊

定義塊的時候,佔的內存區域是分配在中的

給塊發送copy消息拷貝,這樣就能夠把塊從棧複製到堆了。

全局塊不會捕捉任何狀態,運行時也無須有狀態來參與。

爲經常使用的塊類型建立typedef

handler塊下降代碼分散程度

使用block塊引用所屬對象不要出現引用循環

多用派發隊列,少用同步鎖

@synchronized(self)根據給定的對象,自動建立一個鎖,並等待塊中的代碼執行完畢

NSLock

不過最好使用 GCD,它能更簡單,更搞笑的形式爲代碼加鎖

使用串行同步隊列,將讀取操做以及寫入操做都安排在同一個隊列裏,可保證數據同步

_syncQueue = dispatch_queue_create("com.xx", NULL);dispatch_sync(_syncQueue, ^{
    _someString = someString;
});

把設置和獲取操做都安排在序列化的隊列裏執行,這樣的花全部針對屬性的訪問操做都同步了

dispatch_barrier_async(dispatch_queue_t queue,disaptch_block_t block);barrier中必須單獨執行,不能與其餘塊並行

多使用 GCD,少用performSelector系列方法

延後執行某個任務

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    [self doSomething];
});

想把任務放在主線程上執行

dispatch_async(dispatch_get_main_queue(), ^{
    [self doSomething];
});

掌握 GCD 及操做隊列的使用時機

在執行後臺任務時,GCD 並不必定是最佳方式,還有一種技術叫作NSOperationQueue

好比,從服務器端下載並處理文件的動做,能夠用操做來表示,而在處理其餘文件以前,必須先下載清單文件,後續的下載操做,都要依賴於先下載清單文件這一操做。若是操做隊列容許併發的話,那麼後續的多個下載操做就能夠同時執行,但前提是它們所依賴的那個清單文件下載操做已經執行完畢

NSOperation對象有許多屬性都適合 KVO 來監聽,isCancelled來判斷任務是否取消,或者經過isFinished來判斷任務是否完成。

NSOperation對象也有線程優先級

經過dispatch group機制,根據系統資源情況來執行任務

使用dispatch_once來執行只需一次的線程安全代碼

單例使用

不要使用dispatch_get_current_queue

熟悉系統框架

CFNetwork,此框架提供了 C 語言級別的網絡通訊能力,它將 BSD 抽象成易於使用的網絡接口。而 Foundation 則將該框架李的部份內容封裝爲 OC 的接口

CoreAudio,此框架提供的 C 語言 API 能夠用來操做設備商的音頻硬件

AVFoundation,用來回訪並錄製音頻及視頻

CoreData,將對象放入數據庫

CoreText,能夠高效執行文字排版及渲染操做

多用塊枚舉,少用 for 循環

構建緩存時選用NSCache而非NSDictionary

精簡initializeload的實現代碼

+ (void)load,只調用一次。

+ (void)initialize,該方法在程序首次使用該類以前調用,且只調用一次

NSTimer會保留其目標對象

NSTimer很容易出現引用循環

(未完待續)

總結

純屬我的筆記,特別是底層機制頗有做用,現在iOS開發再也不僅僅是把一個內容展示出來,裏面還有涉及到各類安全性能,瞭解根本纔是持續發展之道。

相關文章
相關標籤/搜索