How a Swift can fly through the Objective Sea (part 2): Powerful Objective-C generics
Generics are a powerful part of swift programming. Its too bad Objective-c can’t benefit from it…or can it?
Most projects are a mix of objective-c and swift. When that combo is present we need to hold back swift in order to preserve cross compatability.
One such area is generics. Objective-c can not use swift generics, but swift can use objective-c generics.
At least up to a certain point. Let’s explore that shall we?
Suppose we had an objective-c class called Box
@interface Box<__covariant T> : NSObject
@property T contents;
@end
It is a class that can hold on to any type like an array of strings.
Box <NSArray <NSString *>*>* luggage;
In objective-c, accessing luggage would correctly assume that the box holds an NSArray with strings in it and would warn you.
However if you access luggage from swift it sees it as a box of NSArray.
Box<NSArray>
This is a problem because swift wants to know what is inside the array! Also swift does not auto convert the NSArray into a swift array!
A quick work around would be to simply cast it into the right type
luggage.contents as! Array<String>
But that isn’t very elegant.
What you can do is Box the box!
Who will be the next heavyweight champion! ding ding!
Wait, not that kind of box…
class ArrayOfStringsBox {
var contents: [String]
init(contents: [String]) {
self.contents = contents
}
}
Then when we declare the variable in objective-c we can use the ArrayOfStringsBox
Box <ArrayOfStringsBox *>* luggage;
It works. It is quick.
But it kind of takes away from the point of generics.
@interface Passenger : NSObject
@property Box <NSArray <NSString *>*>* luggage NS_REFINED_FOR_SWIFT;
@end
Suppose Passenger has been holding his luggage. how can we make this swift friendly? If we access this in swift we get Box<NSArray>. the answer may be something known as NS_REFINED_FOR_SWIFT.
extension Passenger {
var luggage: SwiftBox<[String]> {
get {
return SwiftBox(box: __luggage)
} set {
__luggage = newValue._box as! Box<NSArray>
}
}
}class SwiftBox<T> {
var _box: Box<AnyObject>
var contents: T {
get {
return _box.contents as! T
} set {
_box.contents = newValue as AnyObject
}
}
init<V>(box: V) {
self._box = box as! Box<AnyObject>
}
}
First we create a SwiftBox object since it satisfies the AnyObject part of Box.
SwiftBox has an instance of Box. Yes, SwiftBox is a box around Box.
NS_REFINED_FOR_SWIFT allows us to make a swift version of the luggage property. I redefine luggage as a SwiftBox<[String]>. The objective-c luggage is interpreted as __luggage. The GET block packs the __luggage instance in the SwiftBox while the SET block extracts and sets the __luggage.
Now, regardless of whether we access the luggage property from obj or swift the array is in the proper type.
In summary, we have 3 strategies
- Cast before use; Easy but defeats the purpose of generics
- Box the box; Easy but needs box class
- refine for swift; A bit more work, but more elegant.