I had this code in my Swift App
func parseJSON() {
let urlString = "www.websitethatlinkstoJSONfile.com"
if NSURL(string: urlString) == true {
let url = NSURL(string: urlString)
let data = try? NSData(contentsOfURL: url!, options: []) as NSData
let json = NSData(data: data!)
// more code
However, even though the link actually worked and was true, the if statement was never met and it kept skipping it and moving to else. So I changed the code to
if NSURL(string: urlString) != false
and it worked perfectly. I'm not sure why though?
As already explained in the other answers, comparing the optional
NSURL? against true or false is not what you want, and you should
use optional binding instead.
But why does it compile at all? And how can the result be interpreted?
In NSURL(string: urlString) == true, the left-hand side has the type
NSURL?, and NSURL is a subclass of NSObject.
There is a == operator taking two optional operands:
public func ==<T : Equatable>(lhs: T?, rhs: T?) -> Bool
The compiler uses the implicit conversion of Bool to NSNumber
to make that compile. So your code is equivalent to
if NSURL(string: urlString) == NSNumber(bool: true)
and that will always be false, and
if NSURL(string: urlString) != NSNumber(bool: false)
will always be true, simply because the left-hand side is not a
number.
Here is a demonstration of the effect:
func foo(x: NSObject?) {
print(x == true, x == false)
}
foo(NSNumber(bool: true)) // true, false
foo(NSNumber(bool: false)) // false, true
foo(NSObject()) // false, false !!!
The last case is what you observed: Both x == true and x == false
return false.
For classes not inheriting from NSObject it would not compile:
class A { }
let a: A? = A()
if a == true { } // cannot convert value of type 'A?' to expected argument type 'Bool'
Remark: This is another argument for not comparing boolean values
against true or false, i.e.
if a == true && b == false { ... }
is better written as
if a && !b { ... }
Applied to your case, you would get a compiler error indicating
the problem:
let urlString = "http://www.websitethatlinkstoJSONfile.com"
if NSURL(string: urlString) { }
// error: optional type '_' cannot be used as a boolean; test for '!= nil' instead
You don't really want to check for a boolean value when creating a NSURL, but rather make sure the NSURL you create is non-nil. Try wrapping it it in an if let statement like so to make sure whatever URL's you create are non-nil before executing further code.
let urlString = "www.websitethatlinkstoJSONfile.com"
if let url = NSURL(string: urlString) {
if let data = try? NSData(contentsOfURL: url, options: []) {
let json = NSData(data: data)
} else {
//Handle case where data is nil
}
} else {
//Handle case where url is nil
}
Using if let statements in this way makes sure that the NSURL and NSData objects you are creating are non-nil and valid objects and then you can add an else statement to them to handle cases where your url or data objects are nil. This will save you from unwanted crashes due to force unwrapping with the ! operator.
Yes there is a different take a look at some of the documentation. In this init method it is fallible.
public convenience init?(string URLString: String)
the question mark indicates it is fallible.
So it will return a NSURL object or nil.
convenience init?(parameter: AnyObject) {
if parameter == nil {
return nil
}
self.init()
}
So in a specific example like your example. You can test it in playground
let urlString = "100"
if NSURL(string: urlString) == true {
print("true")
//Never prints in any circumstance//
}
if NSURL(string: urlString) != false {
print("true")
//always prints//
}
if NSURL(string: urlString) != nil {
print("true")
//object was created//
}
The == operator checks whether both side value is equal or not.For example:
var data:Int = 6
if data == 5 {
//if this block is executed that means `data` is exactly equal to 5
//do something
}
else {
//In this situation, this block of code will be executed.
//if this block is executed that means data is anything other than 5.
//do something
}
The != operator checks that two values are not equal to each other.For example:
var data:Int = 6
if data != 5 {
//In this situation, this block of code will be executed.
//If this block is executed that means `data` is anything other than 5.
//do something
}
else {
//if this block is executed that means data is exactly equal to 5.
//do something
}
In your case code if NSURL(string: urlString) == true checks that if NSURL(string: urlString) return true then it should excute if block otherwise else block.
NSURL(string: urlString) is convenience initializer which creates NSURL object and returns. In case if it fails to do so then it returns nil
In any case, it does not return either true or false.So when you compare this with true it always fails and goes to else block.
And when you check that it is not equal to false (!= false) becomes true because NSURL(string: urlString) returning NSURL object and that is not equal to false.
So if you want to check that whether NSURL object is created or not you can check whether the return value is nil or not.
if NSURL(string: urlString) != nil {
//Object is created successfully.Now you can do whatever you want this object.
}
else {
//It failed to create an object.
}
Related
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
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
In a function like this:
func getMessageDetails()->URL{
if let theLinks = theMessage.links as? [[String:String]]{
let thisLink = theLinks[3]["Href"]
let url = URL(string: thisLink)
return url!
}
return nil// is unacceptable, what should I put here?
}
what should I return outside the closure?
In this case the best solution is to return an optional URL, it can also handle the case if the link is not a valid URL. And you should check if the thisLink array contains more than 3 items to avoid an out-of-range exception:
func getMessageDetails() -> URL? {
if let theLinks = theMessage.links as? [[String:String]],
theLinks.count > 3,
let thisLink = theLinks[3]["Href"] {
return URL(string: thisLink)
}
return nil
}
I have a static function which just reads a security scoped bookmark from NSUserdefaults. Like this:
static func desktopURL()->NSURL {
let desktopData = NSUserDefaults.standardUserDefaults().dataForKey("desktopSecurityBookmark")
if (desktopData != nil){
let desktop = try! NSURL(byResolvingBookmarkData: desktopData!, options: NSURLBookmarkResolutionOptions.WithSecurityScope, relativeToURL: nil, bookmarkDataIsStale: nil)
desktop.startAccessingSecurityScopedResource()
return desktop
} else {
// what the heck to return when URL was not set in the first place??
let desktopNil = NSURL() // :D ??? I need to return at leas something or not?
return desktopNil
}
}
I mean I need to return at least something or not?
The best way to deal with these situations is to make your return type optional, then you can return nil if your desktopData is nil. It is usually easier to handle the logic for a nil response in the function calling this function
Eg
static func desktopURL() -> NSURL? {
if let desktopData = NSUserDefaults.standardUserDefaults().dataForKey("desktopSecurityBookmark")
{
let desktop = try! NSURL(byResolvingBookmarkData: desktopData!, options: NSURLBookmarkResolutionOptions.WithSecurityScope, relativeToURL: nil, bookmarkDataIsStale: nil)
desktop.startAccessingSecurityScopedResource()
return desktop
} else {
return nil
}
}
Let's say I have the following class definition:
class Request {
let url: NSURL?
init?(url: String) {
guard let self.url = NSURL(string: url) else {
self.url = nil
return nil
}
}
}
The guard statement doesn't work so I'm having to do the following:
let url: NSURL?
init?(url: String) {
guard let _ = NSURL(string: url) else {
self.url = nil
return nil
}
self.url = NSURL(string: url)!
}
Which feels quite long - is there another, simpler way to achieve what I'm trying to achieve which is to create a failable initialiser whose only property takes a value from NSURL (which returns an optional). The initialiser is to fail and return nil if the NSURL value returns nil.
Your first version could work, you just forgot to give guard a variable name for the unwrapped value (you used self.url instead, that's the mistake):
class Request {
let url: NSURL?
init?(url: String) {
guard let value = NSURL(string: url) else {
self.url = nil
return nil
}
self.url = value
}
}
I have this function that uses guard to make sure the parameters are correct:
func foo(bundle: NSBundle?, _ url: NSURL?)->Bool{
guard let _ = url, let _ = bundle else{
print("crap")
return false
}
return true
}
I noticed that the else clause will run if ANY of the parameters is nil. It behaves as an OR clause.
However, what I need is to check that BOTH are nil (an AND).
How can I do this?
If you don't actually care about your bound values (which I assume you don't, given the _ names), you can do this:
func foo(bundle: NSBundle?, _ url: NSURL?) -> Bool {
guard url != nil || bundle != nil else {
print("crap")
return false
}
return true
}
If you do actually want to bind the values, then you cannot accomplish this with a single guard statement. If it fails to unwrap a value from either of the optionals then it will execute the else block, as you've noted.
If you were still set on using a guard statement, you would have to put in a guard as I have above followed by various if let statements to unwrap things as appropriate (or just omit the guard and have an else statement in the code below that was the false case):
func foo(bundle: NSBundle?, _ url: NSURL?) -> Bool {
guard url != nil || bundle != nil else {
print("crap")
return false
}
if url == nil, let someBundle = bundle {
// Handle the bundle only case
}
else if bundle == nil, let someUrl = url {
// Handle the url only case
}
else if let someUrl = url, let someBundle = bundle {
// Handle the case for both
}
return true
}
This is not really a job for a guard statement. What you want is:
if(url == nil && bundle == nil)
{
print("crap")
return false
}
return true