《編寫高質量OC代碼》已順利完成一二三四五六七八篇!
附上連接:
iOS 編寫高質量Objective-C代碼(一)—— 簡介
iOS 編寫高質量Objective-C代碼(二)—— 面向對象
iOS 編寫高質量Objective-C代碼(三)—— 接口和API設計
iOS 編寫高質量Objective-C代碼(四)—— 協議與分類
iOS 編寫高質量Objective-C代碼(五)—— 內存管理機制
iOS 編寫高質量Objective-C代碼(六)—— block專欄
iOS 編寫高質量Objective-C代碼(七)—— GCD專欄
iOS 編寫高質量Objective-C代碼(八)—— 系統框架html
這篇將從面向對象的角度分析如何提升OC的代碼質量。git
屬性(@property
)是OC的一項特性。 @property
:編譯器會自動生成實例變量
和getter
和setter
方法。 For Example:swift
@property (nonatomic, strong) UIView *qiShareView;
複製代碼
等價於:緩存
@synthesize qiShareView = _qiShareView;
- (UIView *)qiShareView;
- (void)setQiShareView:(UIView *)qiShareView;
複製代碼
若是不但願生成存取方法和實例變量,那就要使用@dynamic關鍵字安全
@dynamic qiShareView;
複製代碼
屬性特質有四類:bash
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
監聽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(關聯策略類型) | 等效的@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 |
/**
* 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)
複製代碼
/**
* 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)
複製代碼
/**
* 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)
複製代碼
小結:
首先咱們要區分兩個基本概念: 1 .靜態綁定(static binding):在
編譯期
就能決定運行時所應調用的函數。~表明語言:C、C++等~ 2 .動態綁定 (dynamic binding):所要調用的函數直到運行期
才能肯定。~表明語言:OC、swift等~
OC是一門強大的動態語言,它的動態性體如今它強大的runtime機制
上。
解釋:在OC中,若是向某對象傳遞消息,那就會使用動態綁定機制來決定須要調用的方法。在底層,全部方法都是普通的C語言函數,然而對象收到消息後,由運行期決定究竟調用哪一個方法,甚至能夠在程序運行時改變,這些特性使得OC成爲一門強大的動態語言
。
objc_msgSend
,定義以下:void objc_msgSend(id self, SEL cmd, ...)
複製代碼
這是一個參數個數可變的函數,第一參數表明接受者,第二個參數表明選擇子(OC函數名),以後的參數就是消息中傳入的參數。
id return = [git commit:parameter];
複製代碼
上面的方法會在運行時轉換成以下的OC函數:
id return = objc_msgSend(git, @selector(commit), parameter);
複製代碼
objc_msgSend
函數會在接收者所屬的類中搜尋其方法列表,若是能找到這個跟選擇子名稱相同的方法,就跳轉到其實現代碼,往下執行。如果當前類沒找到,那就沿着繼承體系繼續向上查找,等找到合適方法以後再跳轉 ,若是最終仍是找不到,那就進入消息轉發(下一條具體展開)的流程去進行處理了。
但是若是每次傳遞消息都要把類中的方法遍歷一遍,這麼多消息傳遞加起來確定會很耗性能。因此如下講解OC消息傳遞的優化方法。
objc_msgSend
在搜索這塊是有作緩存的,每一個OC的類都有一塊這樣的緩存,objc_msgSend
會將匹配結果緩存在快速映射表(fast map)
中,這樣以來這個類一些頻繁調用的方法會出如今fast map
中,不用再去一遍一遍的在方法列表中搜索了。首先區分兩個基本概念: 1 .消息傳遞:對象正常解讀消息,傳遞過去(見上一條)。 2 .消息轉發:對象沒法解讀消息,以後進行消息轉發。
流程解釋:
resolveInstanceMethod
:徵詢接受者(所屬的類)是否能夠添加方法以處理未知的選擇子?~(此過程稱爲動態方法解析)~如有,轉發結束。若沒有,走第二步。forwardingTargetForSelector
:詢問接受者是否有其餘對象能處理此消息。如有,轉發結束,一切如常。若沒有,走第三步。forwardInvocation
:運行期系統將消息封裝到NSInvocation對象中,再給接受者一次機會。unrecognized selector sent to instance xxxx
方法調配(Method Swizzling):使用另外一種方法實現來替換原有的方法實現。~(實際應用中,經常使用此技術向原有實現中添加新的功能。)~
<objc/runtime.h>裏的兩個經常使用的方法:
/**
* 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;
複製代碼
在<objc/runtime.h>中能看到他的實現:
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》第二章