歡迎光臨
每天分享高質量文章

iOS編寫高質量Objective-C程式碼(二)

作者:QiShare

連結:https://www.jianshu.com/p/373987700962

上一篇:iOS 編寫高質量Objective-C程式碼(一)

這篇將從面向物件的角度分析如何提高OC的程式碼質量。

一、理解“ 屬性 ”這一概念


屬性(@property)是OC的一項特性。
@property:編譯器會自動生成實體變數和getter和setter方法。
下文中,getter和setter方法合稱為存取方法
For Example:

@property (nonatomicstrongUIView *qiShareView;

等價於:


@synthesize qiShareView = _qiShareView;
- (UIView *)qiShareView;
- (void)setQiShareView:(UIView *)qiShareView;

如果不希望自動生成存取方法和實體變數,那就要使用@dynamic關鍵字


@dynamic qiShareView;

屬性特質有四類:

1、原子性:預設為atomic

  • nonatomic:非原子性,讀寫時不加同步鎖

  • atomic:原子性,讀寫時加同步鎖

2、讀寫許可權:預設為readwrite

  • readwrite:擁有getter和setter方法

  • readonly:僅擁有getter方法

3、記憶體管理:

  • assign:對“純量型別”做簡單賦值操作(NSInteger、CGFloat等)。

  • strong:強擁有關係,設定方法 保留新值,並釋放舊值。

  • weak:弱擁有關係,設定方法 不保留新值,不釋放舊值。當指標指向的物件銷毀時,指標置nil。

  • copy:複製擁有關係,設定方法不保留新值,將其複製。

  • unsafe_unretained:非擁有關係,標的物件被釋放,指標不置nil,這一點和assign一樣。區別於weak

4、方法名:

  • getter=指定get方法的方法名,常用

  • setter=指定set方法的方法名,不常用


例如:


@property (nonatomicgetter=isOn) BOOL on;

在iOS開發中,99.99..%的屬性都會宣告為nonatomic。
一是atomic會嚴重影響效能,
二是atomic只能保證讀/寫操作的過程是可靠的,並不能保證執行緒安全。
關於第二點可以參考我的部落格:iOS 為什麼屬性宣告為atomic依然不能保證執行緒安全?

二、在物件內部儘量直接訪問實體變數


1、實體變數( _屬性名 )訪問物件的場景:

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


2、用存取方法訪問物件的場景:

  • 重寫了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_AssociationPolicy(物件關聯策略型別):


三個方法管理關聯物件:


  • 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 objectconst 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 objectconst 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。

  • 但也有具體應用場景:比如說前幾篇說到 控制Button響應時間間隔 的demo中就遇到了:附上連結

六、理解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 中,不用再去一遍一遍的在方法串列中搜索了。

  • 尾呼叫最佳化
    這裡,我們Q·i Share團隊專門整理了一篇關於尾呼叫最佳化的文章。點這裡點這裡

七、理解訊息轉發機制


首先區分兩個基本概念:

  • 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;  //!

#if !__OBJC2__
    Class _Nullable super_class                              OBJC2_UNAVAILABLE;  //!
    const char * _Nonnull name                               OBJC2_UNAVAILABLE;  //!
    long version                                             OBJC2_UNAVAILABLE;  //!
    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)。那什麼是元類呢?元類是類物件的類。也可以換一種容易理解的說法:


1、當你給物件傳送訊息時,runtime處理時是在這個物件的類的方法串列中尋找

2、當你給類發訊息時,runtime處理時是在這個類的元類的方法串列中尋找

我們來看一個很經典的圖來加深理解:


可以總結如下:

1、每一個Class都有一個isa指標指向一個唯一的Meta Class(元類)

2、每一個Meta Class的isa指標都指向最上層的Meta Class,這個Meta Class是NSObject的Meta Class。(包括NSObject的Meta Class的isa指標也是指向的NSObject的Meta Class)

3、每一個Meta Class的super class指標指向它原本Class的 Super Class的Meta Class (這裡最上層的NSObject的Meta Class的super class指標還是指向自己)

4、最上層的NSObject Class的super class指向 nil

最後,特別緻謝《Effective Objective-C 2.0》第二章



編號291,輸入編號直達本文

●輸入m獲取文章目錄

推薦↓↓↓

Web開發

更多推薦18個技術類微信公眾號

涵蓋:程式人生、演演算法與資料結構、駭客技術與網路安全、大資料技術、前端開發、Java、Python、Web開發、安卓開發、iOS開發、C/C++、.NET、Linux、資料庫、運維等。

贊(0)

分享創造快樂