上次上线之后,在接口监控中,就有个接口时不时的就会报验签失败。并且全部都是iOS的系统报错的,验签的规则是这样的,把请求的所有参数进行转成字符串之后,进行某些操作,生成一个签名,把这个签名放到参数里,而服务端拿到参数之后进行相同的操作,也生成一个签名,进行比较,如果不一致,则会报签名错误。
在这个错误发生之后,有个组内的同学说,检查一下参数里有没有double类型的数据,如果有的话,可能会导致签名错误,一检查参数,果然有double类型的参数。可double类型为什么会造成签名错误呢?我们进行签名的时候是把double类型转成字符串,而传给后端进行urlEncoding的时候,也是把double类型转成string放到httpbody中的。
下面就解开谜底,原因是这样的
在我们的项目中,把Double类型转成String的操作是"\(double)"
这种方式,如果double的值是恰好是整数,比如是1,则会转成”1.0″,而Alamofire
中的URLEncoding则是通过下面的方法把map中的key、value转成字符串的。
public func queryComponents(fromKey key: String, value: Any) -> [(String, String)] {
var components: [(String, String)] = []
switch value {
case let dictionary as [String: Any]:
for (nestedKey, value) in dictionary {
components += queryComponents(fromKey: "\(key)[\(nestedKey)]", value: value)
}
case let array as [Any]:
for value in array {
components += queryComponents(fromKey: arrayEncoding.encode(key: key), value: value)
}
case let number as NSNumber:
if number.isBool {
components.append((escape(key), escape(boolEncoding.encode(value: number.boolValue))))
} else {
components.append((escape(key), escape("\(number)")))
}
case let bool as Bool:
components.append((escape(key), escape(boolEncoding.encode(value: bool))))
default:
components.append((escape(key), escape("\(value)")))
}
return components
}
复制代码
经过debug,当value类型为Double时,竟然进入到了NSNumber的case里,在这里也就是说,当value为Double时会把value转成NSNumber再转成字符串,如果value类型为Double, 而小数部分为0,比如1,则会转成“1”,到这里验签失败的原因是找到了。
但是有产生了一个疑问?下面的代码为什么会转换成功,而as操作符又干了什么?
let d = 1.1
let number = d as NSNumber
复制代码
通过源码加上debug发现了_ObjectiveCBridgeable这个协议,这个协议是在Swift类型和OC类型通过as转换的时候调用的,下面通过一个例子来展示一下这个协议如何使用。注释说明了协议中的每个方法是如何使用的。
/// 自定义一个结构体来实现和NSNumber进行相互转换
struct Test: _ObjectiveCBridgeable {
var value : Int
/// 定义了和OC相互转换的类型
typealias _ObjectiveCType = NSNumber
/// 当使用NSNumber as! Test时会调用
static func _forceBridgeFromObjectiveC(_ source: NSNumber, result: inout Test?) {
if !_conditionallyBridgeFromObjectiveC(source, result: &result) {
fatalError("Unable to bridge \(_ObjectiveCType.self) to \(self)")
}
}
/// 当使用NSNumber as? Test时会调用
static func _conditionallyBridgeFromObjectiveC(_ source: NSNumber, result: inout Test?) -> Bool {
result = Test(value: source.intValue )
return true
}
/// 当OC文件和swift相互调用时,自动转换会调用此方法
static func _unconditionallyBridgeFromObjectiveC(_ source: NSNumber?) -> Test {
return Test(value: source?.intValue ?? 0)
}
/// 当使用Test as NSNumber时会调用
func _bridgeToObjectiveC() -> NSNumber {
return NSNumber(value: value)
}
}
let t1 = Test(value: 10) //t1:Test
let n = t1 as NSNumber //n: NSNumber
let t2 = n as! Test // t2:Test
复制代码
Swift源码中Double实现这个协议的源码如下
extension Double : _ObjectiveCBridgeable {
@available(swift, deprecated: 4, renamed: "init(truncating:)")
public init(_ number: __shared NSNumber) {
self = number.doubleValue
}
public init(truncating number: __shared NSNumber) {
self = number.doubleValue
}
public init?(exactly number: __shared NSNumber) {
let type = number.objCType.pointee
if type == 0x51 {
guard let result = Double(exactly: number.uint64Value) else { return nil }
self = result
} else if type == 0x71 {
guard let result = Double(exactly: number.int64Value) else { return nil }
self = result
} else {
// All other integer types and single-precision floating points will
// fit in a `Double` without truncation.
guard let result = Double(exactly: number.doubleValue) else { return nil }
self = result
}
}
@_semantics("convertToObjectiveC")
public func _bridgeToObjectiveC() -> NSNumber {
return NSNumber(value: self)
}
public static func _forceBridgeFromObjectiveC(_ x: NSNumber, result: inout Double?) {
if !_conditionallyBridgeFromObjectiveC(x, result: &result) {
fatalError("Unable to bridge \(_ObjectiveCType.self) to \(self)")
}
}
public static func _conditionallyBridgeFromObjectiveC(_ x: NSNumber, result: inout Double?) -> Bool {
if x.doubleValue.isNaN {
result = x.doubleValue
return true
}
result = Double(exactly: x)
return result != nil
}
@_effects(readonly)
public static func _unconditionallyBridgeFromObjectiveC(_ source: NSNumber?) -> Double {
var result: Double?
guard let src = source else { return Double(0) }
guard _conditionallyBridgeFromObjectiveC(src, result: &result) else { return Double(0) }
return result!
}
}
复制代码
总结
当使用as操作符转换类型时,如果as操作符两端分属于OC和Swift类型,就会调用_ObjectiveCBridgeable协议中对应的方法。
Swift有很多类型都实现了这个协议。比如我们很常见的Array,Dictionary。如果本篇文章对你有帮助的话,帮忙点个赞呗