URL Handle in Swift (一) -- URL 分解

更新时间: 2018-6-6

在程序开发过程之中, 我们总是希望模块化处理某一类相似的事情。 在 ezbuy 开发中, 我接触到了对于 URL 处理的优秀的代码, 学习、改进、记录下来。希望对你有所帮助。

对于 URL 处理, 我将分为两个部分来说明:

  • URL Handle in Swift (一) - URL 分解;
  • URL Handle in Swift (二) - 响应链处理 URL 。

一、 URL 格式分析

URL(Uniform Resource Locator) 全称为“统一资源定位符”, 有时也被俗称为网页地址网址)

URL标准格式如下:

url标准格式

截取《网络是怎样连接的》的图片来解释一下(图侵删)

实际上, URL的标准格式中, 我们可以通过参数来传递相关的信息, 如 https://www.google.com.hk/search?q=rsenjoyer

二、 iOS 客户端处理 URL

iOS客户端的URL大体可以分为两类:

  • 自定义协议的URL, 如: igame://myNote
  • 标准的HTTP(S) URL;

理论上你可以自定义任意的协议来构建URL, 响应协议URL。 在 iOS 开发中,你可以来自定义 URL Scheme 来实现应用程序间的通信。 URL Scheme 相关的信息你可以参考URL Schemes 使用详解 以及 Inter-App Communication

对于标准的 HTTP(S) URL,不同的URL有着不一样的处理的方式。如:大多情况下, 应用程序受到一个URL就直接用 WKWebView 或者 UIWebView 直接打开; 但处理 Universal Link是需要打开应用程序内对应的页面等。

根据需求, 可以设计出如下的结构图:

不同的URL通过Bridge最终都转换为 “统一”的 Instruction, Instruction保存着 URL所有的信息, “Handler”通过 Instruction的信息执行不一样的处理。

Talk is Cheap, Show me the code!!

2.1 Bridge代码的实现

Bridge 根据URL的不同而转换成 Instruction。

//
//  IGBridge.swift
//  eziNode
//
//  Created by enjoy on 2018/4/9.
//  Copyright © 2018年 enjoy. All rights reserved.
//

import Foundation

protocol IGBridge {
    
    func bridgeToIG(from url: URL?) -> IGInstruction?
    
    func bridgeToURL(from instruction: IGInstruction?) -> URL?
}

class IGameAppURLBridge: IGBridge {
    
    private static let iGameAppScheme = "igame"
    
    static let `default` = IGameAppURLBridge()
    
    func bridgeToIG(from url: URL?) -> IGInstruction? {
        
        guard let url = url else { return nil }
        
        guard let urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: true), urlComponents.scheme?.lowercased() == IGameAppURLBridge.iGameAppScheme else { return nil }
        
        guard let host = urlComponents.host else { return nil }
        
        let queries = makeDictionary(fromQueryItems: urlComponents.queryItems)
        
        let components = urlComponents.path.components(separatedBy: "/").filter { !$0.isEmpty }
        
        let instruction = IGInstruction(type: IGInstructionType(identifier: host), components: components, queryItems: queries, bridge: self)
        
        return instruction
    }
    
    func bridgeToURL(from instruction: IGInstruction?) -> URL? { 
        // 转换为 URL
    }
}

class HttpWebURLBridge: IGBridge {
    // HttpWebURLBridge 的实现
}

class UniversalLinURLBridge: IGBridge {
    // UniversalLinURLBridge 的实现
}

2.2 Instruction代码实现

“Instruction”需要包含着 URL 完整的以及需要怎样被处理. 客户端所支持的 URL 类型实际上可以用一个枚举来列出。


import Foundation

// 客户端支持的URL 类型
public enum IGInstructionType: String {
    
    case specialExercise
    case examinationPaper
    case redoWrong
    case collection
    case myNote
    case refreshQuestionSet
    case unKnown
    
    init(identifier: String?) {
        guard let identifier = identifier else { self = .unKnown;  return }
        
        switch identifier {
        case IGInstructionType.specialExercise.rawValue:
            self = .specialExercise
        case IGInstructionType.examinationPaper.rawValue:
            self = .examinationPaper
        case IGInstructionType.redoWrong.rawValue:
            self = .redoWrong
        case IGInstructionType.collection.rawValue:
            self = .collection
        case IGInstructionType.myNote.rawValue:
            self = .myNote
        case IGInstructionType.refreshQuestionSet.rawValue:
            self = .refreshQuestionSet
        default:
            self = .unKnown
        }
    }
    
}

struct IGInstruction {
    
    let type: IGInstructionType
    
    let components: [String]
    
    var path: String {
        return self.components.isEmpty ? "" : ([""] + self.components).joined(separator: "/")
    }
    
    let queryItems: [String: String]
    
    var bridge: IGBridge?
    
    init(type: IGInstructionType,  components: [String] = [], queryItems: [String: String] = [:], bridge: IGBridge? = nil ) {
        self.type = type
        self.components = components
        self.queryItems = queryItems
        self.bridge = bridge
    }
}

extension IGInstruction {
    
    // Options 下面会提到
    init?(url: URL?, options: IGURLBridgeOptionsInfo = [.iGameApp]) {
        
        if let bridge = options.iGameAppUrlBridge, let ins = bridge.bridgeToIG(from: url) {
            self = ins
            return
        }
        
        if let bridge = options.httpWebUrlBridge, let ins = bridge.bridgeToIG(from: url) {
            self = ins
            return
        }
        
        if let bridge = options.universalUrlBridge, let ins = bridge.bridgeToIG(from: url) {
            self = ins
            return
        }
        
        return nil
    }
}

extension IGInstruction {
    
    var url: URL? {
        return self.bridge?.bridgeToURL(from: self)
    }
}

2.3 Options 可选值

import Foundation

typealias IGURLBridgeOptionsInfo = [IGURLBridgeInfoItem]

enum IGURLBridgeInfoItem {
    
    case iGameApp
    
    case httpWeb
    
    case universalLink
}

precedencegroup ItemComparisonPrecedence {
    associativity: none
    higherThan: LogicalConjunctionPrecedence
}

infix operator <== : ItemComparisonPrecedence

func <== (lhs: IGURLBridgeInfoItem, rhs: IGURLBridgeInfoItem) -> Bool {
    
    switch (lhs, rhs) {
    case (.iGameApp, .iGameApp): return true
    case (.httpWeb, .httpWeb): return true
    case (.universalLink, .universalLink): return true
    default: return false
    }
}

extension Collection where Iterator.Element == IGURLBridgeInfoItem {
    
    func lastMatchIgnoringAssociatedValue(_ target: Iterator.Element) -> Iterator.Element? {
        return reversed().first { $0 <== target }
    }
    
    func removeAllMatchesIgnoringAssociatedValue(_ target: Iterator.Element) -> [Iterator.Element] {
        return filter { !($0 <== target) }
    }
}

extension Collection where Iterator.Element == IGURLBridgeInfoItem {
    
    var iGameAppUrlBridge: IGameAppURLBridge? {
        if let item = lastMatchIgnoringAssociatedValue(.iGameApp),
            case .iGameApp = item
        {
            return IGameAppURLBridge.default
        }
        return nil
    }
    
    var httpWebUrlBridge: HttpWebURLBridge? {
        if let item = lastMatchIgnoringAssociatedValue(.httpWeb),
            case .httpWeb = item
        {
            return HttpWebURLBridge.default
        }
        return nil
    }
    
    var universalUrlBridge: UniversalLinURLBridge? {
        if let item = lastMatchIgnoringAssociatedValue(.universalLink),
            case .universalLink = item
        {
            return UniversalLinURLBridge.default
        }
        return nil
    }
}

AppDelegate实现

iOS AppDelegate 默认是一个全局的单例,因此可以将其设置为处理的入口;

    
    @objc static let current: AppDelegate = UIApplication.shared.delegate as! AppDelegate
    
    @discardableResult
    func handleIGURL(_ url: URL, httpConvertible: Bool = true) -> Bool {
        
        guard let instruction = IGInstruction(url: url, options: httpConvertible ? [.iGameApp, .httpWeb] : [.iGameApp]) else { return false }
        
        return handleIGInstruction(instruction)
    }
    
    func handleIGInstruction(_ ins: IGInstruction) -> Bool {
        
        switch ins.type {
        case .specialExercise, .examinationPaper, .redoWrong, .collection, .myNote, .refreshQuestionSet:
            print("handler --- \(ins.type.rawValue) 类型数据")
        default:
            break
        }
        return true
        
    }
}
posted @ 2018-06-06 16:43  洒水先生  阅读(553)  评论(0编辑  收藏  举报