This is a translation. The link to the original article is here

Swift3 incorporates objective-C apis in a more powerful way than previous versions. For instance variables, Swift2 maps the ID type in Objective-C to the AnyObject type in Swift, which typically can only hold the class type. Swift2 also provides implicit conversions to AnyObject types for some bridge value types, for example: String, Array, Dictionary, Set, and some numbers, as a convenience, make Swift native types easy to use with Cocoa APIS that require NSString, NSArray, or other container classes from Foundation. These conversions are inconsistent with the rest of the Swift language, which makes it difficult to understand exactly what can be used as an AnyObject variant, leading to errors.

In Swift3, the ID type is mapped to the Any type, which describes a value of Any type, whether class, enumeration, struct, or Any other Swift type. This change makes the Objective-C API more flexible in Swift, because the value types defined by Swift can be passed to the Objective-C API and retrieved as Swift types, eliminating the need for manual encapsulation. These benefits also extend to collections: Objective-C collection types NSArray, NSDictionary, and NSSet, which previously only accepted elements of type AnyObject, can now hold elements of type Any. For hash containers such as Dictionary and Set, there is a new type AnyHashable that can hold a value of any type that complies with the Hashable protocol in Swift. In general, the following type mapping changes occur from Swift2 to Swift3:

Objective-C Swift 2 Swift 3
id AnyObject Any
NSArray * [AnyObject] [Any]
NSDictionary * [NSObject: AnyObject] [AnyHashable: Any]
NSSet * Set<NSObject> Set<AnyHashable>

In many cases, your code does not need to change significantly in response to this change. Code that implicitly converts Swift2 dependent value types to AnyObject still works in Swift3 and is only passed as Any. However, there are places where you have to change the declaration types of variables and methods to get the best Swift3 coding experience. And if your code explicitly uses AnyObject or Cocoa classes, such as NSString, NSArray, or NSDictionary, you’ll need to explicitly use as NSString or as String to introduce more characters, because in Swift3, Implicit conversions between object types and value types are no longer allowed.

Override methods and comply with protocols

When inheriting an Objective-C class and overriding its methods, or complying with an Objective-C protocol, the type signature of those methods needs to be updated when the superclass methods use ids. Some common examples are NSObject’s isEqual: method and NSCopying’s copyWithZone: method. In Swift2, you would write a subclass that inherits NSObject and complies with the NSCopying protocol like this:

// Swift 2 class Foo: NSObject, NSCopying { override func isEqual(_ x: AnyObject?) -> Bool { ... } func copyWithZone(_ zone: NSZone?) -> AnyObject { ... }}Copy the code

In Swift3, in addition to changing the method name from copyWithZone(_:) to copy(with:), you also need to change the signature of these methods using Any instead of AnyObject:

// Swift 3 class Foo: NSObject, NSCopying { override func isEqual(_ x: Any?) -> Bool { ... } func copy(with zone: NSZone?) -> Any { ... }}Copy the code

Untyped set

Property lists, JSON, and user information dictionaries are common in Cocoa, and Cocoa natively treats these as untyped collections. For this purpose, in Swift2, it is necessary to build arrays, dictionaries, or sets with AnyObject or NSObject elements, relying on implicit bridging for processing value types:

// 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
	}
}
let california = State(name: "California",
                       abbreviation: "CA",  
                       population: 39_000_000)
NSNotification(name: "foo", object: nil, userInfo: california.asPropertyList)
Copy the code

Alternatively, you can use Cocoa’s container classes, such as NSDictionary:

// Swift 2 struct State { var name: String var abbreviation: String var population: Int var asPropertyList: NSDictionary {var result = NSMutableDictionary() // 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.copy()
	}
}
let california = State(name: "California",  abbreviation: "CA", population: 39_000_000)
// NSDictionary then implicitly converts to [NSObject: AnyObject] here.
NSNotification(name: "foo", object: nil, userInfo: california.asPropertyList)
Copy the code

In Swift3, implicit conversion does not exist, so none of the above code snippets work. The migrator might suggest adding an AS transformation individually to each transformation to make it work, but there is a better solution. Swift now introduces Cocoa API as an acceptable set of types Any and/or AnyHashable, so we can modify the set type with [AnyHashable: Any] instead of [NSObject: AnyObject] or NSDictionary without changing other code:

// Swift 3
struct State {
	var name: String
	var abbreviation: String
	var population: Int
	// Change the dictionary type to [AnyHashable: Any] here...
	var asPropertyList: [AnyHashable: Any] {
		var result: [AnyHashable: Any] = [:]
		// No implicit conversions necessary, since String and Int are subtypes
		// of Any and AnyHashable
		result["name"] = self.name
		result["abbreviation"] = self.abbreviation
		result["population"] = self.population
		return result
	}
}
let california = State(name: "California", abbreviation: "CA", population: 39_000_000) // ... and you can still use it with Cocoa API here Notification(name:"foo", object: nil, userInfo: california.asPropertyList)
Copy the code

AnyHashable type

Swift’s Any type can hold Any type, but Dictionary and Set require keys to be Hashable, so Any is too broad. Starting with Swift3, the Swift standard library provides a new type, AnyHashable. Like Any, it is the parent of all Hashable types, so String, Int, and other hashed values can be implicitly used as AnyHashable values, and an AnyHashable internal type can be used as is, as! , or as? These dynamic cast operators check dynamically. AnyHashable is used when you need to import untyped NSDictionary or NSSet objects from object-C, but it is also useful to create complex collections and dictionaries in pure Swift.

Display conversion for unbridged contexts

Swift cannot automatically bridge C and Objective-C constructs under certain restricted circumstances. For example, some C and Cocoa apis use id * Pointers as input and output parameters, and since Swift cannot statically determine how this pointer is used, it cannot automatically perform a bridge conversion on this value in memory. In cases like this, the pointer will still be UnsafePointer

. If you need to use one of these unbridged apis, you can use an explicit bridge conversion to explicitly write as Type or as AnyObject in your code.

// ObjC
@interface Foo
- (void)updateString:(NSString **)string;
- (void)updateObject:(id *)obj;
@end
Copy the code

// Swift
func interactWith(foo: Foo) -> (String, Any) {
	var string = "string" as NSString // explicit conversion
	foo.updateString(&string) // parameter imports as UnsafeMutablePointer<NSString>
	let finishedString = string as String
	var object = "string" as AnyObject
	foo.updateObject(&object) // parameter imports as UnsafeMutablePointer<AnyObject>
	let finishedObject = object as Any
	return (finishedString, finishedObject)
}
Copy the code

In addition, the objective-C protocols are still class-bound in Swift, so you can’t make Swift structures and enums directly adhere to objective-C or use them with lightweight generic classes. You need to use their protocols and apis to explicitly convert String as NSString, Array as NSArray and so on.

AnyObject member check

Any has no magic method lookup behavior like AnyObject. This can break the Swift2 code that looks up a property or sends a message to an untyped object-C Object. For example, this Swift2 code:

// Swift 2
func foo(x: NSArray) {
	// Invokes -description by magic AnyObject lookup
	print(x[0].description)
}
Copy the code

In Swift3 it will be complained that Description is not a member of Any. You can convert this value to x[0] as AnyObject to regain the dynamic properties:

// Swift 3
func foo(x: NSArray) {
	// Result of subscript is now Any, needs to be coerced to get method lookup
	print((x[0] as AnyObject).description)
}
Copy the code

Alternatively, cast the value to the entity object you want:

func foo(x: NSArray) {
	// Cast to the concrete object type you expect
	print((x[0] as! NSObject).description)
}
Copy the code

Type of Swift value in object-c

Any can hold Any structs, enumerations, tuples, or other types that you can define in the language. The object-c bridge in Swift3 can in turn render any Swift value as an ID-object-C compatible Object. This makes it easier to store custom Swift value types in Cocoa containers, userInfo dictionaries, and other objects. For example, in Swift2, you need to change your data type to class, or manually wrap them to append their values to an NSNotification:

// Swift 2
struct CreditCard { number: UInt64, expiration: NSDate }
let PaymentMade = "PaymentMade"
// We can't attach CreditCard directly to the notification, since it
// isn't a class, and doesn't bridge. // Wrap it in a Box class. class Box
      
        { let value: T init(value: T) { self.value = value } } let paymentNotification = NSNotification(name: PaymentMade, object: Box(value: CreditCard(number: 1234_0000_0000_0000, expiration: NSDate())))
      Copy the code

In Swift3, we don’t need a packet, we can just append this object to the notification:

// Swift 3
let PaymentMade = Notification.Name("PaymentMade")
// We can associate the CreditCard value directly with the Notification
let paymentNotification =
	Notification(name: PaymentMade, object: CreditCard(number: 1234_0000_0000_0000, expiration: Date()))
Copy the code

In object-c, this CreditCard value is going to be an id-compliant, NSObject– an Object that implements isEqual:, hash, and description, Use Swift’s Equatable, Hashable, and CustomStringConvertible implementations if they exist with the original Swift type. In Swift, this value can be retrieved by dynamically converting back to its original type:

// Swift 3
let paymentCard = paymentNotification.object as! CreditCard
print(paymentCard.number) // 1234000000000000
Copy the code

In Swift3.0, you should be aware that some common Swift and object-C structure types bridge to opaque objects, rather than the language conventions of Cocoa objects. For example, while Int, UInt, Double, and Bool are bridged to NSNumber, other numeric types with sizes such as Int8, UInt16, etc., are bridged to opaque objects only. Cocoa constructs such as CGRect, CGPoint, and CGSize are also bridged as opaque objects, even though most Of the Cocoa apis that use them expect them to be encapsulated as NSValue instances. If you see an error like unrecognized selector sent to _SwiftValue, it’s a sign that Objective-C code is trying to introduce a method on an opaque Swift value type, You might need to manually encapsulate that value with an instance of the class that objective-C code expects.

One issue that needs special attention is optional values. Any can hold anything, including an optional value, so it is possible to pass a wrapped optional value to an Objective-C API without checking it first, even if the API declares that it requires a nonNULL ID. This will usually show up as a runtime error with _SwiftValue, not a compile-time error. Swift3.0.1 in Xcode8.1beta implements these proposals by locating the above NSNumber, NSValue, and Optional bridge constraints to explicitly handle numeric types, objective-c constructs, and Optional values.

To avoid pass-compatibility issues, you should not rely on the implementation details of opaque objects of the _SwiftValue class, because future versions of Swift may allow more Swift types to bridge to object-C types that conform to language conventions.