How a Swift can fly through the Objective Sea (part 2): Powerful Objective-C generics

Derrick Ho
2 min readMay 6, 2017

--

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

  1. Cast before use; Easy but defeats the purpose of generics
  2. Box the box; Easy but needs box class
  3. refine for swift; A bit more work, but more elegant.

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

--

--

No responses yet

Write a response