前言
OC是面向?qū)ο蟮淖兂烧Z(yǔ)言,“對(duì)象”就是“基本構(gòu)造單元”。而對(duì)象之間傳遞數(shù)據(jù)并執(zhí)行任務(wù)的過(guò)程,就是“消息傳遞”。對(duì)于OC這種消息結(jié)構(gòu)的語(yǔ)言理解“消息傳遞”的理解,非常有必要。
當(dāng)應(yīng)用程序運(yùn)行起來(lái),圍棋提供相關(guān)支持的代碼叫做"Object-c運(yùn)行期環(huán)境"(Objective-C runtime), 它提供了一些是得對(duì)象之間能夠傳遞信息的重要函數(shù),并且包含創(chuàng)建類實(shí)例所用的全部邏輯。理解運(yùn)行期環(huán)境中各個(gè)部分協(xié)同工作的原理,你的開(kāi)發(fā)水平將會(huì)進(jìn)一步的提升。
第一、關(guān)于屬性(property)
1.property、 synthesize、 dynamic
參考一下框圖已經(jīng)非常詳細(xì)了。

2.在對(duì)象內(nèi)部使用訪問(wèn)實(shí)例變量
筆者建議在讀取實(shí)例變量的時(shí)候采用直接訪問(wèn)的形式,而在設(shè)置實(shí)例變量的時(shí)候采用屬性來(lái)做。
直接訪問(wèn)實(shí)際變量?jī)?yōu)點(diǎn)
1.由于不經(jīng)過(guò)OC的“方法派發(fā)”步驟,所以直接訪問(wèn)實(shí)例變量的速度比較快。
不會(huì)調(diào)用“設(shè)置方法”,繞過(guò)了相關(guān)屬性的“內(nèi)存管理語(yǔ)義”,比如:ARC下直接訪問(wèn)一個(gè)聲明為copy的屬性,那么并不會(huì)copy該屬性,只會(huì)保留新值,釋放舊的值。
不會(huì)觸發(fā)“KVO(key-Value Observing, KVO)”通知。這樣做是否會(huì)產(chǎn)生問(wèn)題還需要取決于具體的對(duì)象的行為
通過(guò)屬性來(lái)分文有助排查錯(cuò)誤,因?yàn)榭梢越o“獲取方法”和“設(shè)置方法”中新增斷點(diǎn),監(jiān)控調(diào)用者和調(diào)用時(shí)機(jī)。
注意點(diǎn)
1.在初始化方法中應(yīng)該如何設(shè)置屬性值,因?yàn)樽宇惪赡軙?huì)覆寫(xiě)該方法。為了防止初始化父類用set方法調(diào)用到不確定子類的set的方法,所以在初始化方法和delloc方法中,總是通過(guò)實(shí)例變量來(lái)讀寫(xiě)數(shù)據(jù)
2.當(dāng)存在著懶加載的情況,那么一定需要使用屬性的方法來(lái)讀取數(shù)據(jù)。
第二、關(guān)于“對(duì)象等同性”
== 比較出來(lái)的是指針的比較,學(xué)習(xí)C語(yǔ)言指針的話,對(duì)此將非常容易理解。
NSString *fir = @"number:123";
NSString *sec = @"number:123";
BOOL equalA = (fir == sec); // NO
BOOL equalB = [fir isEqual:sec]; // YES
BOOL equalC = [fir isEqualToString:sec]; //YES
從以上的例子可以看出,==是比較指針,isEqualToString是NSString提供自己獨(dú)特的等同性的判斷方法。并且調(diào)用該方法比isEqual方法快。后者還需奧執(zhí)行額外的步驟。
1.關(guān)于isEqual方法
如果isEqual的方法判定兩個(gè)對(duì)象相等,那么其hash方法也必須返回同一個(gè)值,如果兩個(gè)對(duì)象的hash方法返回同一個(gè)值,但是"isEqual:"方法未必會(huì)認(rèn)為二者相等。
如果經(jīng)常需要判斷等同性,那么可以自己創(chuàng)建等同性的方法,提高性能。正如 isEqualToString一樣
對(duì)于NSArray對(duì)象中,如果對(duì)應(yīng)位置上的對(duì)象均相等,那么這兩個(gè)數(shù)組就等同,稱之為“深度等同性判定”。當(dāng)然比如從數(shù)據(jù)庫(kù)中創(chuàng)建出來(lái)的對(duì)象代用Primary Key那么我們只需要比較readonly的Primagry Key是否相等來(lái)判定等同,不需要檢測(cè)所有的字段。所以只有類的編寫(xiě)者才可以確定兩個(gè)對(duì)象實(shí)例在何種情況下可以被判定為相等。
2.工廠模式和Class Cluster
類族模式可以把所有實(shí)現(xiàn)的細(xì)節(jié)隱藏到一套簡(jiǎn)單的公共接口的后面。
系統(tǒng)框架中經(jīng)常使用類族
從類族的公共抽象基類中繼承子類是要當(dāng)心。
3.技巧篇
在既有類中使用關(guān)聯(lián)對(duì)象存放自定義數(shù)據(jù),如下代碼不過(guò)需要注意該部分被包含在頭文件 中
import <objc/runtime.h>
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
OBJC_ASSOCIATION_ASSIGN = 0, /**< Specifies a weak reference to the associated object. */
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**< Specifies a strong reference to the associated object.
* The association is not made atomically. */
OBJC_ASSOCIATION_COPY_NONATOMIC = 3, /**< Specifies that the associated object is copied.
* The association is not made atomically. */
OBJC_ASSOCIATION_RETAIN = 01401, /**< Specifies a strong reference to the associated object.
* The association is made atomically. */
OBJC_ASSOCIATION_COPY = 01403 /**< Specifies that the associated object is copied.
* The association is made atomically. */
};
@implementation Person (Property)
//使用的是不透明的指針,一般選用靜態(tài)全局變量
static NSString const *kcountryKey = @"PersonCountryKey";
static NSString const *kdetailAddressKey = @"PersonDetailAddressKey";
@dynamic country, detailAddress;
-(NSString *) country{
return objc_getAssociatedObject(self, &kcountryKey);
}
-(NSString *)detailAddress{
return objc_getAssociatedObject(self, &kdetailAddressKey);
}
-(void) setCountry:(NSString *)country{
objc_setAssociatedObject(self, &kcountryKey, country, OBJC_ASSOCIATION_COPY);
}
-(void) setDetailAddress:(NSString *)detailAddress{
objc_setAssociatedObject(self, &detailAddress, detailAddress, OBJC_ASSOCIATION_COPY);
}
@end
@interface Person (Property)
@property (nonatomic, readwrite, copy) NSString* country;
@property (nonatomic, readwrite, copy) NSString* detailAddress;
@end
如何應(yīng)用,如下應(yīng)用,拓展了person的屬性,并且能很好的處理了多個(gè)AlertView的判斷問(wèn)題。但是采用該方案也需要注意:block可能要捕獲(capture)某些常量,這樣也許會(huì)造成"保留環(huán)"。筆者建議創(chuàng)建一個(gè)UIAlertView的子類,把塊作為子類的屬性。這種做法要比關(guān)聯(lián)好。
#import "ViewController.h"
#import "Person+Property.h"
#import <objc/runtime.h>
typedef void (^AlertBlock)(NSInteger buttonIndex);
@interface ViewController ()<UIAlertViewDelegate>
@end
@implementation ViewController
static NSString* const kAlertViewKey = @"MyAlertViewKey";
- (void)viewDidLoad {
[super viewDidLoad];
Person* xiaoMing = [[Person alloc] initWithFirstName:@"Ren" lastName:@"XiaoMing"];
NSString* fullName = [NSString stringWithFormat:@"你輸入的名字是:%@", xiaoMing.fullName];
NSLog(@"%@", fullName);
UIAlertView* alertView = [[UIAlertView alloc] initWithTitle:@"請(qǐng)確認(rèn)" message:fullName delegate:self cancelButtonTitle:@"確定" otherButtonTitles:nil, nil];
[alertView show];
AlertBlock nameBlock = ^(NSInteger buttonIndex){
NSLog(@"姓名確認(rèn)中你選擇了%ld", buttonIndex);
};
objc_setAssociatedObject(alertView, &kAlertViewKey, nameBlock, OBJC_ASSOCIATION_COPY);
xiaoMing.country = @"China";
xiaoMing.detailAddress = @"Shen Zhen";
NSString* address = [NSString stringWithFormat:@"小明居住的地址是:%@ %@", xiaoMing.country, xiaoMing.detailAddress];
NSLog(@"%@", address);
UIAlertView* addressAlertView = [[UIAlertView alloc] initWithTitle:@"請(qǐng)確認(rèn)" message:address delegate:self cancelButtonTitle:@"確定" otherButtonTitles:nil, nil];
[addressAlertView show];
AlertBlock addressBlock = ^(NSInteger buttonIndex){
NSLog(@"地址確認(rèn)中你選擇了%ld", buttonIndex);
};
objc_setAssociatedObject(addressAlertView, &kAlertViewKey, addressBlock, OBJC_ASSOCIATION_COPY);
}
#pragma mark - UIAlertViewDelegate
-(void) alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{
AlertBlock tempblock = objc_getAssociatedObject(alertView, &kAlertViewKey);
tempblock(buttonIndex);
}
第三、關(guān)于objc_msgSend
1. 概述
- 用OC的話來(lái)說(shuō),這玩樣叫做"傳遞消息(pass a message)"。消息有"名稱(name)"或者"selector",可以接受的參數(shù),而且可能還有返回值。
- C語(yǔ)言是“靜態(tài)綁定”,也就是說(shuō),在編譯期就能決定運(yùn)行時(shí)所調(diào)用的函數(shù)。如果不考慮"內(nèi)聯(lián)"的情況,那么編譯期在編譯代碼的時(shí)候就知道了程序中存在的函數(shù),并且將其硬編址,這個(gè)進(jìn)行過(guò)單片機(jī)開(kāi)發(fā)和仿真過(guò)的,對(duì)此將深有體會(huì),調(diào)用的函數(shù)最終將映射到某個(gè)硬件地址。
- OC語(yǔ)言使用"動(dòng)態(tài)綁定",所需要調(diào)用的函數(shù)知道運(yùn)行期才能確定。
- 對(duì)于OC語(yǔ)言而言,如果想某個(gè)對(duì)象傳遞消息,那就會(huì)使用動(dòng)態(tài)綁定的機(jī)制來(lái)決定需要調(diào)用的方法。在底層,所有的方法都是普通的C語(yǔ)言函數(shù),然而對(duì)象接收到消息之后,究竟該調(diào)用哪個(gè)方法則完全取決于運(yùn)行期,甚至可以在程序運(yùn)行時(shí)改變,這些特性使得OC成為一門(mén)真正的動(dòng)態(tài)語(yǔ)言。
id returnValue = [someObject messageName:parameter];
- 其中someObject就是消息的接受者receiver, messageName叫做selector,而參數(shù)parameter+selector合起來(lái)就稱為"消息"
- 將其轉(zhuǎn)化成一條標(biāo)準(zhǔn)的C語(yǔ)言函數(shù),原型如下:
void objc_msgSend(id self, SEL cmd, ...)

簡(jiǎn)單點(diǎn)歸納的來(lái)說(shuō),消息由接收者、selector以及參數(shù)構(gòu)成的。給某個(gè)對(duì)象“發(fā)送消息”也就是相當(dāng)于在該對(duì)象上“調(diào)用方法”。
發(fā)送給對(duì)象的消息都要經(jīng)過(guò)"動(dòng)態(tài)消息派發(fā)系統(tǒng)",來(lái)處理,該系統(tǒng)會(huì)查出對(duì)應(yīng)的方法,并執(zhí)行代碼。
2. 消息轉(zhuǎn)發(fā)的過(guò)程
1.首先通過(guò)該方法來(lái)詢問(wèn)是否能處理該消息
<PS:如果想通過(guò)運(yùn)行期動(dòng)態(tài)插入方法,那么就需要考慮在這里插入該方法了CALayer里面就應(yīng)用類似的方法實(shí)現(xiàn)。于是開(kāi)發(fā)者能夠向其中新增加自定義的屬性,而這些屬性的存儲(chǔ)工作由基類來(lái)完成>
+(BOOL) resolveInstanceMethod:(SEL) selector;
+(BOOL) resolveClassMethod:(SEL) selector;
2.如果不能處理,接收還有第二次機(jī)會(huì)能處理未知的子,運(yùn)行系統(tǒng)會(huì)問(wèn)它,能不能把這條消息轉(zhuǎn)發(fā)給其他接收者。
-(id) forwardingTargetForSelector:(SEL) selector;
如果當(dāng)前接收者找到了可以處理該部分的對(duì)象的則將它返回(備援接收者), 否者返回nil.
第四、OC業(yè)界的黑魔法
1.原理概述
OC的黑魔法通常是用來(lái)調(diào)試“黑盒方法”。
類的方法列表會(huì)把選擇子的名稱映射到相關(guān)的方法實(shí)現(xiàn)之上,使得“動(dòng)態(tài)分配系統(tǒng)”能夠據(jù)此找應(yīng)該調(diào)用的方法。這些方法均以函數(shù)指針的形式表示,這種指針叫做:IMP,原型如下:
id(* IMP)(id, SEL, ...)
比如以NSString的方法為例,如下圖

2. 如何實(shí)現(xiàn)
下面給了一個(gè)代碼實(shí)現(xiàn)的例子
- (void)viewDidLoad {
[super viewDidLoad];
NSString* apple = @"apple";
NSLog(@"執(zhí)行前===================================");
NSLog(@"Uppercase = %@", [apple uppercaseString]);
NSLog(@"LowerCase = %@", [apple lowercaseString]);
Method originalMethod = class_getInstanceMethod([NSString class], @selector(lowercaseString));
Method swappedMethod = class_getInstanceMethod([NSString class], @selector(uppercaseString));
method_exchangeImplementations(originalMethod, swappedMethod);
NSLog(@"執(zhí)行后===================================");
NSLog(@"Uppercase = %@", [apple uppercaseString]);
NSLog(@"LowerCase = %@", [apple lowercaseString]);
}
程序運(yùn)行的輸出結(jié)果:
2016-12-29 14:25:00.535 OC黑魔法[14618:194380] 執(zhí)行前===================================
2016-12-29 14:25:00.535 OC黑魔法[14618:194380] Uppercase = APPLE
2016-12-29 14:25:00.535 OC黑魔法[14618:194380] LowerCase = apple
2016-12-29 14:25:00.536 OC黑魔法[14618:194380] 執(zhí)行后===================================
2016-12-29 14:25:00.536 OC黑魔法[14618:194380] Uppercase = apple
2016-12-29 14:25:00.537 OC黑魔法[14618:194380] LowerCase = APPLE
總之通過(guò)該方案,開(kāi)發(fā)者可以為那些“完全不知道其實(shí)現(xiàn)的”黑盒方法增加日記功能,這非常有助于調(diào)試。但是筆者認(rèn)為應(yīng)該盡量少用,要不然代碼將非常不好維護(hù),不容易懂。
第四、OC中類的本質(zhì)
1.OC中類的本質(zhì)
- 從下圖中可以才看出,每個(gè)對(duì)象的結(jié)構(gòu)體的收個(gè)成員都是Class類的變量,該變量定義了對(duì)象所屬的類,通常稱之為"is a"指針。比如:NSString的isa就是指向NSString。
- 類的結(jié)構(gòu)體存放類的“元數(shù)據(jù)(metadata)”,例如類的實(shí)現(xiàn)實(shí)現(xiàn)了幾個(gè)方法,具備多少個(gè)變量等信息。此結(jié)構(gòu)體的首個(gè)變量也是isa指針,這個(gè)說(shuō)明Class本身也是OC對(duì)象。還有一個(gè)super_class,而且二者的類型也是Class。類對(duì)象所屬的類型又是另外一個(gè)類,叫做“元類(metaclass)”,用來(lái)表述類對(duì)象所具備的元數(shù)據(jù)??梢岳斫鉃?,每個(gè)類僅有一個(gè)類對(duì)象,而每個(gè)類對(duì)象僅有一個(gè)之相關(guān)的元類。
//參見(jiàn):objc.h
typedef struct objc_class *Class;
/// Represents an instance of a class.
struct objc_object {
Class isa OBJC_ISA_AVAILABILITY;
};
/// A pointer to an instance of a class.
typedef struct objc_object *id;
//參見(jiàn)Objc runtime.h中
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;
//#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE;
const char *name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE;
struct objc_method_list **methodLists OBJC2_UNAVAILABLE;
struct objc_cache *cache OBJC2_UNAVAILABLE;
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE;
//#endif
} OBJC2_UNAVAILABLE;

2.判斷類繼承體系中查詢類型信息
NSMutableDictionary* dict = [NSMutableDictionary new];
[dict isMemberOfClass:[NSDictionary class]]; //NO
[dict isMemberOfClass:[NSMutableDictionary class]]; //YES
[dict isKindOfClass:[NSDictionary class]]; //YES
[dict isKindOfClass:[NSArray class]]; //NO
以上這樣類型查詢方法是使用isa指針來(lái)獲取對(duì)象所屬的類,然后通過(guò)super_class在繼承體系下游走。
比較兩個(gè)類對(duì)象是否等同的也可以采用==操作符,而不要使用Objective-C對(duì)象常用的"isEqual:"方法來(lái)做。原因在于,類對(duì)象是“單例”,再應(yīng)用程序范圍內(nèi),每個(gè)類僅僅有一個(gè)實(shí)例。
總之,盡量要使用類型查詢方法來(lái)確定對(duì)象類型,而不要直接比較類對(duì)象,因?yàn)槟承?duì)象實(shí)現(xiàn)可能實(shí)現(xiàn)了消息轉(zhuǎn)發(fā)功能。