Hi everyone. SE-0116 should be landing in snapshots soon, which changes how Objective-C APIs import into Swift. Instead of relying on special implicit conversions to AnyObject, we now import the ObjC APIs using `Any` to get the same effect without special language rules. I've written up a draft guide about the kinds of changes necessary, along with some known issues. I'd also like to gather feedback from anyone who's picked up the snapshot about their experiences to make sure we address any pain points introduced by the changes. Thank you for any feedback you can provide!
-Joe
`id` as `Any` ============= Swift 3 preview 6 introduces the following changes to how Objective-C APIs are presented in Swift: - Swift value types, such as `String`, `Array`, `Dictionary`, and `Set` no longer implicitly convert to the corresponding Cocoa class types `NSString`, `NSArray`, etc. - Instead, Objective-C APIs with the `id` type now use the `Any` type when used in Swift instead of `AnyObject`. This allows Swift value types to be used seamlessly with these APIs without error-prone implicit conversion behavior affecting the entire language. - Similarly, Objective-C APIs that use untyped `NSArray` containers appear in Swift as collections of `Any`. Since `Dictionary` and `Set` require keys that are `Hashable`, untyped `NSDictionary` and `NSSet` containers use keys of the new `AnyHashable` type from the Swift standard library, which can hold a value of any `Hashable` type. In summary, the following type mappings change from Swift 2 to Swift 3: Objective-C | Swift 2 | Swift 3 ---------------- | ----------------------- | -------------------- `id` | `AnyObject` | `Any` `NSArray *` | `[AnyObject]` | `[Any]` `NSDictionary *` | `[NSObject: AnyObject]` | `[AnyHashable: Any]` `NSSet *` | `Set<NSObject>` | `Set<AnyHashable>` The collection conversions for `Array`, `Dictionary`, and `Set` have also been generalized to work with Swift protocol types and `Any`, so a `[String]` array can be implicitly converted to an `[Any]` array, for example. When passing Swift value types to untyped Objective-C APIs, in many cases no change is necessary at all, and things will work as they did in Swift 2. However, there are some places where code changes are necessary. Overriding methods and conforming to protocols ---------------------------------------------- The type signatures of class methods that override methods from a base class or conform to Objective-C protocols need to be updated when the parent method uses `id` in Objective-C. The `NSObject` class's `isEqual(_:)` method and the `NSCopying` protocol's `copy(with:)` method are common examples of this: ```swift // Swift 2 class Foo: NSObject, NSCopying { override func isEqual(_ x: AnyObject?) -> Bool { ... } func copyWithZone(_ zone: NSZone?) -> AnyObject { ... } } ``` In Swift 3, change the signatures to use `Any` instead of `AnyObject`: ```swift // Swift 3 class Foo: NSObject, NSCopying { override func isEqual(_ x: Any?) -> Bool { ... } func copy(with zone: NSZone?) -> Any { ... } } ``` Heterogeneous Collections ------------------------- It is common to build heterogeneous collections to interoperate with Cocoa for things like user info dictionaries and property lists. Outside of Cocoa, heterogeneous collections also naturally map to popular interchange formats like JSON and YAML. In Swift 2, it was common to build collections of `AnyObject` and/or `NSObject` for this purpose, relying on implicit bridging conversions to adapt value types, since that's what Cocoa would produce and consume: ```swift // Swift 2 struct State { var name: String var abbreviation: String var population: Int var asPropertyList: [NSObject: AnyObject] { var result: [NSObject: AnyObject] = [:] // Implicit conversions turn String into NSString here⦠result["name"] = self.name result["abbreviation"] = self.abbreviation // â¦and Int into NSNumber here. result["population"] = self.population return result } } ``` In Swift 3, the implicit conversions are gone, so this code will no longer work as-is. However, Cocoa APIs now accept collections of `Any` and/or `AnyHashable`, so we can change the collection to use `Any` and use its natural subtyping relationship instead: ```swift // Swift 3 struct State { var name: String var abbreviation: String var population: Int var asPropertyList: [AnyHashable: Any] { var result: [AnyHashable: Any] = [:] // No implicit conversions necessary, since String and Int are subtypes // of Any result["name"] = self.name result["abbreviation"] = self.abbreviation result["population"] = self.population return result } } ``` Unbridged Contexts ------------------ There are still certain C and Objective-C constructs where Swift is unable to perform automatic bridging, particularly when C APIs use pointers as "out" or "in-out" parameters. In these cases, the bridging conversions can still be manually invoked by explicitly writing `as NSType`. In Swift 3, any type can also be bridged `as AnyObject`. ```objc // ObjC @interface Foo - (void)updateString:(NSString **)string; - (void)updateObject:(id *)obj; @end ``` ```swift // Swift func interactWith(foo: Foo) -> (String, Any) { var string = "string" as NSString // explicit conversion foo.updateString(&string) let finishedString = string as String var object = "string" as AnyObject foo.updateObject(&object) let finishedObject = object as Any return (finishedString, finishedObject) } ``` Objective-C generics and protocols are still imported into Swift as class-constrained, so manual bridging to objects may also be necessary when working with generic Objective-C APIs. Known Limitations ----------------- In the current version of the compiler there are some limitations to `id`-as-`Any` that lead to more explicit conversions being necessary than desirable. ### Literal `Dictionary`s and `Set`s with `AnyHashable` Keys `Dictionary` and `Set` provide special overloads of their indexing and membership operations when they contain `AnyHashable` keys so that they work with any type that conforms to `Hashable`. This support does not yet extend to literal syntax, so keys have to be individually wrapped in `AnyHashable`. ### Indexing `NSDictionary`s `NSDictionary`'s subscript operator is still imported as taking `NSCopying`, so explicit conversions to object are necessary when indexing. ### Literal `NS` Containers `NSArray`, `NSDictionary`, and `NSSet` still conform to `ExpressibleBy{Array,Dictionary}Literal` with classes as their associated element types, necessitating explicit bridging of elements in literals of those types. ### Transitive Coercions Due to compiler bugs, some explicit conversions with `as` don't always successfully apply transitively, making it necessary to do two conversions when going from a string to a type such as `Notification.Name`. ```swift "foo" as NSNotification.Name // should work, but doesn't "foo" as NSString as NSNotification.Name // workaround ``` This also occasionally comes up with literals that require explicit coercion: ```swift ["foo": "bar"] as NSDictionary // should work, but doesn't ["foo": "bar"] as Dictionary as NSDictionary // workaround ``` ### Nested Hetergeneous Container Literals The type checker sometimes fails to infer the element type of nested heterogeneous collections, necessitating explicit `as` annotations on the inner literals: ```swift let x: [AnyHashable: Any] = [ "foo": 1, "bar": "two", "bas": [ 1, "two", "three" ] as [Any] // shouldn't be necessary, but is ] ```
_______________________________________________ swift-users mailing list swift-users@swift.org https://lists.swift.org/mailman/listinfo/swift-users