NSCalendar in Swift - init may return null, but is optional

NSCalendar(calendarIdentifier: calendarName) can return nil if calendarName invalid - this is the original Objective-C behavior, and also true in Swift. However, does the compiler seem to think that the initializer returns NSCalendar , not NSCalendar? as below:

 let c1 = NSCalendar(calendarIdentifier: "gregorian")// _NSCopyOnWriteCalendarWrapper let c2 = NSCalendar(calendarIdentifier: "buddhist")// _NSCopyOnWriteCalendarWrapper //let c3:NSCalendar = NSCalendar(calendarIdentifier: "rubbish") // run-time error let c3:NSCalendar? = NSCalendar(calendarIdentifier: "rubbish") // nil 

So, if the initializer can return zero, I understand that I should be able to

 if let c4 = NSCalendar(calendarIdentifier: "rubbish") as? NSCalendar { //error: conditional downcast from 'NSCalendar' to 'NSCalendar' always succeeds } 

However, this is a compile-time error, as shown.

What I don't understand here, and how can I safely verify that a named calendar really exists?

+5
source share
2 answers

Note. . This applies only to Swift 1.0. There are error initializers in Swift 1.1.


It works:

 if let c4 = NSCalendar(calendarIdentifier: "rubbish") as NSCalendar? { } 

There is a known issue in Swift 1.0 in the Xcode release notes about Swift that do not support Objective-C initializers that return nil . Basically, what happens is that according to Swift, the NSCalendar(...) expression is of type NSCalendar , which is an optional type (cannot be nil ). However, this is a truly imported initializer from Objective-C, where it can return nil .

So what is happening at the moment is that when you call this and it returns nil , you have a value that is nil at runtime, but Swift considers this to be an optional NSCalendar (which cannot be nil ). This is a very bad situation when you have a value that is not possible for the type. The Xcode release notes mention a "workaround" in which you convert the initializer result to an optional type before using it. The reason for this is that at run time, both optional and optional object pointers are represented as simple pointers, where nil pointer to an object is a null pointer, and not optional, are not null pointers. The conversion operation from an optional pointer to an optional object is a simple assignment (in both cases it is a non-zero pointer). But in the case where the value is nil , a simple assignment turns it into a (real) nil value of an optional type. So everyone is happy.

But if you do not first convert it to optional, all hell breaks because you have a value that should not be possible for this type.

as? your attempt to downcast using as? NSCalendar to NSCalendar not allowed, because dropping from NSCalendar to NSCalendar cannot fail (theoretically).

+1
source

Although the initializer must return the full NSCalendar object, it looks like it behaves like an implicit extra (NSCalandar!). If you look at the object in the debugger, it will appear as an NSCalendar:

(NSCalendar) $ r1 = 0x0000000000000000 {ObjectiveC.NSObject = parent NULL}

Even stranger, the following code does not generate runtime errors when accessing nil NSCalendar - at least for me:

  let x = NSCalendar(calendarIdentifier: "asdasda") let y = NSCalendar(calendarIdentifier: NSCalendarIdentifierGregorian) if (x.isDateInToday(NSDate())) { println("x works") } if (y.isDateInToday(NSDate())) { println("y works") } if (x == nil) { println("x not calendar") } if (y == nil) { println("y not calendar") } 

For me, this displays "y works" and "x is not a calendar." It would be in pure Swift to have a run-time error if using nil is implicit optional, but pqnet indicates that calling the method on the nil pointer in object c does not cause an error, it is simply ignored - this is what seems to be doing. So strange, but in this case it is safe:

  let x = NSCalendar(calendarIdentifier: "asdasda") if (x != nil) { //do what you need with the calendar } 

However, a faster way is to use an explicit optional - i.e. do the following:

  let x : NSCalendar? = NSCalendar(calendarIdentifier: "asdasda") if let cal = x { //do what you need with the calendar as cal } 

You can also use the implicit optional let x : NSCalendar =... and check with if x != nil as above - your choice seems to be that sometimes different contexts cause implicit or explicit options ...

Edit:

From the Xcode release notes :

Swift does not support object initializers that do not return null. (16480364)! Workaround: If a factory method exists, use it instead. Otherwise, write the result to optional. For instance:

 let url: NSURL? = NSURL(string: "not a url") 
+2
source

Source: https://habr.com/ru/post/1200212/


All Articles