Swift简版路由
我眼中的路由
提到路由我最先联想到的是平时用来上网的无线路由器,而无线路由给我们带来的好处在我看来有两点。一、用户不用关心无线路由是连接的网线、还是一个桥接的路由器,只需要使用账号密码即可上网。二、我们更换上网方式比如拨号上网换个账号,只要保持路由器wifi名称账号密码不变,用户就可以不做任何更改继续使用wifi。
在我看来路由就是一个映射规则,通过输入得到输出。就像无线路由器一样输入的是wifi的名称密码,得到的是网络数据。我们定义好生成规则后就可以很简单的从输入得到输出结果。
那移动开发中的路由是什么呢?以iOS开发为例在我看来,就是一个根据规则生成控制器、视图等的一个东西。
1、开发中使用路由的好处
个人理解和无线路由器好处类似。一、按照某种规则比如用链接和页面建立对应关系后,我们通过链接就可以构造出对应的页面、控件,调用者和被调用者没有直接依赖也不用关心具体的初始化步骤。二、当我们修改映射结果时,比如以前链接url对应的是页面A现在换成页面B,调用的地方不用做任何修改,更加灵活。提到路由往往就会讲到组件化,路由是组件化中很重要的一部分但不在本篇文章的讨论范围,这里推荐一篇文章大家有兴趣可以看看iOS 组件化方案探索。
2、我们项目的需求
①、通过h5、远程推送、公众号等打开app跳转到指定页面。②、应用内有个任务系统,可能跳转到很多不同页面。③、一些目标页需要登录后方可进入、一些目标页需要先出一个询问弹窗点击确定后才会跳转。
3、页面调用方式
①、app外调用分为三种远程推送、UniversalLink、URL Schemes。②、应用内调用
③、其中推送和应用内调用传参客户端可以随意设定,主要是看deeplink和url schemes这两种,而这两种都是以链接的形式打开app的,所以我们就以链接来和页面建立绑定关系。
4、使用链接和页面建立绑定关系
①、简单介绍下链接的组成部分
例如:https://www.baidu.com/s?inputT=3358&rsv_sug4=3358
scheme(https)、host(www.baidu.com)、path(/s)、参数(?inputT=3358&rsv_sug4=3358)
②、首先建立绑定关系
由于使用deeplink打开使用的是https、使用urlscheme使用的是自己定义的一个字符串,所以scheme是不固定的。每个链接参数肯定也是不固定的,所以我们选用host+path来作为key对应具体的页面。
protocol AIRouterProtocol {
static func targetWith(pa: [String: Any]) -> AIRouterProtocol?
func needLogin() -> Bool
func isPush() -> Bool
}
var targetDict = [String: AIRouterProtocol.Type]()
func registerRouter(target: AIRouterProtocol.Type, key: String) {
targetDict.updateValue(target, forKey: key)
}
如上面代码所示我们将页面和链接的映射关系存储在了一个字典里,以连接的host+path为key,以一个遵从我们定义的路由协议为value。协议主要定义了三个方法,targetWith(页面的构造方法)、needLogin(页面是否需要登录)、isPush(页面跳转方式)。
5、通过链接获取页面
①、获取链接各个组成部分,取出host+path作为key值获取对应页面,取出链接中的参数部分初始化页面。考虑到应用内使用时传递一些链接无法传递的参数类型,如block、UIImage等,提供了一个externParameter字典类型参数和链接里的参数共同组成参数部分来初始化页面。
func targetWith(urlStr: String, externParameter: [String: Any]? = nil) -> AIRouterProtocol? {
let encodeUrlStr = urlStr.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? ""
if let urlComponents = URLComponents(string: encodeUrlStr) {
let scheme = urlComponents.scheme ?? ""
let host = urlComponents.host ?? ""
let path = urlComponents.path
//AILog("scheme:\(scheme) host:\(host) path:\(path)")
var parameter = [String: Any]()
if let queryItems = urlComponents.queryItems {
for query in queryItems {
parameter.updateValue(query.value ?? "", forKey: query.name)
}
}
if let externDic = externParameter {
for (key, value) in externDic {
parameter.updateValue(value, forKey: key)
}
}
if scheme == kAppScheme {
return targetWith(key: host + path, parameter: parameter)
} else if kHttp.contains(scheme) {
if let target = targetWith(key: host + path, parameter: parameter) {
return target
}
}
}
return nil
}
func targetWith(key: String, parameter: [String: Any]) -> AIRouterProtocol? {
if let router = targetDict[key] {
return router.targetWith(pa: parameter)
}
return nil
}
6、调用
通过链接初始化页面,然后通过协议约定的方法获取是否需要登录、跳转方式完成跳转。(如果不需跳转初始化方法返回nil即可,然后做自己想做的事儿,如展示一个弹窗、tabbar切换选中tab等)
/// 处理链接(打开页面/其它处理)
/// - Parameter urlStr: 链接
/// - Parameter externParameter: 额外参数(一些参数无法放在链接中如block、UIImage等可以放在这里)
static func openUrl(urlStr: String, externParameter: [String: Any]? = nil) {
if let target = AIRouter.share.targetWith(urlStr: urlStr, externParameter: externParameter) {
let needLogin = target.needLogin()
let isPush = target.isPush()
if let vc = target as? UIViewController {
self.openVC(vc: vc, needLogin: needLogin, isPush: isPush)
}
}
}
static func openVC(vc: UIViewController, needLogin: Bool, isPush: Bool) {
if let topVC = UIViewController.topViewController() {
if needLogin && UserManager.share.UserIsLogin == false {//登录处理
let loginVC = LoginViewController {
self.openVC(vc: vc, needLogin: needLogin, isPush: isPush)
}
self.openVC(vc: loginVC, needLogin: false, isPush: true)
} else {
if isPush {
if let _ = topVC.navigationController {
topVC.aiPushToVC(toVC: vc)
} else {
let navi = UINavigationController(rootViewController: vc)
topVC.aiPresent(navi, animated: true, completion: nil)
}
} else {
topVC.aiPresent(vc, animated: true, completion: nil)
}
}
}
}