編寫高質量代碼的52個有效方法(二)—對象、消息、運行期

image

6.屬性

將屬性聲明為@dynamic,編譯器則不會為其自動生成實例變量及存取方法(setter、getter方法);

@implementation SomeClass

@dynamic productId,productName;

  1. 可以用@property語法來定義對象中所封裝的數據;
  2. 通過“修飾詞”來指定存儲數據所需的正確語義;
  3. 在設置屬性所對應的實例變量時,一定要遵從該屬性所聲明的語義;
  4. 開發iOS程序時應該使用nonatomic屬性,因為atomic(同步鎖)屬性嚴重影響性能。

7.在對象內部盡量直接訪問實例變量

在對象內部訪問實例變量時,是通過屬性(self.proper)來訪問還是通過_proper來訪問區別在于是否執行屬性的setter、getting方法。

要點:

  1. 在對象內部讀取數據時,應該直接通過實例變量來讀,而寫入數據時,則應通過屬性來寫;
  2. 在初始化方法及dealloc方法中,總是應該直接通過實例變量來讀寫數據。
  3. 有時會使用惰性初始化技術配置某份數據,這種情況下,需要通過屬性來讀取數據。

8.理解“對象等同性”這一概念

“等同性”(equality)在開發中時常作為邏輯判斷的依據。按照 “==”操作符比較,對于常規的數據類型比較是值,比如 9 == 9 ;對于對象的比較,使用 == 則比較的是兩個指針本身(可理解為內存地址)。對于系統框架中的對象相等 比較,我們可以使用NSObject協議中聲明的“isEqual:”方法來判斷兩個對象的等同性。

//NSObject協議中有兩個用于判斷等同性的關鍵方法
- (BOOL)isEqual:(id)object;
- (NSUInteger)hash;

NSObject類對這兩個方法的默認實現是:當且僅當其“指針值”完全相等時,這兩個對象才相等。

要點:

  1. 要想檢測對象的等同性,請提供“isEqual:”與hash方法;
  2. 相同的對象必須具有相同的哈希碼,但是兩個哈希碼相同的對象未必相同;
  3. 不要盲目地逐個檢測每條屬性,而是應該依照具體需求來定制檢測方案;
  4. 編寫hash方法時,應該使用計算速度快而且哈希碼碰撞幾率低的算法。

9.以“類族模式”隱藏實現細節

做過Java開發的同學應該知道,被abstract修飾的類是抽象類,而Objective-C中也有抽象類,比如CAAnaimation、NSOperation等,我們只是使用其子類,而不能直接使用抽象類。類族模式 就是定義一個基類,多個不同特性與功能的子類繼承自它,基類提供一個初始化類的方法以及相關功能方法,子類重寫父類的方法.

//確定一個對象是否是該類的實例,或者是該類子類的實例
- (BOOL)isKindOfClass:(Class)aClass;

//確定一個對象是否是當前類的實例.
- (BOOL)isMemberOfClass:(Class)aClass;

要點:

  1. 類族模式可以把實現細節隱藏在一套簡單的公共接口后面;
  2. 系統框架中經常使用到類族;
  3. 從類族的公共抽象基類中繼承子類時要當心,若有開發文檔,則應首先閱讀。

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

//此方法以給定的鍵和策略為某對象設置關聯對象值
void objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key,
                         id _Nullable value, objc_AssociationPolicy policy)
                         
//此方法根據給定的鍵從某對象中獲取相應的關聯對象值 
id _Nullable objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key)

//此方法移除指定對象的全部關聯對象
void objc_removeAssociatedObjects(id _Nonnull object)

要點:

  1. 可以通過“關聯對象”機制來把兩個對象連起來;
  2. 定義關聯對象時可指定內存管理語義,用以模仿定義屬性時采用的擁有關系與非擁有關系;
  3. 只有在其他做法不可行時才應選用關聯對象,因為這種做法通常會引入難于查找的bug。

11.理解objc_msgSend(消息發送)的作用

開發中時常會遇到 unrecognized selector sent to instance 0x87 ... 的問題,對分類的方法無法找到,我們知道配置一下 -ObjC就可以解決。該問題的本質原因就是方法調用也就是消息發送過程中無法找到對應的方法。

//方法調用實際上就是消息發送
objc_msgSend(id obj, SEL cmd,...)

eg:
id returnValue = [someObject messageName:parameter];
//someObject叫做“接受者”(receiver),messageName叫做“選擇子”(selector)。選擇子與參數合起來稱為“消息”(message).編譯器看到此消息后,將其轉換為一條標準的C語言函數調用,所調用的函數乃是消息傳遞機制中的核心函數,就是objc_msgSend,編譯器把上面的方法調用會轉換為如下函數

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

方法調用的過程:objc_msgSend函數會依據接受者與選擇子的類型來調用適當的方法。為了完成此操作,該方法需要在接受者所屬的類中搜尋其“方法列表”(list of methods),如果能找到與選擇子名稱相符的方法,就跳至其實現代碼;若是找不到,就沿著繼承體系繼續向上查找,等找到合適的方法之后再跳轉。如果最終還是找不到相符的方法,那就執行“消息轉發”(mesageforwarding)操作。

要點:

  1. 消息由接受者、選擇子及參數構成。給某對象“發送消息”(invoke a message)也就相當于在該對象上“調用方法”(call a method)。
  2. 發給對象的全部消息都要由“動態消息派發系統”來處理,該系統會查出對應的方法,并執行其代碼。

12.理解消息轉發機制

首先理解NSObject的方法:

//接受到無法解讀的 類方法消息 時調用
+ (BOOL)resolveClassMethod:(SEL)sel ;


//接受到無法解讀的 實例方法的消息 時調用
+ (BOOL)resolveInstanceMethod:(SEL)sel ;

//備授接受者
- (id)forwardingTargetForSelector:(SEL)aSelector;

//轉發消息
- (void)forwardInvocation:(NSInvocation *)anInvocation;
//方法簽名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;

//無法找到方法時調用
- (void)doesNotRecognizeSelector:(SEL)aSelector;

對于一個方法調用無法找到相應方法時運行期系統的執行過程:

  1. 對象發送一條無法解讀的消息,也就是調用一個沒有實現方法;
  2. 首先,征詢接受者所屬的類,看其是否能動態添加方法,以處理這個“未知的方法”,這叫做動態方法解析。就是上面的resolveClassMethod:與resolveInstanceMethod:方法
  3. 倘若沒有動態新增方法來響應該選擇子,則該對象會檢查是否有其他對象來處理這條消息,也就是執行forwardingTargetForSelector:方法尋找備授接受者
  4. 若forwardingTargetForSelector返回nil,沒有其他對象處理該消息,則運行期系統會啟動完整的消息轉發機制,運行期系統會把與消息有關的全部細節都封裝到NSInvocation對象中,再給接受者最后一次機會,令其設法解決當前還未處理的這條消息。
  5. 若最終仍無法處理該消息,那么會調用NSObject的- (void)doesNotRecognizeSelector:(SEL)aSelector方法,拋出異常。

模擬消息發送無法找到相應方法的步驟:


image

13.用“方法調配技術”調試“黑盒方法”

方法調配技術也就是方法交換技術。用到的運行時方法是

//獲取類的實例方法 返回一個Method對象
class_getInstanceMethod(Class obj, SEL cmd)

//替換方法的實現
method_exchangeImplementations(method1, method2)

方法交換常常用于對系統方法的補充,比如:我們要打印每次調用imageWithNamed:方法的時間,則我們可以自定義一個方法,在該方法中進行圖片讀取,同時再打印出當前時間,然后把自定義的方法與UIImage的該方法進行交換,這樣我們就不必修改項目中每一處使用UIImageNamed:的方法

運行時機制runtime(交換方法)

要點:

  1. 在運行期,可以向類中新增或替換選擇子所對應的方法實現;
  2. 使用另一份實現來替換原有的方法實現,這道工序叫做“方法調配”,開發者常用此技術向原有實現中添加新功能;
  3. 一般來說,只有調試程序的時候才需要在運行期修改方法實現,這種做法不宜濫用。

14.理解“類對象”的用意

要點:

  1. 每個實例都有一個指向Class對象的指針,用以表明其類型,而這些Class對象則構成了類的繼承體系;
  2. 如果對象類型無法在編譯期確定,那么就應該使用類型信息查詢方法來探知;
  3. 盡量使用類型信息查詢方法來確定對象類型,而不要直接比較類對象,因為某些對象可能實現了消息轉發功能。



PDF格式的資料來自iOS開發交流群、感覺作者的貢獻,對于知識的系統歸納總結很有幫助。
編寫高質量代碼的52個有效方法
編寫高質量代碼的52個有效方法(一)—熟悉OC
編寫高質量代碼的52個有效方法(二)—對象、消息、運行期
編寫高質量代碼的52個有效方法(三)—接口與API設計
編寫高質量代碼的52個有效方法(四)—協議與分類
編寫高質量代碼的52個有效方法(五)—內存管理
編寫高質量代碼的52個有效方法(六)—塊與大中樞派發
編寫高質量代碼的52個有效方法(七)---系統框架

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容