Swift 實現一個兼容iOS、tvOS、OSX的抽象層

級別: ★☆☆☆☆
標籤:「iOS」「Swift」「抽象層」
做者: dac_1033
審校: QiShare團隊php


有時開發一個工具包或者一個framework時,會要求兼容iOS、tvOS、OSX等蘋果相關的平臺,這些平臺裏的庫、類及方法的名字和功能都很相近,若是想封裝一套代碼能夠同時運行在三個平臺上,那麼就須要對相關的庫、類及方法進行一個簡單的抽象。在git上學習三方庫源碼的過程當中,遇到過相似的實現,大體的抽象過程實現以下:git

import Foundation

#if os(iOS) || os(tvOS)

import UIKit

    // MARK: - 關於類型

    public typealias NSUIScreen = UIScreen

	public typealias NSUIFont = UIFont
	public typealias NSUIColor = UIColor
	public typealias NSUIEvent = UIEvent
	public typealias NSUITouch = UITouch
	public typealias NSUIImage = UIImage

	public typealias NSUIGestureRecognizer = UIGestureRecognizer
    public typealias NSUIGestureRecognizerState = UIGestureRecognizer.State
	public typealias NSUIGestureRecognizerDelegate = UIGestureRecognizerDelegate
	public typealias NSUITapGestureRecognizer = UITapGestureRecognizer
	public typealias NSUIPanGestureRecognizer = UIPanGestureRecognizer

#if !os(tvOS)
    public typealias NSUIPinchGestureRecognizer = UIPinchGestureRecognizer
    public typealias NSUIRotationGestureRecognizer = UIRotationGestureRecognizer
#endif
    
	public typealias NSUIDisplayLink = CADisplayLink


    // MARK: - 關於類型方法擴展

    extension NSUITapGestureRecognizer {
        
        @objc final func nsuiNumberOfTouches() -> Int {
            return numberOfTouches
        }
        
        @objc final var nsuiNumberOfTapsRequired: Int {
            get {
                return self.numberOfTapsRequired
            }
            set {
                self.numberOfTapsRequired = newValue
            }
        }
    }
    
    extension NSUIPanGestureRecognizer {
        
        @objc final func nsuiNumberOfTouches() -> Int {
            return numberOfTouches
        }
        
        @objc final func nsuiLocationOfTouch(_ touch: Int, inView: UIView?) -> CGPoint {
            return super.location(ofTouch: touch, in: inView)
        }
    }
    
#if !os(tvOS)
    extension NSUIRotationGestureRecognizer
    {
        @objc final var nsuiRotation: CGFloat
        {
            get { return rotation }
            set { rotation = newValue }
        }
    }
#endif
    
#if !os(tvOS)
    extension NSUIPinchGestureRecognizer
    {
        @objc final var nsuiScale: CGFloat
        {
            get
            {
                return scale
            }
            set
            {
                scale = newValue
            }
        }
        
        @objc final func nsuiLocationOfTouch(_ touch: Int, inView: UIView?) -> CGPoint
        {
            return super.location(ofTouch: touch, in: inView)
        }
    }
#endif

    extension UIView
    {
        @objc final var nsuiGestureRecognizers: [NSUIGestureRecognizer]?
        {
            return self.gestureRecognizers
        }
    }
    
    extension UIScreen
    {
        @objc final var nsuiScale: CGFloat
        {
            return self.scale
        }
    }


    // MARK: - 定義新類NSUIView

	open class NSUIView: UIView
    {
		public final override func touchesBegan(_ touches: Set<NSUITouch>, with event: NSUIEvent?)
        {
			self.nsuiTouchesBegan(touches, withEvent: event)
		}

		public final override func touchesMoved(_ touches: Set<NSUITouch>, with event: NSUIEvent?)
        {
			self.nsuiTouchesMoved(touches, withEvent: event)
		}

		public final override func touchesEnded(_ touches: Set<NSUITouch>, with event: NSUIEvent?)
        {
			self.nsuiTouchesEnded(touches, withEvent: event)
		}

		public final override func touchesCancelled(_ touches: Set<NSUITouch>, with event: NSUIEvent?)
        {
			self.nsuiTouchesCancelled(touches, withEvent: event)
		}

		@objc open func nsuiTouchesBegan(_ touches: Set<NSUITouch>, withEvent event: NSUIEvent?)
        {
			super.touchesBegan(touches, with: event!)
		}

		@objc open func nsuiTouchesMoved(_ touches: Set<NSUITouch>, withEvent event: NSUIEvent?)
        {
			super.touchesMoved(touches, with: event!)
		}

		@objc open func nsuiTouchesEnded(_ touches: Set<NSUITouch>, withEvent event: NSUIEvent?)
        {
			super.touchesEnded(touches, with: event!)
		}

		@objc open func nsuiTouchesCancelled(_ touches: Set<NSUITouch>?, withEvent event: NSUIEvent?)
        {
			super.touchesCancelled(touches!, with: event!)
		}

		@objc var nsuiLayer: CALayer?
        {
			return self.layer
		}
	}

#endif


#if os(OSX)
	import Cocoa
	import Quartz

  public typealias NSUIScreen = NSScreen
    
	public typealias NSUIFont = NSFont
	public typealias NSUIColor = NSColor
	public typealias NSUIEvent = NSEvent
	public typealias NSUITouch = NSTouch
	
	public typealias NSUIGestureRecognizer = NSGestureRecognizer
	public typealias NSUIGestureRecognizerState = NSGestureRecognizer.State
	public typealias NSUIGestureRecognizerDelegate = NSGestureRecognizerDelegate
	public typealias NSUITapGestureRecognizer = NSClickGestureRecognizer
	public typealias NSUIPanGestureRecognizer = NSPanGestureRecognizer
	public typealias NSUIPinchGestureRecognizer = NSMagnificationGestureRecognizer
	public typealias NSUIRotationGestureRecognizer = NSRotationGestureRecognizer
	
    
	public class NSUIDisplayLink
    {
        private var timer: Timer?
        private var displayLink: CVDisplayLink?
        private var _timestamp: CFTimeInterval = 0.0
        
        private weak var _target: AnyObject?
        private var _selector: Selector
        
        public var timestamp: CFTimeInterval
        {
            return _timestamp
        }

		init(target: AnyObject, selector: Selector)
        {
            _target = target
            _selector = selector
            
            if CVDisplayLinkCreateWithActiveCGDisplays(&displayLink) == kCVReturnSuccess
            {
                
                CVDisplayLinkSetOutputCallback(displayLink!, { (displayLink, inNow, inOutputTime, flagsIn, flagsOut, userData) -> CVReturn in
                    
                    let _self = unsafeBitCast(userData, to: NSUIDisplayLink.self)
                    
                    _self._timestamp = CFAbsoluteTimeGetCurrent()
                    _self._target?.performSelector(onMainThread: _self._selector, with: _self, waitUntilDone: false)
                    
                    return kCVReturnSuccess
                    }, Unmanaged.passUnretained(self).toOpaque())
            }
            else
            {
                timer = Timer(timeInterval: 1.0 / 60.0, target: target, selector: selector, userInfo: nil, repeats: true)
            }
		}
        
        deinit
        {
            stop()
        }

		open func add(to runloop: RunLoop, forMode mode: RunLoopMode)
        {
            if displayLink != nil
            {
                CVDisplayLinkStart(displayLink!)
            }
            else if timer != nil
            {
                runloop.add(timer!, forMode: mode)
            }
		}

		open func remove(from: RunLoop, forMode: RunLoopMode)
        {
            stop()
		}
        
        private func stop()
        {
            if displayLink != nil
            {
                CVDisplayLinkStop(displayLink!)
            }
            if timer != nil
            {
                timer?.invalidate()
            }
        }
	}
    
    
	extension NSUITapGestureRecognizer
    {
		final func nsuiNumberOfTouches() -> Int
        {
			return 1
		}
        
		final var nsuiNumberOfTapsRequired: Int
        {
			get
            {
				return self.numberOfClicksRequired
			}
			set
            {
				self.numberOfClicksRequired = newValue
			}
		}
	}

	extension NSUIPanGestureRecognizer
    {
		final func nsuiNumberOfTouches() -> Int
        {
			return 1
		}
        
        /// FIXME: Currently there are no more than 1 touch in OSX gestures, and not way to create custom touch gestures.
		final func nsuiLocationOfTouch(_ touch: Int, inView: NSView?) -> NSPoint
        {
			return super.location(in: inView)
		}
    }
    
    extension NSUIRotationGestureRecognizer
    {
        /// FIXME: Currently there are no velocities in OSX gestures, and not way to create custom touch gestures.
        final var velocity: CGFloat
        {
            return 0.1
        }
        
        final var nsuiRotation: CGFloat
        {
            get { return -rotation }
            set { rotation = -newValue }
        }
    }
    
    extension NSUIPinchGestureRecognizer
    {
        final var nsuiScale: CGFloat
        {
            get
            {
                return magnification + 1.0
            }
            set
            {
                magnification = newValue - 1.0
            }
        }
        
        /// FIXME: Currently there are no more than 1 touch in OSX gestures, and not way to create custom touch gestures.
        final func nsuiLocationOfTouch(_ touch: Int, inView view: NSView?) -> NSPoint
        {
            return super.location(in: view)
        }
    }

	extension NSView
    {
		final var nsuiGestureRecognizers: [NSGestureRecognizer]?
        {
			return self.gestureRecognizers
		}
	}
    
    extension NSScreen
    {
        final var nsuiScale: CGFloat
        {
            return self.backingScaleFactor
        }
    }
    
    
	open class NSUIView: NSView
    {
		public final override var isFlipped: Bool
        {
			return true
		}

		func setNeedsDisplay()
        {
			self.setNeedsDisplay(self.bounds)
		}

        
		public final override func touchesBegan(with event: NSEvent)
        {
			self.nsuiTouchesBegan(event.touches(matching: .any, in: self), withEvent: event)
		}

		public final override func touchesEnded(with event: NSEvent)
        {
			self.nsuiTouchesEnded(event.touches(matching: .any, in: self), withEvent: event)
		}

		public final override func touchesMoved(with event: NSEvent)
        {
			self.nsuiTouchesMoved(event.touches(matching: .any, in: self), withEvent: event)
		}

		open override func touchesCancelled(with event: NSEvent)
        {
			self.nsuiTouchesCancelled(event.touches(matching: .any, in: self), withEvent: event)
		}

		open func nsuiTouchesBegan(_ touches: Set<NSUITouch>, withEvent event: NSUIEvent?)
        {
			super.touchesBegan(with: event!)
		}

		open func nsuiTouchesMoved(_ touches: Set<NSUITouch>, withEvent event: NSUIEvent?)
        {
			super.touchesMoved(with: event!)
		}

		open func nsuiTouchesEnded(_ touches: Set<NSUITouch>, withEvent event: NSUIEvent?)
        {
			super.touchesEnded(with: event!)
		}

		open func nsuiTouchesCancelled(_ touches: Set<NSUITouch>?, withEvent event: NSUIEvent?)
        {
			super.touchesCancelled(with: event!)
        }
        
		open var backgroundColor: NSUIColor?
        {
            get
            {
                return self.layer?.backgroundColor == nil
                    ? nil
                    : NSColor(cgColor: self.layer!.backgroundColor!)
            }
            set
            {
                self.wantsLayer = true
                self.layer?.backgroundColor = newValue == nil ? nil : newValue!.cgColor
            }
        }

		final var nsuiLayer: CALayer?
        {
			return self.layer
		}
	}
    
#endif
複製代碼
  1. 用宏定義 #if 來判斷os(iOS)、os(tvOS)、os(OSX),以區分不一樣平臺;
  2. 在swift中,能夠用typealias對不一樣平臺上的類型起一樣的別名,如NSUIFont做爲別名分別對應UIFont和NSFont;
  3. 在swift中,能夠用 extension 擴展類的方法以達到在不一樣平臺上,類中方法同名的目的。例如在使用platform這個類的地方,調用NSUIScreen. nsuiScale;
  4. 在上述代碼中,os(OSX)平臺上沒有的UIDisplayLink對應的雷,可是代碼中全新定義了一個NSUIDisplayLink,NSUIDisplayLink中的方法也與UIDisplayLink中相應方法同名。

這樣,一個簡單的跨平臺抽象層就能夠使用了,在須要這個platform時,若是你的代碼裏還用到其餘誇端使用的類型,加載platform裏就能夠了。github


瞭解更多iOS及相關新技術,請關注咱們的公衆號:objective-c

小編微信:可加並拉入《QiShare技術交流羣》。swift

關注咱們的途徑有:
QiShare(簡書)
QiShare(掘金)
QiShare(知乎)
QiShare(GitHub)
QiShare(CocoaChina)
QiShare(StackOverflow)
QiShare(微信公衆號)微信

推薦文章:
iOS Password AutoFill
iOS 給UILabel添加點擊事件
用SwiftUI給視圖添加動畫
用SwiftUI寫一個簡單頁面
Swift 5.1 (7) - 閉包
iOS App啓動優化(三)—— 本身作一個工具監控App的啓動耗時
iOS App啓動優化(二)—— 使用「Time Profiler」工具監控App的啓動耗時
iOS App啓動優化(一)—— 瞭解App的啓動流程
奇舞週刊閉包

相關文章
相關標籤/搜索