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

Reply via email to