Swift 实现一个兼容iOS、tvOS、OSX的抽象层

级别: ★☆☆☆☆
标签:「iOS」「Swift」「抽象层」
作者: dac_1033
审校: QiShare团队


有时开发一个工具包或者一个framework时,会要求兼容iOS、tvOS、OSX等苹果相关的平台,这些平台里的库、类及方法的名字和功能都很相近,如果想封装一套代码可以同时运行在三个平台上,那么就需要对相关的库、类及方法进行一个简单的抽象。在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里就可以了。


推荐文章:
iOS Password AutoFill
iOS 给UILabel添加点击事件
用SwiftUI给视图添加动画
用SwiftUI写一个简单页面
Swift 5.1 (7) - 闭包
iOS App启动优化(三)—— 自己做一个工具监控App的启动耗时
iOS App启动优化(二)—— 使用“Time Profiler”工具监控App的启动耗时
iOS App启动优化(一)—— 了解App的启动流程

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,233评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,013评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,030评论 0 241
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,827评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,221评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,542评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,814评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,513评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,225评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,497评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,998评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,342评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,986评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,812评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,560评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,461评论 2 266

推荐阅读更多精彩内容

  • 有时开发一个工具包或者一个framework时,会要求兼容iOS、tvOS、OSX等苹果相关的平台,这些平台里的库...
    大成小栈阅读 335评论 2 3
  • 痛苦的根源,到底是来自于哪里?我们很多时候觉得自己会非常痛苦,可能是我们没有办法跳出过去的回忆,以及对未来的追求。...
    lxmic阅读 277评论 0 0
  • 近日做课件,朋友发来模板让我参考,陪我幼稚,陪我面对问题,站在我这边,和我一起想办法,怪不得近日眼睛湿润,我的神先...
    九思猫阅读 192评论 0 0