After the last online, in the interface monitoring, there is an interface from time to time will report failure. And all of them are the iOS system error, the attestation rule is like this, all parameters of the request into a string, for certain operations, generate a signature, the signature on the parameters, and the server to get the parameters for the same operation, and generate a signature, comparison, if inconsistent, errors will be reported to the signature.

After this error occurred, one of the students in the group said, check if there is a double in the parameter, if there is, it may cause the signature error, check the parameter, sure enough, there is a double. But why does double cause a signature error? When we sign, we convert a double to a string, and when we pass urlEncoding to the back end, we convert a double to a string in the HttpBody.

Here’s the answer, and here’s why

In our project, the operation to convert a Double to a String is “\(Double)”. If the value of a Double is exactly an integer, such as 1, In Alamofire, URLEncoding converts key and value in map into strings through the following method.

  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
}
Copy the code

After debugging, when the value type is Double, it actually enters the case of NSNumber. In this case, that is, when the value is Double, the value will be converted to NSNumber and then converted to string. If the value type is Double and the decimal part is 0, For example, “1” will be changed to “1”. The reason for the failure to check the visa here is found.

But there is a question, right? Why does the following code succeed, and what does the AS operator do?

Let d = 1.1 let number = d as NSNumberCopy the code

The _ObjectiveCBridgeable protocol is called when Swift and OC are converted through AS. The following example shows how to use this protocol. Annotations explain how each method in the protocol is used.

Struct Test: _ObjectiveCBridgeable {var value: Typealias _ObjectiveCType = NSNumber // If NSNumber as! Test called when static func _forceBridgeFromObjectiveC (_ source: NSNumber, result: inout Test?) { if ! _conditionallyBridgeFromObjectiveC(source, result: &result) {fatalError("Unable to bridge \(_objectivectype. self) to \(self)")}} Test called when static func _conditionallyBridgeFromObjectiveC (_ source: NSNumber, result: inout Test?) -> Bool { result = Test(value: Source.intvalue) return true} // when OC files and swift call each other, Automatic conversion will this method is called static func _unconditionallyBridgeFromObjectiveC (_ source: NSNumber?) - > Test {return Test (value: } // 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:TestCopy the code

Swift source code Double implementation of this protocol source code as follows

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! }}Copy the code

conclusion

When the AS operator is used to convert types, if both ends of the AS operator belong to OC and Swift types, the corresponding method in the _ObjectiveCBridgeable protocol will be called.

There are many types of Swift that implement this protocol. For example, Array, Dictionary. If this post has helped you, please give it a thumbs up