事件流 - Event Flow

LionEvents是雄獅資訊目前 iOS 行動開發使用的一個框架,它實踐了 iOS 所沒有的事件機制,效能比 KVO 以及 Notification 還要快,目前的版本還少了 weak 弱引用的宣告,所以在 ARC 中還有些記憶體釋放的問題,在短時間內會盡快改版(等到我有空時)。

事件流是觀察者模式的一種實踐,觀察者與被觀察者在事件流中,被稱為「偵聽者」與「被偵聽者」。

不用通過NSNotificationCenter來發送通知,而是自己本身就當成「事件通知」的角色,在事件流裡又稱為「廣播事件」。

我們再打開 PhotoView.swift 並且先引用LionEvents框架。

import LionEvents

我們想要讓PhotoView也能像按鈕一樣有TouchUpInside的功能,我們跟之前實踐通知的方式一樣,在PHotoView.swift也宣告一個靜態常數字串來當事件名稱:

static let TOUCH_UP_INSIDE:String = "touchUpInside"

然後我們再找到之前覆寫的touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?),在self.delegate?.onTouchUpInside(_touchendPoint)的下面再追加一行:

    override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
        let _touch:UITouch = touches.first!
        let _touchendPoint:CGPoint = _touch.locationInView(self)
        if _touchendPoint.x > 0 && _touchendPoint.x < self.bounds.width && _touchendPoint.y > 0 && _touchendPoint.y < self.bounds.height {
            self.delegate?.onTouchUpInside(_touchendPoint)
            let _event:Event = Event(aType:PhotoView.TOUCH_UP_INSIDE, aBubbles: true)
            _event.information = _touchendPoint
            self.dispatchEvent(_event)
        }
    }

這時候,只要手指在PhotoView裡面按下又在裡面抬起來的時候,就會「廣播」一個叫做PhotoView.TOUCH_UP_INSIDE的事件。

他的功能跟之前的委派有點類似,但是耦合性卻是更低,我們再打開MainViewController.swift,先引入LionEvents框架,然後再改寫剛剛的createDemoLayoutSecond,在裡面對PhotoView註冊PhotoView.TOUCH_UP_INSIDE事件。

不過別忘記導入LionEvents框架。

    private func createDemoLayoutSecond(){
        NSNotificationCenter.defaultCenter().addObserver(self, selector:"onFlickrDataCompleteHandler:", name: FlickrModel.FLICKR_DATA_COMPLETE, object: nil)

        mFlickrModel.loadFlickrData(1)

        for _index:UInt in 0..<10 {
            let _photoView  :PhotoView = PhotoView()
            let _boardWidth :CGFloat = 5
            let _dWidth     :CGFloat = (self.view.bounds.width - _boardWidth * 5)  / 4
            let _dHeight    :CGFloat = (self.view.bounds.height - _boardWidth * 6) / 5
            let _dX         :CGFloat = _boardWidth + CGFloat(_index % 4) * (_dWidth + _boardWidth)
            let _dY         :CGFloat = 50 + CGFloat(_index / 4) * (_dHeight + _boardWidth)
            _photoView.frame = CGRectMake( _dX, _dY , _dWidth, _dHeight)

            self.view.addSubview(_photoView)
            mPhotoViews.append(_photoView)
            _photoView.addEventListener(PhotoView.TOUCH_UP_INSIDE, onPhotoViewTouchHandler)
        }
    }
    private func onPhotoViewTouchHandler(aEvent:Event){
        print("\(aEvent.type),target:\(aEvent.target),currentTarget:\(aEvent.currentTarget)")
        if let _photoView:PhotoView = aEvent.target as? PhotoView {
            _photoView.setHighlight(true)
        }
    }

在觀察者模式下,都要記得deinit時要移除觀察者,也就是事件流下的偵聽者:

    deinit {
        NSNotificationCenter.defaultCenter().removeObserver(self)

        for _view:PhotoView in mPhotoViews {
            _view.removeEventListener(PhotoView.TOUCH_UP_INSIDE)
        }
    }

此時可以發現點擊之後,所選的PhotoView實體物件都會被設為高光背景了。

事件還有一個強大的地方,稱為冒泡。冒泡是針對於視覺顯示物件才有的一個機制,視覺物件往往會被一層一層的顯示容器包著,以現在的MainViewController來說,我們執行時看到的藍色底的PhotoView,是被MainViewControllerView包著的,我們要顯示PhotoView都需要執行這一行程式碼才可以self.view.addSubview(_photoView)

每一個PhotoView都會廣播PhotoView.TOUCH_UP_INSIDE這個名稱的事件,而MainViewControllerView「包住」了這些PhotoView,而廣報的事件會冒泡的話,就是包著PhotoView的外面一層的顯示容器,也可以接收到這個事件,就像泡泡在水裡冒出來一樣。

我們在將程式碼改寫成這樣:

    private func createDemoLayoutSecond(){
        NSNotificationCenter.defaultCenter().addObserver(self, selector:"onFlickrDataCompleteHandler:", name: FlickrModel.FLICKR_DATA_COMPLETE, object: nil)

        mFlickrModel.loadFlickrData(1)

        for _index:UInt in 0..<10 {
            let _photoView  :PhotoView = PhotoView()
            let _boardWidth :CGFloat = 5
            let _dWidth     :CGFloat = (self.view.bounds.width - _boardWidth * 5)  / 4
            let _dHeight    :CGFloat = (self.view.bounds.height - _boardWidth * 6) / 5
            let _dX         :CGFloat = _boardWidth + CGFloat(_index % 4) * (_dWidth + _boardWidth)
            let _dY         :CGFloat = 50 + CGFloat(_index / 4) * (_dHeight + _boardWidth)
            _photoView.frame = CGRectMake( _dX, _dY , _dWidth, _dHeight)

            self.view.addSubview(_photoView)
            mPhotoViews.append(_photoView)
            //_photoView.addEventListener(PhotoView.TOUCH_UP_INSIDE, onPhotoViewTouchHandler)
        }
        self.view.addEventListener(PhotoView.TOUCH_UP_INSIDE, onPhotoViewTouchHandler)
    }
    deinit {
        NSNotificationCenter.defaultCenter().removeObserver(self)

//        for _view:PhotoView in mPhotoViews {
//            _view.removeEventListener(PhotoView.TOUCH_UP_INSIDE)
//        }
        self.view.removeEventListener(PhotoView.TOUCH_UP_INSIDE)
    }

我們可以只要對 MainViewControllerView 的顯示容器偵聽他裡面包含的全部的顯示物件是否有廣播PhotoView.TOUCH_UP_INSIDE事件。

實際上操作看看,這一次每次點擊後, Console 所列印出來的 Event.target以及Event.currentTarget就都不同了。

  • Event.currentTarget:被偵聽的實體

  • Event.target:廣播該事件的實體

完成到這一步的Demo:

Last updated