你好,歡迎來到IOS教程網

 Ios教程網 >> IOS編程開發 >> IOS開發基礎 >> 在 OC 中實現消息的一箭雙雕

在 OC 中實現消息的一箭雙雕

編輯:IOS開發基礎

很慚愧,標題很浮誇,其實就是消息轉發啦。想這個標題想了很久,本想著取個形象生動有意思的標題,不想水平不夠,於是就搞了這麼個奇怪的名字出來。但今天這篇博客的確有那麼點一箭雙雕的意思。

擺事實,講道理。我們先看這麼一個場景。我正在寫一個自定義的 TableView 滾動條,可以實時顯示當前 Cell 的編號,比原生的黑條更具可視性。

blob.png

實現原理也很簡單,KVO 監聽 contentOffset 然後根據 public func indexPathForRowAtPoint(point: CGPoint) -> NSIndexPath? 返回的 indexPath 顯示編號。contentOffset , contentSize 倒是可以用 KVO,可是否正在滾動、是否正在減速、手指是否離開這些特征盡管有 tracking,dragging,decelerating等屬性可以獲取,但是無法 KVO ,也就是不能實時獲取了。所以只能在回調方法:

optional public func scrollViewWillBeginDragging(scrollView: UIScrollView)
optional public func scrollViewDidEndDragging(scrollView: UIScrollView, willDecelerate decelerate: Bool)
optional public func scrollViewDidEndDecelerating(scrollView: UIScrollView)

裡獲取。

為了隱藏細節使暴露的接口盡可能簡單,通常可以把這些代理方法寫在滾動條裡面,也就是讓滾動條成為 tableView 的代理。說到這裡你可能知道問題所在了。如果這時其他類也需要在這些代理方法裡干點什麼(比如有一個刷新控件也需要通過 scrollViewDidEndDragging 監聽手指釋放時機),而這些方法卻被滾送條單獨占有了,顯然就不行了。況且由於 UITableView 的 delegate 中還有 tableView 特有的協議方法(heightForRowAtIndexPath...),一旦 delagte 設置成滾動條了,那麼 ViewController 將無法實現 TableView 的相應功能。

那麼有沒有什麼辦法可以既在滾動條內部監聽這些協議方法,又在 ViewController 裡面監聽這些方法呢?簡而言之就是設置"兩個代理"。

消息轉發。

實現的效果就像下面這樣。

blob.png

我們只需要使用 NSObject 裡面的這兩個方法就可以實現:

- (void)forwardInvocation:(NSInvocation *)anInvocation OBJC_SWIFT_UNAVAILABLE("");
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector OBJC_SWIFT_UNAVAILABLE("");

我們有時會遇到 unrecognized selector sent to instance 的崩潰,原因就是你給一個對象發送了它不具有的消息。而在此之前,運行時其實會去調用一次 - (void)forwardInvocation:(NSInvocation *)anInvocation。顧名思義,這個方法就是用來轉發消息的。你可以實現forwardInvocation:方法來對不能處理的消息做一些默認的處理,以避免程序崩潰。既然可以實現消息的轉發,是不是可以給多個對象發送消息,也就是實現我們上面希望達到的效果 ———— 把一條消息發送給多個對象,即同時激發多個對象的同一個方法。

其實是可以的。我們可以創建一個 delegate 轉發器,作為多個 delegate 的容器,也是對外唯一的 delegate。當消息發送給這個轉發器的時候會觸發對應的 forwardInvocation 方法。我們在這裡進行轉發。

- (void)forwardInvocation:(NSInvocation *)anInvocation{
    SEL aSelector = [anInvocation selector];
    if([self.firstDelegate respondsToSelector:aSelector]){
        [anInvocation invokeWithTarget:self.firstDelegate];
    }
    if([self.secondDelegate respondsToSelector:aSelector]){
        [anInvocation invokeWithTarget:self.secondDelegate];
    }
}

消息可以通過invokeWithTarget:方法發送。

與此同時,我們還需要獲取一下消息的簽名:

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    NSMethodSignature *first = [(NSObject *)self.firstDelegate methodSignatureForSelector:aSelector];
    NSMethodSignature *second = [(NSObject *)self.secondDelegate methodSignatureForSelector:aSelector];
    if(first){
        return first;
    } else if(second) {
        return second;
    }
    return nil;
}

另外,非常重要的一步。因為我們這個 delegate 只是作為一個轉發器使用,不是真正用來實現協議方法的,所以還需要重載 respondsToSelector ,讓消息可以轉發到這個中轉站。

- (BOOL)respondsToSelector:(SEL)aSelector{
    if([self.firstDelegate respondsToSelector:aSelector] || [self.secondDelegate respondsToSelector:aSelector]){
        return YES;
    } else {
        return NO;
    }
}

現在 ViewController 裡的 scrollViewDidEndDragging 和滾動條裡的 scrollViewDidEndDragging 代理方法都可以被調用了。至此就實現了一個消息通知多個對象的功能。曲線救國得實現了「一對多」的功能。如果你有更友好的解決方法,歡迎在評論中告訴我。另外還有一個硬傷,就是以上的 workaroud 不支持 Swift 。歡迎討論。


  1. 上一頁:
  2. 下一頁:
蘋果刷機越獄教程| IOS教程問題解答| IOS技巧綜合| IOS7技巧| IOS8教程
Copyright © Ios教程網 All Rights Reserved