iOS編寫高質量Objective-C程式碼(二)
《編寫高質量OC程式碼》已經順利完成一二三四五篇!
附上鍊接:
iOS 編寫高質量Objective-C程式碼(一)
iOS 編寫高質量Objective-C程式碼(二)
iOS 編寫高質量Objective-C程式碼(三)
iOS 編寫高質量Objective-C程式碼(四)
iOS 編寫高質量Objective-C程式碼(五)
這篇將從物件導向的角度分析如何提高OC的程式碼質量。
一、理解“ 屬性 ”這一概念
屬性(@property
)是OC的一項特性。@property
:編譯器會自動生成例項變數
和getter
和setter
方法。
For Example:
@property (nonatomic, strong) UIView *qiShareView;
等價於:
@synthesize qiShareView = _qiShareView;
- (UIView *)qiShareView;
- (void)setQiShareView:(UIView *)qiShareView;
如果不希望生成存取方法和例項變數,那就要使用@dynamic關鍵字
@dynamic qiShareView;
屬性特質有四類:
-
原子性:預設為
atomic
-
nonatomic
:非原子性,讀寫時不加同步鎖 -
atomic
:原子性,讀寫時加同步鎖
-
-
讀寫許可權:預設為
readwrite
-
readwrite
:讀寫,擁有getter
和setter
方法 -
readonly
:只讀,僅擁有getter
方法
-
-
記憶體管理:
-
assign
:對“純量型別”做簡單賦值操作(NSInteger
、CGFloat
等)。 -
strong
:強擁有關係,設定方法 保留新值,並釋放舊值。 -
weak
:弱擁有關係,設定方法 不保留新值,不釋放舊值。當指標指向的物件銷燬時,指標置nil
。 -
copy
:拷貝擁有關係,設定方法不保留新值,將其拷貝。 -
unsafe_unretained
:非擁有關係,目標物件被釋放,指標不置nil
,這一點和assign
一樣。區別於weak
-
-
方法名:
-
getter=<name>
:指定get方法的方法名,常用 -
setter=<name>
:指定set方法的方法名,不常用
例如:
-
@property (nonatomic, getter=isOn) BOOL on;
在iOS開發中,99.99..%的屬性都會宣告為nonatomic。
一是atomic
會嚴重影響效能,
二是atomic
只能保證讀/寫操作的過程是可靠的,並不能保證執行緒安全。
關於第二點可以參考我的部落格:iOS 為什麼屬性宣告為atomic依然不能保證執行緒安全?
二、在物件內部儘量直接訪問例項變數
-
例項變數( _屬性名 )訪問物件的場景:
- 在
init
和dealloc
方法中,總是應該通過訪問例項變數讀寫資料 - 沒有重寫
getter
和setter
方法、也沒有使用KVO
監聽 - 好處:不走OC的方法派發機制,直接訪問記憶體讀寫,速度快,效率高。
- 在
For Example:
- (instancetype)initWithDic:(NSDictionary *)dic {
self = [super init];
if (self) {
_qi = dic[@"qi"];
_share = dic[@"share"];
}
return self;
}
-
用存取方法訪問物件的場景:
- 重寫了
getter/setter
方法(比如:懶載入) - 使用了
KVO
監聽值的改變
- 重寫了
For Example:
- (UIView *)qiShareView {
if (!_qiShareView) {
_qiShareView = [UIView new];
}
return _qiShareView;
}
三、理解“物件等同性”
思考下面輸出什麼?
NSString *aString = @"iPhone 8";
NSString *bString = [NSString stringWithFormat:@"iPhone %i", 8];
NSLog(@"%d", [aString isEqual:bString]);
NSLog(@"%d", [aString isEqualToString:bString]);
NSLog(@"%d", aString == bString);
答案是110
==操作符只是比較了兩個指標所指物件的地址是否相同,而不是指標所指的物件的值
所以最後一個為0
四、以類族模式隱藏實現細節
為什麼下面這個例子的if永遠為false?
id maybeAnArray = @[];
if ([maybeAnArray class] == [NSArray class]) {
//Code will never be executed
}
因為[maybeAnArray class]
的返回永遠不會是NSArray
,NSArray
是一個類族,返回的值一直都是NSArray的實體子類
。大部分collection類都是某個類族中的抽象基類
所以上面的if想要有機會執行的話要改成
id maybeAnArray = @[];
if ([maybeAnArray isKindOfClass [NSArray class]) {
// Code probably be executed
}
這樣判斷的意思是,maybeAnArray
這個物件是否是NSArray
類族中的一員
使用類族的好處:可以把實現細節隱藏再一套簡單的公共介面後面
五、在既有類中使用關聯物件存放自定義資料
先引入runtime類庫
#import <objc/runtime.h>
objc_AssociationPolicy(物件關聯策略型別):
objc_AssociationPolicy(關聯策略型別) | 等效的@property屬性 |
---|---|
OBJC_ASSOCIATION_ASSIGN | 等效於 assign |
OBJC_ASSOCIATION_RETAIN_NONATOMIC | 等效於 nonatomic, retain |
OBJC_ASSOCIATION_COPY_NONATOMIC | 等效於 nonatomic, copy |
OBJC_ASSOCIATION_RETAIN | 等效於 retain |
OBJC_ASSOCIATION_COPY | 等效於 copy |
三個方法管理關聯物件:
- objc_setAssociatedObject(設定關聯物件)
/**
* Sets an associated value for a given object using a given key and association policy.
*
* @param object The source object for the association.
* @param key The key for the association.
* @param value The value to associate with the key key for object. Pass nil to clear an existing association.
* @param policy The policy for the association. For possible values, see “Associative Object Behaviors.”
*/
OBJC_EXPORT void
objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key,
id _Nullable value, objc_AssociationPolicy policy)
- objc_getAssociatedObject(獲得關聯物件)
/**
* Returns the value associated with a given object for a given key.
*
* @param object The source object for the association.
* @param key The key for the association.
*
* @return The value associated with the key e key for e object.
*/
OBJC_EXPORT id _Nullable
objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key)
- objc_removeAssociatedObjects(去除關聯物件)
/**
* Removes all associations for a given object.
*
* @param object An object that maintains associated objects.
*
* @note The main purpose of this function is to make it easy to return an object
* to a "pristine state”. You should not use this function for general removal of
* associations from objects, since it also removes associations that other clients
* may have added to the object. Typically you should use c objc_setAssociatedObject
* with a nil value to clear an association.
*
*/
OBJC_EXPORT void
objc_removeAssociatedObjects(id _Nonnull object)
小結:
- 可以通過“關聯物件”機制可以把兩個物件聯絡起來
- 定義關聯物件可以指定記憶體管理策略
- 應用場景:只有在其他做法(代理、通知等)不可行時,才可以選擇使用關聯物件。這種做法難於找bug
六、理解objc_msgSend(物件的訊息傳遞機制)
首先我們要區分兩個基本概念:
1 .靜態繫結(static binding):在編譯期
就能決定執行時所應呼叫的函式。~代表語言:C、C++等~
2 .動態繫結 (dynamic binding):所要呼叫的函式直到執行期
才能確定。~代表語言:OC、swift等~
OC是一門強大的動態語言,它的動態性體現在它強大的runtime機制
上。
解釋:在OC中,如果向某物件傳遞訊息,那就會使用動態繫結機制來決定需要呼叫的方法。在底層,所有方法都是普通的C語言函式,然而物件收到訊息後,由執行期決定究竟呼叫哪個方法,甚至可以在程式執行時改變,這些特性使得OC成為一門強大的動態語言
。
底層實現:基於C語言函式實現。
- 實現的基本函式是
objc_msgSend
,定義如下:
void objc_msgSend(id self, SEL cmd, ...)
這是一個引數個數可變的函式,第一引數代表接受者,第二個引數代表選擇子(OC函式名),之後的引數就是訊息中傳入的引數。
- 舉例:git提交
id return = [git commit:parameter];
上面的方法會在執行時轉換成如下的OC函式:
id return = objc_msgSend(git, @selector(commit), parameter);
objc_msgSend
函式會在接收者所屬的類中搜尋其方法列表,如果能找到這個跟選擇子名稱相同的方法,就跳轉到其實現程式碼,往下執行。若是當前類沒找到,那就沿著繼承體系繼續向上查詢,等找到合適方法之後再跳轉 ,如果最終還是找不到,那就進入訊息轉發(下一條具體展開)的流程去進行處理了。
可是如果每次傳遞訊息都要把類中的方法遍歷一遍,這麼多訊息傳遞加起來肯定會很耗效能。所以以下講解OC訊息傳遞的優化方法。
OC對訊息傳遞的優化:
-
快速對映表(快取)優化:
objc_msgSend
在搜尋這塊是有做快取的,每個OC的類都有一塊這樣的快取,objc_msgSend
會將匹配結果快取在快速對映表(fast map)
中,這樣以來這個類一些頻繁呼叫的方法會出現在fast map
中,不用再去一遍一遍的在方法列表中搜尋了。 -
尾呼叫優化:
原理:這裡專門總結了一篇部落格。 連結:看這裡看這裡~~
好處:最大限度的合理的分配使用的資源,避免過早發生棧溢位的現象。
七、理解訊息轉發機制
首先區分兩個基本概念:
1 .訊息傳遞:物件正常解讀訊息,傳遞過去(見上一條)。
2 .訊息轉發:物件無法解讀訊息,之後進行訊息轉發。
訊息轉發完整流程圖:
流程解釋:
- 第一步:呼叫
resolveInstanceMethod
:徵詢接受者(所屬的類)是否可以新增方法以處理未知的選擇子?~(此過程稱為動態方法解析)~若有,轉發結束。若沒有,走第二步。 - 第二步:呼叫
forwardingTargetForSelector
:詢問接受者是否有其他物件能處理此訊息。若有,轉發結束,一切如常。若沒有,走第三步。 - 第三步:呼叫
forwardInvocation
:執行期系統將訊息封裝到NSInvocation物件中,再給接受者一次機會。 - 最後:以上三步還不行,就丟擲異常:
unrecognized selector sent to instance xxxx
八、用“方法調配技術”除錯“黑盒方法”
方法調配(Method Swizzling):使用另一種方法實現來替換原有的方法實現。~(實際應用中,常用此技術向原有實現中新增新的功能。)~
裡的兩個常用的方法:
- 獲取給定類的指定例項方法:
/**
* Returns a specified instance method for a given class.
*
* @param cls The class you want to inspect.
* @param name The selector of the method you want to retrieve.
*
* @return The method that corresponds to the implementation of the selector specified by
* e name for the class specified by e cls, or c NULL if the specified class or its
* superclasses do not contain an instance method with the specified selector.
*
* @note This function searches superclasses for implementations, whereas c class_copyMethodList does not.
*/
OBJC_EXPORT Method _Nullable
class_getInstanceMethod(Class _Nullable cls, SEL _Nonnull name)
- 交換兩種方法實現的方法:
/**
* Exchanges the implementations of two methods.
*
* @param m1 Method to exchange with second method.
* @param m2 Method to exchange with first method.
*
* @note This is an atomic version of the following:
* code
* IMP imp1 = method_getImplementation(m1);
* IMP imp2 = method_getImplementation(m2);
* method_setImplementation(m1, imp2);
* method_setImplementation(m2, imp1);
* endcode
*/
OBJC_EXPORT void
method_exchangeImplementations(Method _Nonnull m1, Method _Nonnull m2)
利用這兩個方法就可以交換指定類中的指定方法。在實際應用中,我們會通過這種方式為既有方法新增新功能。
For Example:交換method1與method2的方法實現
Method method1 = class_getInstanceMethod(self, @selector(method1:));
Method method2 = class_getInstanceMethod(self, @selector(method2:));
method_exchangeImplementations(method1, method2);
九、理解“類物件”的用意
Objective-C類是由Class型別來表示的,實質是一個指向objc_class結構體的指標。它的定義如下:
typedef struct objc_class *Class;
在中能看到他的實現:
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY; //!< 指向metaClass(元類)的指標
#if !__OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE; //!< 父類
const char * _Nonnull name OBJC2_UNAVAILABLE; //!< 類名
long version OBJC2_UNAVAILABLE; //!< 類的版本資訊,預設為0
long info OBJC2_UNAVAILABLE; //!< 類資訊,供執行期使用的一些位標識
long instance_size OBJC2_UNAVAILABLE; //!< 該類的例項變數大小
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE; //!< 該類的成員變數連結串列
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE; //!< 方法定義的連結串列
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE; //!< 方法快取表
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE; //!< 協議連結串列
#endif
} OBJC2_UNAVAILABLE;
/* Use `Class` instead of `struct objc_class *` */
此結構體存放的是類的“後設資料”(metadata),例如類的例項實現了幾個方法,父類是誰,具備多少例項變數等資訊。
這裡的isa指標指向的是另外一個類叫做元類(metaClass)。那什麼是元類呢?元類是類物件的類。也可以換一種容易理解的說法:
- 當你給物件傳送訊息時,
runtime
處理時是在這個物件的類的方法列表中尋找 - 當你給類發訊息時,
runtime
處理時是在這個類的元類的方法列表中尋找
我們來看一個很經典的圖來加深理解:
可以總結如下:
- 每一個
Class
都有一個isa指標
指向一個唯一的Meta Class(元類)
- 每一個
Meta Class
的isa指標
都指向最上層的Meta Class
,這個Meta Class
是NSObject
的Meta Class
。(包括NSObject的Meta Class
的isa指標
也是指向的NSObject
的Meta Class
) - 每一個
Meta Class
的super class
指標指向它原本Class
的Super Class
的Meta Class
(這裡最上層的NSObject
的Meta Class
的super class
指標還是指向自己) - 最上層的
NSObject Class
的super class
指向nil
最後,特別緻謝《Effective Objective-C 2.0》第二章
相關文章
- iOS 編寫高質量Objective-C程式碼(二)iOSObjectC程式
- iOS 編寫高質量Objective-C程式碼iOSObjectC程式
- iOS 編寫高質量Objective-C程式碼(四)iOSObjectC程式
- iOS 編寫高質量Objective-C程式碼(一)iOSObjectC程式
- iOS 編寫高質量Objective-C程式碼(三)iOSObjectC程式
- iOS 編寫高質量Objective-C程式碼(八)iOSObjectC程式
- iOS編寫高質量Objective-C程式碼(四)iOSObjectC程式
- iOS 編寫高質量Objective-C程式碼(五)iOSObjectC程式
- iOS 編寫高質量Objective-C程式碼(六)iOSObjectC程式
- iOS 編寫高質量Objective-C程式碼(七)iOSObjectC程式
- iOS編寫高質量Objective-C程式碼(六)iOSObjectC程式
- iOS 編寫高質量Objective-C程式碼(一)—— 簡介iOSObjectC程式
- iOS 有效編寫高質量Objective-C方法(一)iOSObject
- iOS-有效編寫高質量Objective-C方法-三iOSObject
- 編寫高質量iOS與OS X程式碼的52個有效方法(二)iOS
- [轉]高質量JAVA程式碼編寫規範Java
- 怎樣編寫高質量的java程式碼Java
- 編寫高質量的程式碼,從命名入手
- [編寫高質量iOS程式碼的52個有效方法](九)塊(block)iOSBloC
- 編寫高質量程式碼
- 如何編寫高質量的C#程式碼(一)C#
- 編寫高質量iOS與OS X程式碼的52個有效方法(一)iOS
- 編寫高質量iOS與OS X程式碼的52個有效方法(五)iOS
- 編寫高質量iOS有效方法總結(一)iOS
- 如何編寫高質量和可維護的程式碼
- [編寫高質量iOS程式碼的52個有效方法](七)記憶體管理(上)iOS記憶體
- [編寫高質量iOS程式碼的52個有效方法](八)記憶體管理(下)iOS記憶體
- [編寫高質量iOS程式碼的52個有效方法](十)Grand Central Dispatch(GCD)iOSGC
- 我們應該如何編寫高質量的前端程式碼前端
- 藉助 SublimeLinter 編寫高質量的 JS & CSS 程式碼JSCSS
- 藉助SublimeLinter編寫高質量的JavaScript & CSS程式碼JavaScriptCSS
- 🐒編寫高質量程式碼(手撕程式碼)
- 編寫高質量程式碼的思考
- 《Effective JavaScript 編寫高質量JavaScript程式碼的68個有效方法》JavaScript
- 如何書寫高質量的jQuery程式碼jQuery
- 編寫靈活、穩定、高質量的HTML程式碼的規範HTML
- 編寫靈活、穩定、高質量的CSS程式碼的規範CSS
- iOS-《編寫高質量程式碼》筆記-第一章iOS筆記