URLRequest equality doesn't include httpBody

Viewed 1077

Overview

There are 2 URLRequests, one with httpBody and one with no httpBody.
However when compared, it shows both are equal.

Question

Is this expected behaviour or am I missing something ?

Code

let url = URL(string: "www.somevalidURL.com")!

var r1 = URLRequest(url: url)
r1.addValue("Content-Type", forHTTPHeaderField: "application/json; charset=utf-8")
r1.httpBody = makeBody(withParameters: ["email" : "a@b.com"])

var r2 = URLRequest(url: url)
r2.addValue("Content-Type", forHTTPHeaderField: "application/json; charset=utf-8")

if r1 == r2 {
    print("requests are equal")
}
else {
    print("requests are not equal")
}

if r1.httpBody == r2.httpBody {
    print("body is equal")
}
else {
    print("body is not equal")
}

func makeBody(withParameters bodyParameters: [String : Any]?) -> Data? {
    guard let bodyParameters = bodyParameters,
        !bodyParameters.isEmpty else {
            return nil
    }
    let body : Data?
    do {
        body = try JSONSerialization.data(withJSONObject: bodyParameters,
                                          options: .prettyPrinted)
    }
    catch {
        print("Error in creating Web Service Body = \(error)")
        body = nil
    }
    return body
}

Output

requests are equal
body is not equal

Xcode 10
Swift Version: 4.2

2 Answers

URLRequest is the Swift overlay type for the Foundation type NSURLRequest, so that that == ultimately calls the isEqual() method of the NSURLRequest.

The Foundation library is open source for non-Apple platforms, and at NSURLRequest.swift#L252 we find:

open override func isEqual(_ object: Any?) -> Bool {
    //On macOS this fields do not determine the result:
    //allHTTPHeaderFields
    //timeoutInterval
    //httBody
    //networkServiceType
    //httpShouldUsePipelining
    guard let other = object as? NSURLRequest else { return false }
    return other === self
        || (other.url == self.url
            && other.mainDocumentURL == self.mainDocumentURL
            && other.httpMethod == self.httpMethod
            && other.cachePolicy == self.cachePolicy
            && other.httpBodyStream == self.httpBodyStream
            && other.allowsCellularAccess == self.allowsCellularAccess
            && other.httpShouldHandleCookies == self.httpShouldHandleCookies)

So that seems to be intentional.

If you've come here wondering why your identical URLRequests aren't equal to each other like I did I found the reason to be that URLRequest's equatable implementation differentiates between httpBody being set to nil or being defaulting to nil.

If we do the following:

let request = URLRequest(url: URL(string: "test.com"))
var expectedRequest = URLRequest(url: URL(string: "test.com"))
expectedRequest.httpBody = nil

Then:

request == expectedRequest //false
print(request.httpBody) // nil
request.httpBody = nil
request == expectedRequest //true

Explicitly settings the httpBody to nil will fix this issue. In fact what the body is doesn't matter, only that it has been explicitly set.

request.httpBody = Data()
request == expectedRequest //true

Potential explanation

As Martin rightly points out Swift's source code does not include httpBody. However all of the properties (in the link) being equal (and both being castable as NSURLRequests) does not return true (see lldb output below). I can only presume the linked code is overridden to include another property and this is modified when didSet or willSet is called on httpBody lldb Terminal Log

Related