This is an introductory explanation of the Swift 3 programming language as it relates to practical real-life iOS programming, from my book, iOS 10 Programming Fundamentals with Swift. Copyright 2016 Matt Neuburg. Please note that this edition is outdated; the current books are iOS 15 Programming Fundamentals with Swift and Programming iOS 14. If my work has been of help to you, please consider purchasing one or both of them, or you can reward me through PayPal at http://www.paypal.me/mattneub. Thank you!
You are an iOS programmer, and you’ve chosen to adopt Apple’s new language, Swift. And this means that you’ll never have to concern yourself with Apple’s old language, Objective-C, right? Wrong.
Objective-C is not dead. Far from it. You may be using Swift, but Cocoa is not. Programming iOS involves communicating with Cocoa and its supplementary frameworks. The APIs for those frameworks are written in Objective-C — or in its underlying base language, C. Messages that you send to Cocoa using Swift are being translated for you into Objective-C. Objects that you send and receive back and forth across the Swift/Objective-C bridge are Objective-C objects. Some objects that you send from Swift to Objective-C are even being translated for you into other object types, or into nonobject types.
You need to understand what Objective-C expects from you when you are sending messages across the language bridge. You need to know what Objective-C is going to do with those messages. You need to know what is coming from Objective-C, and how it will be represented in Swift. Your app may include some Objective-C code as well as Swift code, so you need to know how the parts of your own app will communicate with each other.
This appendix summarizes certain linguistic features of C and Objective-C, and describes how Swift interfaces with those features. I do not explain here how to write Objective-C! For example, I’ll talk about Objective-C methods and method declarations, because you need to know how to call an Objective-C method from Swift; but I’m not going to explain how to call an Objective-C method in Objective-C. Earlier editions of this book teach C and Objective-C systematically and in detail, and I recommend consulting one for information about those languages.
Objective-C is a superset of C; to put it another way, C provides the linguistic underpinnings of Objective-C. Everything that is true of C is true also of Objective-C. It is possible, and often necessary, to write long stretches of Objective-C code that are, in effect, pure C. Some of the Cocoa APIs are written in C. Therefore, in order to know about Objective-C, it is necessary to know about C.
C statements, including declarations, must end in a semicolon. Variables must be declared before use. A variable declaration consists of a data type name followed by the variable name, optionally followed by assignment of an initial value:
int i; double d = 3.14159;
The C typedef
statement starts with an existing type name and defines a new synonym for it:
typedef double NSTimeInterval;
C is not an object-oriented language; its data types are not objects (they are scalars). The basic built-in C data types are all numeric: char (one byte), int (four bytes), float and double (floating-point numbers), and varieties such as short (short integer), long (long integer), unsigned short, and so on. Objective-C adds NSInteger, NSUInteger (unsigned), and CGFloat. The C bool type is actually a numeric, with zero representing false; Objective-C adds BOOL, which is also a numeric. The C native text type (string) is actually a null-terminated array of char.
Swift explicitly supplies numeric types that interface directly with C numeric types, even though Swift’s types are objects and C’s types are not. Swift type aliases provide names that correspond to the C type names: a Swift CBool is a C bool, a Swift CChar is a C char (a Swift Int8), a Swift CInt is a C int (a Swift Int32), a Swift CFloat is a C float (a Swift Float), and so on. Swift Int interchanges with NSInteger; Swift UInt interchanges with NSUInteger. Swift Bool interchanges with Swift ObjCBool, which represents Objective-C BOOL. CGFloat is adopted as a Swift type name.
A major difference between C and Swift is that C (and therefore Objective-C) implicitly coerces when values of different numeric types are assigned, passed, or compared to one another; Swift doesn’t, so you must coerce explicitly to make types match exactly.
The native C string type, a null-terminated array of char, is typed in Swift either as an array of Int8 or, for reasons that will be clear later, as UnsafePointer<Int8>
(recall that CChar is Int8). A C string can’t be formed as a literal in Swift, but you can pass a Swift String where a C string is expected. If you need to create a C string variable, the NSString utf8String
property or NSString cString(using:)
method can be used to form a C string. Alternatively, you can use the String utf8CString
property or the withCString
method; in this example, I cycle through the “characters” of the C string until I reach the null terminator (I’ll explain the pointee
property a bit later):
"hello".withCString { var cs = $0 while cs.pointee != 0 { print(cs.pointee) cs += 1 // or: cs = cs.successor() } }
In the other direction, a UTF-8 C string (including ASCII) can be rendered into a Swift String by way of a Swift String initializer such as init(cString:)
.
A C enum is numeric; values are some form of integer, and can be implicit (starting from 0) or explicit. Enums arrive in various forms into Swift, depending on exactly how they are declared. Let’s start with the simplest (and oldest) form:
enum State { kDead, kAlive }; typedef enum State State;
(The typedef
in the last line merely allows C programs to use the term State
as the name of this type instead of the more verbose enum State
.) The C enumerand names kDead
and kAlive
are not “cases” of anything; they are not namespaced. They are constants, and as they are not explicitly initialized, they represent 0 and 1 respectively. An enum declaration can specify the integer type further; this one doesn’t, so the values are typed in Swift as UInt32.
This old-fashioned sort of C enum arrives as a Swift struct adopting the RawRepresentable protocol. This struct is used automatically as a medium of interchange wherever a State enum arrives from or is expected by C. Thus, if a C function setState
takes a State enum parameter, you can call it with one of the State enumerand names:
setState(kDead)
I’m sure you noticed that I didn’t say .kDead
. As in C, enumerand names are not namespaced in Swift; there’s no such thing as State.kDead
. In this way, Swift has done its best to import these names helpfully and usefully, trying to represent State as a type even though, in C, it really isn’t one. If you are curious about what integer is represented by the name kDead
, you have to take its rawValue
. You can also create an arbitrary State value by calling its init(rawValue:)
initializer — there is no compiler or runtime check to see whether this value is one of the defined constants. But you aren’t expected to do either of those things.
Starting back in Xcode 4.4, a new C enum notation was introduced, using the NS_ENUM
macro:
typedef NS_ENUM(NSInteger, UIStatusBarAnimation) { UIStatusBarAnimationNone, UIStatusBarAnimationFade, UIStatusBarAnimationSlide, };
That notation explicitly specifies the integer type and associates a type name with this enum as a whole. Swift imports an enum declared this way as a Swift enum with the name and raw value type intact. This is a true Swift enum, so the enumerand names become namespaced case names. Moreover, Swift automatically subtracts the common prefix from the case names:
enum UIStatusBarAnimation : Int { case none case fade case slide }
Going the other way, a Swift enum with an Int raw value type can be exposed to Objective-C using the @objc
attribute. Thus, for example:
@objc enum Star : Int { case blue case white case yellow case red }
Objective-C sees that as an enum with type NSInteger and enumerand names StarBlue
, StarWhite
, and so on.
There is another variant of C enum notation, using the NS_OPTIONS
macro, suitable for bitmasks:
typedef NS_OPTIONS(NSUInteger, UIViewAutoresizing) { UIViewAutoresizingNone = 0, UIViewAutoresizingFlexibleLeftMargin = 1 << 0, UIViewAutoresizingFlexibleWidth = 1 << 1, UIViewAutoresizingFlexibleRightMargin = 1 << 2, UIViewAutoresizingFlexibleTopMargin = 1 << 3, UIViewAutoresizingFlexibleHeight = 1 << 4, UIViewAutoresizingFlexibleBottomMargin = 1 << 5 };
An enum declared this way arrives into Swift as a struct adopting the OptionSet protocol. The OptionSet protocol adopts the RawRepresentable protocol, so this is a struct with a rawValue
instance property holding the underlying integer. The C enum case names are represented by static properties, each of whose values is an instance of this struct; the names of these static properties are imported with the common prefix subtracted:
struct UIViewAutoresizing : OptionSet { init(rawValue: UInt) static var flexibleLeftMargin: UIViewAutoresizing { get } static var flexibleWidth: UIViewAutoresizing { get } static var flexibleRightMargin: UIViewAutoresizing { get } static var flexibleTopMargin: UIViewAutoresizing { get } static var flexibleHeight: UIViewAutoresizing { get } static var flexibleBottomMargin: UIViewAutoresizing { get } }
Thus, for example, when you say UIViewAutoresizing.flexibleLeftMargin
, it looks as if you are initializing a case of a Swift enum, but in fact this is an instance of the UIViewAutoresizing struct, whose rawValue
property has been set to the value declared by the original C enum — which, for .flexibleLeftMargin
, is 1<<0
. Because a static property of this struct is an instance of the same struct, you can, as with an enum, omit the struct name when supplying a static property name where the struct is expected:
self.view.autoresizingMask = .flexibleWidth
Moreover, because this is an OptionSet struct, set-like operations can be applied — thus permitting you to manipulate the bitmask by working with instances as if this were a Set:
self.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
In Objective-C, where an NS_OPTIONS
enum is expected, you pass 0
to indicate that no options are provided. In Swift, where a corresponding struct is expected, you pass []
(an empty set) or omit the options:
parameter entirely. Some NS_OPTIONS
enums have an explicit option that means 0
; Swift 3 sometimes won’t bother to import its name, because passing []
means the same thing. For example, to set a UIViewAutoresizing value to UIViewAutoresizingNone
in Swift 3, set it to []
(not .none
).
Unfortunately, you may occasionally encounter a bitmask enum whose Cocoa API hasn’t been marked NS_OPTIONS
, and thus isn’t imported into Swift as an OptionSet. Here’s an example:
typedef enum NSGlyphProperty : NSInteger { NSGlyphPropertyNull = (1 << 0), NSGlyphPropertyControlCharacter = (1 << 1), NSGlyphPropertyElastic = (1 << 2), NSGlyphPropertyNonBaseCharacter = (1 << 3) } NSGlyphProperty;
That’s obviously a bitmask, but in Swift it’s a simple enum. To work with it as a bitmask, you’ll have to take its raw value and use the arithmetic bitwise-or and bitwise-and operators, just as in Objective-C:
let property = self.lm.propertyForGlyph(at:lastCharRange) let mask1 = property.rawValue let mask2 = NSGlyphProperty.controlCharacter.rawValue return mask1 & mask2 != 0 // can't say .contains here
Also, some common lists of alternatives are not implemented as enums in the first place. This is not problematic, but it is inconvenient. For example, the names of the AVFoundation audio session categories are just global NSString constants:
NSString *const AVAudioSessionCategoryAmbient; NSString *const AVAudioSessionCategorySoloAmbient; NSString *const AVAudioSessionCategoryPlayback; // ... and so on ...
Even though this is a list of alternatives with an obvious common prefix, Swift doesn’t magically transform it into an AVAudioSessionCategory enum or struct with abbreviated names. When you want to specify the Playback category, you have to use the whole name, AVAudioSessionCategoryPlayback
.
A C struct is a compound type whose elements can be accessed by name using dot-notation after a reference to the struct. For example:
struct CGPoint { CGFloat x; CGFloat y; }; typedef struct CGPoint CGPoint;
After that declaration, it becomes possible to talk like this in C:
CGPoint p; p.x = 100; p.y = 200;
A C struct arrives wholesale into Swift as a Swift struct, which is thereupon endowed with Swift struct features. Thus, for example, CGPoint in Swift has x
and y
CGFloat instance properties, as you would expect; but it also magically acquires the implicit memberwise initializer! In addition, a zeroing initializer with no parameters is injected; thus, saying CGPoint()
makes a CGPoint whose x
and y
are both 0. Extensions can supply additional features, and the Swift CoreGraphics header adds a few to CGPoint:
extension CGPoint { static var zero: CGPoint { get } init(x: Int, y: Int) init(x: Double, y: Double) }
As you can see, a Swift CGPoint has additional initializers accepting Int or Double arguments, along with another way of making a zero CGPoint, CGPoint.zero
. CGSize is treated similarly. CGRect is particularly well endowed with added methods and properties in Swift, allow you to do things in a Swiftier way. (I’ll talk more about this in a bit.)
The fact that a Swift struct is an object, while a C struct is not, does not pose any problems of communication. You can assign or pass a Swift CGPoint, for example, where a C CGPoint is expected, because CGPoint came from C in the first place. The fact that Swift has endowed CGPoint with object methods and properties doesn’t matter; C doesn’t see them. All that C cares about are the x
and y
elements of this CGPoint, which are communicated from Swift to C without difficulty.
A C pointer is an integer designating the location in memory (the address) where the real data resides. Allocating and disposing of that memory is a separate matter. The declaration for a pointer to a data type is written with an asterisk after the data type name; a space can appear on either or both sides of the asterisk. These are equivalent declarations of a pointer-to-int:
int *intPtr1; int* intPtr2; int * intPtr3;
The type name itself is int*
(or, with a space, int *
). Objective-C, for reasons that I’ll explain later, uses C pointers heavily, so you’re going to be seeing that asterisk a lot if you look at any Objective-C.
A C pointer arrives into Swift as an UnsafePointer or, if writable, an UnsafeMutablePointer; this is a generic, and is specified to the actual type of data pointed to. (A pointer is “unsafe” because Swift isn’t managing the memory for, and can’t even guarantee the integrity of, what is pointed to.)
For example, here’s an Objective-C UIColor method declaration; I haven’t discussed this syntax yet, but just concentrate on the types in parentheses:
- (BOOL) getRed: (CGFloat *) red green: (CGFloat *) green blue: (CGFloat *) blue alpha: (CGFloat *) alpha;
CGFloat is a basic numeric type. The type CGFloat *
, despite the space, states that these parameters are all CGFloat*
— that is, pointer-to-CGFloat.
The Swift 3 translation of that declaration looks like this:
func getRed(_ red: UnsafeMutablePointer<CGFloat>, green: UnsafeMutablePointer<CGFloat>, blue: UnsafeMutablePointer<CGFloat>, alpha: UnsafeMutablePointer<CGFloat>) -> Bool
UnsafeMutablePointer in this context is used like a Swift inout
parameter: you declare and initialize a var
of the appropriate type beforehand, and then pass its address as argument by way of the &
prefix operator. When you pass the address of a reference in this way, you are in fact creating and passing a pointer:
var r : CGFloat = 0 var g : CGFloat = 0 var b : CGFloat = 0 var a : CGFloat = 0 c.getRed(&r, green: &g, blue: &b, alpha: &a)
In C, to access the memory pointed to by a pointer, you use an asterisk before the pointer’s name: *intPtr
is “the thing pointed to by the pointer intPtr
.” In Swift, you use the pointer’s pointee
property.
In this example, we receive a stop
parameter typed originally as a BOOL*
, a pointer-to-BOOL; in Swift, it’s an UnsafeMutablePointer<ObjCBool>
. To set the BOOL at the far end of this pointer, we set the pointer’s pointee
:
// mas is an NSMutableAttributedString, r is an NSRange mas.enumerateAttribute("HERE", in:r) { value, r, stop in if let value = value as? Int, value == 1 { // ... stop.pointee = true } }
The most general type of C pointer is pointer-to-void (void*
), also known as the generic pointer. The term void
here means that no type is specified; it is legal in C to use a generic pointer wherever a specific type of pointer is expected, and vice versa. In effect, pointer-to-void casts away type checking as to what’s at the far end of the pointer. This will appear in Swift as a “raw” pointer, either UnsafeRawPointer or UnsafeMutableRawPointer. In general, when you encounter pointers of this type, if you need to access the underlying data, you’ll start by rebinding its memory to an unsafe pointer generic specified to the underlying type:
// context is an UnsafeMutableRawPointer let c = context.bindMemory(to: String.self, capacity: 1) // now c is an UnsafeMutablePointer<String>
A C array contains a fixed number of elements of a certain data type. Under the hood, it is a contiguous block of memory sized to accommodate this number of elements of this data type. For this reason, the name of an array in C is the name of a pointer — to the first element of the array. For example, if arr
has been declared as an array of int, the term arr
can be used wherever a value of type int*
(a pointer-to-int) is expected. The C language will indicate an array type either by appending square brackets to a reference or as a pointer.
(That explains why C strings are often typed in Swift as an unsafe pointer to Int8 or CChar: a C string is an array of char, so it’s a pointer to char.)
For example, the C function CGContextStrokeLineSegments
is declared like this:
void CGContextStrokeLineSegments(CGContextRef c, const CGPoint points[], size_t count );
The second parameter is a C array of CGPoints; that’s what the square brackets tell you. A C array carries no information about how many elements it contains, so to pass this C array to this function, you must also tell the function how many elements the array contains; that’s what the third parameter is for. A C array of CGPoint is a pointer to a CGPoint, so this function’s declaration is translated into Swift 3 like this:
func __strokeLineSegments( between points: UnsafePointer<CGPoint>?, count: Int)
Now, you’re not expected to call this function; the CGContext Swift overlay provides a pure Swift version, strokeLineSegments
, which takes a Swift array of CGPoint (with no need to provide a count
). But let’s say you wanted to call __strokeLineSegments
instead. How would you do it?
To call __strokeLineSegments
and pass it a C array of CGPoints, it would appear that you need to make a C array of CGPoints. A C array is not, by any stretch of the imagination, a Swift array; so how on earth will you do this? Surprise! You don’t have to. Even though a Swift array is not a C array, you can pass a pointer to a Swift array here. In fact, you don’t even need to pass a pointer; you can pass a reference to a Swift array itself. And since this is not a mutable pointer, you can declare the array with let
; indeed, you can even pass a Swift array literal! No matter which approach you choose, Swift will convert to a C array for you as the argument crosses the bridge from Swift to C:
let c = UIGraphicsGetCurrentContext()! let arr = [CGPoint(x:0,y:0), CGPoint(x:50,y:50), CGPoint(x:50,y:50), CGPoint(x:0,y:100), ] c.__strokeLineSegments(between: arr, count: arr.count)
However, you can form a C array if you really want to. To do so, you must first set aside the block of memory yourself: declare an UnsafeMutablePointer of the desired type, calling the class method allocate(capacity:)
with the desired number of elements. You can then write the element values directly into memory. You could do this by manipulating the pointee
, but you can also use subscripting, which might be a lot more convenient. Finally, since the UnsafeMutablePointer is a pointer, you pass it, not a pointer to it, as argument:
let c = UIGraphicsGetCurrentContext()! let arr = UnsafeMutablePointer<CGPoint>.allocate(capacity:4) arr[0] = CGPoint(x:0,y:0) arr[1] = CGPoint(x:50,y:50) arr[2] = CGPoint(x:50,y:50) arr[3] = CGPoint(x:0,y:100) c.__strokeLineSegments(between: arr, count: 4)
If you’re going to do that, however, you really need to take upon yourself the full details of memory management. Having allocated this pointer’s memory and assigned values into it, you should remove the values and deallocate the memory:
let arr = UnsafeMutablePointer<CGPoint>.allocate(capacity:4) defer { arr.deinitialize() arr.deallocate(capacity:4) }
The same convenient subscripting is available when you receive a C array. For example:
let comp = col.cgColor.__unsafeComponents // col is a UIColor
comp
is typed as an UnsafePointer to CGFloat. This is really a C array of CGFloat — and so you can access its elements by subscripting:
if let comp = col.cgColor.__unsafeComponents, let sp = col.cgColor.colorSpace, sp.model == .rgb { let red = comp[0] let green = comp[1] let blue = comp[2] let alpha = comp[3] // ... }
A C function declaration starts with the return type (which might be void
, meaning no returned value), followed by the function name, followed by a parameter list, in parentheses, of comma-separated pairs consisting of the type followed by the parameter name. The parameter names are purely internal. C functions are global, and Swift can call them directly.
For example, here’s the C declaration for an Audio Services function:
extern OSStatus AudioServicesCreateSystemSoundID( CFURLRef inFileURL, SystemSoundID* outSystemSoundID)
Ignoring the term extern
, which I’m not going to explain, an OSStatus is basically an Int32. A CFURLRef is a CFTypeRef, called CFURL in Swift. A SystemSoundID is a UInt32, and the *
makes this a C pointer, as we already know. The whole thing thus translates directly into Swift:
func AudioServicesCreateSystemSoundID( _ inFileURL: CFURL, _ outSystemSoundID: UnsafeMutablePointer<SystemSoundID>) -> OSStatus
CFURL is (for reasons that I’ll explain later) interchangeable with NSURL and Swift URL; so here we are, calling this C function in Swift:
let sndurl = Bundle.main.url(forResource: "test", withExtension: "aif")! var snd : SystemSoundID = 0 AudioServicesCreateSystemSoundID(sndurl as CFURL, &snd)
In iOS programming, the vast majority of commonly used C global functions operate on a struct; they have the name of that struct as the first element of their name, and have that struct itself as their first parameter. In Swift 3, such functions are often overshadowed by instance method representations; the struct name is stripped from the name of the function, and the function is applied as a method to an instance of that struct.
For example, in Objective-C, the way to construct a CGRect from scratch is with the CGRectMake
function, and the way to divide a CGRect is with the CGRectDivide
function:
CGRect rect = CGRectMake(10,10,100,100); CGRect arrow; CGRect body; CGRectDivide(rect, &arrow, &body, ARHEIGHT, CGRectMinYEdge);
In Swift, CGRectMake
is overshadowed by the CGRect struct initializer init(x:y:width:height:)
, and CGRectDivide
is overshadowed by the CGRect divided
method:
let myRect = CGRect(x: 10, y: 10, width: 100, height: 100) let (arrowRect, bodyRect) = myRect.divided(atDistance: Arrow.ARHEIGHT, from: .minYEdge)
In C, a function has a type based on its signature, and the name of a function is a reference to the function, and so it is possible to pass a function — sometimes referred to as a pointer-to-function — by using the function’s name where a function of that type is expected. In a declaration, a pointer-to-function may be symbolized by an asterisk in parentheses.
For example, here’s the declaration for a C function from the Audio Toolbox framework:
extern OSStatus AudioServicesAddSystemSoundCompletion(SystemSoundID inSystemSoundID, CFRunLoopRef __nullable inRunLoop, CFStringRef __nullable inRunLoopMode, AudioServicesSystemSoundCompletionProc inCompletionRoutine, void * __nullable inClientData)
(I’ll explain the term __nullable
later.) What’s an AudioServicesSystemSoundCompletionProc? It’s this:
typedef void (*AudioServicesSystemSoundCompletionProc)( SystemSoundID ssID, void* __nullable clientData);
A SystemSoundID is a UInt32, so that tells you, in the rather tortured syntax that C uses for these things, that an AudioServicesSystemSoundCompletionProc is a pointer to a function taking two parameters (typed UInt32 and pointer-to-void) and returning no result.
Amazingly, you can pass a Swift function where a C pointer-to-function is expected! As always when passing a function, you can define the function separately and pass its name, or you can form the function inline as an anonymous function. If you’re going to define the function separately, it must be a function — meaning that it cannot be a method. A function defined at the top level of a file is fine; so is a function defined locally within a function.
So here’s my AudioServicesSystemSoundCompletionProc, declared at the top level of a file:
func soundFinished(_ snd:UInt32, _ c:UnsafeMutableRawPointer?) -> Void { AudioServicesRemoveSystemSoundCompletion(snd) AudioServicesDisposeSystemSoundID(snd) }
And here’s my code for playing a sound file as a system sound, including a call to AudioServicesAddSystemSoundCompletion
:
let sndurl = Bundle.main.url(forResource: "test", withExtension: "aif")! var snd : SystemSoundID = 0 AudioServicesCreateSystemSoundID(sndurl as CFURL, &snd) AudioServicesAddSystemSoundCompletion(snd, nil, nil, soundFinished, nil) AudioServicesPlaySystemSound(snd)
Objective-C is built on the back of C. It adds some syntax and features, but it continues to use C syntax and data types, and remains C under the hood.
Unlike Swift, Objective-C has no namespaces. For this reason, different frameworks distinguish their contents by starting their names with different prefixes. The “CG” in “CGFloat” stands for Core Graphics, because it is declared in the Core Graphics framework. The “NS” in “NSString” stands for NeXTStep, a historical name for the framework that later became Cocoa. And so on.
All the data types and syntax of C are part of Objective-C. But Objective-C is also object-oriented, so it needs a way of adding objects to C. It does this by taking advantage of C pointers. C pointers accommodate having anything at all at the far end of the pointer; management of whatever is pointed to is a separate matter, and that’s just what Objective-C takes care of. Thus, Objective-C object types are expressed using C pointer syntax.
For example, here’s the Objective-C declaration for the addSubview:
method:
- (void)addSubview:(UIView *)view;
I haven’t discussed Objective-C method declaration syntax yet, but focus on the type declaration for the view
parameter, in parentheses: it is UIView*
. This appears to mean “a pointer to a UIView.” It does mean that — and it doesn’t. All Objective-C object references are pointers. Thus, the fact that this is a pointer is merely a way of saying it’s an object. What’s at the far end of the pointer is a UIView instance.
The Swift translation of this method declaration doesn’t appear to involve any pointers:
func addSubview(_ view: UIView)
In general, in Swift, you will simply pass a reference to a class instance where Objective-C expects a class instance; the fact that an asterisk is used in the Objective-C declaration to express the fact that this is an object won’t matter. What you pass as argument when calling addSubview(_:)
from Swift is a UIView instance. There is, of course, a sense in which you are passing a pointer when you pass a class instance — because classes are reference types! Thus, a class instance is actually seen the same way by both Swift and Objective-C. The difference is that Swift doesn’t use pointer notation.
Objective-C’s id
type is a general pointer to an object — the object equivalent of C pointer-to-void. Any object type can be assigned or cast to or from an id
. Because id
is itself a pointer, a reference declared as id
doesn’t use an asterisk; it is rare (though not impossible) to encounter an id*
.
Objective-C objects are classes and instances of classes. They arrive into Swift more or less intact. You won’t have any trouble subclassing Objective-C classes or working with instances of Objective-C classes.
The same is true in reverse. If Objective-C expects an object, it expects a class, and Swift can provide it. In the most general case, where Objective-C expects an id
, you can pass a class instance, and Objective-C will be able to deal with it. But the only kind of object that Objective-C can fully deal with is an instance of an NSObject subclass. Instances of other classes can’t be introspected by Objective-C, and everything else has to be bridged or boxed in order to survive the journey into Objective-C’s world, as I’ll explain in the next section.
Swift can see just about all aspects of an Objective-C class type. But much of Swift, while not problematic for Objective-C, is simply invisible to it. Objective-C can’t see any of the following:
@objc
enum with an Int raw value@objc
What, precisely, do I mean by “can’t see” a type? I mean that it can’t cope with it as a type, and therefore whatever uses it as a type becomes invisible. For example, suppose we have a class MyClass not derived from NSObject. It is perfectly fine to hand Objective-C an instance of this type where an id
is expected. But if your UIViewController subclass has a property typed as a MyClass, Objective-C is unaware of it. If your UIViewController subclass has a method that receives or returns a value typed as a MyClass, Objective-C is unaware of it.
Nevertheless, you are perfectly free to use such properties and methods, even in a subclass or extension of an Objective-C class type such as UIViewController; they won’t give Objective-C any difficulty, because as far as Objective-C is concerned, they won’t be present at all.
The @objc
attribute exposes to Objective-C something that it normally would not be able to see, provided it is legal for Objective-C to see it; for example, you can use it to expose to Objective-C an individual method of a class not derived from NSObject. And it has another purpose: when you mark something with @objc
, you can add parentheses containing the name by which you want Objective-C to see this thing. You are free to do this even for a class or a class member that Objective-C can see already, as in this example:
@objc(ViewController) class ViewController : UIViewController { // ...
That code demonstrates something that is in fact useful to do. By default, Objective-C sees your class’s name as being namespaced (prefixed) by the module name (typically, the project name). Thus, this ViewController class might be seen by Objective-C as MyCoolApp.ViewController
. This can wreck the association between the class name and something else. For example, when you’re translating an existing Objective-C project into Swift, you may want to use @objc(...)
syntax to prevent a nib object or an NSCoding archive from losing track of its associated class.
Swift will convert certain native nonclass types to their Objective-C class equivalents for you. The following native Swift structs are bridged to Objective-C class types:
New in Swift 3, Cocoa Foundation classes are overlaid by Swift types, whose names are the same but without the “NS” prefix. Often, extra functionality is injected to make the type behave in a more Swift-like way; and, where appropriate, the Swift type may be a struct, thus allowing you to take advantage of Swift value type semantics. For example, NSMutableData becomes largely otiose, because Data, the overlay for Objective-C NSData, is a struct with mutating
methods and can thus be declared with let
or var
. And Date, the overlay for Objective-C NSDate, adopts Equatable and Comparable, so that (for example) an NSDate method like earlierDate:
can be replaced by the >
operator.
The Swift overlay types are all bridged to their Foundation counterparts, and Swift will prefer the Swift types wherever possible. For example, a Cocoa method that takes or returns an NSDate in Objective-C will take or return a Date in Swift. If necessary, you can access the Objective-C Foundation type by casting.
Also new in Swift 3, any type of object can be handed to Objective-C, even if Objective-C wouldn’t see it as an object and the type isn’t bridged to an Objective-C class type. Objective-C can’t do anything with such an object; but the compiler won’t complain and the app won’t crash. For example, this code is legal:
let lay = CALayer() lay.setValue(CGPoint(x:100,y:100), forKey: "point") lay.setValue([CGPoint(x:100,y:100)], forKey: "pointArray")
Where Objective-C expects an object, we provide a CGPoint and then an array of CGPoint. But a CGPoint is not an Objective-C object — it’s a struct — and an Objective-C array must contain only objects (not structs). How can this be?
The answer, in a nutshell, is that when a Swift object is handed to Objective-C by way of an Any value, if that object can’t be bridged and Objective-C can’t see it as an object, Swift boxes it into something that Objective-C can see as an object. How Swift does this is irrelevant; it’s an implementation detail, and none of your business. It happens that in this case, the two objects are a _SwiftValue
and a _SwiftDeferredNSArray
, but those names are secret and subject to change, and knowing them is useless. What’s important is that they are Objective-C objects, wrapping the values we provided. In this way, Objective-C is able to store these objects for us, in their boxes, and hand them back to us intact upon request. Like Pandora, Objective-C will cope perfectly well as long as it doesn’t look in the box!
In Objective-C, method parameters can (and nearly always do) have external names, and the name of a method as a whole is not distinct from the external names of the parameters. The parameter names are part of the method name, with a colon appearing where each parameter would need to go. For example, here’s a typical Objective-C method declaration from Cocoa’s NSString class:
- (NSString *)stringByReplacingOccurrencesOfString:(NSString *)target withString:(NSString *)replacement
The Objective-C name of that method is stringByReplacingOccurrencesOfString:withString:
. The name contains two colons, so the method takes two parameters, which the declaration tells us are NSString parameters.
The declaration for an Objective-C method has three parts:
+
or -
, meaning that the method is a class method or an instance method, respectively.void
, meaning no returned value.When Swift calls an Objective-C method, there’s an obvious mismatch between the rules and conventions of the two languages. A Swift method is a function; it has parentheses, and if its parameters have external names (labels), they appear inside the parentheses. The function name that precedes the parentheses is clearly distinct from the parameter labels inside the parentheses. But an Objective-C method name involves no parentheses. So where should the Swift parentheses go?
Before Swift 3, the general solution to this mismatch was a simple algorithm. Everything before the first colon in Objective-C precedes the parentheses in Swift, and the first Swift parameter will therefore have no external name:
func stringByReplacingOccurrencesOfString( _ target:String, withString replacement:String) // Swift 2.2 and before
Swift 3, however, goes further: it takes this opportunity to render the Objective-C method’s Swift name more Swift-like, by a rather involved process called renamification, which is performed by a component called the Clang importer, mediating between the two languages.
The renamification rules are rather elaborate, but you don’t need to know the details; you can get a sense of how they behave from the way they transform the stringByReplacingOccurrencesOfString:withString:
method into a Swift function:
string
at the start. We are thus left with byReplacingOccurrencesOfString:withString:
.by
. That’s a common Cocoa locution, but Swift finds it merely verbose. Now we’re down to replacingOccurrencesOfString:withString:
.string
at the end of the parameter names. That leaves replacingOccurrencesOf:with:
.of
, so it splits before that preposition.Here’s the resulting Swift 3 renamification of this method:
func replacingOccurrences( of target:String, with replacement:String) // Swift 3
And here’s an actual example of calling it:
let s = "hello" let s2 = s.replacingOccurrences(of: "ell", with:"ipp") // s2 is now "hippo"
How does Objective-C see methods declared in Swift? The simplest case is that the first parameter should have no externalized name. For example, here’s a Swift method intended as the action method of a button in the interface:
@IBAction func doButton(_ sender: Any?) { // ... }
That method is seen by Objective-C as doButton:
. That is the canonical form for an action method with one parameter, and for that reason I like to declare my action methods along those lines.
If a Swift method’s first parameter does have an externalized name, then, as seen by Objective-C, that externalized name is appended to what precedes the parentheses — in general, by inserting a preposition with
. For example, here’s a Swift method:
func makeHash(ingredients stuff:[String]) { // ... }
That method is seen by Objective-C as makeHashWithIngredients:
. But if the externalized name of the first parameter is a preposition, then it is appended directly to what precedes the parentheses. For example:
func makeHash(of stuff:[String]) { // ... }
That method is seen by Objective-C as makeHashOf:
.
What about internal parameter names? When you call an Objective-C method from Swift, Objective-C’s internal names for the parameters don’t matter; you don’t use them, and you don’t need to know or care what they are. When you override an Objective-C method in Swift, on the other hand, code completion will suggest internal names corresponding to the Objective-C internal names, but you are free to change them. For example, here’s the Objective-C declaration of the UIViewController prepareForSegue:sender:
instance method:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(nullable id)sender;
When you override that method in your UIViewController subclass, the suggested template, in accordance with the renamification rules, looks like this:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) { // ... }
But, as I just said, you are free to change the internal names; they are local variable names for your use inside the function body, and Objective-C doesn’t care about them. This is a valid (but weird) override of prepareForSegue:sender:
in Swift:
override func prepare(for war: UIStoryboardSegue, sender bow: Any?) { // ... }
Unlike Swift, Objective-C does not permit overloading of methods. Two ViewController instance methods called myMethod:
returning no result, one taking a CGFloat parameter and one taking an NSString parameter, would be illegal in Objective-C. Therefore, two such Swift methods, though legal as far as Swift is concerned, would be illegal if they were both visible to Objective-C. You can use the @nonobjc
attribute to hide from Objective-C something that it would normally be able to see. Thus, marking all but one of the overloaded methods @nonobjc
solves the problem.
Objective-C has its own version of a variadic parameter. For example, the NSArray instance method arrayWithObjects:
is declared like this:
+ (id)arrayWithObjects:(id)firstObj, ... ;
Unlike Swift, such methods in Objective-C must somehow be told explicitly how many arguments are being supplied. Many such methods, including arrayWithObjects:
, use a nil
terminator; that is, the caller supplies nil
after the last argument, and the callee knows when it has reached the last argument because it encounters nil
. A call to arrayWithObjects:
in Objective-C would look something like this:
NSArray* pep = [NSArray arrayWithObjects: manny, moe, jack, nil];
Objective-C cannot call (or see) a Swift method that takes a variadic parameter. Swift, however, can call an Objective-C method that takes a variadic parameter, provided that it is marked NS_REQUIRES_NIL_TERMINATION
. arrayWithObjects:
is marked in this way, so you can say NSArray(objects:1, 2, 3)
and Swift will supply the missing nil
terminator.
Objective-C initializer methods are instance methods; actual instantiation is performed using the NSObject class method alloc
, for which Swift has no equivalent (and doesn’t need one), and the initializer message is sent to the instance that results. For example, this is how you create a UIColor instance by supplying red, green, blue, and alpha values in Objective-C:
UIColor* col = [[UIColor alloc] initWithRed:0.5 green:0.6 blue:0.7 alpha:1];
The name of that initializer, in Objective-C, is initWithRed:green:blue:alpha:
. It’s declared like this:
- (UIColor *)initWithRed:(CGFloat)red green:(CGFloat)green blue:(CGFloat)blue alpha:(CGFloat)alpha;
In short, an initializer method, to all outward appearances, is just an instance method like any other in Objective-C.
Swift, nevertheless, is able to detect that an Objective-C initializer is an initializer, because the name is special — it starts with init
! Therefore, Swift is able to translate an Objective-C initializer into a Swift initializer. The word init
is stripped from the start of the method name, and the preposition with
, if it appears, is stripped as well. What’s left is the external name of the first parameter. Thus, Swift translates Objective-C initWithRed:green:blue:alpha:
into the Swift initializer init(red:green:blue:alpha:)
, which is declared like this:
init(red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat)
And you’d call it like this:
let col = UIColor(red: 0.5, green: 0.6, blue: 0.7, alpha: 1.0)
The same principle operates in reverse: for example, a Swift initializer init(value:)
is visible to and callable by Objective-C under the name initWithValue:
.
There is a second way to create an instance in Objective-C. Very commonly, a class will supply a class method that is a factory for an instance. For example, the UIColor class has a class method colorWithRed:green:blue:alpha:
, declared as follows:
+ (UIColor*) colorWithRed: (CGFloat) red green: (CGFloat) green blue: (CGFloat) blue alpha: (CGFloat) alpha;
Swift detects a factory method of this kind by some pattern-matching rules — a class method that returns an instance of the class, and whose name begins with the name of the class, stripped of its prefix — and translates it as an initializer, stripping the class name (and the with
) from the start of the first parameter name. If the resulting initializer exists already, as it does in this example, then Swift treats the factory method as superfluous and suppresses it completely! Thus, the Objective-C class method colorWithRed:green:blue:alpha:
isn’t callable from Swift, because it would be identical to the init(red:green:blue:alpha:)
that already exists.
An Objective-C method will sometimes expect as parameter the name of a method to be called later. Such a name is called a selector. For example, the Objective-C UIControl addTarget:action:forControlEvents:
method can be called as a way of telling a button in the interface, “From now on, whenever you are tapped, send this message to this object.” The message, the action:
parameter, is a selector.
You may imagine that, if this were a Swift method, you’d be passing a function here. A selector, however, is not the same as a function. It’s just a name. Objective-C, unlike Swift, is so dynamic that it is able, at runtime, to construct and send an arbitrary message to an arbitrary object based on the name alone. But even though it is just a name, a selector is not exactly a string, either. It is, in fact, a separate object type, designated in Objective-C declarations as SEL and in Swift declarations as Selector.
Before Swift 2.2, the only way to form a selector in Swift was to use a literal string. In most cases, you could use a bare literal string:
b.addTarget(self, action: "doNewGame:", forControlEvents: .TouchUpInside) // before Swift 2.2
The trouble with this syntax is that it is error-prone. Forming a literal selector string by hand is an invitation to form the string incorrectly, resulting in a selector that at best will fail to work, and at worst will cause your app to crash. As I explained in Chapter 2, this problem is solved starting in Swift 2.2 by the introduction of #selector
syntax. In Swift 3, that example would be written like this:
b.addTarget(self, action: #selector(doNewGame), for: .touchUpInside)
The #selector
syntax means that you will probably never need to form a selector from a literal string. Nevertheless, you can do so if you want to. The rules for forming the string from the method name are completely mechanical:
With
and a capitalized version of that name, unless it is a preposition, in which case append a capitalized version of it directly.Observe that this means that if the method takes any parameters, its Objective-C name string will end with a colon. Capitalization counts, and the name should contain no spaces or other punctuation except for the colons.
To illustrate, here are some Swift method declarations, with their Objective-C name strings given in a comment:
func sayHello() -> String // "sayHello" func say(_ s:String) // "say:" func say(string s:String) // "sayWithString:" func say(of s:String) // "sayOf:" func say(_ s:String, times n:Int) // "say:times:"
Objective-C cannot use a selector to call a method that it can’t see! An attempt to do so will cause a crash at runtime. Use of the #selector
syntax will allow the compiler to stop you from making this mistake.
A CFTypeRef is a pointer to an opaque struct that acts as a pseudo-object. CFTypeRef functions are global C functions. Swift can call C functions, as I’ve already said; and before Swift 3, CFTypeRef code would appear almost as if Swift were C. For example:
// before Swift 3: let con = UIGraphicsGetCurrentContext()! let sp = CGColorSpaceCreateDeviceGray() // ... colors and locs are arrays of CGFloat ... let grad = CGGradientCreateWithColorComponents (sp, colors, locs, 3) ❶ CGContextDrawLinearGradient ( con, grad, CGPointMake(89,0), CGPointMake(111,0), []) ❷
In Swift 3, as part of renamification, many commonly used CFTypeRef functions (such as those in the Core Graphics framework) are recast as if the CFTypeRef objects were class instances, with the functions themselves recast as instance methods, parallel to what I described for CGRect structs earlier. The last two lines of the preceding code are a case in point:
In Objective C, a CFTypeRef is created with some sort of | |
In Objective C, a CFTypeRef function operating on a CFTypeRef pseudo-object takes that object as its first parameter. In Swift 3, the pseudo-object is treated as a real object, and the function becomes a method call sent to it, again with external parameter names. |
Thus, those lines are recast in Swift 3 like this:
let con = UIGraphicsGetCurrentContext()! let sp = CGColorSpaceCreateDeviceGray() // ... colors and locs are arrays of CGFloat ... let grad = CGGradient(colorSpace: sp, colorComponents: colors, locations: locs, count: 3) con.drawLinearGradient(grad, start: CGPoint(x:89,y:0), end: CGPoint(x:111,y:0), options:[])
A CFTypeRef is a pointer, so it is interchangeable with C pointer-to-void. And because it is a pointer to a pseudo-object, it is interchangeable with Objective-C id
.
Many CFTypeRefs are toll-free bridged to corresponding Objective-C object types. For example, CFString and NSString, CFNumber and NSNumber, CFArray and NSArray, CFDictionary and NSDictionary are all toll-free bridged (and there are many others). Such pairs are interchangeable by casting. Again, this is much easier in Swift than in Objective-C. In Objective-C, ARC memory management doesn’t apply to CFTypeRefs. Therefore you must perform a bridging cast, to tell Objective-C how to manage this object’s memory as it crosses between the memory-managed world of Objective-C objects and the unmanaged world of C and CFTypeRefs. But in Swift, CFTypeRefs are memory-managed, and so there is no need for a bridging cast; you can just cast, plain and simple.
For example, in this code from one of my apps, I’m using the ImageIO framework. This framework has a C API (which has not been renamified) and uses CFTypeRefs. CGImageSourceCopyPropertiesAtIndex
returns a CFDictionary whose keys are CFStrings. The easiest way to obtain a value from a dictionary is by subscripting, but you can’t do that with a CFDictionary, because it isn’t an object — so I cast it to an NSDictionary. The key kCGImagePropertyPixelWidth
is a CFString, but when I try to use it directly in a subscript, Swift allows me to do so:
let result = CGImageSourceCopyPropertiesAtIndex(src, 0, nil)! as NSDictionary let width = result[kCGImagePropertyPixelWidth] as! CGFloat
Similarly, in this code, I form an NSDictionary d
using CFString keys — and then I pass it to the CGImageSourceCreateThumbnailAtIndex
function where a CFDictionary is expected:
let d : NSDictionary = [ kCGImageSourceShouldAllowFloat : true, kCGImageSourceCreateThumbnailWithTransform : true, kCGImageSourceCreateThumbnailFromImageAlways : true, kCGImageSourceThumbnailMaxPixelSize : w ] let imref = CGImageSourceCreateThumbnailAtIndex(src, 0, d)!
A block is a C language feature introduced by Apple starting in iOS 4. It is very like a C function, but it is not a C function; it behaves as a closure and can be passed around as a reference type. A block, in fact, is parallel to and compatible with a Swift function, and indeed the two are interchangeable: you can pass a Swift function where a block is expected, and when a block is handed to you by Cocoa it appears as a function.
In C and Objective-C, a block declaration is signified by the caret character (^
), which appears where a function name (or an asterisk in parentheses) would appear in a C function declaration. For example, the NSArray instance method sortedArrayUsingComparator:
takes an NSComparator parameter, which is defined through a typedef
like this:
typedef NSComparisonResult (^NSComparator)(id obj1, id obj2);
To read that declaration, it helps to start in the middle and work your way outward; it says: “NSComparator is the type of a block taking two id
parameters and returning an NSComparisonResult.” In Swift, therefore, that typedef
is translated as (Any, Any) -> ComparisonResult
. It is then trivial to supply a function of the required type as argument when you call sortedArray(comparator:)
in Swift.
In many cases, there won’t be a typedef
, and the type of the block will appear directly in a method declaration. Here’s the Objective-C declaration for a UIView class method that takes two block parameters:
+ (void)animateWithDuration:(NSTimeInterval)duration animations:(void (^)(void))animations completion:(void (^ __nullable)(BOOL finished))completion;
In that declaration, animations:
is a block taking no parameters (void
) and returning no value, and completion:
is a block taking one parameter, a BOOL, and returning no value. Here’s the Swift translation:
class func animate(withDuration duration: TimeInterval, animations: () -> Swift.Void, completion: ((Bool) -> Swift.Void)? = nil)
Those are examples of methods that you would call, passing a function as argument where a block parameter is expected. Here’s an example of a method that you would implement, where a function is passed to you. This is the Objective-C declaration:
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler;
You implement this method, and it is called when the user taps a link in a web view, so that you can decide how to respond. The third parameter is a block that takes one parameter — a WKNavigationActionPolicy, which is an enum — and returns no value. The block is passed to you as a Swift function, and you respond by calling the function to report your decision:
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Swift.Void) { // ... decisionHandler(.allow) }
A C function is not a block, but you can also use a Swift function where a C function is expected, as I demonstrated earlier. Going in the other direction, to declare a type as a C pointer-to-function, mark the type as @convention(c)
. For example, here are two Swift method declarations:
func blockTaker(_ f:()->()) {} func functionTaker(_ f:@convention(c)() -> ()) {}
Objective-C sees the first as taking an Objective-C block, and the second as taking a C pointer-to-function.
As soon as Swift was first introduced to the programming public in June of 2014, it was evident that Swift’s strict, specific typing was a poor match for Objective-C’s dynamic, loose typing. The chief problems were:
In Objective-C, any object instance reference can be nil
. But in Swift, only an Optional can be nil
.
The default solution was to use implicitly unwrapped Optionals as the medium of object interchange between Objective-C and Swift. But this was a blunt instrument, especially because most objects arriving from Objective-C were never in fact going to be nil
.
In Objective-C, a collection type such as NSArray can contain elements of multiple object types, and the collection itself is agnostic as to what types of elements it may contain. But a Swift collection type can contain elements of just one type, and is itself typed according that element type.
The default solution was for every collection to arrive from Objective-C typed in the most general way, using AnyObject; it then had to be cast down explicitly on the Swift side. It was particularly galling to ask for a view’s subviews
, for example, and get back an [AnyObject]
which had to be cast down to a [UIView]
— when nothing could be more obvious than that a view’s subviews would in fact all be UIView objects.
These problems have subsequently been solved by modifying the Objective-C language to permit markup of declarations in such a way as to communicate to Swift a more specific knowledge of what to expect.
An Objective-C object type can be marked as nonnull
or nullable
, to specify, respectively, that it will never be nil
or that it might be nil
. In the same way, C pointer types can be marked __nonnull
or __nullable
. Using these markers obviates all need for implicitly unwrapped Optionals as a medium of interchange; every type can be either a normal type or a normal Optional. Thus, implicitly unwrapped Optionals are a rare sight in the Cocoa APIs nowadays.
If you’re writing an Objective-C header file and you don’t mark up any of it as to nullability, you’ll return to the bad old days: Swift will see your types as implicitly unwrapped Optionals. For example, here’s an Objective-C method declaration:
- (NSString*) badMethod: (NSString*) s;
In the absence of markup, Swift sees that as follows:
func badMethod(_ s: String!) -> String!
As soon as your header file contains any markup, the Objective-C compiler will complain until it is completely marked up. To help you with this, you can mark an entire stretch of your header file with a default nonnull
setting; you will then need to mark up only the exceptional nullable
types:
NS_ASSUME_NONNULL_BEGIN - (NSString*) badMethod: (NSString*) s; - (nullable NSString*) goodMethod: (NSString*) s; NS_ASSUME_NONNULL_END
Swift sees that with no implicitly unwrapped Optionals:
func badMethod(_ s: String) -> String func goodMethod(_ s: String) -> String?
To mark an Objective-C collection type as containing a certain type of element, the element type can appear in angle brackets (<>
) after the name of the collection type but before the asterisk. This is an Objective-C method that returns an array of strings:
- (NSArray<NSString*>*) pepBoys;
Swift sees the return type of that method as [String]
, and there will be no need to cast it down.
In the declaration of an actual Objective-C collection type, a placeholder name stands for the type in angle brackets. For example, the declaration for NSArray starts like this:
@interface NSArray<ObjectType> - (NSArray<ObjectType> *)arrayByAddingObject:(ObjectType)anObject; // ...
The first line says that we’re going to use ObjectType as the placeholder name for the element type. The second line says that the arrayByAddingObject:
method takes an object of the element type and returns an array of the element type. If a particular array is declared as NSArray<NSString*>*
, the ObjectType placeholder would be resolved to NSString*
. (You can see why Apple refers to this as a “lightweight generic.”)
In Swift 3, classes marked up as lightweight generics are imported into Swift as actual generics even if they are not bridged collection types. For example, suppose I declare my own Objective-C class, parallel to NSArray:
@interface Thing<ObjectType> : NSObject - (void) giveMeAThing:(nonnull ObjectType)anObject; @end
The Thing class arrives into Swift declared as a generic class Thing<ObjectType : AnyObject> : NSObject
, and has to be instantiated by resolving the generic somehow. Often, it will be resolved explicitly:
let t = Thing<NSString>() t.giveMeAThing("howdy") // an Int would be illegal here
The details are quite involved; for full information, see proposal SE-0057 at https://github.com/apple/swift-evolution.
It is legal for a target to be a bilingual target — one that contains both Swift files and Objective-C files. A bilingual target can be useful for various reasons. You might want to take advantage of Objective-C language features. You might want to incorporate third-party code written in Objective-C. You might want to incorporate your own existing code written in Objective-C. Your app itself may have been written in Objective-C originally, and now you want to migrate part of it (or all of it, in stages) into Swift.
The key question is how, within a single target, Swift and Objective-C hear about one another’s code in the first place. Recall that Objective-C, unlike Swift, has a visibility problem already: Objective-C files cannot automatically see one another. Instead, each Objective-C file that needs to see another Objective-C file must be instructed explicitly to see that file, usually with an #import
directive at the top of the first file. In order to prevent unwanted exposure of private information, an Objective-C class declaration is conventionally spread over two files: a header file (.h) containing the @interface
section, and a code file (.m) containing the @implementation
section. Also conventionally, only .h files are ever imported. Thus, if declarations of class members, constants, and so forth are to be public, they are placed in a .h file.
Visibility of Swift and Objective-C to one another depends upon this convention: it works through .h files. There are two directions of visibility, and they operate separately through two special .h files:
When you add a Swift file to an Objective-C target, or an Objective-C file to a Swift target, Xcode offers to create a bridging header. This is a .h file in the project. Its default name is derived from the target name — for example, MyCoolApp-Bridging-Header.h — but the name is arbitrary and can be changed, provided you change the target’s Objective-C Bridging Header build setting to match. (Similarly, if you decline the bridging header and you decide later that you want one, create a .h file manually and point to it in the target’s Objective-C Bridging Header build setting.)
An Objective-C .h file will then be visible to Swift provided you #import
it in this bridging header.
If you have a bridging header, then when you build your target, the appropriate top-level declarations of all your Swift files are automatically translated into Objective-C and are used to construct a hidden bridging header inside the Intermediates build folder for this target, deep inside your DerivedData folder. The easiest way to see this is with the following Terminal command:
$ find ~/Library/Developer/Xcode/DerivedData -name "*Swift.h"
This will reveal the name of the hidden bridging header. For example, for a target called MyCoolApp, the hidden bridging header is called MyCoolApp-Swift.h. The name may involve some transformation; for example, a space in the target name is translated into an underscore. Alternatively, examine (or change) the target’s Objective-C Generated Interface Header Name build setting.
Your Objective-C files will be able to see your Swift declarations, provided you #import
this hidden bridging header into each Objective-C file that needs to see it.
For simplicity, I will refer to these two bridging headers as the visible and invisible bridging headers, respectively.
For example, let’s say that I’ve added to my Swift target, called MyCoolApp, a Thing class written in Objective-C. It is distributed over two files, Thing.h and Thing.m. Then:
#import "Thing.h"
in the visible bridging header (MyCoolApp-Bridging-Header.h).#import "MyCoolApp-Swift.h"
) at the top of Thing.m.Once you’ve imported the invisible bridging header into an Objective-C file, you can Command-click its name to open it. This shows you your app’s entire Swift API as seen as by Objective-C!
On that basis, here’s the procedure I use for turning my own Objective-C apps into Swift apps:
#import
s the corresponding .h file, remove that #import
statement and import in its place the invisible bridging header (if you aren’t importing it in this file already).#import
statement.@UIApplicationMain
attribute in the app delegate class declaration) and the .pch (precompiled header) file.Your app should now run, and is now written in pure Swift (or is, at least, as pure as you intend to make it). Now go back and think about the code, making it more Swifty and idiomatic. You may well find that things that were clumsy or tricky in Objective-C can be made much neater and clearer in Swift.
Note also that you can do a partial conversion of an Objective-C class by extending it in Swift. This can be useful as a stage along the path to total conversion, or you might quite reasonably write only one or two methods of an Objective-C class in Swift, just because Swift makes it so much easier to say or understand certain kinds of thing. However, Swift cannot see the Objective-C class’s members unless they are made public, so methods and properties that you were previously keeping private in the Objective-C class’s .m file may have to be declared in its .h file.