Why is `as Date` required for `timeIntervalSince()` with two `NSDate` objects? - swift

I have an NSManagedObject object with:
#NSManaged public var timestamp: NSDate
I needed the time interval between two of these, so I implemented:
let interval = next.timestamp.timeIntervalSince(current.timestamp)
Why does this result in the following error?
'NSDate' is not implicitly convertible to 'Date'; did you mean to use
'as' to explicitly convert?
I'm surprised because both next and current are of type NSDate, and timeIntervalSince() is an NSDate method.
It's easily fixed by following the suggestion in the error, but I'd like to understand what going on here:
let interval = next.timestamp.timeIntervalSince(current.timestamp as Date)
In case it matters, this is on Swift 3.0.

Referring to NSDate Apple Documentation:
The Swift overlay to the Foundation framework provides the Date
structure, which bridges to the NSDate class. The Date value type
offers the same functionality as the NSDate reference type, and the
two can be used interchangeably in Swift code that interacts with
Objective-C APIs. This behavior is similar to how Swift bridges
standard string, numeric, and collection types to their corresponding
Foundation classes.
If you check timeIntervalSince method signature, it is func timeIntervalSince(_ anotherDate: Date) -> TimeInterval, note that anotherDate date type is Date (not NSDate anymore).
For more information about new value types, check this proposal of mutability and foundation value types, there is a bunch of new value types such as: NSData, NSMutableData -> Data, NSIndexPath -> IndexPath, NSNotification -> Notification...

Related

Why can't NSSpeechsynthesier's class method availableVoices be bridged to a Swift array of strings?

In Apple's Working with Cocoa Frameworks it reads as if the Foundation and Swift Foundation frameworks work together through bridging. However, I noticed that while attempting to use NSSpeechSynthesizer's class method availableVoices() it allows me to received an returned array of NSStrings but not Strings.
This compiles and runs just fine:
let voices = NSSpeechSynthesizer.availableVoices as [NSString]
print(voices)
However this won't compile:
let voicesTwo = NSSpeechSynthesizer.availableVoices as [String]
Why wouldn't this work if the voiceName documentation shows that VoiceName is a string property?
I see the term 'rawValue' in the VoiceName documentation so is the reasoning having anything to do with this being some sort of an enum?
It looks like NSSpeechSynthesizer.VoiceName is an Enum with a rawValue of String. That is not the same thing as being a string.
Try using
NSSpeechSynthesizer.availableVoices.map { $0.rawValue }

How to fix the warning of type casting in 'if let' statement in Xcode 8.3?

Consider the following code:
let nsdate: NSDate? = NSDate()
if let date = nsdate as? Date { // a warning occurs here
print(date)
}
The compiler complains like this: Conditional downcast from 'NSDate?' to 'Date' is a bridging conversion; did you mean to use 'as'?
A cast from NSData to Data has the same problem. How to fix the it?
Try to cast to an optional Date:
if let date = nsdate as Date?
You're trying to optional cast of optional NSDate to NON optional Date. As long as NSDate is bridged from obj-c to Date, so this cast always success, so no optional cast required here, just basic cast as is enough. Then you need to cast optional value, so the resulting value has to be optional too, therefore Date? is appropriate here.
Swift 3.1 distinguishes
An optional down cast as? Foo
It casts a more unspecific to a more specific type for example
let dict : [String:Any] = ["Foo" : 12]
let number = dict["Foo"] as? Int
A bridge cast of an optional type as Foo?
It bridges a (Core)Foundation type to a toll free Swift type and vice versa.
It's the optional equivalent of the usual non-optional syntax
let string : NSString = "Foo"
let swiftString = string as String
The difference is subtle for the developer but very useful for the compiler.
Basically don't use the NS... Foundation classes in Swift 3 if there is a native Swift counterpart.
Try this:
let nsdate: NSDate? = NSDate()
if let date = nsdate {
print(date)
}
The compiler knows it´s an NSDate if unwrapped, so what you are doing is actually casting an NSDate to a Date

NSCalendar.startOfDayForDate(date:) equivalent for iOS 7 with non-optional return type

Is it possible to change an NSDate object so that the result is equivalent to NSCalendar.startOfDayForDate(date:)? That method is only available to iOS 8 and newer, but I am looking for something that works on iOS 7.
I have looked at two methods:
NSCalendar.dateFromComponents(comps:) as described here: NSDate beginning of day and end of day. For instance, like this:
class func startOfDay(date: NSDate, calendar: NSCalendar) -> NSDate {
if #available(iOS 8, *) {
return calendar.startOfDayForDate(date)
} else {
let dateComponents = calendar.components([.Year, .Month, .Day], fromDate: date)
return calendar.dateFromComponents(dateComponents)!
}
}
NSDateFormatter.dateFromString(string:) by way of
stringFromDate(date:), i.e. converting the NSDate object into a string without the time, then converting it back into an NSDate object.
The problem with both methods is that they return an optional NSDate. I am reluctant to unwrap this implicitly and I’d rather avoid changing the return type of the method within which these methods are called.
I think the calendar.components() method returns an optional, because you can theoretically enter components that do not create valid date, like 2000-02-30. If, as in your case, the components already come from a valid date, I would not be reluctant to implicitly unwrap the optional.

Swift: Why class method has return type AnyObject?

NSDate.distantFuture() is documented to return an object of type NSDate.
So then, why does it have a return type of AnyObject, instead of NSDate?
In Objective-C distantFuture returns an id, not NSDate. Automatic Swift conversion makes this an AnyObject. When the class will be reviewed by Apple they will probably switch that to NSDate.
NSDate's distantFuture is actually from the distant past (at least Mac OS X 10.0, probably even before that). At this time many factory methods returned id because there was no instancetype. It was just to make it easier to call a subclasses method on the returned object.
This is actually documented as returning AnyObject.
returns nil if an event specified in the event mask does not happen before the specified date.
https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSDate_Class/#//apple_ref/occ/clm/NSDate/distantFuture**
distantFuture
Creates and returns an NSDate object representing a date in the distant future.
Declaration
SWIFT
class func distantFuture() -> AnyObject
OBJECTIVE-C
+ (id)distantFuture
Return Value
An NSDate object representing a date in the distant future (in terms of centuries).
Discussion
You can pass this value when an NSDate object is required to have the date argument essentially ignored. For example, the NSWindow method nextEventMatchingMask:untilDate:inMode:dequeue: returns nil if an event specified in the event mask does not happen before the specified date. You can use the object returned by distantFuture as the date argument to wait indefinitely for the event to occur.
myEvent = [myWindow nextEventMatchingMask:myEventMask
untilDate:[NSDate distantFuture]
inMode:NSDefaultRunLoopMode
dequeue:YES];
Import Statement
import Foundation
Availability
Available in OS X v10.0 and later.

Using setValue(value, forKey: key) on Int? types fires non key value coding-compliant method

I'm successfully using the setValue(value, forKey: key) method in my NSKeyValueCoding compliant Swift NSObject subclass.
This works perfectly well on String optionals, e.g.
var name:String?
However, on Int optionals, it fails, triggering the undefined key method that I have overridden for debugging purposes:
override func setValue(value: AnyObject!, forUndefinedKey key: String!) {
println("\(self) this class is not key value coding-compliant for the key \(key)")
}
So for example, a key of myId with a good integer value would trigger the undefined key method above.
var myId:Int?
If I change the above definition to be non-optional, then everything works fine:
var myId:Int = 0
With myId as an optional, I have tried absolutely everything I can think of in the way of casting, unwrapping, initialising, and so on. It just doesn't see the class as key value compliant for those numeric values.
I know that it is a good numeric value. Changing the var declaration to String? crashes. It also looks fine in lldb:
Printing description of key:
myId
key NSObject 0x00007fb8d530ca20 0x00007fb8d530ca20
k NSString "myId" 0x00007fa2aa942f20
value __NSCFNumber * Int64(4348129) 0xb000000004258e13
Printing description of value:
4348129
(lldb)
So, the question is, has anyone used - in Swift - the NSKeyValueCoding method setValue(value, forKey: key) on an Int type successfully?
KVO cannot function with pure Swift optionals because pure Swift optionals are not Objective-C objects. Swift forbids the use of dynamic or #objc with generic classes and structures because there is no valid Objective-C equivalent, and so the runtime is not setup to support KVO on instances of those kinds of objects. As for why it works with String?, that type is toll-free bridged to NSString, so it is semantically equivalent to NSString *, an Objective-C type that the runtime knows full-well how to deal with. But compare that to Int? whose semantic equivalent would be Optional<Int>, not UnsafePointer<Int> or NSNumber * like you might expect. For now, you'll need to convince the typechecker that it is safe to represent in Objective-C by using NSNumber!.
This is completely backwards and, in my opinion, an unfortunate limitation of the type system. For any engineers that come across this post, see rdar://18624182.
If you're willing to ditch Swift types change:
var myId:Int?
to:
var myId:NSNumber?