YYKit--YYWeakProxy

ZenonHuang發表於2017-12-25

YYWeakProxy 作用

關於 YYWeakProxy 的作用,在它的標頭檔案就可以清楚的看到:

/**
 A proxy used to hold a weak object.
 It can be used to avoid retain cycles, such as the target in NSTimer or CADisplayLink.
 
 sample code:
 
     @implementation MyView {
        NSTimer *_timer;
     }
     
     - (void)initTimer {
        YYWeakProxy *proxy = [YYWeakProxy proxyWithTarget:self];
        _timer = [NSTimer timerWithTimeInterval:0.1 target:proxy selector:@selector(tick:) userInfo:nil repeats:YES];
     }
     
     - (void)tick:(NSTimer *)timer {...}
     @end
 */
複製程式碼

YYWeakProxy 是用來持有一個 weak 物件的代理,避免迴圈引用。

這裡引用鏈是:

self -> timer -> proxy -> (訊息轉發)target -> self

由於 target 為弱引用,當 self 引用計數為 0 時, target 將為 nil, 於是打破了引用鏈。

NSProxy

YYWeakProxy 繼承於 NSProxy.

NSProxy 是什麼?

NSProxy 是除了NSObject之外的另一個基類。

同時它也是一個抽象類,你可以通過繼承它,並重寫訊息轉發的方法,以實現訊息轉發到另一個例項的目的。

文件是這麼說的:

NSProxy implements the basic methods required of a root class, including those defined in the NSObjectProtocol protocol. However, as an abstract class it doesn’t provide an initialization method, and it raises an exception upon receiving any message it doesn’t respond to. A concrete subclass must therefore provide an initialization or creation method and override the forwardInvocation(_:) and methodSignatureForSelector: methods to handle messages that it doesn’t implement itself

NSProxy可以說除了過載訊息轉發機制外沒有別的用法,這也是它被設計的初衷,自己什麼都不幹,轉給代理物件就好。往 proxy 發訊息是註定會走訊息轉發的。

文件規定,NSProxy 子類必須重寫這兩個方法進行訊息轉發:

- (void)forwardInvocation:(NSInvocation *)anInvocation;
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel;
複製程式碼

NSProxy 和 NSObject 差異

兩者同樣都可以作為訊息轉發建立的代理類,但是存在一定的差異。

NSProxy 自動轉發

通過繼承自 NSObject 的代理類是不會自動轉發 respondsToSelector:和isKindOfClass: 這兩個方法的, 而繼承自 NSProxy 的代理類卻是可以的.

NSObject 的所有 Category 方法不能完成轉發

例如 valueForKey: 是定義在 NSKeyValueCoding 這個 NSObject 的 Category 中的方法.

通過繼承自 NSObject 的代理類,無法完成 NSObject 的 Category 裡方法的轉發。

原因是 NSObject 具備了這樣的介面, 而訊息轉發是隻有當接收者無法處理時,才會走 forwardInvocation: 方法。

NSProxy 模擬多繼承

NSProxy 可以通過訊息的轉發,模擬多繼承的效果。讓 proxy 接受處理多個不同 class 裡的訊息。

YYWeakProxy 實現

YYWeakProxy 作為 NSProxy 的子類, 必須 實現 forwardInvocation:methodSignatureForSelector: 方法進行物件轉發,這是在蘋果官方文件中說明的。

以開頭的場景為例子,當傳送訊息時,proxy 的方法列表裡找不到 tick: ,就會開始走訊息轉發。

訊息轉發機制

當物件接受到無法響應的訊息時,就會進入訊息轉發(message forwarding)的流程。

轉發流程

Dynamic method resolution

第一階段,先徵詢訊息接收者所屬的類,看其是否能動態新增方法,以處理當前這個無法響應的 selector,這叫做 動態方法解析(dynamic method resolution)。

如果執行期系統(runtime system) 第一階段執行結束,接收者就無法再以動態新增方法的手段來響應訊息,進入第二階段。

呼叫以下兩個方法,對其進行解析,動態增加方法。

+ (BOOL)resolveInstanceMethod:(SEL)sel

+ (BOOL)resolveClassMethod:(SEL)sel
複製程式碼

首先,系統會呼叫resolveInstanceMethod(當然,如果這個方法是一個類方法,就會呼叫resolveClassMethod)讓你自己為這個方法增加實現。

Fast forwarding

第二階段,看看有沒有其他物件能處理此訊息。

如果有,執行期系統會把訊息轉發給那個物件,轉發過程結束; 如果沒有,則啟動完整的訊息轉發機制。

- (id)forwardingTargetForSelector:(SEL)aSelector;
複製程式碼
Normal forwarding

第三階段,完整的訊息轉發機制。執行期系統會把與訊息有關的全部細節,都封裝到 NSInvocation 物件中,再給接收者最後一次機會,令其設法解決當前還未處理的訊息。

- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector;

- (void)forwardInvocation:(NSInvocation *)invocation;
複製程式碼

methodSignatureForSelector用來生成方法簽名,這個簽名就是給forwardInvocation中的引數NSInvocation呼叫的。

如果 methodSignatureForSelector: 返回nil,Runtime則會發出doesNotRecognizeSelector:訊息,程式這時也就掛掉了。

YYWeakProxy 的訊息轉發實現

forwardingTargetForSelector

- (id)forwardingTargetForSelector:(SEL)selector {
    return _target;
}
複製程式碼

forwardingTargetForSelector 會返回你需要轉發訊息的物件,假如返回的是 nil,那麼就走到 forwardInvocation: 做轉發處理。

這裡返回的是 _target 物件,那麼實際就是呼叫 _target 對應的 selector 。

methodSignatureForSelector

- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector {
    return [NSObject instanceMethodSignatureForSelector:@selector(init)];
}
複製程式碼

這裡返回的是 NSObject 的 init 方法的簽名。

forwardInvocation

這裡只進行對 invocation 設定了一個返回值的型別,卻並沒有做轉發。

- (void)forwardInvocation:(NSInvocation *)invocation {
    void *null = NULL;
    [invocation setReturnValue:&null];
}
複製程式碼
setReturnValue
- (void)setReturnValue:(void *)retLoc;
複製程式碼

蘋果文件 對於 setReturnValue 的解釋是:

Sets the receiver’s return value.

設定訊息接受者的返回值。

這裡應該是沒有返回值。

YYWeakProxy 的實際指向

由於在 forwardingTargetForSelector: 方法裡,返回的實際上是 weak 修飾的 target 。

YYWeakProxy *proxy = [YYWeakProxy proxyWithTarget:self];
複製程式碼

所以這段程式碼裡,target 就是 weak 修飾的 self。

這就意味著當 self 引用計數為 0 的時候, target 將被置為 nil.從而打破迴圈引用.

疑問

根據訊息轉發的機制,我們知道,如果 forwardingTargetForSelector: 方法裡,返回了不為 nil 的物件。那麼就不會進入後面的轉發方法。

YYWeakProxy 裡重寫的 methodSignatureForSelectorforwardInvocation 方法內容,又是有什麼具體的作用呢?

參考: 使用NSProxy和NSObject設計代理類的差異 NSProxy——少見卻神奇的類 Objective-C中的訊息轉發機制