日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

iOS之深入解析如何使用Block实现委托方法

發布時間:2024/5/28 编程问答 30 豆豆
生活随笔 收集整理的這篇文章主要介紹了 iOS之深入解析如何使用Block实现委托方法 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

一、前言

  • Block 和 Delegate 是對象間傳遞消息的常用機制,這兩個機制可以說是各有千秋。 Delegate 可以很方便把目標動作的執行過程劃分為多個方法,以展現不同時間節點下特定的操作;Block 則擅長處理一個回調多個落點的情況,并且它可以通過捕捉上下文信息,來達到減少創建額外變量,集中消息處理邏輯的目的。
  • 結合以上兩種通信方式的特點,我們可以添加一些額外的橋接處理,讓 Delegate 機制也能享有 Block 機制所擁有的部分優點,橋接處理的核心就是用 Block 實現委托方法。
  • 由于 Runtime 的存在,在消息轉發的最后一步,可以輕松地攔截對未定義方法的調用,并且針對當前消息做一些額外的處理,比如改變它的入參、設置另一個消息接受者等。借助于這一特性,我們可以創建一個統一的 Delegate 對象,并在這個對象的 -forwardInvocation: 方法中,用預先設置的 Block 替換對委托方法的調用,以達到用 Block 實現委托方法的目的。

二、NSInvocation 基本使用

  • 蘋果官方對 NSInvocation 的用途給出的解釋如下:
NSInvocation objects are used to store and forward messages between objects and between applications
  • 一個 NSInvocation 對象包含了 Objective-C 消息的所有要素:消息接收對象、 方法選擇器 (SEL) 、參數以及返回值,并且這些要素都可以由開發者直接設置。如下所示,簡單使用 NSInvocation:
NSString *foo = @"foo"; NSMethodSignature *signature = [foo methodSignatureForSelector:@selector(stringByAppendingString:)]; NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature]; invocation.selector = @selector(stringByAppendingString:);NSString *bar = @"bar"; [invocation setArgument:&bar atIndex:2]; [invocation invokeWithTarget:foo];void *result = nil; [invocation getReturnValue:&result];NSString *resultString = (__bridge NSString *)(result); NSLog(@"%@", resultString);
  • 上述代碼執行結果:
foobar
  • 可以看到,這個結果和執行 [foo stringByAppendingString:bar] 的結果是一致的。
  • 關于 NSInvocation 的使用,需要注意以下兩點:
    • 一般方法的自定義參數從索引 2 開始,前兩個分別是對象自身以及發送方法的 SEL;
    • 從 -getArgument:atIndex: 和 -getReturnValue: 方法中獲取的對象是不會被 retain 的,所以如果使用 ARC ,那么以下代碼都是錯誤的:
NSString *bar = nil; [invocation getArgument:&bar atIndex:2];NSString *result = nil; [invocation getReturnValue:&result];
    • ARC 編譯環境下局部對象默認具有 __strong 屬性,它會針對這個對象添加 release 代碼,所以可能會因為 release 已經釋放的對象而崩潰。正確代碼如下:
void *bar = nil; // __unsafe_unretained NSString *bar = nil; // __weak NSString *bar = nil; [invocation getArgument:&bar atIndex:2];void *result = nil; // __unsafe_unretained NSString *result = nil; // __weak NSString *result = nil; [invocation getReturnValue:&result];
    • 如果是在兩個 NSInvocation 對象間傳遞參數/返回值,那么可以直接傳入指針獲取并設置目標地址,以返回值為例:
.... NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature]; NSInvocation *shadowInvocation = [NSInvocation invocationWithMethodSignature:signature]; .... void *resultBuffer = malloc(invocation.methodSignature.methodReturnLength); memset(resultBuffer, 0, invocation.methodSignature.methodReturnLength);[invocation getReturnValue:resultBuffer]; [shadowInvocation setReturnValue:resultBuffer]; .... free(resultBuffer);
  • 這時,如果返回值是一個 NSString 對象,那么 resultBuffer 實際上是指向 NSString 對象指針的指針,這時可以這樣讀取實際內容:
NSString *result = (__bridge NSString *)(*(void **)resultBuffer);
  • 不過在已經知道返回值是一個對象時,一般會直接傳入對象指針的地址,以便直接讀取對象。

三、獲取方法簽名

① 從對象中獲取方法簽名

  • NSMethodSignature 是創建一個有效 NSInvocation 對象的必要成分,它提供了方法調用所必須的參數和返回值信息。
  • NSObject 類用以下兩個方法獲取實例方法的方法簽名:
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector OBJC_SWIFT_UNAVAILABLE(""); + (NSMethodSignature *)instanceMethodSignatureForSelector:(SEL)aSelector OBJC_SWIFT_UNAVAILABLE("");
  • 既然類也是對象,那么類方法的方法簽名也就可以通過 -methodSignatureForSelector: 方法獲取。

② 從協議中獲取方法簽名

  • 由于協議定義了接口的參數和返回值信息,所以從協議中也可以獲取到特定方法的方法簽名。利用 protocol_getMethodDescription 函數,可以獲取到描述類型的 C 字符串,再通過這個字符串構造方法簽名。
  • 針對協議中的接口有 required 和 optional 兩種,并且不允許重復這一特點,可以創建構造方法簽名的函數:
static NSMethodSignature *ydw_getProtocolMethodSignature(Protocol *protocol, SEL selector, BOOL isInstanceMethod) {struct objc_method_description methodDescription = protocol_getMethodDescription(protocol, selector, YES, isInstanceMethod);if (!methodDescription.name) {methodDescription = protocol_getMethodDescription(protocol, selector, NO, isInstanceMethod);}return [NSMethodSignature signatureWithObjCTypes:methodDescription.types]; }

③ 從 Block 中獲取方法簽名

  • 蘋果并沒有提供一個開放的接口,供開發者獲取 Block 的方法簽名。不過根據 LLVM 對 Block 結構描述,可以通過操作指針獲取簽名字符串。
  • Block 的結構如下:
// Block internals. typedef NS_OPTIONS(int, YDWBlockFlags) {YDWBlockFlagsHasCopyDisposeHelpers = (1 << 25),YDWBlockFlagsHasSignature = (1 << 30) }; typedef struct ydw_block {__unused Class isa;YDWBlockFlags flags;__unused int reserved;void (__unused *invoke)(struct ydw_block *block, ...);struct {unsigned long int reserved;unsigned long int size;// requires YDWBlockFlagsHasCopyDisposeHelpersvoid (*copy)(void *dst, const void *src);void (*dispose)(const void *);// requires YDWBlockFlagsHasSignatureconst char *signature;const char *layout;} *descriptor;// imported variables } *YDWBlockRef;
  • 可以看到,只要獲取 descriptor 指針,然后根據不同條件添加特定的偏移量,就可以獲取到 signature:
static NSMethodSignature *ydw_signatureForBlock(id block) {YDWBlockRef layout = (__bridge YDWBlockRef)(block);// 沒有簽名,直接返回空if (!(layout->flags & YDWBlockFlagsHasSignature)) {return nil;}// 獲取 descriptor 指針void *desc = layout->descriptor;// 跳過 reserved 和 size 成員desc += 2 * sizeof(unsigned long int);// 如果有 Helpers 函數, 跳過 copy 和 dispose 成員if (layout->flags & YDWBlockFlagsHasCopyDisposeHelpers) {desc += 2 * sizeof(void *);}// desc 為 signature 指針的地址,轉換下給 objcTypeschar *objcTypes = (*(char **)desc);return [NSMethodSignature signatureWithObjCTypes:objcTypes]; }

四、方法調用 -> Block 調用

  • 經過上文,已經可以獲取到 Block 和接口方法的簽名信息,現在根據這個簽名信息,結合方法對應的 NSInvocation 對象,創建和 Block 關聯的 NSInvocation 對象。

① 存儲 Block 信息

  • 首先要做的是,存儲 Block 的簽名信息,并且和接口方法的簽名信息做匹配處理。因為在調用前,需要將接口方法得到的參數轉換成 Block 的入參,調用之后需要將 Block 的返回值重新傳給接口方法,所以必須確保兩者的簽名信息在一定程度上是兼容的。
- (instancetype)initWithMethodSignature:(NSMethodSignature *)methodSignature block:(id)block {return [self initWithMethodSignature:methodSignature blockSignature:ydw_signatureForBlock(block) block:block]; }- (instancetype)initWithMethodSignature:(NSMethodSignature *)methodSignature blockSignature:(NSMethodSignature *)blockSignature block:(id)block {NSAssert(ydw_isCompatibleBlockSignature(blockSignature, methodSignature), @"Block signature %@ is not compatible with method signature %@", blockSignature, methodSignature);if (self = [super init]) {_methodSignature = methodSignature;_blockSignature = blockSignature;_block = block;}return self; }

② 簽名匹配

  • Block 的簽名信息相較于方法的簽名信息,只在參數類型上少了 SEL。
  • 方法的簽名信息如果要獲取自定義參數類型的話,需要從索引 2 開始,而 Block 的自定義參數類型信息則從索引 1 開始。
static BOOL ydw_isCompatibleBlockSignature(NSMethodSignature *blockSignature, NSMethodSignature *methodSignature) {NSCParameterAssert(blockSignature);NSCParameterAssert(methodSignature);if ([blockSignature isEqual:methodSignature]) {return YES;}// block 參數個數需要小于 method 的參數個數 (針對 block 調用替換 method 調用)// 兩者返回類型需要一致if (blockSignature.numberOfArguments >= methodSignature.numberOfArguments ||blockSignature.methodReturnType[0] != methodSignature.methodReturnType[0]) {return NO;}// 參數類型需要一致BOOL compatibleSignature = YES;// 自定義參數從第二個開始for (int idx = 2; idx < blockSignature.numberOfArguments; idx++) {// block 相比 method ,默認參數少了 SEL// method: id(@) SEL(:) ....// block: block(@?) ....const char *methodArgument = [methodSignature getArgumentTypeAtIndex:idx];const char *blockArgument = [blockSignature getArgumentTypeAtIndex:idx - 1];if (!methodArgument || !blockArgument || methodArgument[0] != blockArgument[0]) {compatibleSignature = NO;break;}} return compatibleSignature; }

③ Invocation 調用

  • 得到有效的 Block 簽名信息,就可以構造 NSInvocation 對象,不過還需要接口方法的實參信息,這可以通過讓外部傳入接口方法的 NSInvocation 對象實現。
- (void)invokeWithMethodInvocation:(NSInvocation *)methodInvocation {NSParameterAssert(methodInvocation);NSAssert([self.methodSignature isEqual:methodInvocation.methodSignature], @"Method invocation's signature is not compatible with block signature");NSMethodSignature *methodSignature = methodInvocation.methodSignature;NSInvocation *blockInvocation = [NSInvocation invocationWithMethodSignature:self.blockSignature];void *argumentBuffer = NULL;for (int idx = 2; idx < methodSignature.numberOfArguments; idx++) {// 獲取參數類型const char *type = [methodSignature getArgumentTypeAtIndex:idx];NSUInteger size = 0;// 獲取參數大小NSGetSizeAndAlignment(type, &size, NULL);// 參數緩存if (!(argumentBuffer = reallocf(argumentBuffer, size))) {return;}// 把 method 的參數傳遞給 block[methodInvocation getArgument:argumentBuffer atIndex:idx];[blockInvocation setArgument:argumentBuffer atIndex:idx - 1];}// 調用 block[blockInvocation invokeWithTarget:self.block];// 返回值緩存if (methodSignature.methodReturnLength &&(argumentBuffer = reallocf(argumentBuffer, methodSignature.methodReturnLength))) {// 把 block 的返回值傳遞給 method[blockInvocation getReturnValue:argumentBuffer];[methodInvocation setReturnValue:argumentBuffer];}// 釋放緩存free(argumentBuffer); }
  • reallocf 函數是 realloc 函數的增強版,它可以在后者無法申請到堆空間時,釋放舊的堆空間:
void *reallocf(void *p, size_t s) {void *tmp = realloc(p, s);if(tmp) return tmp;free(p);return NULL; }
  • 這樣就可以直接用 argumentBuffer = reallocf(argumentBuffer, size) 形式的語句,否則如果使用 realloc,一旦返回的是 NULL,會造成舊的堆空間無法釋放的問題。

五、實現委托方法

  • 現在已經可以構造 Block 的 NSInvocation 對像,就缺攜帶參數和返回值信息的接口方法 NSInvocation 對象,接下來就針對實例方法,簡單地實現動態委托類。

① 儲存 Block Invocation 信息

  • 以接口方法選擇器對應的字符串為 Key,以 Block 對應的 Invocation 封裝類為 Value 儲存調用信息:
- (instancetype)initWithProtocol:(Protocol *)protocol {_protocol = protocol;_selectorInvocationMap = [NSMutableDictionary dictionary];return self; }- (void)implementInstanceMethodOfSelector:(SEL)selector withBlock:(id)block {NSMethodSignature *methodSignature = ydw_getProtocolMethodSignature(self.protocol, selector, YES);YDWBlockInvocation *invocation = [[YDWBlockInvocation alloc] initWithMethodSignature:methodSignature block:block];self.selectorInvocationMap[NSStringFromSelector(selector)] = invocation; }

② 消息轉發

  • 向動態委托類發送委托消息后,會觸發消息轉發機制。在消息轉發的最后一步,可以構造委托方法對應的 NSInvocation 對象:
- (void)forwardInvocation:(NSInvocation *)invocation {YDWBlockInvocation *blockInvocation = self.selectorInvocationMap[NSStringFromSelector(invocation.selector)];[blockInvocation invokeWithMethodInvocation:invocation]; }- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {return self.selectorInvocationMap[NSStringFromSelector(sel)].methodSignature; }- (BOOL)respondsToSelector:(SEL)aSelector {return !!self.selectorInvocationMap[NSStringFromSelector(aSelector)]; }

六、實例

  • 如何使用這個動態委托類:
@class Computer; @protocol ComputerDelegate <NSObject> @required - (void)computerWillStart:(Computer *)computer; - (BOOL)computerShouldBeLocked:(Computer *)computer; @end@interface Computer : NSObject @property (weak, nonatomic) id <ComputerDelegate> delegate;- (void)start; - (void)lock;@end@implementation Computer - (void)start {[self.delegate computerWillStart:self];// start }- (void)lock {__unused BOOL locked = [self.delegate computerShouldBeLocked:self];printf("computer should be locked: %d \n", locked);// lock } @end
  • 應用如下:
YDWDynamicDelegate <ComputerDelegate> *dynamicDelegate = (id)[[YDWDynamicDelegate alloc] initWithProtocol:@protocol(ComputerDelegate)]; [dynamicDelegate implementInstanceMethodOfSelector:@selector(computerWillStart:) withBlock:^(Computer *c) {NSLog(@"%@ will start", c); }]; [dynamicDelegate implementInstanceMethodOfSelector:@selector(computerShouldBeLocked:) withBlock:^BOOL(Computer *c) {NSLog(@"%@ should not be locked", c);return NO; }];Computer *computer = [Computer new]; computer.delegate = dynamicDelegate; [computer start]; [computer lock];
  • 執行結果:
will start should not be locked computer should be locked: 0

七、總結

  • 用 Block 實現委托方法的開源方案在比較早的時候就已經出來了,本文的實現就是 BlocksKit 的 A2BlockInvocation 和 A2DynamicDelegate 類的簡易版本,不過省略了類方法以及一些邊界條件的處理,不過大體的思路基本是一致的,都是圍繞 NSInvocation 和消息轉發。

總結

以上是生活随笔為你收集整理的iOS之深入解析如何使用Block实现委托方法的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。