iOS消息轉發機制與BlocksKit
最近看了『神奇的 BlocksKit』系列,里面說到動態代理是BlocksKit的精華部分,對于使用block實現委托方法比較好奇,于是下載了源碼閱讀了一下。
Block已被廣泛用于iOS編程。它們通常被用作可并發執行的邏輯單元的封裝,或者作為事件觸發的回調。Block比傳統回調函數有2點優勢:
- 允許在調用點上下文書寫執行邏輯,不用分離函數
- Block可以使用local variables.
iOS消息轉發機制
消息發送
Objective-C中調用方法其實就是向對象發送消息,比如:
[obj msg];
這句代碼的含義就是向對象obj發送msg的消息,編譯器會調用底層的obj_msgSend( ),從緩存和方法表中,找到對應的IMP指針并執行。有時候在編寫程序時,經常會遇到異常報錯:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Obj msg]: unrecognized selector sent to instance'
這類報錯的根本原因是類Obj的實例并沒有實現方法msg,但是在報錯之前,系統會通過消息轉發機制來確保方法msg沒有其他的實現。再三確認沒有“補救”措施后,然后拋出異常提示。
消息轉發
對于消息轉發,在博客『 輕松學習之 Objective-C消息轉發 』中有個很好的比喻:
比賽足球時,腳下有球的那名球員,如果他的位置不利于射門或者他的球即將被對方球員搶斷,這時最好是把球傳出去,這里的球就相當于消息。
在Objective-c中,消息轉發機制有三套方案:
- 動態轉發
- 快速轉發
- 慢速轉發
按照動態轉發 -> 快速轉發 ->慢速轉發的過程依次進行補救。
1. 動態解析(Dynamic Method Resolution)
實現動態加解析需要重寫如下方法
+(BOOL)resolveInstanceMethod:(SEL)sel;
+(BOOL)resolveClassMethod:(SEL)sel;
當我們調用沒有實現的方法[obj msg],系統會則進入resolveInstanceMethod:(SEL)sel順著繼承鏈往上查找是否有msg的實現。若沒找到,則返回NO,告訴系統沒有找到該方法的實現。通過動態加載,我們可以實現在類中先聲明方法,在運行時添加方法實現。
@dynamic聲明屬性就是一個很好的動態解析的實現,@dynamic告訴編譯器在編譯期間不要自動創建存取方法,然后在動態解析階段將存取方法的IMP加入到Class中。
嚴格來說,動態解析并不是真正意義上的轉發。因為在這一過程中,執行方法的依然是消息的接受對象obj,只不過指向msg實現的IMP指針在運行時才加入到obj的方法列表中,而不是在一開始定義的時候,所以動態解析還有一個英文名稱叫Lazy Method Resolution。
- (BOOL)respondsToSelector:(SEL)selector;
- (BOOL)instancesRespondToSelector:(SEL)selector;
我們在使用以上兩方法來判斷當前對象是否能夠響應msg方法時,也會調用resolveInstanceMethod方法。resolveInstanceMethod返回的BOOL值,通過respondsToSelector返回給判斷語句。
動態解析實例如下:
void runTest(id self, SEL _cmd) {
NSLog(@"runTest");
}
@interface Test : NSObject
- (void)run;
@end
@implementation Test
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if(sel_isEqual(sel, @selector(run))) {
class_addMethod([self class], @selector(run), (IMP)runTest, "v@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
NSLog(@"Hello, World!");
Test *test = [[Test alloc]init];
BOOL flag = [test respondsToSelector:@selector(run)];
NSLog(@"flag is %d", flag);
[test run];
}
return 0;
}
2. 快速轉發(Fast Forwarding)
快速轉發的過程有點像網頁的重定向,當obj發送的msg消息經過動態解析也沒有實現時,系統會將msg消息重定向到另一個對象,在這個對象中實現了與msg消息同名的方法。快速轉發需要在obj中實現如下方法:
- (id)forwardingTargetForSelector:(SEL)aSelector {
return redirectObj;
}
該方法返回的是被重定向的對象,這樣就讓obj有機會將未知的消息重定向一個新的對象,新對象將作為消息新的接受者來執行msg方法。
快轉發實例,添加新的類型Test2實現了run方法,并重定義了快轉發所需方法:
@interface Test2 : NSObject
- (void)run;
@end
@implementation Test2
- (void)run {
NSLog(@"Test2");
}
@end
@interface Test : NSObject
- (void)run;
@end
@implementation Test
- (id)forwardingTargetForSelector:(SEL)aSelector {
return [[Test2 alloc]init];
}
@end
3. 慢速轉發(Normal Forwarding)
了解慢速轉發,首先需要知道兩個類:NSInvocation和NSMethodSignature。
- NSInvocation的對象保存了目標、選擇器、參數等消息發送所必需的全部元素。
- NSMethodSignature對象定義了方法簽名,方法簽名中保存著方法的參數和返回值。
實現慢轉發需要首先重定義如下方法:
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel;
- (void)forwardInvocation:(NSInvocation *)anInvocation;
- (void)invokeWithTarget:(id)anObject;
methodSignatureForSelector:被用于協議的實現中,也用于在消息轉發期間NSInvocation對象的創建,需要返回合適方法簽名對象。
forwardInvocation:當obj發送的消息沒有相應的實現時,運行時系統會給接受者一個機會讓它把消息委派給另一個新的接受者。
在快速轉發失敗后,運行時系統先調用methodSignatureForSelector:返回一個方法簽名用于創建NSInvocation對象,創建好后調用forwardInvocation:方法,在改方法中NSInvocation對象將調用invokeWithTarget:方法喚醒新接受者中的同名方法。
當我們在forwardInvocation:中不想對消息進行處理時,可以調用
- (void)doesNotRecognizeSelector:(SEL)aSelector;
該方法會拋出異常報錯NSInvalidArgumentException,并生成錯誤消息。
慢轉發實例,在上例的Test類中分別重定義慢轉發所需方法
@interface Test2 : NSObject
- (void)run;
@end
@implementation Test2
- (void)run {
NSLog(@"Test2");
}
@end
@interface Test : NSObject
- (void)run;
@end
@implementation Test
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
NSString *selStr = NSStringFromSelector(aSelector);
if([selStr isEqualToString:@"run"]) {
NSMethodSignature *sig = [NSMethodSignature signatureWithObjCTypes:"v@:"];
return sig;
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
SEL sel = [anInvocation selector];
Test2 *test = [[Test2 alloc]init];
if([test respondsToSelector:sel]) {
[anInvocation invokeWithTarget:test];
}
else {
[self doesNotRecognizeSelector:sel];
}
}
小結
Runtime系統提供的三種消息轉發方式按照:動態解析 -> 快速轉發 -> 慢速轉發的流程來執行
具體在實現時按照以下的要求來選擇實現哪個一個:
- 動態解析:只適用于將原來的類中的方法替換掉或者延遲加載。
- 快轉發:它可以將消息處理轉發給其他對象,使用范圍更廣,不只是限于原來的對象。
- 慢轉發:它跟快轉發一樣可以消息轉發,但它能通過NSInvocation對象獲取更多消息發送的信息,例如:target、selector、arguments和返回值等信息。
BlocksKit如何使用消息轉發
BlocksKit是一個提供了更好的block支持的第三方框架,它的主要功能如下:
- 通過block傳入事件處理函數
- 創建動態代理,傳入block給想要實現的方法。
- 在很多基礎的類上增加額外的方法。
所謂的動態代理就是將原本需要使用delegate實現的方法,可以使用block實現。例如:使用tableView時,經常需要實現tableView:numberOfRowsInSection:、tableView:cellForRowAtIndexPath:、numberOfSectionsInTableView:等代理方法,現在通過BlocksKit,我們可以使用block來實現這些方法的功能。
以下為BlocksKit中與動態代理有關的幾個關鍵類:
- A2BlockInvocation:該類的作用類似NSInvocation,主要作用創建和存儲與block相關的信息,例如block的方法簽名。
- A2DynamicDelegate:進行消息轉發的主體,實現了block與委托方法的映射
- NSObject+A2DynamicDelegate:拓展NSObject對象的借口,返回bk_dynamicDelegate 和bk_dynamicDataSource 等 A2DynamicDelegate 類型的實例
- NSObject+A2BlockDelegate:對A2DynamicDelegate進行了拓展,能夠創建自定義的block屬性,動態實現所有添加的block屬性的存取方法。
其中A2BlockInvocation、A2DynamicDelegate、NSObject+A2DynamicDelegate是實現BlocksKit動態代理的核心。通過NSObject+A2BlockDelegate,BlocksKit封裝了部分UIKit分類,提供了對應的block屬性。本文主要講BlocksKit中動態代理的基本實現,因此不涉及NSObject+A2BlockDelegate中的實現邏輯。
整個動態代理的流程如下:
- 開發者使用NSObject+A2DynamicDelegate提供的接口給委托方法指定要執行的block。
- NSObject+A2DynamicDelegate通過創建A2DynamicDelegate對象,來建立委托方法與block之間的映射關系,同時在委托方法執行時,進行消息轉發。
- A2DynamicDelegate對象建立映射關系時,通過A2BlockInvocation類來保存block的信息。消息轉發時,根據映射關系找到相應的A2BlockInvocation對象執行NSInvocation
NSObject+A2DynamicDelegate類
以下是NSObject+A2DynamicDelegate的聲明
@interface NSObject (A2DynamicDelegate)
@property (readonly, strong) id bk_dynamicDataSource;
@property (readonly, strong) id bk_dynamicDelegate;
- (id)bk_dynamicDelegateForProtocol:(Protocol *)protocol;
@end
聲明很簡單,分類提供了一個動態的DataSource代理和動態的Delegate代理,以及一個返回動態代理的方法。通過該方法,系統會從該類以及該類的繼承鏈中查找對應的以A2Dynamic開頭的動態代理對象。這些對象都在后臺進程中惰性初始化:
//沿著繼承鏈查找對應的A2Dynamic**協議的類
static Class a2_dynamicDelegateClass(Class cls, NSString *suffix)
{
while (cls) {
//1、將cls的委托名改成A2Dynamic**的形式
NSString *className = [NSString stringWithFormat:@"A2Dynamic%@%@", NSStringFromClass(cls), suffix];
//2、根據委托名查找對應的類,若存在則返回
Class ddClass = NSClassFromString(className);
if (ddClass) return ddClass;
//3、不存在則順則繼承鏈往父類找
cls = class_getSuperclass(cls);
}
//在繼承鏈中找不到委托名對應的類,返回A2DynamicDelegate的類
return [A2DynamicDelegate class];
}
// 根據協議名查找cls中的關聯對象
- (id)bk_dynamicDelegateWithClass:(Class)cls forProtocol:(Protocol *)protocol
{
__block A2DynamicDelegate *dynamicDelegate;
dispatch_sync(a2_backgroundQueue(), ^{
//1、獲取NSObject的中關聯對象
dynamicDelegate = objc_getAssociatedObject(self, (__bridge const void *)protocol);
//2、關聯對象不存在,則為NSObject創建一個相應的關聯對象
//這一步模擬了UITableView.h中的@property (nonatomic, weak, nullable) id <UITableViewDelegate> delegate;
if (!dynamicDelegate)
{
dynamicDelegate = [[cls alloc] initWithProtocol:protocol];
//NSObject對象中的delegate的作為屬性定義為weak引用,而dynamicDelegate在作用域結束后會馬上被dealloc,為了確保
//dynamicDelegate的生命周期和委托對象一致,必須使用strong引用(為什么生命周期要一致?)
objc_setAssociatedObject(self, (__bridge const void *)protocol, dynamicDelegate, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
});
return dynamicDelegate;
}
A2DynamicDelegate類
A2DynamicDelegate類是BlocksKit中非常重要的類,它實現了類的Delegate和DataSource等協議,是實現動態委托的核心類之一。
與一般類都以NSObject為父類不一樣,A2DynamicDelegate繼承自NSProxy類。NSObject類遵循NSObject協議,同樣NSProxy也遵循NSObject協議,NSObject類實現了比NSObject協議更多的東西,比如鍵值編碼,可能你根本不需要用它。
建立proxy對象的目是為了預留大多數未實現的方法,使用forwardInvocation:方法來轉發。如果把NSObject作為子類則可能會引入更多包,那樣容易導致沖突。而NSProxy通過提供了一個不含多余內容的簡潔超類來避免此類發生。
作為實現動態委托的核心類,A2DynamicDelegate的主要功能是:進行委托方法到對應block之間的映射和消息轉發。
A2DynamicDelegate類包涵以下屬性:
@property (nonatomic, readonly) Protocol *protocol;
@property (nonatomic, readonly) NSMapTable *invocationsBySelectors;
@property (nonatomic, strong, readonly) NSMutableDictionary *handlers;
@property (nonatomic, weak, readonly, nullable) id realDelegate;
protocol存儲了A2DynamicDelegate實現的委托協議;invocationsBySelectors是一個NSMapTable對象,它建立了委托方法到block之間的映射;handlers則建立了block屬性名到具體block實現的映射關系;realDelegate是對象真正的代理,它在NSObject+A2BlockDelegate 中會進行方法調劑,修改原有方法的實現,每次在設置delegate時,會將這個值設置傳到realDelegate中。由于handlers和realDelegate主要用于NSObject+A2BlockDelegate拓展動態的block屬性,因此不作講解。
首先我們來看一下,委托方法到block之間映射的建立。
- (void)implementMethod:(SEL)selector withBlock:(id)block
{
NSCAssert(selector, @"Attempt to implement or remove NULL selector");
BOOL isClassMethod = self.isClassProxy;
if (!block) {
[self.invocationsBySelectors bk_removeObjectForSelector:selector];
return;
}
//查詢selector在protocol中對應的方法描述
struct objc_method_description methodDescription = protocol_getMethodDescription(self.protocol, selector, YES, !isClassMethod);
if (!methodDescription.name) methodDescription = protocol_getMethodDescription(self.protocol, selector, NO, !isClassMethod);
//根據方法描述來選擇如何創建A2BlockInvocation對象
A2BlockInvocation *inv = nil;
if (methodDescription.name) {
NSMethodSignature *protoSig = [NSMethodSignature signatureWithObjCTypes:methodDescription.types];
inv = [[A2BlockInvocation alloc] initWithBlock:block methodSignature:protoSig];
} else {
inv = [[A2BlockInvocation alloc] initWithBlock:block];
}
//建立A2BlockInvocation與selector之間的映射關系
[self.invocationsBySelectors bk_setObject:inv forSelector:selector];
}
代碼邏輯很清晰,根據protocol中的selector對應的方法描述來創建A2BlockInvocation對象,然后以selector為key,以A2BlockInvocation對象放入的NSMapTable中。
接下來,我們來看一下methodSignatureForSelector:和forwardInvocation:方法的實現。在前面的消息轉發機制中說過,methodSignatureForSelector:返回一個用于創建NSInvocation對象的方法簽名,而forwardInvocation:根據創建的NSInvocation對象將消息轉發出去。
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
A2BlockInvocation *invocation = nil;
if ((invocation = [self.invocationsBySelectors bk_objectForSelector:aSelector]))
return invocation.methodSignature;
else if ([self.realDelegate methodSignatureForSelector:aSelector])
return [self.realDelegate methodSignatureForSelector:aSelector];
else if (class_respondsToSelector(object_getClass(self), aSelector))
return [object_getClass(self) methodSignatureForSelector:aSelector];
//NSObject中是否包含sel的方法
return [[NSObject class] methodSignatureForSelector:aSelector];
}
這里的邏輯如下:
- 從映射表中查找selector對應的A2BlockInvocation對象,返回這個對象的方法簽名
- 從實際的代理對象中查找方法簽名
- 從A2DynamicDelegate類查找方法簽名
- 從NSObject中查找方法簽名
- (void)forwardInvocation:(NSInvocation *)outerInv
{
SEL selector = outerInv.selector;
A2BlockInvocation *innerInv = nil;
//從映射表中取出A2BlockInvocation
if ((innerInv = [self.invocationsBySelectors bk_objectForSelector:selector])) {
[innerInv invokeWithInvocation:outerInv];
} else if ([self.realDelegate respondsToSelector:selector]) {
[outerInv invokeWithTarget:self.realDelegate];
}
}
forwardInvocation:的實現邏輯如下:
- 判斷invocationsBySelectors屬性中是否存儲了selector對應的 A2BlockInvocation,然后調用 invokeWithInvocation: 傳入outerInv 轉發這個方法,最終會調用 - [A2BlockInvocation invokeWithInvocation:returnValue:setOnInvocation:]
- 判斷realDelegate 是否實現了該方法,如果真正的代理能做出響應,將方法轉發給 realDelegate
A2BlockInvocation類
A2BlockInvocation類是BlocksKit能夠實現動態代理的另一核心類。在消息轉發機制中,慢速轉發不僅要重寫方法,還需要創建相應的NSMethodSignature對象和NSInvocation對象。在BlocksKit中,A2BlcokInvocation類則扮演了NSInvocation的角色。
@property (nonatomic, strong, readonly) NSMethodSignature *methodSignature;
@property (nonatomic, copy, readonly) id block;
@property (nonatomic, readonly) NSMethodSignature *blockSignature;
A2BlockInvocation類有三個屬性:
- methodSignature是與block兼容的方法簽名
- block則實現了委托方法的功能
- blockSignature是block的方法簽名
在Objective-C中,方法和block都有各自的類型簽名,其中方法的簽名有NSMethodSignature類表示,但是block并沒有一個類來存儲其簽名信息。在BlocksKit中,有一個_BKBlock結構存儲了block的簽名信息,這是一個仿照runtime的block結構體定義的一個數據接口。
block的數據結構:
//_BKBlock結構
typedef struct _BKBlock {
__unused Class isa;
BKBlockFlags flags;
__unused int reserved;
void (__unused *invoke)(struct _BKBlock *block, ...);
struct {
unsigned long int reserved;
unsigned long int size;
// requires BKBlockFlagsHasCopyDisposeHelpers
void (*copy)(void *dst, const void *src);
void (*dispose)(const void *);
// requires BKBlockFlagsHasSignature
const char *signature;
const char *layout;
} *descriptor;
// imported variables
} *BKBlockRef;
由于NSInvocation需要有NSMethodSignature才能進行消息轉發,因此光有block的數據結構并不起作用。還需要將block數據結構中的信息轉換為NSMethodSignature表示;
//檢查給定的block,并返回兼容的類型簽名
+ (NSMethodSignature *)typeSignatureForBlock:(id)block __attribute__((pure, nonnull(1)))
{
BKBlockRef layout = (__bridge void *)block;
if (!(layout->flags & BKBlockFlagsHasSignature))
return nil;
void *desc = layout->descriptor;
desc += 2 * sizeof(unsigned long int);
if (layout->flags & BKBlockFlagsHasCopyDisposeHelpers)
desc += 2 * sizeof(void *);
if (!desc)
return nil;
const char *signature = (*(const char **)desc);
return [NSMethodSignature signatureWithObjCTypes:signature];
}
上述方法的主要功能是找出block的類型編碼,然后根據類型編碼創建一個NSMethodSignature對象來表示block的簽名blockSignature。
BlocksKit在初始化A2BlockInvocation對象時,會調用調用以下兩個方法中的一個:isSignature:compatibleWithSignature:和methodSignatureForBlockSignature:
+ (BOOL)isSignature:(NSMethodSignature *)signatureA compatibleWithSignature:(NSMethodSignature *)signatureB __attribute__((pure))
{
if (!signatureA || !signatureB) return NO;
if ([signatureA isEqual:signatureB]) return YES;
//返回值類型是否兼容
if (!typesCompatible(signatureA.methodReturnType, signatureB.methodReturnType)) return NO;
NSMethodSignature *methodSignature = nil, *blockSignature = nil;
//參數多的為方法簽名,參數少的為block簽名,否則兩個簽名不兼容(IMP的第一個參數傳self,第二個是selector(即_cmd),但block調用并沒有selector)
if (signatureA.numberOfArguments > signatureB.numberOfArguments) {
methodSignature = signatureA;
blockSignature = signatureB;
} else if (signatureB.numberOfArguments > signatureA.numberOfArguments) {
methodSignature = signatureB;
blockSignature = signatureA;
} else {
return NO;
}
NSUInteger numberOfArguments = methodSignature.numberOfArguments;
//比較參數是否相等
for (NSUInteger i = 2; i < numberOfArguments; i++) {
if (!typesCompatible([methodSignature getArgumentTypeAtIndex:i], [blockSignature getArgumentTypeAtIndex:i - 1])) {
return NO;
}
}
return YES;
}
該方法當使用initWithBlock:methodSignature:初始化時會被調用,其作用主要時比較methodSignature和blockSignature是否兼容
//創造與給定的block簽名兼容的方法簽名
+ (NSMethodSignature *)methodSignatureForBlockSignature:(NSMethodSignature *)original
{
if (!original) return nil;
if (original.numberOfArguments < 1) {
return nil;
}
if (original.numberOfArguments >= 2 && strcmp(@encode(SEL), [original getArgumentTypeAtIndex:1]) == 0) {
return original;
}
// initial capacity is num. arguments - 1 (@? -> @) + 1 (:) + 1 (ret type)
// optimistically assuming most signature components are char[1]
NSMutableString *signature = [[NSMutableString alloc] initWithCapacity:original.numberOfArguments + 1];
const char *retTypeStr = original.methodReturnType;
[signature appendFormat:@"%s%s%s", retTypeStr, @encode(id), @encode(SEL)];
for (NSUInteger i = 1; i < original.numberOfArguments; i++) {
const char *typeStr = [original getArgumentTypeAtIndex:i];
NSString *type = [[NSString alloc] initWithBytesNoCopy:(void *)typeStr length:strlen(typeStr) encoding:NSUTF8StringEncoding freeWhenDone:NO];
[signature appendString:type];
}
return [NSMethodSignature signatureWithObjCTypes:signature.UTF8String];
}
當使用initWithBlock:初始化A2BlockInvocation對象時,調用上面的方法,該方法的作用是根據blockSignature來創造methodSignature。
最終當A2BlcokInvocation對象需要執行NSInvocation時,將調用如下方法:
- (BOOL)invokeWithInvocation:(NSInvocation *)outerInv returnValue:(out NSValue **)outReturnValue setOnInvocation:(BOOL)setOnInvocation
{
NSParameterAssert(outerInv);
NSMethodSignature *sig = self.methodSignature;
//NSInvocation包涵的methodSignature與A2BlcokInvocation的methodSignature是否相等
if (![outerInv.methodSignature isEqual:sig]) {
NSAssert(0, @"Attempted to invoke block invocation with incompatible frame");
return NO;
}
//創建blockSignature對應的NSInvocation對象
NSInvocation *innerInv = [NSInvocation invocationWithMethodSignature:self.blockSignature];
void *argBuf = NULL;
//將block的參數信息傳遞給NSInvocation對象
for (NSUInteger i = 2; i < sig.numberOfArguments; i++) {
const char *type = [sig getArgumentTypeAtIndex:i];
NSUInteger argSize;
NSGetSizeAndAlignment(type, &argSize, NULL);
if (!(argBuf = reallocf(argBuf, argSize))) {
return NO;
}
//block的簽名中沒有selector參數
[outerInv getArgument:argBuf atIndex:i];
[innerInv setArgument:argBuf atIndex:i - 1];
}
//NSInvocation執行block
[innerInv invokeWithTarget:self.block];
NSUInteger retSize = sig.methodReturnLength;
if (retSize) {
if (outReturnValue || setOnInvocation) {
if (!(argBuf = reallocf(argBuf, retSize))) {
return NO;
}
[innerInv getReturnValue:argBuf];
if (setOnInvocation) {
[outerInv setReturnValue:argBuf];
}
if (outReturnValue) {
*outReturnValue = [NSValue valueWithBytes:argBuf objCType:sig.methodReturnType];
}
}
} else {
if (outReturnValue) {
*outReturnValue = nil;
}
}
free(argBuf);
return YES;
}
小結
BlocksKit中應用消息轉發機制實現動態代理的實例很多,本文僅講述了BlocksKit動態代理最基本的實現,關于動態代理實現更多的擴展,可以了解NSObject+A2Block的使用。
參考
Objective-C runtime 拾遺(一)——NSInvocation 調用Block
來自:http://blog.flight.dev.qunar.com/2016/12/29/BlockskitAndiOSMessage/