1. 背景

基于客户端已经实现了Swift版本的路由动态注册能力,为了扩展路由能力,结合服务模块对路由进行改造,使其具有动态调用服务的能力。可通过WebSocket、MQTT、JSBridge等调起服务。
2. 实现方式
如何让LARouter也具有调用远端服务的能力
类似整个dubbo服务的注册调用流程

通过路由,将路由对服务的调用转发到具体的服务进行调用,并返回结果
核心实现
// 服务调用
// 服务调用
public class func routerService(_ uriTuple: (String, [String: Any])) -> Any? {
let request = LARouterRequest.init(uriTuple.0)
let queries = request.queries
guard let protocols = queries["protocol"] as? String,
let methods = queries["method"] as? String else {
assert(queries["protocol"] != nil, "The protocol name is empty")
assert(queries["method"] != nil, "The method name is empty")
shareInstance.routerLogHandle?(uriTuple.0, .logError, "protocol or method is empty,Unable to initiate service")
return nil
}
// 为了使用方便,针对1个参数或2个参数,依旧可以按照ivar1,ivar2进行传递,自动匹配。对于没有ivar1参数的,但是方法中必须有参数的,将queries赋值作为ivar1。
shareInstance.routerLogHandle?(uriTuple.0, .logNormal, "")
if let functionResultType = uriTuple.1[LARouterFunctionResultKey] as? Int {
if functionResultType == LARouterFunctionResultType.voidType.rawValue {
self.performTargetVoidType(protocolName: protocols,
actionName: methods,
param: uriTuple.1[LARouterIvar1Key],
otherParam: uriTuple.1[LARouterIvar2Key])
return nil
} else if functionResultType == LARouterFunctionResultType.valueType.rawValue {
let exectueResult = self.performTarget(protocolName: protocols,
actionName: methods,
param: uriTuple.1[LARouterIvar1Key],
otherParam: uriTuple.1[LARouterIvar2Key])
return exectueResult?.takeUnretainedValue()
} else if functionResultType == LARouterFunctionResultType.referenceType.rawValue {
let exectueResult = self.performTarget(protocolName: protocols,
actionName: methods,
param: uriTuple.1[LARouterIvar1Key],
otherParam: uriTuple.1[LARouterIvar2Key])
return exectueResult?.takeRetainedValue()
}
}
return nil
}
//实现路由转发协议
public class func performTarget(protocolName: String,
actionName: String,
param: Any? = nil,
otherParam: Any? = nil,
classMethod: Bool = false) -> Unmanaged<AnyObject>? {
if classMethod {
let serviceClass = LARouterServiceManager.default.servicesCache[protocolName] as? AnyObject ?? NSObject()
assert(LARouterServiceManager.default.servicesCache[protocolName] != nil, "No corresponding service found")
let selector = NSSelectorFromString(actionName)
guard let _ = class_getClassMethod(serviceClass as? AnyClass, selector) else {
assert(class_getClassMethod(serviceClass as? AnyClass, selector) != nil, "No corresponding class method found")
shareInstance.routerLogHandle?("\(protocolName)->\(actionName)", .logError, "No corresponding class method found")
return nil
}
return serviceClass.perform(selector, with: param, with: otherParam)
} else {
let serviceClass = LARouterServiceManager.default.servicesCache[protocolName] as? AnyObject ?? NSObject()
let selector = NSSelectorFromString(actionName)
guard let _ = class_getInstanceMethod(type(of: serviceClass), selector) else {
assert(class_getInstanceMethod(serviceClass as? AnyClass, selector) != nil, "No corresponding instance method found")
shareInstance.routerLogHandle?("\(protocolName)->\(actionName)", .logError, "No corresponding instance method found")
return nil
}
return serviceClass.perform(selector, with: param, with: otherParam)
}
}
使用路由调用服务
异步带Block调用
let callBack: @convention(block) (Bool, [AnyHashable: Any]?) -> Void = { success, orderDetailInfo in
print("Completed \(success) \(String(describing: orderDetailInfo))")
}
let userInfo: [String: Any] = ["ivar1": "164030287699771401711", "ivar2": callBack]
let result = LARouterBuilder().buildService(OrderServiceProtocol.self, methodName: LARouterOrderServiceApi.requestOrderDetailCallBack).buildDictionary(param: userInfo).fetchService()
// 效果同上
let result2 = LARouter.openURL(("scheme://services?protocol=OrderServiceProtocol&method=requestOrderDetail:callBack:",userInfo))
同步调用
let result1 = LARouterBuilder().buildService(OrderServiceProtocol.self, methodName: LARouterOrderServiceApi.hasProcessOrder).fetchService() as? String
print("同步服务调用 \(String(describing: result1?.xl.bool))")
// 效果同上
let result2 = LARouter.openURL("scheme://services?protocol=OrderServiceProtocol&method=hasProcessOrder")
服务按照字符串方式调用
struct XLTSLAlertServiceDebug: XLDebugable {
var name = { "特斯拉弹框服务" }()
var action: DebugableClosure = {
let resultArray = LARuntimeUtils.instanceMethodsSelectors(from: OrderServiceProtocol.self)
print("resultArray: \(resultArray)")
var data: LANeuronData = LANeuronData()
data.title = "特斯拉弹框服务"
data.content = " 基于客户端已经实现了Swift版本的路由动态注册能力,为了扩展路由能力,结合服务模块对路由进行改造,使其具有动态调用服务的能力。可通过WebSocket、MQTT、JSBridge等调起服务。"
data.leftButtonText = "取消"
data.leftButtonPath = ""
data.rightButtonText = "进入订单列表"
data.leftButtonPath = ""
data.rightButtonPath = "scheme://order/list?jumpType=1"
let remoteDict = ["title": data.title ?? "",
"content": data.content ?? "",
"leftButtonText": data.leftButtonText ?? "",
"rightButtonText": data.rightButtonText ?? "",
"rightButtonPath": data.rightButtonPath ?? "",
"leftButtonPath": data.leftButtonPath ?? ""]
let dict = ["ivar1": remoteDict]
let url = "scheme://services?protocol=OrderServiceProtocol&method=showTeslaAlertWithInfo:"
LARouter.openURL((url, dict))
}
}
struct XLWalletServiceDebug: XLDebugable {
var name = { "钱包路由服务-保证金弹框" }()
var action: DebugableClosure = {
let resultArray = LARuntimeUtils.instanceMethodsSelectors(from: WalletServiceProtocol.self)
print("resultArray: \(resultArray)")
LARouterBuilder().buildService(WalletServiceProtocol.self, methodName: LARouterWalletServiceApi.showDepositAlertView).buildString(key: "ivar1", value: "保证金到期了请立即续费吧").fetchService()
let dict = ["ivar1": "保证金到期了请立即续费吧"]
LARouter.openURL(("scheme://services?protocol=WalletServiceProtocol&method=showDepositAlertViewWithMsg:", dict))
}
}
硬编码问题解决
为了防止硬编码的问题,在LARouter中,增加了服务的构造器方法,传入ProtocolName\MethodName即可构造出服务路由,具体构造方法如下
@discardableResult
public func buildService<ServiceProtocol>(_ protocolInstance: ServiceProtocol.Type, methodName: String) -> Self {
let protocolName = String(describing: protocolInstance)
buildResult.0 = "\(LASerivceHost)protocol=\(protocolName)&method=\(methodName)"
return self
}
public func fetchService() -> Any? {
let result = LARouter.generate(buildResult.0, params: buildResult.1, jumpType: .push)
return LARouter.openURL(result)
}
注意点: 我们将方法名的调用使用常量字符串的形式
//let resultArray = LARuntimeUtils.instanceMethodsSelectors(from: OrderServiceProtocol.self)
//print("resultArray: \(resultArray)")
// MARK: - 订单服务方法常量定义,使用runtime方式获取服务方法
/// 钱包服务
public struct LARouterWalletServiceApi {
public static let showDepositAlertView = "showDepositAlertViewWithMsg:"
}
3. 实现过程中遇到的问题
这里需要注意点是
- 方法名称出入问题
/// 展示特斯拉弹框
func showTeslaAlert(info: [String: Any])
showTeslaAlert 在服务调用中的真实方法是 showTeslaAlertWithInfo:
小工具: 获取协议的方法列表
+ (NSArray *)instanceMethodsSelectorsFromProtocol:(Protocol *)aProtocol {
NSMutableArray *tmp = [NSMutableArray array];
unsigned int numMethods;
struct objc_method_description *methods = protocol_copyMethodDescriptionList(aProtocol, NO, YES, &numMethods);
if (methods) {
for (int i = 0; i < numMethods; i++) {
NSString *methodName = NSStringFromSelector(methods[i].name);
[tmp addObject:methodName];
}
free(methods);
}
methods = protocol_copyMethodDescriptionList(aProtocol, YES, YES, &numMethods);
if (methods) {
for (int i = 0; i < numMethods; i++) {
NSString *methodName = NSStringFromSelector(methods[i].name);
[tmp addObject:methodName];
}
free(methods);
}
return [NSArray arrayWithArray:tmp];
}
- 支持服务调用的返回值不能是常用的数据类型,比如Bool 、Int、CGFlocat等类型,会崩溃,目前解决方案是转为指针类型
@objc
public protocol OrderServiceProtocol: ServiceProtocol {
/// 是否有行程中的订单
var hasProcessOrder: String { get }
// var hasProcessOrder: Bool { get }
/// 展示特斯拉弹框
func showTeslaAlert(info: [String: Any])
}
4. 限制条件
-
func perform(_ aSelector: Selector!, with object1: Any!, with object2: Any!) -> Unmanaged<AnyObject>! 最多支持两个参数,需要我们对现有的服务接口进行改造,多于两个参数的使用字典进行包装,解析时根据key赋值即可。
-
协议的方法、类的方法,无法以selector的形式传入,必须传入字符串
5. 支持如下调用方式
-
支持直接使用路由进行服务调用
-
service调用也可以做到远端调用
-
通过路由可以拿到对应页面调转事件结果,可以拿到对应service的返回结果,可以通过远端下发做到某些特定的操作,例如打开某个service的计时器,改变某个页面的颜色等。
网友评论