[Swift]正确的使用 extension 的方式

6,109 阅读3分钟

官网文档:Swift - Extensions

extension可以做什么:

  • Add computed instance properties and computed type properties (添加计算型实例属性和计算型类型属性)
  • Define instance methods and type methods (定义实例方法和类型方法)
  • Provide new initializers (提供新的构造器)
  • Define subscripts (定义下标)
  • Define and use new nested types (定义和使用新的嵌套类型)
  • Make an existing type conform to a protocol (使已存在的类型遵守某个协议)

几个比较方便的 extension 使用技巧

  • 1 .划分区域,区分代码功能

    • 比如:

      class AppDelegate: UIResponder {
      
      var window: UIWindow?
      }
      ///实现 App 的声明周期
      extension AppDelegate: UIApplicationDelegate {
          func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
              return true
          }
          func applicationWillResignActive(_ application: UIApplication) {
          }
      }
      extension AppDelegate {
          func thirdSetting() {
              ///配置第三方信息
          }
      }
      
      class ViewController: UIViewController {
          fileprivate (set) var tableView: UITableView!
      }
      typealias DataSource = ViewController
      extension DataSource: UITableViewDataSource, UITableViewDelegate {
          public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
              ///code
          }
      
          public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
              ///code
          }
      }
      
  • 2.像 OC 一样添加分类

    OC的 Category 添加类方法和对象方法时一般会在方法前添加前缀,比如 SDWebImage 中的 UIImageView+WebCache 方法都是以 sd_开头,ReactiveObjC都是以 rac_开头,当然 swift 的 extension 也可以这样使用前缀来区分系统方法和自定义方法,不过很多大神已经不这么做了,比如 RxCocoa 里的 rx,Kinfisher 的 kf.使用如以下代码:

        ///Rx
        let textFiled = UITextField()
        ///textFiled的 text 有变化的时候会调用 block
        let dispose = textFiled.rx.text.subscribe(onNext: { (text) in
            print(text)
        }, onError: { (error) in
            
        }, onCompleted: {
            
        }, onDisposed: nil)
        dispose.dispose()
    
        ///Kingfisher
        let imgView = UIImageView()
        ///异步设置图片
        let url = URL(string: "xxx")
        imgView.kf.setImage(with: url)
    

    查看 Kinfisher 和 RxCocoa 的源码,这个 rx 和 kf 大概是以下这种形式的代码:

    ///这个就是 kf 和 rx 的定义
    ///
    public struct AriSwift<Base> {
        public let base: Base
        public init(_ base: Base) {self.base = base}
    }
    public protocol AriSwiftCompatible {
        associatedtype CompatibleType
        static var `as`: AriSwift<CompatibleType>.Type { get set }
        var `as`: AriSwift<CompatibleType> { get set }
    }
    
    public extension AriSwiftCompatible {
        static var `as`: AriSwift<Self>.Type {
            get {return AriSwift<Self>.self}
            set {}
        }
        var `as`: AriSwift<Self> {
            get {return AriSwift(self)}
            set {}
        }
    }
    ///为所有继承 NSObject的类添加 as 的实例对象和类对象
    extension NSObject: AriSwiftCompatible {}
    
    ///给AriSwift 添加Base是 UIScreen 的拓展
    public extension AriSwift where Base: UIScreen {
        public static var width: CGFloat {return UIScreen.main.bounds.width}
        public static var height: CGFloat {return UIScreen.main.bounds.height}
    }
    
    ///调用 
    UIScreen.as.width /// 414.0
    

    在 Swift 中如果经常会用到结构体,为结构体Base 为结构体的对象添加分类时就不能使用 Base: XX 这种形式了,以 String 为例,代码如下

    ///记得 String 要遵守下这个协议
    extension String: AriSwiftCompatible {}
    extension AriSwift where Base == String {
        ///传入时间(秒)返回字符串格式
        ///```
        ///e.g.  180 -> "03:00"
        ///```
        static func formatTime(with seconds: Int) -> String {
            let hour = seconds / 3600
            let minut = (seconds % 3600)/60
            let second = seconds % 60
            if hour == 0 {
                return String(format: "%02zd:%02zd", minut,second)
            }else{
                return String(format: "%zd:%02zd:%02zd",hour,minut,second)
            }
        }
    }
    

    使用这种方式添加extension 能很好的区分自己写的代码和系统的代码,也可以代替前缀.

    还可以使用这个方法在我们的项目中封装 Objc 的第三方框架,比如 MJRefresh,MBProgressHUD

    import AriSwift
    import MJRefresh
    extension AriSwift where Base: UIScrollView {
        func addHeader(with target: Any, action: Selector){
            base.mj_header = MJRefreshNormalHeader(refreshingTarget: target, refreshingAction: action)
        }
        func addHeader(with action: @escaping (()->()) ) {
            base.mj_header = MJRefreshNormalHeader(refreshingBlock: {
                action()
            })
        }
        func addFooter(with target: Any, action: Selector){
            base.mj_footer = MJRefreshBackStateFooter(refreshingTarget: target, refreshingAction: action)
        }
        func addFooter(with action: @escaping (()->()) ){
            base.mj_footer = MJRefreshBackStateFooter(refreshingBlock: {
                action()
            })
        }
        func headerEndRefreshing(){
            base.mj_header.endRefreshing()
        }
        func footerEndRefreshing(){
            base.mj_footer.endRefreshing()
        }
        func footerEndRefreshingWithNoMoreData(){
            base.mj_footer.endRefreshingWithNoMoreData()
        }
    }
    
    import AriSwift
    import MBProgressHUD
    extension AriSwift where Base: UIViewController {
        func showHud(message: String, to view: UIView? = nil) -> MBProgressHUD{
            let targetView = view ?? UIApplication.shared.keyWindow!
            let hud = MBProgressHUD.showAdded(to: targetView, animated: true)
            hud.label.text = message
            hud.removeFromSuperViewOnHide = true
            return hud
        }
    }
    

    这样也可以减少项目中使用这些第三方的地方频繁的 import

    p.s. 我自己制作常用的 extension 工具库 AriSwift

    git: github.com/AnnieAri/Ar…

    也可以使用 pod 'AriSwift' 安装到项目中