美文网首页
Swift - 简单Router

Swift - 简单Router

作者: hukun | 来源:发表于2022-12-02 10:33 被阅读0次

router工具,属于项目的基础工具类之一,也查阅了一些第三方库,不过都不是特别满意,我就尝试着自己简单封装了一个。

先说下思路,因与后台、安卓、前端人员沟通,依旧采用key, value的形式来处理跳转,比如,后台给个

    let dic = ["target":"setting","para":["testKey":"123"]]

这种结果的json,这里的target,就是约定的key,用来指定某一页面,后面的para代表传递的参数,如果想OC那样,用plist表来记录与维护的话,虽然也能实现,但是在自己想用这种方式的跳转的时候,就会因为这里的target没提示,而会有写错的可能性,所以这里采用枚举的方式实现。如下:

enum RouterType: String {
    typealias RawValue = String
    
    case setting
    case accountSafe
    case webView
    
    func getController() -> BaseViewController {
        switch self {
        case .setting:
            return UserSettingViewController.init()
        case .accountSafe:
            return AccountSafeVC.init()
        default:
            return BaseViewController.init()
        }
    }
}

这样,在使用的时候,直接提示出来,而不会因为全是字符串而拼写错误。

let target = RouterType.setting.getController()

然后就是赋值,开始时,会很自然的想到kvc来赋值

let vcKeys = RouterUtil.getAllPropertys(vc)
para.allKeys().forEach { key in
            if vcKeys.contains(key) {
                // 有这个值
                let value = para[key]
                // MARK: 该方法需要vc实现 setValue:forUndefinedKey: 方法
                vc.setValue(value, forKeyPath: key)
            }
        }

这里的allKeys与getAllPropertys是我封装的小工具,allkeys,就是字典的所有key,而getAllPropertys是利用反射Mirror,来获取对应vc的属性,在一一赋值。
可是这里需要注意:使用kvc 需要目标vc实现

 setValue:forUndefinedKey: 

方法,且被赋值的属性前,需要加上@objc的标识

@objc var testKey:String?

而且类型要一致,才能赋值成功,虽然可以使用基类来实现setValue:forUndefinedKey: 但是,每个属性前都需要加上@objc就会很麻烦,而且如果哪天后台传来的参数中,类型不对了,也会导致赋值失败,这是我们不想看到的。这时,我就想起来了HandyJSON框架,它就可以不用加@objc,然后正确的给属性赋值,虽然已经不维护了,但是我还是决定去看看它的实现原理,然后看看能不能借鉴。

在查阅了相关资料后,我们得知,HandyJSON其实是通过操作内存的方式来完成赋值的。

在HandyJSON中,不管是class,还是struct,在赋值时,都会进入下面这个方法

static func _transform(dict: [String: Any], to instance: inout Self) 

在这个方法中会有两个结构体需要提前看一下:

struct Property {
    let key: String
    let value: Any

    /// An instance property description
    struct Description {
        /// key名称
        public let key: String
        /// 类型
        public let type: Any.Type
        /// 属性所需内存大小
        public let offset: Int
        public func write(_ value: Any, to storage: UnsafeMutableRawPointer) {
            return extensions(of: type).write(value, to: storage.advanced(by: offset))
        }
    }
}

详情

struct PropertyInfo {
    let key: String
    let type: Any.Type
  // 内存地址
    let address: UnsafeMutableRawPointer
    let bridged: Bool
}

下面仔细看该方法:

static func _transform(dict: [String: Any], to instance: inout Self) {
        /// 获取类型的属性列表 包含 每个属性的内存大小
        guard let properties = getProperties(forType: Self.self) else {
            InternalLogger.logDebug("Failed when try to get properties from type: \(type(of: Self.self))")
            return
        }

        // do user-specified mapping first
        let mapper = HelpingMapper()
        instance.mapping(mapper: mapper)

        // get head addr 对象的Head节点
        let rawPointer = instance.headPointer()
        InternalLogger.logVerbose("instance start at: ", Int(bitPattern: rawPointer))

        // process dictionary 对dict进行格式化
        let _dict = convertKeyIfNeeded(dict: dict)

        // 判断是否是OC对象
        let instanceIsNsObject = instance.isNSObjectType()
        // 桥接属性
        let bridgedPropertyList = instance.getBridgedPropertyList()
        
        // 循环properties数组
        for property in properties {
            let isBridgedProperty = instanceIsNsObject && bridgedPropertyList.contains(property.key)
            
            // 属性的地址
            let propAddr = rawPointer.advanced(by: property.offset)
            InternalLogger.logVerbose(property.key, "address at: ", Int(bitPattern: propAddr))
            if mapper.propertyExcluded(key: Int(bitPattern: propAddr)) {
                InternalLogger.logDebug("Exclude property: \(property.key)")
                continue
            }
            
            /// 属性信息结构体
            let propertyDetail = PropertyInfo(key: property.key, type: property.type, address: propAddr, bridged: isBridgedProperty)
            InternalLogger.logVerbose("field: ", property.key, "  offset: ", property.offset, "  isBridgeProperty: ", isBridgedProperty)
            
            /// 获取对应属性值
            if let rawValue = getRawValueFrom(dict: _dict, property: propertyDetail, mapper: mapper) {
                /// 转换成需要的类型
                if let convertedValue = convertValue(rawValue: rawValue, property: propertyDetail, mapper: mapper) {
                    /// 进行赋值
                    assignProperty(convertedValue: convertedValue, instance: instance, property: propertyDetail)
                    continue
                }
            }
            InternalLogger.logDebug("Property: \(property.key) hasn't been written in")
        }
    }

这里在HandyJSON中,引用了Reflection https://github.com/Zewo/Reflection 用来实现类属性解析的功能。

看到这里,基本上就找到了 我们处理后台路由传值的方案了,所以我们项目的RouterUtil基础版就诞生了。
使用枚举,通过string来得到对应的VC,然后在通过这种方式进行赋值。赋值过程如下:

static func transform(dict: [String: Any], to instance: UIViewController) {
        let type = type(of: instance)
        /// 获取类型所有的属性
        guard let properties = getProperties(forType: type) else {
            return
        }
        
        /// 获取对象内存中的head节点
        let rawPointer = instance.headPointerOfClass()
        
        properties.forEach { property in
            /// 属性在内存中的地址
            let propAddr = rawPointer.advanced(by: property.offset)
            
            let detail = PropertyInfo(key: property.key, type: property.type, address: propAddr)
            
            /// 从字典中获取对应值
            if let rawValue = dict[property.key] {
                /// 把值转成需要的类型,比如 需要string,后台给了Int 直接赋值会失败,需要转成string
                if let convertedValue = convertValue(rawValue: rawValue, property: detail) {
                    /// 写入对应内存地址内
                    extensions(of: property.type).write(convertedValue, to: detail.address)
                }
            }
        }
    }

转属性的方法,我借鉴了HandyJSON库里面的方式,并进行了改造

   fileprivate func convertValue(rawValue: Any, property: PropertyInfo) -> Any? {
    
    /// 需要转化时
    if let transformType = property.type as? BuiltInBasicTypeProtocol.Type {
        /// 转换结果, 比如需要Int,但是后台给了string
        return transformType.transform(from: rawValue)
    } else {
        return extensions(of: property.type).takeValue(from: rawValue)
    }
}

因为HandyJSON需要类遵守HandyJSON协议,而,我们这里只需要被赋值的属性遵守BuiltInBasicTypeProtocol协议就可以了。在BuiltInBasicTypeProtocol协议中,对基础属性进行了扩展。这里就不详细列出了,感兴趣可以查看源码。

使用起来也比较简单

RouterUtil.pushVC(target: "setting", params: ["testKey":123123])

github地址
https://github.com/michael0217/SwiftRouterDemo

相关文章

网友评论

      本文标题:Swift - 简单Router

      本文链接:https://www.haomeiwen.com/subject/ofzyfdtx.html