Swift calls an instance of API written by Objective C

  • 2020-05-07 20:31:15
  • OfStack

Interoperability is a feature that makes Swift and Objective-C coalesce, enabling you to use another language in a file written in one language. When you are ready to start incorporating Swift into your development process, you should know how to use interoperability to redefine and improve your solution for writing Cocoa applications.

Interoperability is important because it allows you to use Objective-C's API interface when writing Swift code. When you import an Objective-C framework, you can instantiate its Class and interact with it using the native Swift syntax.

initializes

To instantiate Class of Objective-C with Swift, you should call one of its initializers using Swift syntax. When the init method of Objective-C changes to Swift, they are rendered using the Swift initialization syntax. The "init" prefix is truncated as a keyword to indicate that the method is an initialization method. For init methods that start with "initWith", "With" is also removed. This part of the method name separated from "init" or "initWith" becomes lowercase and is treated as the parameter name of the first parameter. The rest of the method names change the parameter names for each part. These method names are called in parentheses.

For example, here's what you do with Objective-C:


//Objective-C
UITableView *myTableView = [[UITableView alloc]
initWithFrame:CGRectZero style:UITableViewStyleGrouped];

In Swift, you should do this:


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

You don't need to call alloc, Swift can handle it for you. Note that "init" does not appear when an Swift-style initialization function is used.
You can declare the type of the object explicitly at initialization time, or you can ignore it. Swift correctly determines the type of the object.


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

The UITableView and UITextField objects here have the same functionality as the ones you use in Objective-C. You can use them in 1 way, including accessing properties or calling methods in their respective classes.
For unification and simplicity, Objective-C's factory methods are also mapped to a convenient initialization method in Swift. This mapping allows them to use the same straightforward initialization method. For example, in Objective-C you might call a factory method like this:


//Objective-C
UIColor *color = [UIColor colorWithRed:0.5 green:0.0 blue:0.5 alpha:1.0];

In Swift, you should do this:


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

access property

When accessing and setting the properties of the Objective-C object in Swift, use the dot syntax:


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

When an get or set attribute is used, the attribute name is used without the need for additional parentheses. Note that darkGrayColor is followed by a pair of parentheses because darkGrayColor is a class method of UIColor, not an attribute.

In Objective-C, a parameterless method with a return value can be called as an implicit accessor and can be called in the same way as an accessor. This is no longer possible in Swift, and only properties declared using the @property keyword are introduced as properties.

method

Point syntax is used when the Objective-C method is called in Swift.

When the Objective-C method is converted to Swift, the first part of es1118en of Objective-C will be the method name and appear before the parenthesis, while the first parameter will appear directly in the parenthesis without the parameter name, and the remaining parameter name and parameter 11 will be filled in the parenthesis.

For example, here's what you do with Objective-C:


//Objective-C
[myTableView insertSubview:mySubview atIndex:2];

In Swift, you should do this:


//Swift
myTableView.insertSubview(mySubview, atIndex: 2)

If you call a method with no arguments, you still have to put a pair of parentheses after the method name


//Swift
myTableView.layoutIfNeeded()

id compatibility (id Compatibility)

Swift contains one protocol type called AnyObject, which represents objects of any type, like id1 in Objective-C. The AnyObject protocol allows you to write type-safe Swift code while maintaining the flexibility of untyped objects. Because the AnyObject protocol guarantees this security, Swift imports the id object as AnyObject.

For example, like id 1, you can assign objects of any other type to objects of AnyObject type, and you can also assign objects of any other type to them.


//Swift
var myObject: AnyObject = UITableViewCell()
myObject = NSDate()

You can also call the Objective-C method or access a property without converting it to a concrete class type. This includes the method marked @objc in Objcive-C.


//Swift
let futureDate = myObject.dateByAddingTimeInterval(10)
let timeSinceNow = myObject.timeIntervalSinceNow

However, since the object type of AnyObject is not known until runtime, it is possible to inadvertently write unsafe code. Also, unlike Objective-C, if you call a method or access an AnyObject object that is not declared, a runtime error will be reported. For example, the following code will report an unrecognized selector error error at runtime:


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

However, you can eliminate this common error in Objective-C by using Swift's optinals feature. When you call an Objective-C method with an AnyObject object, the call becomes an implicit expansion of optional (implicitly unwrapped optional). You can use the optional feature to determine whether an object of type AnyObject will call this method, and again, you can apply this feature to properties.

For example, in the following code, lines 1 and 2 will not be executed because the length property and characterAtIndex: method do not exist in the NSDate object. The myLength constant is assumed to be of the optional Int type and assigned to nil. You can also use the if-let declaration to conditionally expand the method's return value to determine whether the object can execute the method. Just like we did in line 3.


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

For casts in Swift, converting from an object of type AnyObject to an explicit type is no guarantee of success, so it returns an optional value. You check the type of the value to see if the conversion was successful.


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

Of course, if you can determine the type of the object (and that it is not nil), you can add the as operator to force the call.


//Swift
let myDate = lastRefreshDate as NSDate
let timeInterval = myDate.timeIntervalSinceReferenceDate

USES nil

In Objective-C, a reference to an object can be a primitive pointer to the value NULL (also nil in Objective-C). In Swift, all the values, including references to structures and objects, are guaranteed to be non-null. Instead, you wrap the nullable value as optional type. When you need to declare a value to be null, you need to use nil. You can learn more about it in Optionals.

Since Objective-C does not guarantee that the value of an object is not null, Swift ensures that the return type and parameter type of all functions are optional when introducing Objective-C API. Before you use Objective-C API, you should check and make sure that the value is not null. In some cases, you may be absolutely certain that certain Objective-C methods or properties should never return an object reference to nil. To make objects easier to use in this situation, Swift introduces objects using the implicitly unwrapped optionals method. implicitly unwrapped optionals contains all the security features of the optional type. In addition, you can access the value of the object directly without checking nil. When you access this type of variable, implicitly unwrapped optional first checks to see if the value of the object does not exist. If it does not exist, a runtime error will be thrown.

extension (Extensions)

The Swift extension is similar to the Objective-C category (Category). Extensions to existing classes, structures, and enumerations enrich functionality, including those defined in Objective-C. You can add extensions to the system's framework or to your own types by simply importing the appropriate modules and ensuring that the classes, structures, or enumerations you use in Objective-C have the same name.

For example, you can extend the UIBezierPath class to add an equilateral triangle to it by providing the length and starting point of the triangle.


//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 also use extensions to add properties (both class properties and static properties). However, these properties must be computed, and extensions are not stored as classes, structs, or enumerations. The following example adds a property called area to the CGRect class.


//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 protocols to a class without subclassing it. If the protocol is defined in Swift, you can add comformance to its structure or enumeration whether they are defined in Objective-C or Swift.

You cannot use an extension to override methods and properties of the Objective-C type.

closure (Closures)

blocks in Objective-C is automatically imported as a closure in Swift. For example, here is the block variable in Objective-C:


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

And it's in Swift in the form of


//Swift
let myTableView: UITableView = UITableView(frame: CGRectZero, style: .Grouped)
8

The closure of Swift works well with blocks of Objective-C, so you can pass an Swift closure to an Objective-C function that takes block as an argument. The Swift closure has an interworking type with the function, so you can even pass the name of the Swift function.
Closures are semantically sensible from blocks but differ in one respect: variables can be changed directly, rather than being copied as block does. In other words, the default behavior of the variable in Swift corresponds to that of the variable 1 in Objective-C.

compares

When comparing two objects in Swift, there are two ways to do this. The first is to use (==) to determine whether two objects have the same content. The second is to use (===) to determine whether a constant or variable is an instance of the same object.

Swift compares with Objective-C 1 using the == and === operators. The == operator of Swift provides a default implementation for objects derived from NSObject. When implementing the == operator, Swift calls the isEqual: method defined by NSObject.

The NSObject class only does identity comparisons, so you need to re-implement the isEqual: method in your own class. Since you can pass Swift objects directly to API of Objective-C, you should also implement custom isEqual: methods for these objects if you want to compare whether the contents of two objects are the same rather than just comparing whether they are derived from the same object.

As part 1 of the implementation comparison function, ensure that the hash attribute of the object is implemented according to Object comparison. Further, if you want your class to act as a key in a dictionary, you also need to comply with the Hashable protocol and implement the hashValues attribute.

Swift type compatibility

When you define an Swift class that inherits from NSObject or another Objective-C class, these classes are seamlessly connected to Objective-C. All the steps are done automatically by the Swift compiler, and if you never import the Swift class in the Objective-C code, you don't need to worry about type adaptation. In the other case, if your Swift class does not come from the Objectve-C class and you want to be able to use it in the Objecive-C code, you can use the @objc property described below.

objc lets you use Swift API in Objective-C. In other words, you can make them available in Objective-C code by adding @objc before any Swift methods, classes, or properties. If your class is inherited from Objective-C, the compiler will automatically help you with this step. The compiler also adds @objc to all variables, methods, and properties if the class itself is preceded by the @objc keyword. When you use the @IBOutlet, @IBAction, or @NSManaged attribute, @objc is automatically added to the front. This keyword can also be used in the target-action design pattern in Objetive-C, for example, NSTimer or UIButton.

When you use Swift API in Objective-C, the compiler basically translates the statement directly. For example, Swift API func playSong(name: String) would be interpreted as - (void)playSong:(NSString *)name. However, there is one exception: when using Swift's initialization function in Objective-C, the compiler adds "initWith" before the method and capitalizes the first argument of the original initialization function. For example, this Swift initialization function init (songName: String, artist: String) will be translated as - (instancetype)initWithSongName (NSString *)songName artist (NSString *)artist.

Swift also provides a variant of the @objc keyword, which allows you to customize the function names converted in Objectiv-C. For example, if your Swift class name contains characters that are not supported in Objecytive-C, you can provide an alternative name for Objective-C. If you give the Swift function an Objecytive-C name, remember to add (:) to the function with arguments


//Swift
let myTableView: UITableView = UITableView(frame: CGRectZero, style: .Grouped)
9

When you use @objc ( < #name# > ) keyword, this class can be used in Objective-C without a namespace. This keyword is also useful when migrating the Objecive-C code to Swift. Since archived objects store the name of the class, you should use @objc ( < #name# > ) to declare the same name as the old archived class so that the old class can be unarchived by the new Swift class.

Objective-C selector (Selectors)

One Objective-C selector type points to one Objective-C method name. In the Swift era, the selector of Objective-C was replaced by the Selector structure. You can create a selector from a string such as let mySelector: Selector = "tappedButton:". Because strings are automatically converted to selectors, you can pass strings directly to methods that accept selectors.


//Swift
let myTextField = UITextField(frame: CGRect(0.0, 0.0, 200.0, 40.0))
0

Note: performSelector: methods and associated methods that invoke selectors are not imported into Swift because they are not secure.

If your Swift class inherits from the Objective-C class, all of your methods can be used as selectors for Objective-C. In addition, if your Swift class is not inherited from Objective-C, you will need to add the @objc keyword first if you want to use it as a selector. See Swift type compatibility for details.


Related articles: