Tải bản đầy đủ
Appendix A. C, Objective-C, and Swift

Appendix A. C, Objective-C, and Swift

Tải bản đầy đủ

The C Language
Objective-C is a superset of C; to put it another way, C provides the linguistic under‐
pinnings 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 syno‐
nym for it:
typedef double NSTimeInterval;

C Data Types
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 represent‐
ing 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) implic‐
itly 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 (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
550

|

Appendix A: C, Objective-C, and Swift

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:).

C Enums
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 val‐
ues are typed in Swift as UInt32.
This old-fashioned sort of C enum arrives as a Swift struct adopting the RawRepre‐
sentable protocol. This struct is used automatically as a medium of interchange wher‐
ever 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
C, Objective-C, and Swift

|

551

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 com‐
mon 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 pro‐
tocol. The OptionSet protocol adopts the RawRepresentable protocol, so this is a
552

|

Appendix A: C, Objective-C, and Swift

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 exam‐
ple, 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;

C, Objective-C, and Swift

|

553

That’s obviously a bitmask, but in Swift it’s a simple enum. To work with it as a bit‐
mask, you’ll have to take its raw value and use the arithmetic bitwise-or and bitwiseand 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 abbrevi‐
ated names. When you want to specify the Playback category, you have to use the
whole name, AVAudioSessionCategoryPlayback.

C Structs
A C struct is a compound type whose elements can be accessed by name using dotnotation 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 injec‐
ted; 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:

554

|

Appendix A: C, Objective-C, and Swift

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.

C Pointers
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 UnsafeMutable‐
Pointer; 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 guar‐
antee 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;

C, Objective-C, and Swift

|

555

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,
green: UnsafeMutablePointer,
blue: UnsafeMutablePointer,
alpha: UnsafeMutablePointer) -> 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-toBOOL; in Swift, it’s an UnsafeMutablePointer. 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

556

|

Appendix A: C, Objective-C, and Swift

C Arrays
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?,
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:

C, Objective-C, and Swift

|

557

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 ele‐
ments. 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.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.allocate(capacity:4)
defer {
arr.deinitialize()
arr.deallocate(capacity:4)
}

The same convenient subscripting is available when you receive a C array. For exam‐
ple:
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]
// ...
}

558

|

Appendix A: C, Objective-C, and Swift

C Functions
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 param‐
eter 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 (“Memory Management of CFTypeRefs” on page
533), 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) -> 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 func‐
tion:
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:
C, Objective-C, and Swift

|

559

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 frame‐
work:
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 AudioServicesSystemSoundCom‐
pletionProc? 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 return‐
ing 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 can‐
not 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:

560

|

Appendix A: C, Objective-C, and Swift