如何构造Swift框架(2)功能编写

[中文教程]
分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友 微信微信
查看查看146 回复回复1 收藏收藏2 分享淘帖 转播转播 分享分享 微信
查看: 146|回复: 1
收起左侧

[中文教程] 如何构造Swift框架(2)功能编写

[复制链接]
Caesar 发表于 2016-10-14 10:20:36 | 显示全部楼层 |阅读模式
快来登录
获取优质的苹果资讯内容
收藏热门的iOS等技术干货
拷贝下载Swift Demo源代码
订阅梳理好了的知识点专辑
本帖最后由 Caesar 于 2016-10-14 10:59 编辑


继上一篇《如何构造Swift框架(1)知识储备》,我们开始编写功能代码。




1、环境管理 ModeSwitch

新建ModeSwitch.swift

[Swift] 纯文本查看 复制代码
import Foundation
/// 开发模式
public enum Mode: String {
  /// 开发模式,打印日志,不上报日志
  case develope = "[DEVELOPE MODE]"
  /// 调试模式,关闭日志打印,上报日志
  case prepared = "[PREPARED MODE]"
  /// 生产模式,关闭日志打印,上报日志
  case production = "[PRODUCTION MODE]"
}
/// 模式控制
public class INSModeSwitch {
  /// 默认为调试模式
  public static let `default` = INSModeSwitch(.develope)
  
  public var currentMode: Mode {
    didSet {
    }
  }
  /// 私有初始化方法
  private init(_ mode: Mode) {
    self.currentMode = mode
  }
}
/// 模式选择器
public let ModeSwitcher = INSModeSwitch.default


目前这是一个极其简单的模式控制,毫无别的作用,以后扩展功能。




2、日志模块-基础日志 Console

第一步我们先完成日志输出,新建Logger.swift

[Swift] 纯文本查看 复制代码
import Foundation
/// 日志级别
public enum LogLevel: String {
  /// 打印所有类型日志
  case all = "[ALL]"
  /// 仅打印调试日志
  case debug = "[DEBUG]"
  /// 仅打印警告日志
  case warning = "[WARN]"
  /// 仅打印信息日志
  case information = "[INFO]"
}
final public class INSLogger {
  /// 默认为输出全部日志
  public static let `default` = INSLogger(.all)
  
  public var level: LogLevel {
    didSet {
      
    }
  }
  
  /// 日志输出
  ///
  /// - parameter lev: 日志级别
  /// - parameter content: 日志内容
  public func printLog(_ lev: LogLevel, _ details: String, _ items: Any) {
    guard level == .all || level == lev, ModeSwitcher.currentMode == .develope else {
      return
    }
    
    print(lev.rawValue, details, "\n", items)
  }
  
  private init(_ level: LogLevel) {
    self.level = level
  }
}
/// 公开日志打印模块
public let Logger = INSLogger.default
/// 公开便捷调用方法
public func ILog(_ lev: LogLevel, _ items: Any, _ function: StaticString = #function, _ line: Int = #line, _ file: StaticString = #file) {
  var details = "[\("\(file)".components(separatedBy: "/").last!): \(function): \(line)]"
  Logger.printLog(lev, details, items)
}


这个时候,我们对前2部分进行简单的测试:

[Swift] 纯文本查看 复制代码
ModeSwitcher.currentMode = .develope
print(ModeSwitcher.currentMode)
    
Logger.level = .warning
    
ILog(.all, "Hello world! Current Mode is \(Logger.level)")
ILog(.debug, "Hello world! Current Mode is \(Logger.level)")
ILog(.warning, "Hello world! Current Mode is \(Logger.level)")
ILog(.information, "Hello world! Current Mode is \(Logger.level)")


.developer情况下会输出相应的日志,.all为输出所有日志,预发、生产模式下不会输出日志。

[Swift] 纯文本查看 复制代码
 [WARN] [ViewController.swift: viewDidLoad(): 24] 
 ["2": "3", "4": "5"]


分别为文件,方法,行号,内容。




3、功能扩展,界面扩展

一些Foundation,UIKit的扩展。新建2个文件夹,分别为FoundationExtension,UIKitExtension。在文件夹内添加这些扩展方法。这一块内容暂时省略,接着往下写功能模块。

UISDK这里我依旧支持AsyncDisplayKit,从这个库诞生我就开始一直使用,所以对于我来说,用这个比UIKit顺手:

[Bash shell] 纯文本查看 复制代码
pod "AsyncDisplayKit"


Foundation扩展先添加Keychain的帮助模块

[Bash shell] 纯文本查看 复制代码
pod 'KeychainAccess'





4、权限服务

权限服务:获得一些系统的权限,新建Permission.swift

[Swift] 纯文本查看 复制代码
import Foundation
import CoreBluetooth
import AddressBook;
import AVFoundation;
import CoreBluetooth;
import CoreLocation;
import CoreMotion;
import EventKit;
import Photos
/// 当前的状态
public enum kPermissionAccess {
  /// 用户拒绝
  case denied
  /// 用户同意
  case granted
  /// 系统设置拉黑等错误
  case restricted
  /// 未知错误
  case unknown
  /// 设备不支持
  case unsupported
  /// 开发者没有导入相应的库
  case missingFramework
  /// 用户没选
  case notDetermined
}
public func hasAccessToBluetoothLE() ->kPermissionAccess {
  switch CBCentralManager().state {
    case .unsupported:
    return .unsupported;
    break;
    
    case .unauthorized:
    return .denied;
    break;
    
    default:
    return .granted;
    break;
  }
}
public func hasAccessToCalendar() ->kPermissionAccess {
  
  switch EKEventStore.authorizationStatus(for: .event) {
  case .authorized:
    return .granted;
    break;
    
  case .denied:
    return .denied;
    break;
    
  case .restricted:
    return .restricted;
    break;
    
  case .notDetermined:
    return .notDetermined;
    break;
    
  default:
    return .unknown;
    break;
  }
}
public func hasAccessToContacts() ->kPermissionAccess {
  switch (ABAddressBookGetAuthorizationStatus()) {
  case .authorized:
    return .granted;
    break;
    
  case .denied:
    return .denied;
    break;
    
  case .restricted:
    return .restricted;
    break;
    
  case .notDetermined:
    return .notDetermined;
    break;
    
  default:
    return .unknown;
    break;
  }
}
public func hasAccessToLocation() ->kPermissionAccess {
  switch CLLocationManager.authorizationStatus() {
  case .authorizedAlways:
    return .granted;
    break;
    
  case .authorizedWhenInUse:
    return .granted;
    break;
    
  case .denied:
    return .denied;
    break;
    
  case .restricted:
    return .restricted;
    break;
    
  case .notDetermined:
    return .notDetermined;
    break;
    
  default:
    return .unknown;
    break;
  }
}
public func hasAccessToPhotos() ->kPermissionAccess {
  
  switch PHPhotoLibrary.authorizationStatus() {
  case .authorized:
    return .granted;
    break;
    
  case .denied:
    return .denied;
    break;
    
  case .restricted:
    return .restricted;
    break;
    
  case .notDetermined:
    return .notDetermined;
    break;
    
  default:
    return .unknown;
    break;
  }
}
public func hasAccessToReminders() ->kPermissionAccess {
  switch EKEventStore.authorizationStatus(for: .reminder) {
  case .authorized:
    return .granted;
    break;
    
  case .denied:
    return .denied;
    break;
    
  case .restricted:
    return .restricted;
    break;
    
  case .notDetermined:
    return .notDetermined;
    break;
    
  default:
    return .unknown;
    break;
  }
}
public typealias requestAccessHandler = (Bool?, Any?) ->()
public func requestAccessToCalendarWithSuccess(_ handler: @escaping requestAccessHandler) {
  EKEventStore().requestAccess(to: .event) {
    handler($0, $1)
  }
}
public func requestAccessToContactsWithSuccess(_ handler: @escaping requestAccessHandler) {
  if let addressBook = ABAddressBookCreateWithOptions(nil, nil) {
    ABAddressBookRequestAccessWithCompletion(addressBook as ABAddressBook!) {
      handler($0, $1)
    }
  }
}
public func requestAccessToMicrophoneWithSuccess(_ handler: @escaping requestAccessHandler) {
  let session = AVAudioSession.sharedInstance()
  session.requestRecordPermission {
    handler($0, nil)
  }
}
public func requestAccessToPhotosWithSuccess(_ handler: @escaping requestAccessHandler) {
  PHPhotoLibrary.requestAuthorization {
    if $0 == .authorized {
      handler(true, nil)
    } else {
      handler(false, nil)
    }
  }
}
public func requestAccessToRemindersWithSuccess(_ handler: @escaping requestAccessHandler) {
  EKEventStore().requestAccess(to: .reminder) {
    handler($0, $1)
  }
}
/// 获取位置访问权限
///
/// - parameter whenInUse: 如果为False,则请求总是需要的权限
public func requestAccessToLocationWithSuccess(_ handler: @escaping requestAccessHandler, _ whenInUse: Bool = true) {
  let locationManager = CLLocationManager()
  if whenInUse, let content = Bundle.main.object(forInfoDictionaryKey: "NSLocationWhenInUseUsageDescription") {
    locationManager.requestWhenInUseAuthorization()
  } else {
    locationManager.requestAlwaysAuthorization()
  }
  handler(true, nil)
}
public func requestAccessToMotionWithSuccess(_ handler: @escaping requestAccessHandler) {
  let motionManager = CMMotionActivityManager()
  let motionQueue = OperationQueue()
  motionManager.startActivityUpdates(to: motionQueue) {_ in 
    handler(true, nil)
    motionManager.stopActivityUpdates()
  }
}


可是你们忘记了,我很懒,所以这段代码不需要了,直接:

[Bash shell] 纯文本查看 复制代码
pod 'PermissionScope'


来嗨吧!

如何构造Swift框架(2)功能编写 1

如何构造Swift框架(2)功能编写 - 敏捷大拇指 - 如何构造Swift框架(2)功能编写 1





5、组件处理(核心模块)

要解决的问题暂时有:

  • 如何成为组件(实现协议与方法即可)
  • 如何启动组件(调用组件必须实现的启动方法)
  • 组件之间如何数据传输(实现Router模拟HTTP请求数据传递)
  • 组件之间如何做界面跳转(利用上述Router模拟OpenURL方式调起)
  • 组件服务如何停止(调用协议实现的方法并从组件管理中心移除注册)


规则1:只有已经注册了的组件才可以成为Router的操作对象,想成为组件则必须有单独类实现组件协议。

规则2:Router仅负责数据传递与获取视图实例(可传值,后者拥有前者的弱引用)

规则3:仅在每个模块的组件中心的代理方法中注册相应的URL供被调用

[Swift] 纯文本查看 复制代码
import Foundation
/// 数据流
public typealias INSDataFlowHandler = (Any...) ->Any
/// 错误的Scheme
fileprivate let INSDataUnknowedScheme = "INSDataUnknowedScheme_Key"
/// 插件协议
public protocol INSPlugin {
  var pluginLoaded: Bool { get }
  /// 插件唯一标识
  /// - returns: 插件标识,用来获取插件
  func pluginScheme() ->String!
  
  // MARK: - Mappings
  
  /// 视图控制器Map
  /// - returns: 视图控制器的对应字典
  func classMapping() ->Dictionary<String, AnyClass>?
  /// 数据流Map
  /// - returns: 数据流字典
  func dataflowMapping() ->Dictionary<String, INSDataFlowHandler>?
  
  // MARK: - Plugin status
  
  /// 当插件注册成功的时候会调用这个方法
  func serviceStarted()
  /// 当插件取消注册的时候会调用这个方法
  func serviceStopped()
}
extension INSPlugin {
  /// 插件加载状态
  public var pluginLoaded: Bool {
    return (ModuleCenter.getPlugin(for: pluginScheme()) != nil)
  }
}
/// 插件基类,成为模块插件管理器则必须继承该类,并实现方法
open class Plugin: INSPlugin {
  final public func registePlugin() {
    ModuleCenter.registePlugin(self)
  }
  
  final public func deRegistePlugin() {
    ModuleCenter.deRegistePlugin(self)
  }
  
  open func pluginScheme() -> String! {
    return nil
  }
  
  open func serviceStopped() {}
  
  open func serviceStarted() {}
  open func dataflowMapping() ->Dictionary<String, INSDataFlowHandler>? {
    return nil
  }
  open func classMapping() ->Dictionary<String, AnyClass>? {
    return nil
  }
  
  public init() {
    // I'm a plug-in manager 
  }
}
/// 插件控制中心
final public class ModuleManager: NSObject {
  public static let `default` = ModuleManager()
  /// 已经加载的插件
  private(set) var loadedPlugins: Dictionary<String, Plugin> = [:]
  
  /// 注册新的插件
  @discardableResult
  public func registePlugin(_ item: Plugin) ->Bool {
    guard let scheme = item.pluginScheme(), loadedPlugins[scheme] == nil else {
      return false
    }
    loadedPlugins[scheme] = item
    /// 注册组件
    item.serviceStarted()
    return true
  }
  
  /// 取消注册插件
  public func deRegistePlugin(_ item: Plugin) {
    guard let scheme = item.pluginScheme() else {
      return
    }
    loadedPlugins.removeValue(forKey: scheme)
    /// 移除组件
    item.serviceStopped()
  }
  
  /// 根据唯一标识获取插件
  public func getPlugin(for scheme: String) ->Plugin? {
    return loadedPlugins[scheme]
  }
  
  /// 数据流
  @discardableResult
  public func send(to url: String, and param: Any...) ->Any? {
    let (scheme, host) = validateURL(url)
    
    guard let pluginItem = getPlugin(for: scheme!), let dataMapping = pluginItem.dataflowMapping() else {
      ILog(.warning, "找不到scheme对应的插件或插件没有dataMapping!")
      return nil
    }
    
    guard let sendHandler = dataMapping[host!] else {
      ILog(.warning, "该路径没有对应的Handler")
      return nil
    }
    
    return sendHandler(param)
  }
  
  /// 视图流
  @discardableResult
  public func `class`(for url: String) ->AnyClass? {
    let (scheme, host) = validateURL(url)
    
    guard let pluginItem = getPlugin(for: scheme!), let dataMapping = pluginItem.classMapping() else {
      ILog(.warning, "找不到scheme对应的插件或插件没有classMapping!")
      return nil
    }
    
    return dataMapping[host!]
  }
  
  @discardableResult
  private func validateURL(_ url:String) ->(String?, String?) {
    guard let URLObject = URL(string: url), let scheme = URLObject.scheme else {
      ILog(.warning, "输入的url不能转换为URL对象或URL对象没有Scheme!")
      return (INSDataUnknowedScheme, "")
    }
    return (scheme, URLObject.host)
  }
  
  private override init() {
    // I'm a module manager 
  }
}
public let ModuleCenter = ModuleManager.default


接下来,我们实现一个小的插件继承自Plugin并实现一些代理方法:

[Swift] 纯文本查看 复制代码
import UIKit
class NetworkPlugin: Plugin {
  override func serviceStarted() {
    ILog(.debug, "Network service started")
  }
  
  override func serviceStopped() {
    ILog(.debug, "Network service stopped")
  }
  
  override func pluginScheme() -> String! {
    return "Network"
  }
  
  override func dataflowMapping() ->Dictionary<String, INSDataFlowHandler>? {
    let mapping: [String: INSDataFlowHandler] = [
      "getData": {
        ILog(.warning, "receive call \($0)")
        return "Custom data"
      },
      "supportMethod": {
        ILog(.warning, "receive call \($0)")
        return [
          "GET",
          "POST",
          "PUT",
          "DELETE"
          ]
      }
    ]
    return mapping
  }
  
  override func classMapping() -> Dictionary<String, AnyClass>? {
    let mapping: [String: AnyClass] = [
      "vc1": UIViewController.self,
      "vc2": NSObject.self
    ]
    return mapping
  }
  
  static let `default` = NetworkPlugin()
}


然后在ViewController.swift中测试:

[Swift] 纯文本查看 复制代码
ModeSwitcher.currentMode = .develope
Logger.level = .all
    
let sharedNetwork = NetworkPlugin.default
sharedNetwork.registePlugin()
    
ILog(.information, ModuleCenter.send(to: "Network://getData", and: "Hello World"))
ILog(.information, ModuleCenter.send(to: "Network://supportMethod", and: "Hello World", "Hello real world!", ["a": "b"]))
ILog(.information, ModuleCenter.class(for: "Network://vc1"))
ILog(.information, ModuleCenter.class(for: "Network://vc2"))
    
sharedNetwork.deRegistePlugin()


看到输出为:

如何构造Swift框架(2)功能编写 2

如何构造Swift框架(2)功能编写 - 敏捷大拇指 - 如何构造Swift框架(2)功能编写 2


说明功能已经基本的完成了。这里并没有将打开界面写入到当前模块,还是感觉直接获取类好一点,所以直接把OpenURL的方式改成了获取类型,然后由使用者自己去创建对象分情况使用,这样自定义的程度也高一点。 如果写为OpenURL的方式,那我们又要分很多种了,push还是present,是否需要导航,考虑到仅获取不使用的情况,要写控制器基类,还有必要统一一个初始化方法供使用。但是这些似乎已经不是一个框架应该做的事情了(仅鉴于我对Swift框架的理解,框架不干涉业务,框架不干涉UI)。

文章篇幅过长并且均为代码,所以本文只写到这里。

下篇文章会开始编写数据模块、网络模块。





相关内容

如何构造Swift框架(1)知识储备

如何构造Swift框架(2)功能编写

如何构造Swift框架(3)数据模块

如何构造Swift框架(4)推送服务

作者:dylan

都看到这里了,就把这篇资料推荐给您的好朋友吧,让他们也感受一下。

回帖是一种美德,也是对楼主发帖的尊重和支持。

*声明:敏捷大拇指是全球最大的Swift开发者社区、苹果粉丝家园、智能移动门户,所载内容仅限于传递更多最新信息,并不意味赞同其观点或证实其描述;内容仅供参考,并非绝对正确的建议。本站不对上述信息的真实性、合法性、完整性做出保证;转载请注明来源并加上本站链接,敏捷大拇指将保留所有法律权益。如有疑问或建议,邮件至marketing@swifthumb.com

*联系:微信公众平台:“swifthumb” / 腾讯微博:@swifthumb / 新浪微博:@swifthumb / 官方QQ一群:343549891(满) / 官方QQ二群:245285613 ,需要报上用户名才会被同意进群,请先注册敏捷大拇指

嗯,不错!期待更多好内容,支持一把:
支持敏捷大拇指,用支付宝支付10.24元 支持敏捷大拇指,用微信支付10.24元

评分

参与人数 1金钱 +10 贡献 +10 专家分 +10 收起 理由
Anewczs + 10 + 10 + 10 32个赞!专家给力!

查看全部评分

转行的 发表于 2016-10-14 17:26:24 | 显示全部楼层
环境切换也要写进来啊,我以为直接在部署时改好代码呢
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

做任务,领红包。
我要发帖

分享扩散

都看到这里了,就把这资料推荐给您的好朋友吧,让他们也感受一下。
您的每一位朋友访问此永久链接后,您都将获得相应的金钱积分奖励
热门推荐

合作伙伴

Swift小苹果

  • 北京治世天下科技有限公司
  • ©2014-2016 敏捷大拇指
  • 京ICP备14029482号
  • Powered by Discuz! X3.1 Licensed
  • swifthumb Wechat Code
  •   
快速回复 返回顶部 返回列表