3

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?

  • `NSURL(string:)` will return true for any string be it a valid url or just radom characters. – Khundragpan Jul 28 '16 at 13:21
  • Using guard statements are encouraged in Swift during situations like this to check for nil. Also you may want to research the Equatable protocol for future reference. – Laurence Wingo Jul 28 '16 at 13:39

4 Answers4

3

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
Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
1

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.

NSGangster
  • 2,397
  • 12
  • 22
1

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//
}
Asdrubal
  • 2,421
  • 4
  • 29
  • 37
  • 3
    `NSURL.init(string:)` returns `NSURL` object or `nil`. It does not return `false`. – OOPer Jul 28 '16 at 13:32
  • Thats why said "in a way" to help them understand – Asdrubal Jul 28 '16 at 13:34
  • "in a way" would not be suitable here. Because comparing `NSURL?` to `false` can never be considered to be "equal". – OOPer Jul 28 '16 at 13:36
  • 2
    You should note that `NSURL(string: urlString) != false` is always true, even when `NSURL(string: urlString)` returns `nil`. In fact, it's equivalent to `NSURL(string: urlString) != NSNumber(bool: false)`. Test with giving `urlString` an invalid url. – OOPer Jul 28 '16 at 13:45
  • Thank you for explaining! I'm new to Swift so it can be pretty confusing. – cheesydoritosandkale Jul 28 '16 at 13:50
  • @cheesydoritosandkale you may also be interested in looking at https://developer.apple.com/reference/uikit/uiapplication/1622952-canopenurl – Asdrubal Jul 28 '16 at 13:52
  • I'm using Cocoa for OS X app development. I have been trying to use the API, but it can be pretty confusing hehe :) – cheesydoritosandkale Jul 28 '16 at 13:55
  • @cheesydoritosandkale http://stackoverflow.com/questions/26704852/osx-swift-open-url-in-default-browser – Asdrubal Jul 28 '16 at 13:59
1

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.
}
Sunil Sharma
  • 2,653
  • 1
  • 25
  • 36