這篇將從面向?qū)ο蟮慕嵌确治鋈绾翁岣逴C的代碼質(zhì)量。
一、理解“ 屬性 ”這一概念
屬性(@property)是OC的一項特性。
@property:編譯器會自動生成實例變量和getter和setter方法。
下文中,getter和setter方法合稱為存取方法
For Example:
@property (nonatomic, strong) UIView *qiShareView;
等價于:
@synthesize qiShareView = _qiShareView;
- (UIView *)qiShareView;
- (void)setQiShareView:(UIView *)qiShareView;
如果不希望自動生成存取方法和實例變量,那就要使用@dynamic關(guān)鍵字
@dynamic qiShareView;
屬性特質(zhì)有四類:
1.原子性:默認(rèn)為atomic
- nonatomic:非原子性,讀寫時不加同步鎖
- atomic:原子性,讀寫時加同步鎖
2.讀寫權(quán)限:默認(rèn)為readwrite
- readwrite:擁有g(shù)etter和setter方法
- readonly:僅擁有g(shù)etter方法
3.內(nèi)存管理:
- assign:對“純量類型”做簡單賦值操作(NSInteger、CGFloat等)。
- strong:強(qiáng)擁有關(guān)系,設(shè)置方法 保留新值,并釋放舊值。
- weak:弱擁有關(guān)系,設(shè)置方法 不保留新值,不釋放舊值。當(dāng)指針指向的對象銷毀時,指針置nil。
- copy:拷貝擁有關(guān)系,設(shè)置方法不保留新值,將其拷貝。
- unsafe_unretained:非擁有關(guān)系,目標(biāo)對象被釋放,指針不置nil,這一點(diǎn)和assign一樣。區(qū)別于weak
4.方法名:
- getter=:指定get方法的方法名,常用
- setter=:指定set方法的方法名,不常用
例如
@property (nonatomic, getter=isOn) BOOL on;
在iOS開發(fā)中,99.99..%的屬性都會聲明為nonatomic。
一是atomic會嚴(yán)重影響性能,
二是atomic只能保證讀/寫操作的過程是可靠的,并不能保證線程安全。
二、在對象內(nèi)部盡量直接訪問實例變量
1.實例變量( _屬性名 )訪問對象的場景:
- 在init和dealloc方法中,總是應(yīng)該通過訪問實例變量讀寫數(shù)據(jù)
- 沒有重寫getter和setter方法、也沒有使用KVO監(jiān)聽
- 好處:不走OC的方法派發(fā)機(jī)制,直接訪問內(nèi)存讀寫,速度快,效率高。
For Example:
- (instancetype)initWithDic:(NSDictionary *)dic {
self = [super init];
if (self) {
_qi = dic[@"qi"];
_share = dic[@"share"];
}
return self;
}
2.用存取方法訪問對象的場景:
- 重寫了getter/setter方法(比如:懶加載)
- 使用了KVO監(jiān)聽值的改變
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
==操作符只是比較了兩個指針?biāo)笇ο蟮牡刂肥欠裣嗤?,而不是指針?biāo)傅膶ο蟮闹?br>
所以最后一個為0
四、以類族模式隱藏實現(xiàn)細(xì)節(jié)
為什么下面這個例子的if永遠(yuǎn)為false?
id maybeAnArray = @[];
if ([maybeAnArray class] == [NSArray class]) {
//Code will never be executed
}
因為[maybeAnArray class] 的返回永遠(yuǎn)不會是NSArray,NSArray是一個類族,返回的值一直都是NSArray的實體子類。大部分collection類都是某個類族中的抽象基類
所以上面的if想要有機(jī)會執(zhí)行的話要改成
id maybeAnArray = @[];
if ([maybeAnArray isKindOfClass [NSArray class]) {
// Code probably be executed
}
這樣判斷的意思是,maybeAnArray這個對象是否是NSArray類族中的一員
使用類族的好處:可以把實現(xiàn)細(xì)節(jié)隱藏在一套簡單的公共接口后面
五、在既有類中使用關(guān)聯(lián)對象存放自定義數(shù)據(jù)
objc_AssociationPolicy(對象關(guān)聯(lián)策略類型)
三個方法管理關(guān)聯(lián)對象
objc_setAssociatedObject(設(shè)置關(guān)聯(lián)對象)
/**
* 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(獲得關(guān)聯(lián)對象)
/**
* 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(去除關(guān)聯(lián)對象)
/**
* 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)
- 可以通過“關(guān)聯(lián)對象”機(jī)制可以把兩個對象聯(lián)系起來
- 定義關(guān)聯(lián)對象可以指定內(nèi)存管理策略
- 應(yīng)用場景:只有在其他做法(代理、通知等)不可行時,才會選擇使用關(guān)聯(lián)對象。這種做法難于找bug。
- 但也有具體應(yīng)用場景:比如說前幾篇說到 控制Button響應(yīng)時間間隔 的demo中就遇到了:附上
六、理解objc_msgSend(對象的消息傳遞機(jī)制)
首先我們要區(qū)分兩個基本概念
- 1 .靜態(tài)綁定(static binding):在編譯期就能決定運(yùn)行時所應(yīng)調(diào)用的函數(shù)。代表語言:C、C++等
- 2 .動態(tài)綁定 (dynamic binding):所要調(diào)用的函數(shù)直到運(yùn)行期才能確定。代表語言:OC、swift等
OC是一門強(qiáng)大的動態(tài)語言,它的動態(tài)性體現(xiàn)在它強(qiáng)大的runtime機(jī)制上。
解釋:在OC中,如果向某對象傳遞消息,那就會使用動態(tài)綁定機(jī)制來決定需要調(diào)用的方法。在底層,所有方法都是普通的C語言函數(shù),然而對象收到消息后,由運(yùn)行期決定究竟調(diào)用哪個方法,甚至可以在程序運(yùn)行時改變,這些特性使得OC成為一門強(qiáng)大的動態(tài)語言。
底層實現(xiàn):基于C語言函數(shù)實現(xiàn)。
實現(xiàn)的基本函數(shù)是objc_msgSend,定義如下:
void objc_msgSend(id self, SEL cmd, ...)
這是一個參數(shù)個數(shù)可變的函數(shù),第一參數(shù)代表接受者,第二個參數(shù)代表選擇子(OC函數(shù)名),之后的參數(shù)就是消息中傳入的參數(shù)。
舉例:git提交
id return = [git commit:parameter];
上面的方法會在運(yùn)行時轉(zhuǎn)換成如下的OC函數(shù):
id return = objc_msgSend(git, @selector(commit), parameter);
objc_msgSend函數(shù)會在接收者所屬的類中搜尋其方法列表,如果能找到這個跟選擇子名稱相同的方法,就跳轉(zhuǎn)到其實現(xiàn)代碼,往下執(zhí)行。若是當(dāng)前類沒找到,那就沿著繼承體系繼續(xù)向上查找,等找到合適方法之后再跳轉(zhuǎn) ,如果最終還是找不到,那就進(jìn)入消息轉(zhuǎn)發(fā)(下一條具體展開)的流程去進(jìn)行處理了。
可是如果每次傳遞消息都要把類中的方法遍歷一遍,這么多消息傳遞加起來肯定會很耗性能。所以以下講解OC消息傳遞的優(yōu)化方法。
OC對消息傳遞的優(yōu)化
快速映射表(緩存)優(yōu)化:
objc_msgSend在搜索這塊是有做緩存的,每個OC的類都有一塊這樣的緩存,objc_msgSend會將匹配結(jié)果緩存在快速映射表(fast map)中,這樣以來這個類一些頻繁調(diào)用的方法會出現(xiàn)在fast map 中,不用再去一遍一遍的在方法列表中搜索了。
尾調(diào)用優(yōu)化:
原理:在函數(shù)末尾調(diào)用某個不含返回值函數(shù)時,編譯器會自動把棧空間的內(nèi)存重新進(jìn)行分配,直接釋放所有調(diào)用函數(shù)內(nèi)部的局部變量,存儲調(diào)轉(zhuǎn)至另一函數(shù)需要的指令碼,然后直接進(jìn)入被調(diào)用函數(shù)的地址。(從而不需要為調(diào)用函數(shù)準(zhǔn)備額外的“棧幀”(frame stack))
好處:最大限度的合理的分配使用的資源,避免過早發(fā)生棧溢出的現(xiàn)象。
七、理解消息轉(zhuǎn)發(fā)機(jī)制
首先區(qū)分兩個基本概念:
- 1 .消息傳遞:對象正常解讀消息,傳遞過去(見上一條)。
-
2 .消息轉(zhuǎn)發(fā):對象無法解讀消息,之后進(jìn)行消息轉(zhuǎn)發(fā)。
消息轉(zhuǎn)發(fā)完整流程圖
image.png
流程解釋:
- 第一步:調(diào)用resolveInstanceMethod:征詢接受者(所屬的類)是否可以添加方法以處理未知的選擇子?(此過程稱為動態(tài)方法解析)若有,轉(zhuǎn)發(fā)結(jié)束。若沒有,走第二步。
- 第二步:調(diào)用forwardingTargetForSelector:詢問接受者是否有其他對象能處理此消息。若有,轉(zhuǎn)發(fā)結(jié)束,一切如常。若沒有,走第三步。
- 第三步:調(diào)用forwardInvocation:運(yùn)行期系統(tǒng)將消息封裝到NSInvocation對象中,再給接受者一次機(jī)會。
- 最后:以上三步還不行,就拋出異常:unrecognized selector sent to instance xxxx
八、用“方法調(diào)配技術(shù)”調(diào)試“黑盒方法”
方法調(diào)配(Method Swizzling):使用另一種方法實現(xiàn)來替換原有的方法實現(xiàn)。(實際應(yīng)用中,常用此技術(shù)向原有實現(xiàn)中添加新的功能。)
獲取給定類的指定實例方法:
OBJC_EXPORT Method _Nullable
class_getInstanceMethod(Class _Nullable cls, SEL _Nonnull name)
交換兩種方法實現(xiàn)的方法:
OBJC_EXPORT void
method_exchangeImplementations(Method _Nonnull m1, Method _Nonnull m2)
利用這兩個方法就可以交換指定類中的指定方法。在實際應(yīng)用中,我們會通過這種方式為既有方法添加新功能。
For Example:交換method1與method2的方法實現(xiàn)
Method method1 = class_getInstanceMethod(self, @selector(method1:));
Method method2 = class_getInstanceMethod(self, @selector(method2:));
method_exchangeImplementations(method1, method2);
九、理解“類對象”的用意
Objective-C類是由Class類型來表示的,實質(zhì)是一個指向objc_class結(jié)構(gòu)體的指針。它的定義如下:
typedef struct objc_class *Class;
在中能看到他的實現(xiàn):
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; //!< 類的版本信息,默認(rèn)為0
long info OBJC2_UNAVAILABLE; //!< 類信息,供運(yùn)行期使用的一些位標(biāo)識
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; //!< 協(xié)議鏈表
#endif
} OBJC2_UNAVAILABLE;
/* Use `Class` instead of `struct objc_class *` */
此結(jié)構(gòu)體存放的是類的“元數(shù)據(jù)”(metadata),例如類的實例實現(xiàn)了幾個方法,父類是誰,具備多少實例變量等信息。
這里的isa指針指向的是另外一個類叫做元類(metaClass)。那什么是元類呢?元類是類對象的類。也可以換一種容易理解的說法:
當(dāng)你給對象發(fā)送消息時,runtime處理時是在這個對象的類的方法列表中尋找
當(dāng)你給類發(fā)消息時,runtime處理時是在這個類的元類的方法列表中尋找
