功能编写
环境管理 ModeSwitch
新建ModeSwitch.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
目前这是一个极其简单的模式控制,毫无别的作用,以后扩展功能
日志模块-基础日志 Console
第一步我们先完成日志输出,新建Logger.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部分进行简单的测试:
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为输出所有日志,预发、生产模式下不会输出日志。
🛢 [WARN] [ViewController.swift: viewDidLoad(): 24]
["2": "3", "4": "5"]
分别为文件,方法,行号,内容。
功能扩展,界面扩展
一些Foundation,UIKit的扩展。新建2个文件夹,分别为FoundationExtension,UIKitExtension。在文件夹内添加这些扩展方法。这一块内容暂时省略,接着往下写功能模块。
UISDK这里我依旧支持AsyncDisplayKit,从这个库诞生我就开始一直使用,所以对于我来说,用这个比UIKit顺手:
pod "AsyncDisplayKit"
Foundation扩展先添加Keychain的帮助模块
pod 'KeychainAccess'
权限服务
权限服务:获得一些系统的权限,新建Permission.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()
}
}
可是你们忘记了,我很懒,所以这段代码不需要了,直接:
pod 'PermissionScope'
来嗨吧!
组件处理(核心模块)
要解决的问题暂时有:
- 如何成为组件(实现协议与方法即可)
- 如何启动组件(调用组件必须实现的启动方法)
- 组件之间如何数据传输(实现Router模拟HTTP请求数据传递)
- 组件之间如何做界面跳转(利用上述Router模拟OpenURL方式调起)
- 组件服务如何停止(调用协议实现的方法并从组件管理中心移除注册)
规则1:只有已经注册了的组件才可以成为Router的操作对象,想成为组件则必须有单独类实现组件协议。
规则2:Router仅负责数据传递与获取视图实例(可传值,后者拥有前者的弱引用)
规则3:仅在每个模块的组件中心的代理方法中注册相应的URL供被调用
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?
/// 数据流Map
/// - returns: 数据流字典
func dataflowMapping() ->Dictionary?
// 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? {
return nil
}
open func classMapping() ->Dictionary? {
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 = [:]
/// 注册新的插件
@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并实现一些代理方法:
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? {
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? {
let mapping: [String: AnyClass] = [
"vc1": UIViewController.self,
"vc2": NSObject.self
]
return mapping
}
static let `default` = NetworkPlugin()
}
然后在ViewController.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()
看到输出为:
说明功能已经基本的完成了。这里并没有将打开界面写入到当前模块,还是感觉直接获取类好一点,所以直接把OpenURL的方式改成了获取类型,然后由使用者自己去创建对象分情况使用,这样自定义的程度也高一点。 如果写为OpenURL的方式,那我们又要分很多种了,push还是present,是否需要导航,考虑到仅获取不使用的情况,要写控制器基类,还有必要统一一个初始化方法供使用。但是这些似乎已经不是一个框架应该做的事情了(仅鉴于我对Swift框架的理解,框架不干涉业务,框架不干涉UI)。
文章篇幅过长并且均为代码,所以本文只写到这里。
下篇文章会开始编写数据模块、网络模块。
