Using Swift with Cocoa and Objective-C--互用性-与ObjC API交互

严繁
2023-12-01

https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/BuildingCocoaApps/InteractingWithObjective-CAPIs.html#//apple_ref/doc/uid/TP40014216-CH4-XID_26


http://www.cocoachina.com/newbie/basic/2014/0610/8757.html




Initialization

To instantiate an Objective-C class in Swift, you call one of its initializers with Swift syntax. When Objective-C init methods come over to Swift, they take on native Swift initializer syntax. The “init” prefix gets sliced off and becomes a keyword to indicate that the method is an initializer. For init methods that begin with “initWith,“ the “With” also gets sliced off. The first letter of the selector piece that had “init” or “initWith” split off from it becomes lowercase, and that selector piece is treated as the name of the first argument. The rest of the selector pieces also correspond to argument names. Each selector piece goes inside the parentheses and is required at the call site.

For example, where in Objective-C you would do this:

Objective-C

  • UITableView *myTableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStyleGrouped];

In Swift, you do this:

Swift

  • let myTableView: UITableView = UITableView(frame: CGRectZero, style: .Grouped)

You don’t need to call alloc; Swift correctly handles this for you. Notice that “init” doesn’t appear anywhere when calling the Swift-style initializer.

You can be explicit in typing the object during initialization, or you can omit the type. Swift’s type inference correctly determines the type of the object.

Swift

  • let myTextField = UITextField(frame: CGRect(0.0, 0.0, 200.0, 40.0))

These UITableView and UITextField objects have the same familiar functionality that they have in Objective-C. You can use them in the same way you would in Objective-C, accessing any properties and calling any methods defined on the respective classes.

For consistency and simplicity, Objective-C factory methods get mapped as convenience initializers in Swift. This mapping allows them to be used with the same concise, clear syntax as initializers. For example, whereas in Objective-C you would call this factory method like this:

Objective-C

  • UIColor *color = [UIColor colorWithRed:0.5 green:0.0 blue:0.5 alpha:1.0];

In Swift, you call it like this:

Swift

  • let color = UIColor(red: 0.5, green: 0.0, blue: 0.5, alpha: 1.0)

Accessing Properties

Access and set properties on Objective-C objects in Swift using dot syntax.

Swift

  • myTextField.textColor = UIColor.darkGrayColor()
  • myTextField.text = "Hello world"
  • if myTextField.editing {
  • myTextField.editing = false
  • }

When getting or setting a property, use the name of the property without appending parentheses. Notice that darkGrayColor has a set of parentheses. This is because darkGrayColor is a class method on UIColor, not a property.

In Objective-C, a method that returns a value and takes no arguments can be treated as an implicit getter—and be called using the same syntax as a getter for a property. This is not the case in Swift. In Swift, only properties that are written using the @property syntax in Objective-C are imported as properties. Methods are imported and called as described in Working with Methods.

Working with Methods

When calling Objective-C methods from Swift, use dot syntax.

When Objective-C methods come over to Swift, the first part of an Objective-C selector becomes the base method name and appears outside the parentheses. The first argument appears immediately inside the parentheses, without a name. The rest of the selector pieces correspond to argument names and go inside the parentheses. All selector pieces are required at the call site.

For example, whereas in Objective-C you would do this:

Objective-C

  • [myTableView insertSubview:mySubview atIndex:2];

In Swift, you do this:

Swift

  • myTableView.insertSubview(mySubview, atIndex: 2)

If you’re calling a method with no arguments, you must still include the parentheses.

Swift

  • myTableView.layoutIfNeeded()

id Compatibility

Swift includes a protocol type named AnyObject that represents any kind of object, just as id does in Objective-C. The AnyObject protocol allows you to write type-safe Swift code while maintaining the flexibility of an untyped object. Because of the additional safety provided by the AnyObject protocol, Swift imports id as AnyObject.

For example, as with id, you can assign an object of any class type to a constant or variable typed as AnyObject. You can also reassign a variable to an object of a different type.

Swift

  • var myObject: AnyObject = UITableViewCell()
  • myObject = NSDate()

You can also call any Objective-C method and access any property without casting to a more specific class type. This includes Objective-C compatible methods marked with the @objc attribute.

Swift

  • let futureDate = myObject.dateByAddingTimeInterval(10)
  • let timeSinceNow = myObject.timeIntervalSinceNow

However, because the specific type of an object typed as AnyObject is not known until runtime, it is possible to inadvertently write unsafe code. Additionally, in contrast with Objective-C, if you invoke a method or access a property that does not exist on an AnyObject typed object, it is a runtime error. For example, the following code compiles without complaint and then causes an unrecognized selector error at runtime:

Swift

  • myObject.characterAtIndex(5)
  • // crash, myObject does't respond to that method

However, you can take advantage of optionals in Swift to eliminate this common Objective-C error from your code. When you call an Objective-C method on an AnyObject type object, the method call actually behaves like an implicitly unwrapped optional. You can use the same optional chaining syntax you would use for optional methods in protocols to optionally invoke a method on AnyObject. This same process applies to properties as well.

For example, in the code listing below, the first and second lines are not executed because the length property and the characterAtIndex: method do not exist on an NSDate object. The myLength constant is inferred to be an optional Int, and is set to nil. You can also use an iflet statement to conditionally unwrap the result of a method that the object may not respond to, as shown on line three.

Swift

  • let myLength = myObject.length?
  • let myChar = myObject.characterAtIndex?(5)
  • if let fifthCharacter = myObject.characterAtIndex(5) {
  • println("Found \(fifthCharacter) at index 5")
  • }

As with all downcasts in Swift, casting from AnyObject to a more specific object type is not guaranteed to succeed and therefore returns an optional value. You can check that optional value to determine whether the cast succeeded.

Swift

  • let userDefaults = NSUserDefaults.standardUserDefaults()
  • let lastRefreshDate: AnyObject? = userDefaults.objectForKey("LastRefreshDate")
  • if let date = lastRefreshDate as? NSDate {
  • println("\(date.timeIntervalSinceReferenceDate)")
  • }

Of course, if you are certain of the type of the object (and know that it is not nil), you can force the invocation with the as operator.

Swift

  • let myDate = lastRefreshDate as NSDate
  • let timeInterval = myDate.timeIntervalSinceReferenceDate

Working with nil

In Objective-C, you work with references to objects using raw pointers that could be NULL (also referred to as nil in Objective-C). In Swift, all values—including structures and object references—are guaranteed to be non–nil. Instead, you represent a value that could be missing by wrapping the type of the value in an optional type. When you need to indicate that a value is missing, you use the value nil. You can read more about optionals in Optionals.

Because Objective-C does not make any guarantees that an object is non-nil, Swift makes all classes in argument types and return types optional in imported Objective-C APIs. Before you use an Objective-C object, you should check to ensure that it is not missing.

In some cases, you might be absolutely certain that an Objective-C method or property never returns a nil object reference. To make objects in this special scenario more convenient to work with, Swift imports object types as implicitly unwrapped optionals. Implicitly unwrapped optional types include all of the safety features of optional types. In addition, you can access the value directly without checking for nil or unwrapping it yourself. When you access the value in this kind of optional type without safely unwrapping it first, the implicitly unwrapped optional checks whether the value is missing. If the value is missing, a runtime error occurs. As a result, you should always check and unwrap an implicitly unwrapped optional yourself, unless you are sure that the value cannot be missing.

Extensions

A Swift extension is similar to an Objective-C category. Extensions expand the behavior of existing classes, structures, and enumerations, including those defined in Objective-C. You can define an extension on a type from either a system framework or one of your own custom types. Simply import the appropriate module, and refer to the class, structure, or enumeration by the same name that you would use in Objective-C.

For example, you can extend the UIBezierPath class to create a simple Bézier path with an equilateral triangle, based on a provided side length and starting point.

Swift

  • extension UIBezierPath {
  • convenience init(triangleSideLength: Float, origin: CGPoint) {
  • self.init()
  • let squareRoot = Float(sqrt(3))
  • let altitude = (squareRoot * triangleSideLength) / 2
  • moveToPoint(origin)
  • addLineToPoint(CGPoint(triangleSideLength, origin.x))
  • addLineToPoint(CGPoint(triangleSideLength / 2, altitude))
  • closePath()
  • }
  • }

You can use extensions to add properties (including class and static properties). However, these properties must be computed; extensions can’t add stored properties to classes, structures, or enumerations.

This example extends the CGRect structure to contain a computed area property:

Swift

  • extension CGRect {
  • var area: CGFloat {
  • return width * height
  • }
  • }
  • let rect = CGRect(x: 0.0, y: 0.0, width: 10.0, height: 50.0)
  • let area = rect.area
  • // area: CGFloat = 500.0

You can also use extensions to add protocol conformance to a class without subclassing it. If the protocol is defined in Swift, you can also add conformance to it to structures or enumerations, whether defined in Swift or Objective-C.

You cannot use extensions to override existing methods or properties on Objective-C types.

Closures

Objective-C blocks are automatically imported as Swift closures. For example, here is an Objective-C block variable:

Objective-C

  • void (^completionBlock)(NSData *, NSError *) = ^(NSData *data, NSError *error) {/* ... */}

And here’s what it looks like in Swift:

Swift

  • let completionBlock: (NSData, NSError) -> Void = {data, error in /* ... */}

Swift closures and Objective-C blocks are compatible, so you can pass Swift closures to Objective-C methods that expect blocks. Swift closures and functions have the same type, so you can even pass the name of a Swift function.

Closures have similar capture semantics as blocks but differ in one key way: Variables are mutable rather than copied. In other words, the behavior of __block in Objective-C is the default behavior for variables in Swift.

Object Comparison

There are two distinct types of comparison when you compare two objects in Swift. The first, equality (==), compares the contents of the objects. The second, identity (===), determines whether or not the constants or variables refer to the same object instance.

Swift and Objective-C objects are typically compared in Swift using the == and === operators. Swift provides a default implementation of the == operator for objects that derive from the NSObject class. In the implementation of this operator, Swift invokes the isEqual: method defined on the NSObject class. The NSObject class only performs an identity comparison, so you should implement your own isEqual: method in classes that derive from the NSObject class. Because you can pass Swift objects (including ones not derived from NSObject) to Objective-C APIs, you should implement the isEqual: method for these classes if you want the Objective-C APIs to compare the contents of the objects rather than their identities.

As part of implementing equality for your class, be sure to implement the hash property according to the rules in Object comparison. Further, if you want to use your class as keys in a dictionary, also conform to the Hashable protocol and implement the hashValue property.

Swift Type Compatibility

When you define a Swift class that inherits from NSObject or any other Objective-C class, the class is automatically compatible with Objective-C. All of the steps in this section have already been done for you by the Swift compiler. If you never import a Swift class in Objective-C code, you don’t need to worry about type compatibility in this case as well. Otherwise, if your Swift class does not derive from an Objective-C class and you want to use it from Objective-C code, you can use the @objc attribute described below.

The @objc attribute makes your Swift API available in Objective-C and the Objective-C runtime. In other words, you can use the @objc attribute before any Swift method, property, or class that you want to use from Objective-C code. If your class inherits from an Objective-C class, the compiler inserts the attribute for you. The compiler also adds the attribute to every method and property in a class that is itself marked with the @objc attribute. When you use the @IBOutlet, @IBAction, or @NSManaged attribute, the @objc attribute is added as well. This attribute is also useful when you’re working with Objective-C classes that use selectors to implement the target-action design pattern—for example, NSTimer or UIButton.

When you use a Swift API from Objective-C, the compiler typically performs a direct translation. For example, the Swift API func playSong(name: String) is imported as - (void)playSong:(NSString *)name in Objective-C. However, there is one exception: When you use a Swift initializer in Objective-C, the compiler adds the text “initWith” to the beginning of the method and properly capitalizes the first character in the original initializer. For example, this Swift initializer init (songName: String, artist: String) is imported as - (instancetype)initWithSongName:(NSString *)songName artist:(NSString *)artist in Objective-C.

Swift also provides a variant of the @objc attribute that allows you to specify name for your symbol in Objective-C. For example, if the name of your Swift class contains a character that isn’t supported by Objective-C, you can provide an alternative name to use in Objective-C. If you provide an Objective-C name for a Swift function, use Objective-C selector syntax. Remember to add a colon (:) wherever a parameter follows a selector piece.

Swift

  • @objc(Squirrel)
  • class Белка {
  • @objc(initWithName:)
  • init (имя: String) { /*...*/ }
  • @objc(hideNuts:inTree:)
  • func прячьОрехи(Int, вДереве: Дерево) { /*...*/ }
  • }

When you use the @objc(<#name#>) attribute on a Swift class, the class is made available in Objective-C without any namespacing. As a result, this attribute can also be useful when you migrate an archivable Objective-C class to Swift. Because archived objects store the name of their class in the archive, you should use the @objc(<#name#>) attribute to specify the same name as your Objective-C class so that older archives can be unarchived by your new Swift class.

Objective-C Selectors

An Objective-C selector is a type that refers to the name of an Objective-C method. In Swift, Objective-C selectors are represented by the Selector structure. You can construct a selector with a string literal, such as let mySelector: Selector = "tappedButton:". Because string literals can be automatically converted to selectors, you can pass a string literal to any method that accepts a selector.

Swift

  • import UIKit
  • class MyViewController: UIViewController {
  • let myButton = UIButton(frame: CGRect(x: 0, y: 0, width: 100, height: 50))

  • init(nibName nibNameOrNil: String!, bundle nibBundleOrNil: NSBundle!) {
  • super.init(nibName: nibName, bundle: nibBundle)
  • myButton.targetForAction("tappedButton:", withSender: self)
  • }

  • func tappedButton(sender: UIButton!) {
  • println("tapped button")
  • }
  • }

 类似资料: