CFHipsterRef Low-Level Programming on IOS & Mac OS X
CFHipsterRef Low-Level Programming on IOS & Mac OS X
CFHipsterRef:
Low-Level Programming
on iOS & Mac OS X
!
!
!
!
!
!
!
!
!
!
Copyright © 2014 Mattt Thompson
All rights reserved
ISBN 978-0-9912182-4-0
NSHipster
Portland, Oregon
http://nshipster.com
Contents
Kernel 1
Objective-C Runtime 18
Clang 35
OSAtomic 59
Grand Central Dispatch 70
Inter-Process Communication 91
CoreServices 115
Image I/O 125
Accelerate 136
Security 158
System Configuration 179
International Components for Unicode 192
Dictionary Services 220
Xcode Toolchain 233
Third Party Tools 245
CocoaPods 258
Chapter 1
Kernel
It’s oen remarked that the architecture of soware reflects the
structure of the organization building it. ¹
erefore, if you’re at all familiar with the story of how Apple acquired
NeXT, you already have an intuitive sense about the architecture of the
OS X kernel—even if you don’t really know what a kernel is.
2
PCs were more performant, more ubiquitous, and more cost-efficient
than anything coming out of Cupertino at the time. Between '94 and
'97, the company posted losses in the hundreds of millions, and may
have been forced to declare bankruptcy had it not been for a cash
infusion from Microso, which was fighting an antitrust lawsuit at the
time.
3
Both companies had built modern, eponymous operating systems—Be
Inc.'s BeOS, and NeXT’s NeXTSTEP—from the ground-up, but were
struggling to gain a foothold into the mainstream computer market. ³
BeOS, with an impressively forward-looking architecture and focus on
multimedia, was in many ways the technological forerunner, but it was
ultimately NeXTSTEP that would be given the nod. ⁴
Darwin
Darwin is the UNIX core of OS X. e term "Darwin" specifically
refers to the operating system, composed of the XNU kernel,
Objective-C runtime, and I/O Kit driver framework, though it is oen
used interchangeably to refer to any individual component. ⁶
3 Around the time of acquisition, NeXT had nearly given up on their operating system, and had considered
shiing focus onto WebObjects instead.
4 Regarding the acquisition, Gil Amelio famously quipped, "We choose Plan A instead of Plan Be."
5 Or, to be pedantic, OPENSTEP, which was a specific implementation of the OpenStep API (note the
capitalization), which was jointly developed by NeXT and Sun Microsystems (hence the NS prefix for
"NeXT/Sun")
6 Each major release of Darwin is coordinated with a minor release of OS X. For a given release of OS X,
10.x.y, the corresponding Darwin release is (x + 4).y.0.
4
e source code for Darwin and other underlying technologies can
be downloaded from Apple’s Open Source website.
a http://opensource.apple.com
$ uname -a
Darwin NSHipster.local 13.3.0 Darwin Kernel Version 13.3.0: Tue Jun 3 21:27:35 ←-
PDT 2014; root:xnu-2422.110.17~1/RELEASE_X86_64 x86_64
It’s endearing to view this marriage of Mach and BSD in the XNU
kernel as an extension of Apple’s philosophy of pragmatic eclecticism.
By combining competing technologies in ways that compliment one
another, one gets the best of both worlds. Like how the ideas of
Smalltalk were bolted onto a C implementation to form
Objective-C—under the right circumstances, the whole can be much
greater than the sum of its individual parts.
7 ough, as of OS X 10.5, Darwin has been certified under the Single UNIX Specification version 3
(SUSv3)
8 As such, OS X is oen described as having a "Mach/BSD" kernel.
5
Actually, the parallels between Mach/BSD and Smalltalk/C run deeper
than that.
Both Mach and Smalltalk are designed around the concept of message
passing. For Smalltalk, it’s objects sending messages to one another in
order to invoke methods. For Mach, it’s untyped interprocess
communication (IPC) and remote procedure calls (RPC) as a means of
sending information between processes. Message passing is a safe and
elegant abstraction, but necessarily incurs a performance penalty
because of this indirection.
Mach
At the core of XNU is Mach, which provides a small set of abstractions
for interacting with the system:
6
• Task: e unit of resource allocation, similar to a process, consisting
of a virtual address space and port rights.
• read: e unit of CPU utilization for a task.
• Port: A simplex, or one-way, communication channel with send and
receive capabilities made accessible via port rights.
• Message: A typed collection of data objects.
• Memory Object: An internal unit of memory management,
representing data that can be mapped into address spaces.
Multitasking
7
resources for processes to share. Under this scheme, an application
can still "beachball", but it’s usually possible to recover by
force-quitting any unresponsive processes.
Mach-O
Darwin also adopts the Mach-O file format for executables, object
code, and shared libraries. ⁹ ¹⁰
Mach-O has been the exclusive file format for NeXTSTEP, OS X, and
iOS. One feature of particular importance for these platforms is Mach’s
support of multi-architecture, or "fat", binaries.
For example, a single iOS binary can have six instruction set
architectures:
9 Commonly known by their file extensions: .o, .dylib, and .bundle.
10 Nearly every other Unix-based system adopts the ELF format for executables and shared libraries. Since
Darwin is already POSIX compliant, this would be one of the last steps to compatibility with Solaris,
BSD, and Unix.
8
Architectures & Supported Devices
BSD
Among the most visible features of BSD are its ubiquitous sockets API
for TCP & UDP networking, along with Kqueue, an efficient event
pipeline between kernel and user space. Everything else, from
9
memory protection to multiuser access, are more notable for how
they’re extended by Darwin.
Darwin Additions
iOS vs. OS X
10
Basic Security Module
Jetsam
11
oom on Linux, Jetsam is used to monitor the memory usage of
processes and kill any that are consuming more than their fair share.
12
Purgeable pages: <Number of Pages to be Freed>
Largest process: <Process Causing Crash>
Processes
Name UUID Count resident pages
<Name> <UUID> <Count> (jettisoned) (active)
• UIApplicationDelegate -
applicationDidReceiveMemoryWarning: delegate method
• UIViewController -didReceiveMemoryWarning delegate method
• UIApplicationDidReceiveMemoryWarningNotification
notification
Process Hibernation
13
resumed when memory pressure subsides. is is known as
hibernation.
14
exploited. By randomizing memory offsets within that address space,
however, a process becomes much less susceptible to attack.
I/O Kit
e last piece of the XNU kernel is I/O Kit, a framework for
developing device drivers for OS X & iOS.
e source code for I/O Kit and its companion library libkern can
be downloaded from Apple’s Open Source website.
a http://opensource.apple.com
I/O Kit was created to replace NeXT’s Driver Kit, which critically
lacked the capabilities of hot-swappable hardware and automatically
configuration. Unlike Driver Kit, which is written in Objective-C, I/O
Kit is implemented in Embedded C++, a subset that omits languages
features deemed problematic within a multithreaded kernel
environment. ¹³
I/O Kit is organized into several different families, each responsible for
a particular type of interface:
13 Specifically: no exceptions, no multiple inheritance, and no templates. (Sounds pretty nice, right?)
15
• ADB (Apple Desktop Bus)
• AGP (Accelerated Graphics Port)
• ATA & ATAPI (ATA Packet Interface)
• Audio
• FireWire
• Graphics
• HID (Human Interface Devices)
• Network
• PCI (Peripheral Component Interconnect)
• SBP-2 (Serial Bus Protocol 2)
• SCSI (Small Computer System Interface ¹⁴)
• Serial
• Storage
• USB (Universal Serial Bus)
16
better sense of how everything fits together. To that end, the following
command-line utilities offer a safe way to explore the hidden world of
drivers (io prefix) and kernel extensions (kext prefix):
Driver Utilities
17
Chapter 2
Objective-C
Runtime
Objective-C, by itself, is just talk. All of its high-falutin' ideas about
message passing and dynamism is nothing but a bunch of hot air
without a runtime to do the hard work.
It’s almost unfair how the Objective-C language gets all of the credit,
when it’s really the Objective-C runtime things happen.
19
e Objective-C 2.0 Runtime is open source, and available for
download from Apple’s Open Source website.
a http://opensource.apple.com/source/objc4
libobjc
libobjc is the shared library for the Objective-C 2.0 runtime. It can
be used directly from an Objective-C application to introspect and
change its own behavior.
#import <objc/runtime.h>
20
framework, in ways that would otherwise be impossible.
Message Sending
At the heart of Objective-C’s object-oriented paradigm is the concept
of message passing. It’s enshrined in the syntax of square brackets,
which delimit the act of sending a message to an object.
[object message];
objc_msgSend(object, @selector(message));
21
When a message is sent to an object, it consults its class’s dispatch table
to find an implementation associated with the message’s selector. If a
match is found, the associated function is invoked. If not, the dispatch
table of the superclass is consulted, and so on, until either a match is
found, or the selector is determined to be unrecognized.
Such are the risks and rewards of hacking the Objective-C runtime.
22
• objc_msgSend: Sends a message with a simple return value to a class.
• objc_msgSend_stret: Sends a message with a data-structure return
value to a class.
• objc_msgSendSuper: Sends a message with a simple return value to
the superclass of a class.
• objc_msgSendSuper_stret: Sends a message with a data-structure
return value to the superclass of a class.
- (id)initWithCoder:(NSCoder *)decoder {
self = [super init];
if (!self) {
return self;
}
23
unsigned int count;
objc_property_t *properties = class_copyPropertyList([self class], &count);
for (NSUInteger i = 0; i < count; i++) {
objc_property_t property = properties[i];
NSString *key = [NSString stringWithUTF8String:property_getName( ←-
property)];
[self setValue:[decoder decodeObjectForKey:key]
forKey:key];
}
free(properties);
return self;
}
- (void)encodeWithCoder:(NSCoder *)coder {
unsigned int count;
objc_property_t *properties = class_copyPropertyList([self class], &count);
for (NSUInteger i = 0; i < count; i++) {
objc_property_t property = properties[i];
NSString *key = [NSString stringWithUTF8String:property_getName( ←-
property)];
[coder encodeObject:[self valueForKey:key] forKey:key];
}
free(properties);
}
Associated Objects
Associated objects are a feature of the Objective-C runtime without a
counterpart in the language itself.
24
is feature allows objects to associate arbitrary values for keys at
runtime. It can be used to work around a language constraint that
prevents categories from declaring new storage properties.
- (void)setAssociatedObject:(id)object {
objc_setAssociatedObject(self, @selector(associatedObject), object, ←-
OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (id)associatedObject {
return objc_getAssociatedObject(self, @selector(associatedObject));
}
25
Dynamically Adding a Method
@interface NSObject ()
- (NSString *)greetingWithName:(NSString *)name;
@end
@implementation NSObject ()
- (NSString *)greetingWithName:(NSString *)name {
return [NSString stringWithFormat:@"Hello, %@!", name];
}
@end
26
However, the equivalent behavior could instead be achieved at
runtime:
Method Swizzling
Method swizzling is the process of changing the implementation of an
existing selector. It’s a technique made possible by the fact that method
invocations in Objective-C can be modified at runtime, by changing
how selectors are mapped to underlying functions in a class’s dispatch
table.
27
Consider the task of tracking how many times each view controller in
an application is presented during its lifetime:
#import <objc/runtime.h>
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
28
BOOL didAddMethod =
class_addMethod(class,
originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {
class_replaceMethod(class,
swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}
- (void)swizzled_viewWillAppear:(BOOL)animated {
[self swizzled_viewWillAppear:animated];
NSLog(@"viewWillAppear: %@", self);
}
@end
29
minimize the possibility of race conditions. +load is guaranteed to be
executed during class initialization, thereby affording a modicum of
consistency for changing system-wide behavior. By contrast,
+initialize provides no such guarantee of when it will be executed.
²⁰
20 In fact, unless a class is messaged directly by the app, its +initialize method won’t be called.
30
@interface Product : NSObject
@property (readonly) NSString *name;
@property (readonly) double price;
- (instancetype)initWithName:(NSString *)name
price:(double)price;
@end
@implementation Product
- (instancetype)initWithName:(NSString *)name
price:(double)price
{
self = [super init];
if(!self) {
return nil;
}
self.name = name;
self.price = price;
return self;
}
@end
31
class_addIvar(c, "price", sizeof(double), sizeof(double), @encode(double));
return self;
});
const char *initTypes = [[NSString stringWithFormat:@"%s%s%s%s%s%s",
@encode(id), @encode(id), @encode(SEL), @encode(id), @encode(id),
@encode(NSUInteger)] UTF8String];
class_addMethod(c,
@selector(initWithFirstName:lastName:age:),
initIMP,
initTypes);
32
memcpy(&price, ptr, sizeof(price));
return price;
});
const char *priceTypes = [[NSString stringWithFormat:@"%s%s%s", @encode(double) ←-
, @encode(id), @encode(SEL)] UTF8String];
class_addMethod(c, @selector(age), priceIMP, priceTypes);
objc_registerClassPair(c);
ere’s a lot going on here, so let’s take things one step at a time.
33
In order to add the initializer, the type encodings of each argument
needs to be calculated. It’s an awkward mess of @encode and string
interpolation, but it does the job.
Adding methods for the ivar getters follows much the same process. ²³
Finally, once all of the methods are added, the class is registered with
the runtime. And from that point on, Product can be interacted with
in Objective-C like any other class:
23 Although this example declares readonly properties, setter methods would be implemented in similar
manner to the initializer.
34
Chapter 3
Clang
Apple’s adoption of LLVM in the mid-00’s was the most important
technical decision made since the acquisition of NeXT. In many ways,
it serves as the demarcation point for when the company started to hit
its technological stride once again.
- Source Code
1. Front-end
2. Optimizer
24 GCC is released under the terms of the GNU General Public License, which has a Copyle provision
that mandates redistributed soware to also be released under the same license. Companies like Apple
have oen taken a defensive stance against such terms, preferring non-GPL-licensed projects and even
going as far as to create their own alternatives.
25 LLVM is released under the University of Illinois/NCSA Open Source License, which is based on the
MIT and BSD 3-Clause Licenses, and lacks any such Copyle provisions that might constrain the dis-
tribution of proprietary soware.
36
3. Back-end
- Machine Code
libclang
libclang is the C interface to the Clang LLVM front-end.It’s a
powerful way for C & Objective-C programs to introspect their own
internal structure and composition.
26 LLVM was developed for maximum compatibility with GCC, and as such, can use GCC font-ends as a
fallback for situations where an LLVM front-end is not provided.
37
ere’s a lot of functionality baked into Clang, which libclang
organizes it into several different components:
Clang Components
38
(continued)
Translation Units
CXTranslationUnit tu =
clang_parseTranslationUnit(idx,
filename,
0,
argv,
39
argc,
0,
CXTranslationUnit_None);
{ ... }
clang_disposeTranslationUnit(tu);
clang_disposeIndex(idx);
AST
40
static unsigned Visitor(CXCursor cursor,
CXCursor parent,
CXClientData data)
{
switch (clang_getCursorKind(cursor)) {
case CXCursor_FunctionDecl:
// Function
break;
case CXCursor_VarDecl:
// Variable
break;
case CXCursor_ObjCInstanceMethodDecl:
// Objective-C Instance Method
break;
// ...
default:
break;
}
return CXChildVisit_Recurse;
}
41
CXFile file;
clang_getFileLocation(location, &file, 0, 0, 0);
const char *filename = "/path/to/Source.m";
if (filename != clang_getCString(clang_getFileName(file))) {
return CXChildVisit_Continue;
}
For reference types, Clang can find all of the other local references
within the AST. Xcode uses this functionality to jump between a
reference and its declaration, as well as refactor by renaming all
occurrences. is information could also be used for semantic
highlighting, or using colors to differentiate between individual
references.
Tokens
42
CXCursor cursor = clang_getTranslationUnitCursor(tu);
CXSourceRange range = clang_getCursorExtent(cursor);
CXToken* tokens;
unsigned count;
clang_tokenize(tu, range, &tokens, &count);
43
scope of the change.
Diagnostics
Where Clang really starts to show off its smarts are through
diagnostics.
Clang diagnostics are ranked into different levels of severity, much like
log statements:
44
since the correctness of any one declaration is dependent on its
preceding context. clang_getNumDiagnostics gets the total number
of diagnostics for the translation unit, which can be enumerated in a
for statement:
switch (clang_getDiagnosticSeverity(diagnostic)) {
case CXDiagnostic_Note:
case CXDiagnostic_Warning:
case CXDiagnostic_Error:
case CXDiagnostic_Fatal:
// ...
break;
case CXDiagnostic_Ignored:
default:
break;
}
// ...
clang_disposeString(string);
}
45
code in-line. ²⁷
Fix-Its
It’s one thing to be able to point out problems, but it’s another thing
entirely to fix them as well. Clang fix-its take diagnostics to a whole
new level.
For each diagnostic, there are any number of potential changes that
can be made to address the issue. ese options would typically be
presented to the end-user in order to determine the best course of
action:
// ...
27 Yellow caution icons are displayed for CXDiagnostic_Note and CXDiagnostic_Warning. Red error
icons are displayed for CXDiagnostic_Error and CXDiagnostic_Fatal.
46
clang_disposeString(fixIt);
}
Xcode denotes fix-its with a dot over the yellow or red diagnostic
gutter icon. When activated, all of the available options are presented,
which when selected, automatically make the necessary changes to
(hopefully) fix the code.
Clang CLI
Clang’s insight into code can also be accessed via the command line.
@import Foundation;
/**
This is documentation.
*/
@interface Calculator : NSObject
+ (int)add:(int)a
to:(int)b;
@end
@implementation Calculator
47
+ (int)add:(int)a
to:(int)b
{
int sum = a + b;
return sum;
}
@end
If we pass this into xcrun clang and include the -ast-dump flag, the
output is an abstract syntax tree of the source file:
48
|-ParmVarDecl 0x1036f8480 <line:16:13, col:17> b 'int'
|-VarDecl 0x1036f85c0 <line:18:5, col:19> sum 'int'
| `-BinaryOperator 0x1036f8698 <col:15, col:19> 'int' '+'
| |-ImplicitCastExpr 0x1036f8668 <col:15> 'int' <LValueToRValue>
| | `-DeclRefExpr 0x1036f8618 <col:15> 'int' lvalue ParmVar 0x1036f8420 ' ←-
a' 'int'
| `-ImplicitCastExpr 0x1036f8680 <col:19> 'int' <LValueToRValue>
| `-DeclRefExpr 0x1036f8640 <col:19> 'int' lvalue ParmVar 0x1036f8480 ' ←-
b' 'int'
`-CompoundStmt 0x1036f8738 <line:17:1, line:20:1>
|-DeclStmt 0x1036f86c0 <line:18:5, col:20>
| `-VarDecl 0x1036f85c0 <col:5, col:19> sum 'int'
| `-BinaryOperator 0x1036f8698 <col:15, col:19> 'int' '+'
| |-ImplicitCastExpr 0x1036f8668 <col:15> 'int' <LValueToRValue>
| | `-DeclRefExpr 0x1036f8618 <col:15> 'int' lvalue ParmVar 0 ←-
x1036f8420 'a' 'int'
| `-ImplicitCastExpr 0x1036f8680 <col:19> 'int' <LValueToRValue>
| `-DeclRefExpr 0x1036f8640 <col:19> 'int' lvalue ParmVar 0 ←-
x1036f8480 'b' 'int'
`-ReturnStmt 0x1036f8718 <line:19:5, col:12>
`-ImplicitCastExpr 0x1036f8700 <col:12> 'int' <LValueToRValue>
`-DeclRefExpr 0x1036f86d8 <col:12> 'int' lvalue Var 0x1036f85c0 'sum' ←-
'int'
Look past the noisy memory pointers and angled brackets, and the
structure of the source code emerges from the AST branches. At the
root is the top-level class declaration for Calculator, with its
immediate children pointing to its superclass implementation,
NSObject and the class implementation that follows, as well as the
comment and class method declaration. In the implementation, there
is a reference back to the interface, as well as the class method
49
implementation, with parameters, returns type, variable declarations,
and return statement.
Static Analyzer
Within the context of programming, static analysis refers to algorithms
and techniques used to analyze source code in order to automatically
find bugs. e idea is similar in spirit to compiler warnings, which can
be useful for finding coding errors, but taken a step further to find
bugs that would otherwise be encountered at runtime.
50
Core Analyzer Warnings
51
(continued)
OS X Analyzer Warnings
52
(continued)
53
(continued)
54
Security Analyzer Warnings
55
(continued)
56
(continued)
Xcode integrates Clang’s static analysis into the Build & Analyze
command, which does a pretty remarkable job at tracing problems
back to their root cause. However, the same functionality is available
via the command line as well.
57
$ xcodebuild -project /path/to/Project.xcodeproj -scheme YOU_APP_SCHEME -sdk ←-
iphonesimulator7.1 analyze
$ scan-build !!
58
Chapter 4
OSAtomic
It is telling that the word used to describe structure and organization
in a system is "order".
60
• Integer Operations
• Compare & Swap Operations
• Spinlocks
• Queues
Integer Operations
One of the most basic operations in programming is incrementing an
integer. It’s so common a task, that it can be accomplished with several
equivalent operators:
int x = 3;
// Equivalent
x = x + 1;
x += 1;
x++;
* Get x (3)
* Perform Addition (3 + 1)
* Set x (4)
61
For a single-threaded application, there is a reasonable guarantee x
will only ever be accessed in that order: "get-add-set". But throw
another thread into the mix, and any such guarantee is up in smoke.
62
int64_t x = 3;
// Equivalent
OSAtomicAdd64(1, &x);
OSAtomicIncrement64(&x);
int64_t x = 3, y = 5;
x++ OSAtomicIncrement64(&x)
x-- OSAtomicDecrement64(&x)
x +=y OSAtomicAdd64(y, &x)
x -=y OSAtomicAdd64(-y, &x)
63
p &=q OSAtomicAnd32(q, &p)
p |=q OSAtomicOr32(q, &p)
p ˆ=q OSAtomicXor32(q, &p)
Still not convinced? Here’s how one might implement the previous
atomic integer addition function using OSAtomicCompareAndSwap32:
return new;
}
64
Compare & Swap is actually a single operation in OSAtomic. A naive,
non-thread-safe C implementation might look like this:
Like the integer operations, Compare & Swap functions comes in both
32- and 64-bit, as well as barrier and non-barrier varieties. In addition,
there are versions that take int and long arguments, as well as
OSAtomicCompareAndSwapPtr, which can be used to safely
heap-allocated object pointers.
Memory Barriers
65
and non-barrier varieties of the aforementioned functions differ by
whether or not they incorporate a memory barrier.
³⁰
Spin Locks
Spinlocks are perhaps the easiest type of lock to understand. ey
instruct the thread to hang out and wait until the lock is released. It’s a
30 As a general rule, semaphores, counters, and other standalone values that fit within a single 32- or 64-
bit memory address don’t need barriers. Anything where values aren’t self-contained, or involve data
outside of the value should use a barrier.
66
busy form of waiting, akin to directing an airplane into a holding
pattern until other traffic clears the runway. is might sound
inefficient, but in situations where the lock is not held onto for very
long, a spinlock has superior performance characteristics over its
alternatives.
67
Queues
OSAtomicEnqueue & OSAtomicDequeue are a lock-free, thread-safe
implementation of a LIFO queue. ere are also FIFO queue variants
OSAtomicFifoEnqueue & OSAtomicFifoDequeue.
node_t *n;
n = OSAtomicDequeue(&queue, offsetof(node_t, link));
// n == &b
68
But since GCD does exist, you’re almost certainly better off with
dispatch_queue, which has a number of additional benefits, which
are discussed in depth in the next chapter.
69
Chapter 5
Grand
Central
Dispatch
One of the most striking things about process control is how close
reality matches the domain models of computers. You won’t happen
upon a String as you walk down the street, or decide to hold a lunch
conversation over a socket. But you will stand in a queue, and wait for
a signal before crossing a street. Semaphores were flags and locks
were made of metal long before they were a cause of application
deadlock, aer all.
71
C-language blocks extension in iOS 4 and OS X 10.6, GCD is used
throughout Cocoa APIs to make applications faster and more efficient.
Queues
In GCD, work is divided up into discrete blocks or functions, which
are scheduled on dispatch queues. Queues abstract the concept of
threads from programmers. e system provides a main queue, which
executes work on the main thread, in addition to several global queues
that execute on background threads at different priority levels. In
addition to these, users can create their own queues.
dispatch_queue_t globalQueue =
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
72
dispatch_queue_t customSerialQueue =
dispatch_queue_create("com.example.serial", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t customConcurrentQueue =
dispatch_queue_create("com.example.concurrent", DISPATCH_QUEUE_CONCURRENT);
73
sleep(3);
dispatch_async(mainQueue, ^{
NSLog(@"Finished");
});
});
74
As mentioned in the previous chapter, GCD can be used to implement
robust, thread-safe implementations of common atomic operations.
For example, dispatch_once can guarantee that a statement is
executed exactly once—making it perfectly suited for creating
singletons:
+ (instancetype)sharedInstance {
static id _sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_sharedInstance = [[self alloc] init];
});
return _sharedInstance;
}
Groups
Tasks can also be scheduled in groups, providing a callback for when
all tasks within that group are finished executing:
dispatch_queue_t queue =
dispatch_queue_create(NULL, DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t group =
dispatch_group_create();
75
dispatch_group_enter(group);
dispatch_group_async(group, queue, ^{
sleep((int)n);
dispatch_group_leave(group);
});
});
dispatch_group_notify(group, queue, ^{
// ...
});
Semaphores
Semaphores play a crucial role in GCD, by allowing asynchronous
code to wait and block execution, thereby becoming synchronous. For
many applications, asynchronous execution is strictly preferable to the
alternative. However, in some cases, an API must be run
synchronously. And it is in those cases, where dispatch semaphore
shines:
dispatch_queue_t queue =
dispatch_queue_create(NULL, DISPATCH_QUEUE_CONCURRENT);
dispatch_semaphore_t semaphore =
dispatch_semaphore_create(0);
dispatch_async(queue, ^{
sleep(3);
76
dispatch_semaphore_signal(semaphore);
});
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
Barriers
Barriers are another concept explored in the previous chapter. In the
case of GCD, barriers are used to synchronize access to shared state.
Without Barrier
dispatch_queue_t queue =
dispatch_queue_create(NULL, DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue,^{
sleep(3);
dispatch_async(queue,^{
sleep(5);
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"Finished");
});
});
});
With Barrier
77
dispatch_queue_t queue =
dispatch_queue_create(NULL, DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue,^{
sleep(5);
});
dispatch_async(queue,^{
sleep(3);
});
dispatch_barrier_async(queue,^{
dispatch_async(dispatch_get_main_queue(),^{
// ...
});
});
- (void)setObject:(id)object
forKey:(id)key
{
dispatch_barrier_async(self.queue, ^{
self.mutableDictionary[key] = object;
});
}
78
Sources
GCD can be used to handle events from sources like timers, processes,
mach ports, and file descriptors. Dispatch sources start suspended,
and must be explicitly resumed in order to start.
dispatch_queue_t queue =
dispatch_queue_create(NULL, DISPATCH_QUEUE_CONCURRENT);
dispatch_source_t timer =
dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
dispatch_source_set_event_handler(timer, ^{
NSLog(@"Ding Dong!");
});
dispatch_resume(timer);
79
To monitor a file or directory for changes, create a dispatch event for a
file descriptor. Whenever one of the watched events is triggered, the
event handler will be scheduled on the specified queue:
dispatch_queue_t queue =
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
int fileDescriptor =
open([fileURL fileSystemRepresentation], O_EVTONLY);
dispatch_source_set_event_handler(source, ^{
dispatch_source_vnode_flags_t flags =
dispatch_source_get_data(source);
if (flags) {
dispatch_source_cancel(source);
dispatch_async(dispatch_get_main_queue(), ^{
// ...
});
}
80
});
dispatch_source_set_cancel_handler(source, ^{
close(fileDescriptor);
});
dispatch_resume(source);
dispatch_queue_t globalQueue =
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_source_t stdinReadSource =
dispatch_source_create(DISPATCH_SOURCE_TYPE_READ,
STDIN_FILENO,
0,
globalQueue);
dispatch_source_set_event_handler(stdinReadSource, ^{
uint8_t buffer[1024];
int length = read(STDIN_FILENO, buffer, sizeof(buffer));
if (length > 0) {
NSString *string =
[[NSString alloc] initWithBytes:buffer
length:length
encoding:NSUTF8StringEncoding];
NSLog(@"%@", string);
}
});
dispatch_resume(stdinReadSource);
81
Finally, a dispatch source can listen for process signals, such as a
SIGTERM:
dispatch_queue_t globalQueue =
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_source_t source =
dispatch_source_create(DISPATCH_SOURCE_TYPE_PROC,
ppid,
DISPATCH_PROC_EXIT,
globalQueue);
if (source) {
dispatch_source_set_event_handler(source, ^{
NSLog(@"pid: %d Exited", ppid);
dispatch_source_cancel(source);
});
dispatch_resume(source);
}
I/O
Although dispatch sources provide a convenient way to interact with a
input and output, it requires quite a bit of responsibility on the part of
the API consumer. GCD’s I/O APIs allow the developer to hand over
most of that responsibility, which not only makes for less code to write,
82
but greatly improves the overall capacity for concurrent I/O operations
by reducing resource contention.
dispatch_queue_t queue =
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_io_t stdinChannel =
dispatch_io_create(DISPATCH_IO_STREAM,
STDIN_FILENO,
queue,
^(int error) {
if (error) {
NSLog(@"stdin: (%d) %s", error, strerror(error));
}
});
83
ways: wait for a certain amount of data to accumulate, or wait for a
certain amount of time to pass.
dispatch_io_set_low_water(stdinChannel, 1);
84
off_t offset = 0; // Ignored for stream
UInt length = SIZE_MAX; // Read until EOF
dispatch_io_read(stdinChannel, offset, length, queue,
^(bool done, dispatch_data_t data, int error) {
// ...
});
dispatch_io_t fileChannel =
dispatch_io_create_with_path(DISPATCH_IO_STREAM,
"/path/to/file",
O_RDONLY,
0,
queue,
^(int error) {
if (error) {
NSLog(@"file: (%d) %s", error, strerror(error));
}
});
85
dispatch_io_read and dispatch_io_write for simple, one-off I/O
operations.
Data
When first introduced, dispatch data objects were unique within
Apple’s SDKs for being a container for both contiguous and
noncontiguous data. e major implication being that two data
objects could be concatenated in constant time, without having to
copy over into a single continuous segment. As of iOS 7 and OS X
10.9, NSData added support for noncontiguous access, as well as a
one-way cast from dispatch_data_t objects. ³¹ ³²
Perhaps the best way to understand dispatch data is to say that it has
all of the convenience of NSData in a low-level C interface.
86
size_t length;
void *buffer = malloc(length);
dispatch_data_t data =
dispatch_data_create(buffer,
length,
NULL,
DISPATCH_DATA_DESTRUCTOR_DEFAULT);
free(buffer);
87
__block dispatch_data_t container;
[data enumerateByteRangesUsingBlock:
^(const void *bytes, NSRange byteRange, BOOL *stop) {
if (container) {
dispatch_data_t region =
dispatch_data_create(bytes,
byteRange.length,
queue,
DISPATCH_DATA_DESTRUCTOR_DEFAULT);
container = dispatch_data_create_concat(container, region);
}
}];
return container;
}
88
void *buffer;
size_t length;
dispatch_data_create_map(data, &buffer, &length);
Debugging
As of OS X 10.8 and iOS 6.0, GCD types are full-fledged NSObject
subclasses, which respond to -debugDescription. is means that
doing po in lldb will return useful diagnostic output, e.g.:
<OS_dispatch_queue_root:
com.apple.root.default-priority[0x2024100] = {
xrefcnt = 0x80000000,
refcnt = 0x80000000,
suspend_cnt = 0x0,
locked = 1,
target = [0x0],
width = 0x7fffffff,
running = 0x1,
barrier = 0
}
>
Benchmarking
dispatch_benchmark is part of libdispatch, but is not publicly
available. To use it, it must be re-declared:
89
uint64_t dispatch_benchmark(size_t count, void (^block)(void));;
90
Chapter 6
Inter-Process
Communication
Up until this point in the book, the guiding narrative has been
technologies fusing together through happy accidents of history to
create something better than before. And, while this is true for many
aspects of Apple’s technology stack, inter-process communication is a
flagrant counter-example.
Rather than taking the best of what was available at each juncture,
solutions just kinda piled up. As a result, a handful of overlapping,
mutually-incompatible IPC technologies are scattered across various
abstraction layers. ³³
• Mach Ports
• Distributed Notifications
• Distributed Objects
• AppleEvents & AppleScript
• Pasteboard
• XPC
92
security characteristics. But fundamentally, they’re all mechanisms for
transmitting and receiving data from beyond a context boundary.
Mach Ports
All inter-process communication ultimately relies on functionality
provided by Mach kernel APIs.
natural_t data;
mach_port_t port;
struct {
mach_msg_header_t header;
mach_msg_body_t body;
mach_msg_type_descriptor_t type;
} message;
34 How poorly documented? e most up-to-date authoritative resource was a Mach 3.0 PostScript file
circa 1990 tucked away on a Carnegie Mellon University FTP server.
35 How inconvenient? Well, just look at the code samples.
93
message.header = (mach_msg_header_t) {
.msgh_remote_port = port,
.msgh_local_port = MACH_PORT_NULL,
.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, 0),
.msgh_size = sizeof(message)
};
message.body = (mach_msg_body_t) {
.msgh_descriptor_count = 1
};
message.type = (mach_msg_type_descriptor_t) {
.pad1 = data,
.pad2 = sizeof(data)
};
if (error == MACH_MSG_SUCCESS) {
// ...
}
ings are a little easier on the receiving end, since the message only
needs to be declared, not initialized:
mach_port_t port;
struct {
mach_msg_header_t header;
mach_msg_body_t body;
mach_msg_type_descriptor_t type;
mach_msg_trailer_t trailer;
94
} message;
if (error == MACH_MSG_SUCCESS) {
natural_t data = message.type.pad1;
// ...
}
95
CFMessagePortRef localPort =
CFMessagePortCreateLocal(nil,
CFSTR("com.example.app.port.server"),
Callback,
nil,
nil);
CFRunLoopSourceRef runLoopSource =
CFMessagePortCreateRunLoopSource(nil, localPort, 0);
CFRunLoopAddSource(CFRunLoopGetCurrent(),
runLoopSource,
kCFRunLoopCommonModes);
CFDataRef data;
SInt32 messageID = 0x1111; // Arbitrary
CFTimeInterval timeout = 10.0;
CFMessagePortRef remotePort =
CFMessagePortCreateRemote(nil,
CFSTR("com.example.app.port.client"));
SInt32 status =
CFMessagePortSendRequest(remotePort,
messageID,
data,
timeout,
timeout,
96
NULL,
NULL);
if (status == kCFMessagePortSuccess) {
// ...
}
Distributed Notifications
ere are many ways for objects to communicate with one another in
Cocoa:
ere is, of course, sending a message directly. ere are also the
target-action, delegate, and callbacks, which are all loosely-coupled,
one-to-one design patterns. KVO allows for multiple objects to
subscribe to events, but it strongly couples those objects together.
Notifications, on the other hand, allow messages to be broadcast
globally, and intercepted by any object that knows what to listen for. ³⁶
97
To listen for notifications, add an observer to the distributed
notification center by specifying the notification name to listen for,
and a function pointer to execute each time a notification is received:
CFNotificationCenterRef distributedCenter =
CFNotificationCenterGetDistributedCenter();
CFNotificationSuspensionBehavior behavior =
CFNotificationSuspensionBehaviorDeliverImmediately;
CFNotificationCenterAddObserver(distributedCenter,
NULL,
Callback,
CFSTR("notification.identifier"),
NULL,
behavior);
void *object;
98
CFDictionaryRef userInfo;
CFNotificationCenterRef distributedCenter =
CFNotificationCenterGetDistributedCenter();
CFNotificationCenterPostNotification(distributedCenter,
CFSTR("notification.identifier"),
object,
userInfo,
true);
Distributed Objects
Distributed Objects (DO) is a remote messaging feature of Cocoa that
had its heyday back in the mid-90’s with NeXT. And though its not
widely used any more, the dream of totally frictionless IPC is still
unrealized in our modern technology stack.
99
@protocol Protocol;
id <Protocol> vendedObject;
100
In reality, Distributed Objects can’t be used like local objects, if only
because any message sent to a proxy could result in an exception being
thrown. Unlike other languages, Objective-C doesn’t use exceptions
for control flow. As a result, wrapping everything in a @try/@catch is
a poor fit to the conventions of Cocoa.
All that’s really le are traces of the annotations used by Distributed
Objects to specify the proxying behavior of properties and method
parameters:
101
AppleEvents & AppleScript
AppleEvents are the most enduring legacies of the classic Macintosh
operating system. Introduced in System 7, AppleEvents allowed apps
to be controlled locally using AppleScript, or remotely using a feature
called Program Linking. To this day, AppleScript, using the Cocoa
Scripting Bridge, remains the most direct way to programmatically
interact with OS X applications. ³⁸
at said, it’s easily one of the weirdest technologies to work with.
To get a better sense of the nature of the beast, here’s how to tell Safari
to open a URL in the active tab in the frontmost window:
38 Mac OS’s Apple Event Manager provided the initial low-level transport mechanism for AppleEvents, but
was reimplemented on top of Mach ports for OS X.
102
tell application "Safari"
set the URL of the front document to "http://nshipster.com"
end tell
103
sdef generates scripting definition files for an application. ese files
can then be piped into sdp to be converted into another format—in
this case, a C header. e resulting .h file can then be added and #
import-ed into a project to get a first-class object interface to that
application.
#import "Safari.h"
It’s a bit more verbose than AppleScript, but this is much easier to
integrate into an existing codebase. It’s also a lot clearer to understand
how this same code could be adapted to slightly different behavior
(though that could just be the effect of being more familiar with
Objective-C).
104
Alas, AppleScript’s star appears to be falling, as recent releases of OS X
and iWork applications have greatly curtailed their scriptability. At
this point, it’s unlikely that adding support in your own applications
will be worth it.
Pasteboard
Pasteboard is the most visible inter-process communication
mechanism on OS X and iOS. Whenever a user copies or pastes a
piece of text, an image, or a document between applications, an
exchange of data from one process to another over mach ports is being
mediated by the com.apple.pboard service.
NSImage *image;
105
e reciprocal paste action is a bit more involved, requiring an
iteration over the Pasteboard contents:
106
concept discussed in greater detail in the next chapter.
XPC
XPC is the state-of-the-art for inter-process communication in the
SDKs. Its architectural goals are to avoid long-running process, to
adapt to the available resources, and to lazily initialize wherever
possible. e motivation to incorporate XPC into an application is not
to do things that are otherwise impossible, but to provide better
privilege separation and fault isolation for inter-process
communication.
Introduced in 2011, XPC has provided the infrastructure for the App
Sandbox on OS X, Remote View Controllers on iOS, and App
Extensions on both. It is also widely used by system frameworks and
first-party applications:
By surveying the inventory of XPC services in the wild, one can get a
much better understanding of opportunities to use XPC in their own
107
application. Common themes in applications emerge, like services for
image and video conversion, system calls, webservice integration, and
3rd party authentication.
108
Services call xpc_main with an event handler to receive new XPC
connections:
xpc_connection_resume(peer);
}
109
dispatched onto a queue managed by the runtime. As soon as the
connection is opened on the remote end, messages are dequeued and
sent.
• Data
• Boolean
• Double
• String
• Signed Integer
• Unsigned Integer
• Date
• UUID
• Array
• Dictionary
• Null
110
XPC offers a convenient way to convert from the dispatch_data_t
data type, which simplifies the workflow from GCD to XPC:
void *buffer;
size_t length;
dispatch_data_t ddata =
dispatch_data_create(buffer,
length,
DISPATCH_TARGET_QUEUE_DEFAULT,
DISPATCH_DATA_DESTRUCTOR_MUNMAP);
dispatch_queue_t queue;
xpc_connection_send_message_with_reply(c, message, queue,
^(xpc_object_t reply)
{
if (xpc_get_type(event) == XPC_TYPE_DICTIONARY) {
// ...
}
});
Registering Services
111
CFDistributedNotifications. ese criteria are specified in a service’s
launchd.plist file:
launchd.plist
<key>LaunchEvents</key>
<dict>
<key>com.apple.iokit.matching</key>
<dict>
<key>com.example.device-attach</key>
<dict>
<key>idProduct</key>
<integer>2794</integer>
<key>idVendor</key>
<integer>725</integer>
<key>IOProviderClass</key>
<string>IOUSBDevice</string>
<key>IOMatchLaunchStream</key>
<true/>
<key>ProcessType</key>
<string>Adaptive</string>
</dict>
</dict>
</dict>
112
Process Types and Contention Behavior
xpc_activity_register("com.example.app.activity",
criteria,
^(xpc_activity_t activity)
{
// Process Data
xpc_activity_set_state(activity, XPC_ACTIVITY_STATE_CONTINUE);
dispatch_async(dispatch_get_main_queue(), ^{
// Update UI
113
xpc_activity_set_state(activity, XPC_ACTIVITY_STATE_DONE);
});
});
114
Chapter 7
Core Services
Let’s take a moment to really think about what a file is.
116
applications, the data fork would hold the binary executable, while the
resource fork would contain things like icon bitmaps and localized
strings. Not all files had resource forks, but they were useful for
separating content from presentation, like in the case of a word
processing documentation.
OSType was also used to denote the type of pasteboard contents, error
codes, and AppleEvents, the inter-process communication mechanism
introduced in System 7.
117
Actually, that last point about OSType being repurposed for tasks like
handling copy-paste between applications raises an interesting point:
files are but one of many ways that data gets passed around.
erefore, any system that would replace OSType would have to be able
to accommodate internet media, as well as existing file types.
UTI
UTIs provide an extensible, hierarchical classification system that
affords the developer great flexibility in handling even the most exotic
41 MIME types were originally used in the development of mail applications using SMTP
118
file types. For example, a Ruby source file (.rb) is categorized as "Ruby
Source Code > Source Code > Text > Content > Data"; a QuickTime
Movie file (.mov) is categorized as "Video > Movie > Audiovisual
Content > Content > Data".
• Files
• Directories
• Pasteboard Data
• Bundles
• Frameworks
• Internet Media
• Streaming Data
• Aliases and Symbolic Links
Type Identifiers
• public.text
119
• public.plain-text
• public.jpeg
• public.html
Sometimes a data type does not have a UTI declared for it. UTIs
handle this case transparently by creating a dynamic identifier.
Dynamic identifiers have the domain dyn, with the rest of the string
that follows being opaque. ey can be thought of as a
UTI-compatible wrapper around an otherwise unknown filename
extension, MIME type, OSType, and so on.
When creating a custom type identifier, the aim is to have the UTI
conform to both a physical and functional hierarchy:
120
• A functional hierarchy relates to how the item is used. is should
not inherit from public.item, but instead something like public.
content or public.executable.
Comparing
121
UTTypeConformsTo(CFSTR("public.jpeg"),
CFSTR("public.item")); // YES
UTTypeConformsTo(CFSTR("public.jpeg"),
CFSTR("public.image")); // YES
UTTypeEqual(CFSTR("public.jpeg"),
CFSTR("public.image")); // NO
UTTypeConformsTo(CFSTR("public.jpeg"),
CFSTR("public.png")); // NO
Copying Declarations
UTTypeCopyDeclaration(CFSTR("public.png"));
{
UTTypeConformsTo = "public.image";
UTTypeDescription = "Portable Network Graphics image";
UTTypeIdentifier = "public.png";
UTTypeTagSpecification = {
"com.apple.nspboard-type" = "Apple PNG pasteboard type";
122
"com.apple.ostype" = PNGf;
"public.filename-extension" = png;
"public.mime-type" = "image/png";
};
}
Converting
NSString *contentType =
(__bridge_transfer NSString *)
UTTypeCopyPreferredTagWithClass(CFSTR("public.text"),
kUTTagClassMIMEType);
NSString *UTI =
(__bridge_transfer NSString *)
UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension,
CFSTR("jpg"),
NULL);
123
e tag class arguments for UTTypeCopyPreferredTagWithClass &
UTTypeCreatePreferredIdentifierForTag can be any of the
following string constants: ⁴³
Making the most of UTIs will ensure that applications interact with
files responsibly, and play well with others.
43 For OSType values containing only printable 7-bit ASCII characters, you can still use the CFSTR macro
with a four-character string literal (for example, CFSTR("TEXT") to create a valid OSType tag.
124
Chapter 8
ImageIO
Image I/O is a powerful, albeit lesser-known framework for working
with images. Independent of Core Graphics, it can read and write
between between many different formats, access photo metadata, and
perform common image processing operations. e framework offers
the fastest image encoders and decoders on the platform, with
advanced caching mechanisms and even the ability to load images
incrementally.
UTI iOS OS X
com.adobe.photoshop-image x
com.adobe.raw-image x x
com.apple.icns x
126
(continued)
UTI iOS OS X
com.apple.macpaint-image x
com.apple.pict x
com.apple.quicktime-image x
com.canon.cr2-raw-image x x
com.canon.crw-raw-image x x
com.canon.tif-raw-image x x
com.compuserve.gif x x
com.epson.raw-image x x
com.fuji.raw-image x x
com.hasselblad.3fr-raw-image x x
com.hasselblad.fff-raw-image x x
com.ilm.openexr-image x
com.kodak.flashpix-image x
com.kodak.raw-image x x
com.konicaminolta.raw-image x x
com.leafamerica.raw-image x x
com.leica.raw-image x x
com.leica.rwl-raw-image x x
com.microsoft.bmp x x
com.microsoft.cur x x
com.microsoft.ico x x
com.nikon.nrw-raw-image x x
127
(continued)
UTI iOS OS X
com.nikon.raw-image x x
com.olympus.or-raw-image x x
com.olympus.raw-image x x
com.olympus.sr-raw-image x x
com.panasonic.raw-image x x
com.panasonic.rw2-raw-image x x
com.pentax.raw-image x x
com.samsung.raw-image x x
com.sgi.sgi-image x
com.sony.arw-raw-image x x
com.sony.raw-image x x
com.sony.sr2-raw-image x x
com.truevision.tga-image x x
public.jpeg x x
public.jpeg-2000 x x
public.mpo-image x
public.png x x
public.radiance x
public.tiff x x
public.xbitmap-image x x
128
As it turns out, that does seem like most formats. At least the ones that
matter for applications today. ere is universal support for common
formats: TIFF, JPEG, GIF, PNG, RAW, and Windows Bitmap, Icon,
and Cursor. Additionally, several vendor-specific RAW camera
formats are supported on iOS, but OS X supports a few more of them.
Writing to a File
Image I/O offers advanced output configuration without much
overhead.
Specify the UTI of the desired output format, as well as any options,
like compression quality, orientation, or whether to ignore alpha
channels. A CGImageDestinationRef is created for the destination,
has the CGImage added to it, and is then finalized:
129
CGImageDestinationRef imageDestinationRef =
CGImageDestinationCreateWithURL((__bridge CFURLRef)fileURL,
(__bridge CFStringRef)UTI,
1,
NULL);
Create a file URL to the desired input file, and set any desired flags for
caching or image type hinting. A CGImageSourceRef is created with
that URL, which then reads the data and creates a CGimage with a call
to CGImageSourceCreateImageAtIndex.
130
CGImageSourceRef imageSourceRef =
CGImageSourceCreateWithURL((__bridge CFURLRef)fileURL, NULL);
CGImageRef imageRef =
CGImageSourceCreateImageAtIndex(imageSourceRef,
0,
(__bridge CFDictionaryRef)options);
CFRelease(imageRef);
CFRelease(imageSourceRef);
Since many applications load images over HTTP, the session task
delegate method URLSession:dataTask:didReceiveData: is a great
opportunity for performance gains:
- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
didReceiveData:(NSData *)data
{
[self.mutableResponseData appendData:data];
131
CGImageSourceUpdateData(self.imageSourceRef,
(__bridge CFDataRef)self.mutableResponseData,
[self.mutableResponseData length]
[dataTask countOfBytesExpectedToReceive]);
if (CGSizeEqualToSize(self.imageSize, CGSizeZero)) {
NSDictionary *properties =
(__bridge_transfer NSDictionary *)
CGImageSourceCopyPropertiesAtIndex(self.imageSourceRef,
0,
NULL);
if (properties) {
NSNumber *width = properties[(__bridge id) ←-
kCGImagePropertyPixelWidth];
NSNumber *height = properties[(__bridge id) ←-
kCGImagePropertyPixelHeight];
dispatch_async(dispatch_get_main_queue(), ^{
// delete or block callback to update with image
});
}
132
Given a CGImageSourceRef, which would have been initialized when
the request began loading, this delegate method calls
CGImageSourceUpdateData to update with the response data buffer.
Image Metadata
In the incremental image loading example, the image’s width and
height were retrieved from its metadata so that it could be properly
sized—even before all of the data was loaded.
133
• kCGImagePropertyTIFFDictionary
• kCGImagePropertyGIFDictionary
• kCGImagePropertyJFIFDictionary
• kCGImagePropertyExifDictionary
• kCGImagePropertyPNGDictionary
• kCGImagePropertyIPTCDictionary
• kCGImagePropertyGPSDictionary
• kCGImagePropertyRawDictionary
• kCGImagePropertyCIFFDictionary
• kCGImageProperty8BIMDictionary
• kCGImagePropertyDNGDictionary
• kCGImagePropertyExifAuxDictionary
NSDictionary *properties =
(__bridge_transfer NSDictionary *)
CGImageSourceCopyPropertiesAtIndex(self.imageSourceRef,
0,
NULL);
134
NSString *Fnumber = EXIF[(__bridge id)kCGImagePropertyExifFNumber];
NSString *exposure = EXIF[(__bridge id)kCGImagePropertyExifExposureTime];
NSString *ISO = EXIF[(__bridge id)kCGImagePropertyExifISOSpeedRatings];
135
Chapter 9
Accelerate
Over the last decade, there has been a focus on doing more with less,
when it comes to hardware.
At the same time, the rise of mobile computing has turned the
performance paradigm on its head, emphasizing battery life over
power.
With over 2,000 APIs, Accelerate is easily the single largest framework
in the iOS and OS X SDKs. But far from being monolithic, it’s really
more of an umbrella framework, with several inter-related component
parts.
137
At the top level, Accelerate can be split up between vecLib & vImage.
vecLib contains data types and C functions for digital signal processing
(vDSP) as well as vector and matrix math, including those covered by
the Linear Algebra Package (LAPACK) and the Basic Linear Algebra
Subprograms (BLAS) standard. vImage contains a wide range of image
manipulation functionality, including alpha compositing, conversion,
convolution, morphology, transformation, and histogram generation.
SIMD
If there is a single, unifying concept for Accelerate, it’s SIMD, or "single
instruction, multiple data". SIMD means that a computer can perform
the same operation on several data points simultaneously using a
single command.
138
e hardware found in iPhones, iPads, and Macs boast impressive
capabilities. On x86 architectures (Mac), the key technologies are SSE,
AVX, and AVX2; for AMD (iPhone & iPad), it’s NEON.
Benchmarking Performance
How much of a difference do these advanced routines make in
practice? Consider the following benchmarks for common arithmetic
operations:
Populating an Array
Baseline
139
for (NSUInteger i = 0; i < count; i++) {
array[i] = i;
}
Accelerate
float initial = 0;
float increment = 1;
vDSP_vramp(&initial, &increment, array, 1, count);
Baseline Accelerate Δ
20.664600 msec 2.495000 msec 10x
Multiplying an Array
Baseline
Accelerate
140
Baseline Accelerate Δ
19.969440 msec 2.541220 msec 10x
Summing an Array
Baseline
float sum = 0;
for (NSUInteger i = 0; i < count; i++) {
sum += array[i];
}
Accelerate
Baseline Accelerate Δ
41.704483 msec 2.165160 msec 20x
Searching
141
for (NSUInteger i = 0; i < count; i++) {
array[i] = (float)arc4random();
}
Baseline
NSUInteger maxLocation = 0;
for (NSUInteger i = 0; i < count; i++) {
if (array[i] > array[maxLocation]) {
maxLocation = i;
}
}
Accelerate
Baseline Accelerate Δ
22.339838 msec 5.110880 msec 4x
From these benchmarks, it’s clear that for Accelerate can have huge
performance benefits for operations on large data sets. Of course, like
any optimization, not all situations will benefit equally. e best
approach is always to use Instruments to find bottlenecks in your
code, and measure alternative implementations.
142
In order to understand and identify situations that might benefit from
Accelerate, though, we need to get a sense of everything it can do.
vecLib
vecLib is comprised of the following 9 headers:
143
vDSP.h
Fast-Fourier Transform
int x = 8;
int y = 8;
int dimensions = x * y;
DSPSplitComplex input = {
144
.realp = (float *)malloc(sizeof(float) * dimensions),
.imagp = (float *)malloc(sizeof(float) * dimensions),
};
free(input.realp);
free(input.imagp);
free(data);
vImage
vImage is comprised of 6 headers:
145
Geometry.h Geometric transformations (e.g. rotate, scale, shear,
affine warp).
Histogram.h Functions for calculating image histograms and
image normalization.
Morphology.h Image morphology procedures (e.g. feature
detection, dilation, erosion).
Tranform.h Image transformation operations (e.g. gamma
correction, colorspace conversion).
146
RGBA8888 e image has four interleaved channels, for red,
green, blue, and alpha, in that order. Each pixel is
32 bits, an array of four 8-bit unsigned integers. e
data type for this image format is Pixel_8888.
RGBAFFFF e image has four interleaved channels, for red,
green, blue, and alpha, in that order. Each pixel is
an array of four floating-point numbers. e pixel
data type for this image format is Pixel_FFFF.
Alpha.h
147
CGDataProviderRef topProvider = CGImageGetDataProvider(topImageRef);
CFDataRef topBitmapData = CGDataProviderCopyData(topProvider);
vImage_Buffer topBuffer = {
.data = (void *)CFDataGetBytePtr(topBitmapData),
.width = width,
.height = height,
.rowBytes = bytesPerRow,
};
vImage_Buffer bottomBuffer = {
.data = (void *)CFDataGetBytePtr(bottomBitmapData),
.width = width,
.height = height,
.rowBytes = bytesPerRow,
};
148
if (error) {
NSLog(@"Error: %ld", error);
}
Conversion.h
ere are two ways that images encode this information: interleaved,
such that each pixel has its red, green, blue, and alpha values
represented together, or planar, where all of the values in a channel are
set, followed by the values in the next channel, and so on.
149
size_t width = CGImageGetWidth(imageRef);
size_t height = CGImageGetHeight(imageRef);
size_t bitsPerComponent = CGImageGetBitsPerComponent(imageRef);
size_t bytesPerRow = CGImageGetBytesPerRow(imageRef);
150
kCGBitmapByteOrderDefault |
kCGImageAlphaPremultipliedFirst,
NULL,
NULL);
CGImageRelease(permutedImageRef);
CGContextRelease(destinationContext);
CGColorSpaceRelease(colorSpaceRef);
Convolution.h
Blurring an Image
151
UIImage *inImage = ...;
CGImageRef inImageRef = [inImage CGImage];
vImage_Buffer inBuffer = {
.data = (void *)CFDataGetBytePtr(inBitmapData),
.width = CGImageGetWidth(inImageRef),
.height = CGImageGetHeight(inImageRef),
.rowBytes = CGImageGetBytesPerRow(inImageRef),
};
152
NSLog(@"Error: %ld", error);
}
CGImageRelease(outImageRef);
CGContextRelease(c);
CGColorSpaceRelease(colorSpaceRef);
CFRelease(inBitmapData);
Geometry.h
Resizing an Image
153
double scaleFactor = 1.0 / 5.0;
void *outBytes = malloc(trunc(inBuffer.height * scaleFactor) * inBuffer. ←-
rowBytes);
vImage_Buffer outBuffer = {
.data = outBytes,
.width = trunc(inBuffer.width * scaleFactor),
.height = trunc(inBuffer.height * scaleFactor),
.rowBytes = inBuffer.rowBytes,
};
vImage_Error error =
vImageScale_ARGB8888(&inBuffer,
&outBuffer,
NULL,
kvImageHighQualityResampling);
if (error) {
NSLog(@"Error: %ld", error);
}
Histogram.h
UIImage *image;
CGImageRef imageRef = [image CGImage];
CGDataProviderRef dataProvider = CGImageGetDataProvider(imageRef);
CFDataRef bitmapData = CGDataProviderCopyData(dataProvider);
154
vImagePixelCount *histogram[4] = {a, r, g, b};
vImage_Buffer buffer = {
.data = (void *)CFDataGetBytePtr(bitmapData),
.width = CGImageGetWidth(imageRef),
.height = CGImageGetHeight(imageRef),
.rowBytes = CGImageGetBytesPerRow(imageRef),
};
vImage_Error error =
vImageHistogramCalculation_ARGB8888(&buffer,
histogram,
kvImageNoFlags);
if (error) {
NSLog(@"Error: %ld", error);
}
CGDataProviderRelease(dataProvider);
CFRelease(bitmapData);
Morphology.h
Dilating an Image
155
size_t bitsPerComponent = 8;
size_t bytesPerRow = CGImageGetBytesPerRow([image CGImage]);
CGContextDrawImage(sourceContext,
CGRectMake(0.0f, 0.0f, width, height), [image CGImage]);
156
1, 1, 1,
1, 1, 1,
1, 1, 1,
};
vImageDilate_ARGB8888(&sourceBuffer,
&destinationBuffer,
0,
0,
kernel,
9,
9,
kvImageCopyInPlace);
CGContextRef destinationContext =
CGBitmapContextCreateWithData(destinationData,
width,
height,
bitsPerComponent,
bytesPerRow,
colorSpaceRef,
kCGBitmapByteOrderDefault |
kCGImageAlphaPremultipliedFirst,
NULL,
NULL);
CGImageRelease(dilatedImageRef);
CGContextRelease(destinationContext);
CGContextRelease(sourceContext);
CGColorSpaceRelease(colorSpaceRef);
157
Chapter 10
Security
In this era of widespread surveillance, diminishing privacy, and
ubiquitous connectivity, security is no longer the pet subject of
paranoids—it’s something everyone would do well to understand.
Seeing things in code does a lot to help clarify these concepts, though.
For anyone prone to squeamishness when it comes to cryptographic
acronyms, just take a deep breath and read carefully. All of the really
hard stuff is taken care of by the Security framework.
Keychain Services
Keychain is the password management system on iOS & OS X. It
stores certificates and private keys, as well as passwords for websites,
servers, wireless networks, and other encrypted volumes.
159
Using the Security framework, applications can access the keychain
programmatically, allowing protected access to user data, without
constantly being prompted for authentication. ⁴⁴ ⁴⁵
160
String constants are used for nearly all of the keys, and many of the
values, which makes for a lot of __bridge id casts and
documentation lookups.
NSDictionary *query = @{
(__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrService: service,
(__bridge id)kSecAttrAccount: key,
(__bridge id)kSecMatchLimit: kSecMatchLimitOne,
};
CFTypeRef result;
OSStatus status =
SecItemCopyMatching((__bridge CFDictionaryRef)query, &result);
In this example, the query tells the keychain to find all generic
password items for the service com.example.app with the matching
username. kSecAttrService defines the scope of credentials, while
161
kSecAttrAccount acts as a unique identifier.e search option
kSecMatchLimitOne is passed to ensure that only the first match is
returned, if any.
162
SecItemUpdate((__bridge CFDictionaryRef)query,
(__bridge CFDictionaryRef)updatedAttributes);
} else {
NSMutableDictionary *attributes = [query mutableCopy];
attributes[(__bridge id)kSecValueData] = data;
attributes[(__bridge id)kSecAttrAccessible] =
(__bridge id)kSecAttrAccessibleAfterFirstUnlock;
Following from the previous example, arbitrary data is being set on the
item using the kSecValueData attribute key. e original query is
copied and merged into the additional attributes for SecItemAdd,
whereas on the updated attributes are passed for SecItemUpdate.
163
support non-ASCII characters or attachments. the S in S/MIME refers
to how these messages are sent and received securely.
Encoding a Message
NSData *data;
SecCertificateRef certificateRef;
CMSEncoderRef encoder;
CMSEncoderCreate(&encoder);
// Encrypt
CMSEncoderUpdateContent(encoder, [data bytes], [data length]);
CMSEncoderAddRecipients(encoder, certificateRef);
// Sign
SecIdentityRef identityRef = nil;
SecIdentityCreateWithCertificate(nil, certificateRef, &identityRef);
CMSEncoderUpdateContent(encoder, [data bytes], [data length]);
CMSEncoderAddSigners(encoder, identityRef);
CFRelease(identityRef);
164
CMSEncoderAddSignedAttributes(encoder, kCMSAttrSmimeCapabilities);
CFDataRef encryptedDataRef;
CMSEncoderCopyEncodedContent(encoder, &encryptedDataRef);
NSData *encryptedData = [NSData dataWithData:(__bridge NSData *) ←-
encryptedDataRef];
CFRelease(encoder);
Decoding a Message
CMSDecoderRef decoder;
CMSDecoderCreate(&decoder);
CFDataRef decryptedDataRef;
CMSDecoderCopyContent(decoder, &decryptedDataRef);
NSData *decryptedData = [NSData dataWithData:(__bridge NSData *) ←-
decryptedDataRef];
CFRelease(decryptedDataRef);
CFRelease(decoder);
165
e best way to understand certificates is to open one up and see
what’s inside:
Certificate:
Data:
Version: 1 (0x0)
Serial Number: 4919 (0x1337)
Signature Algorithm: md5WithRSAEncryption
Issuer: C=ZA, ST=Western Cape, L=Cape Town, O=Thawte Consulting cc,
OU=Certification Services Division,
CN=Thawte Server CA/emailAddress=server-certs@thawte.com
Validity
Not Before: Jun 2 18:00:00 2014 GMT
Not After : Jun 2 18:00:00 2015 GMT
Subject: C=US, ST=Oregon, L=Portland, O=Mattt Thompson,
OU=NSHipster, CN=nshipster.com/emailAddress=mattt@nshipster.com
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
RSA Public Key: (1024 bit)
Modulus (1024 bit):
cb:1c:00:aa:bb:89:a0:4c:26:cd:8c:4b:0b:13:88:...
Exponent: 65537 (0x10001)
Signature Algorithm: md5WithRSAEncryption
f5:5c:d6:a0:bf:39:95:fb:fa:ba:f5:f5:5a:d5:d9:f8:42:6b:...
166
Scanning through the plain text output, several pieces of information
come to the surface:
• Certificate issuer
• Validity period
• Certificate holder
• Public key of the owner
• Digital signature from the certification authority
SecTrustResultType result;
assert(SecTrustEvaluate(trust, &result) == errSecSuccess);
167
SecTrustEvaluate validates a certificate by verifying its signature,
along with the signatures of the certificates in its certificate chain—all
the way up to the anchor certificate. A certificate chain is created for
each specified policy, starting with the leaf certificate, and checking
each certificate in the chain until an invalid certificate is encountered,
no more certificates remain, or a certificate with a non-default trust
setting is found. ⁴⁶
168
Base64 encoding maps binary data into 8bit chunks, which are then
represented by 64 printable ASCII characters. It’s a popular choice
because of how it hits a sweet spot between efficiency and practicality.
It encodes data with just a 33% overhead, doesn’t rely on esoteric
characters, and has a relatively straightforward implementation. It’s
sort of the UTF-8 of binary-to-text encoding.
Base64 Encoding
SecTransformRef transform =
SecEncodeTransformCreate(kSecBase64Encoding, NULL);
SecTransformSetAttribute(transform,
kSecTransformInputAttributeName,
(__bridge CFDataRef)data,
169
NULL);
NSData *encodedData =
(__bridge_transfer NSData *)SecTransformExecute(transform, NULL);
CFRelease(transform);
Base64 Decoding
SecTransformRef transform =
SecEncodeTransformCreate(kSecBase64Decoding, NULL);
NSData *decodedData =
(__bridge_transfer NSData *)SecTransformExecute(transform, NULL);
CFRelease(transform);
Randomization Services
Cryptography is predicated on unpredictable, random values. Without
such a guarantee, it’s all just security theater.
170
on Unix systems that streams entropy based on the environmental
noise of the device.
NSMutableData *mutableData =
[NSMutableData dataWithLength:length];
OSStatus success =
SecRandomCopyBytes(kSecRandomDefault,
length,
mutableData.mutableBytes);
__Require_noErr(success, exit);
CommonCrypto
CommonCrypto offers convenient APIs to common cryptographic
operations, and is available on OS X 10.5+, and iOS 5.0+.
Digests
171
of a cryptographic hash function cannot practically be reversed to find
the input. ⁴⁹
uint8_t output[CC_SHA1_DIGEST_LENGTH];
CC_SHA1(data.bytes, data.length, output);
ere are many cryptographic hashing functions out there, each with
different security characteristics and use cases. It is the developer’s
responsibility to evaluate the requirements of their own product to
49 Small changes in the source oen cause chaotic differences in generated hash values.
172
determine the most appropriate security technologies to incorporate.
HMAC
A keyed-hash message authentication code (HMAC) uses
cryptographic hash function and secret key to generate a code that can
be used to simultaneously verify both the integrity and the
authenticity of a message. e strength of an HMAC is contingent on
the strength of the cryptographic hash function as well as the size of
the secret key. HMACs are oen used by web services to ensure that
protected calls are only accessible to verified users.
Symmetric Encryption
173
messages.
174
CCCryptorStatus status = CCKeyDerivationPBKDF(kCCPBKDF2, [password ←-
UTF8String], [password lengthOfBytesUsingEncoding:NSUTF8StringEncoding], ←-
[salt bytes], [salt length], kCCPRFHmacAlgSHA256, 1024, [ ←-
mutableDerivedKey mutableBytes], kCCKeySizeAES128);
return derivedKey;
}
Next, a function to encrypt the data can be created, which takes the
data to encrypt and password, and returns the generated salt and
initialization, as well as any error encountered in performing the
operation, as out arguments:
175
NSCParameterAssert(salt);
size_t numberOfBytesEncrypted = 0;
CCCryptorStatus status = CCCrypt(kCCEncrypt, kCCAlgorithmAES128, ←-
kCCOptionPKCS7Padding, [key bytes], [key length], [*initializationVector ←-
bytes], [data bytes], [data length], buffer, size, & ←-
numberOfBytesEncrypted);
return encryptedData;
176
}
Finally, to decrypt the data, do the same process in reverse, this time
passing the data and password along with the salt and initialization
vector generated from the encryption function:
size_t numberOfBytesDecrypted = 0;
CCCryptorStatus status = CCCrypt(kCCDecrypt, kCCAlgorithmAES128, ←-
kCCOptionPKCS7Padding, [key bytes], [key length], [initializationVector ←-
bytes], [data bytes], [data length], buffer, size, & ←-
numberOfBytesDecrypted);
177
return encryptedData;
}
178
Chapter 11
System
Configuration
System Configuration contains C APIs for determining hardware
configuration and network status.
Reachability
"Am I connected to the internet?" is a deceptively hard question to
answer.
From the user’s perspective, it should be pretty easy, right? Just type
http://apple.com into Safari, and see if anything loads. Nope.
180
Networking is so impossibly ad-hoc and idiosyncratic, that it’s
honestly a surprise that any of this works at all.
@import SystemConfiguration;
SCNetworkReachabilityRef networkReachability =
SCNetworkReachabilityCreateWithName(kCFAllocatorDefault,
[@"www.apple.com" UTF8String]);
SCNetworkReachabilityFlags flags =
SCNetworkReachabilityGetFlags(networkReachability, &flags);
CFRelease(networkReachability);
181
BOOL ignoresAdHocWiFi = NO;
SCNetworkReachabilityRef networkReachability =
SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault,
(struct sockaddr *)ipAddress);
182
(continued)
183
While the intricacies of network interfaces are interesting, they are
little more than an academic exercise for app developers, who, like
their users, would honestly prefer a YES or NO answer.
BOOL isReachable =
((flags & kSCNetworkReachabilityFlagsReachable) != 0);
BOOL needsConnection =
((flags & kSCNetworkReachabilityFlagsConnectionRequired) != 0);
BOOL canConnectionAutomatically =
(((flags & kSCNetworkReachabilityFlagsConnectionOnDemand ) != 0) ||
((flags & kSCNetworkReachabilityFlagsConnectionOnTraffic) != 0));
BOOL canConnectWithoutUserInteraction =
(canConnectionAutomatically &&
(flags & kSCNetworkReachabilityFlagsInterventionRequired) == 0);
BOOL isNetworkReachable =
(isReachable &&
(!needsConnection || canConnectWithoutUserInteraction));
if (isNetworkReachable == NO) {
// Not Reachable
}
184
#if TARGET_OS_IPHONE
else if ((flags & kSCNetworkReachabilityFlagsIsWWAN) != 0) {
// Reachable via WWAN
}
#endif
else {
// Reachable via WiFi
}
185
static void ReachabilityCallback(
SCNetworkReachabilityRef target,
SCNetworkConnectionFlags flags,
void *context)
{
// ...
}
SCNetworkReachabilitySetCallback(networkReachability,
ReachabilityCallback,
&context));
SCNetworkReachabilityScheduleWithRunLoop(reachability,
CFRunLoopGetMain(),
kCFRunLoopCommonModes));
186
SCNetworkReachabilityContext argument.
Querying
187
SCDynamicStoreContext context = { 0, NULL, NULL, NULL, NULL };
SCDynamicStoreRef store = SCDynamicStoreCreate(NULL, NULL, nil, &context);
NSArray *keys =
(__bridge_transfer NSArray *)
SCDynamicStoreCopyKeyList(store, CFSTR(".+"));
• Setup:/Network/Service/.../IPv4,
• Setup:/Network/Service/.../IPv6,
• … 100+ Items …
• State:/Network/Interface/p2p0/Link,
• State:/Network/Interface/lo0/IPv6,
• State:/IOKit/LowBatteryWarning,
• State:/Network/MulticastDNS,
• State:/Network/Global/Proxies,
• State:/Network/Interface/bridge0/Link
188
e semantics here are the same as what’s used for monitoring
network reachability.
• lo0
• gif0
• stf0
• en0
• en1
• en2
• bridge0
• p2p0
• utun0
189
Monitoring
SCDynamicStoreContext context =
{0, (__bridge void *)callback, NULL, NULL, NULL};
SCDynamicStoreRef store =
SCDynamicStoreCreate(NULL,
CFSTR("IPv4AddressMonitor"),
Callback,
190
&context);
NSString *ipv4 =
(NSString *)SCDynamicStoreKeyCreateNetworkServiceEntity(NULL,
kSCDynamicStoreDomainState,
kSCCompAnyRegex,
kSCEntNetIPv4);
191
Chapter 12
International
Components
for Unicode
Language is the essence of our very consciousness. It’s how we reason.
It’s why we can reason at all.
at we can communicate at all is a miracle. at ideas can travel from
one mind to another is beyond belief—and yet is so well-understood
that it barely registers a second thought. By communicating, we create
understanding among one another. We evoke empathy, and expand
the boundaries of our moral consideration to others. Our world gets
bigger.
193
At least in terms of Apple’s SDKs, the role Unicode plays is difficult to
over-state. is chapter will look at how one aspect of Unicode in
particular, the ICU, or International Components for Unicode, are
used in Foundation and Core Foundation.
Unicode
Unicode is the bedrock of international computing. It all started in
1987 with three individuals: Joe Becker from Xerox, and Lee Collins &
Mark Davis from Apple.
ICU
ICU, or International Components for Unicode, is the industry
standard for providing Unicode and globalization support in soware.
194
It was was created by IBM in the 90’s, and has been continuously
maintained since then.
CLDR
e CLDR, or Common Locale Data Repository, is what makes ICU
so compelling as a technology. Weighing in at over 400MB of JSON
data, the CLDR contains the authoritative encoding for all of
humanity’s cultural conventions.
195
e main directory of the CLDR contains a multitude of
subdirectories—one for each available locale. Within each locale
directory are a collection of files describing a particular aspect of that
locale:
Calendars
• Calendar
• Buddhist
• Chinese
• Coptic (a.k.a Alexandrian)
• Dangi
• Ethiopic
• Ethiopic (Amete Alem)
• Hebrew
• Indian
• Islamic
• Islamic (Civil)
196
• Islamic (Saudi Arabia)
• Islamic (Tabular)
• Islamic (Um al-Qura)
• Minguo (Republic of China)
• Japanese
• Persian
Each calendar is represented in a separate file for each locale. Each file
is several hundred lines long, and contains the months of the year and
days of the week in various levels of abbreviation, as well as the
formatting rules for dates and time intervals.
Characters
197
Character inventories are likely used by NSLinguisticTagger as a low
pass for evaluating the NSLinguisticTagSchemeLanguage of a string.
A string with characters beyond the orthographic inventory of a
language is unlikely to match. Conversely, the relative frequencies of
exemplar characters may be informative in deciding between two
likely candidates.
[A B C D E F G H I J K L M N O P Q R S T U V W X Y Z]
[A Á B C Č D Đ E É F G H I J K L M N Ŋ O P Q R S Š T Ŧ U V W X Y Z Ž Ø Æ Å Ä Ö]
52 Order indeed matters! Putting Å entries immediately aer A in Swedish would be as jarring and non-
sensical as clustering b, d, p, and q together in English because they happen to look the same.
198
Ellipsis rules specify how truncated text should be formatted, which,
depending on whether the truncation, is to be done in the initial,
medial, or final position, and whether there’s a word boundary at that
point:
Initial
Medial
Final
Word Initial
Word Medial
Word Final
Currencies
199
NSLocale uses this information to lookup the appropriate currency
code and symbol for a specified locale. is information is, in turn,
passed into NSNumberFormatter when presenting numbers with
NSNumberFormatterCurrencyStyle.
Date Fields
Delimiters
200
Quotation Delimiters
Languages
53 roughout the CLDR, the canonical identifier for a language is its ISO 639 code.
201
NSLocale *frLocale = [[NSLocale alloc] initWithLocaleIdentifier:@"fr_FR"];
NSLog(@"fr: %@",
[frLocale displayNameForKey:NSLocaleLanguageCode
value:@"fr"]);
NSLog(@"en: %@",
[frLocale displayNameForKey:NSLocaleLanguageCode
value:@"en"]);
NSLocaleLanguageCode
fr français
en anglais
Layout
Layout details for a locale are pretty simple: they specify the character
order (le-to-right or right-to-le) and line order (top-to-bottom or
bottom-to-top) for each language.
202
List Patterns
Lists are the heart, body, and soul of the accusative case. Rules for how
items in a list are delimited vary between locales and languages, and all
of those differences are enumerated in the CLDR.
203
Locale Display Names
204
Numbers
is file codifies all of the rules about number formatting for a locale:
number system, formats for decimal, scientific, percent, and currency
styles; preferred symbols for decimal (.), group (,), list (;), plus sign
(+), minus sign (-), percent sign (%), per mille sign (‰), exponential
(E), infinity (∞), and not a number (NaN); and patterns for number
ranges. ⁵⁵
POSIX
205
In Italian, the options are sì / si / s or no / n. In Russian, да / д or нет
/ н are the acceptable answers.
Since most iOS and OS X apps prefer GUIs to CLIs, this is not a
prescient matter, and as such, is not supported by the SDKs.
Scripts
For example, Latn is Latin script, Hira is Japanese Hiragana, and Brai
is Braille.
For each locale in the CLDR, there are localized names for each script.
NSLocale can tap into this by fetching the NSLocaleScriptCode key.
206
Territories
Any programmer who knows enough about time zones knows that
they want nothing to do with coding any of that themselves.
207
Timezones range from UTC−12 to UTC+14—spanning a total of 26
hours, which is weird, considering that a day only has 24. Some time
zones observe daylight savings time, while others don’t. And of the
ones that do, some of them use partial offsets, ±30 or 45 minutes in
some cases. Certain countries that sweep across a wide arc of
longitude, like the United States and Canada, are split up across a
number of different time zones. Other countries, like China, are
standardized to only a single timezone for an equivalent span,
meaning that by the time the sun rises in the western city of Kashgar at
8AM, it’s nearly mid-day in Beijing.
Transform Names
208
standardized conventions for transforms. BGN is used to transform
Russian Cyrillic into Latin, Jamo for Korean Hangul to Latin, and
Pinyin for Chinese to Latin. ere are transforms for CJK (Chinese,
Japanese, Korean) characters to go between half- and full-width
representations. ere is also the UNGEGN (United Nations Group of
Experts on Geographical Names) transform, which standardizes the
transliteration of toponyms, or place names.
Units
209
Variants
Even for Unicode, many of these are pretty obscure. A tag for
specifying the Late Middle French dialect based on Jean Nicot’s 1606
foundational lexicographic text resor de la langue francoyse? Yeah,
probably not going to be relevant for the next big social networking
app.
Supplemental
210
• Calendar Data: Epochs of calendar system eras, and whether the
calendar was based on the lunar or solar cycles.
• Calendar Preference Data: Ordered list of calendars supported in
each locale, sorted by preference.
• Character Fallbacks: Simpler alternatives for less well-supported
characters, such as (C) for "©" or 1/2 for "½", as well as currency
symbols, ligatures, and compound characters in Korean and Hebrew.
• Code Mappings: Top-level domain code mappings.
• Currency Data: Histories of currencies used by different countries,
including start and end date of usage.
• Day Periods: Various schemes for dividing up the hours of a day,
from simple: "a.m. / p.m.", to excessively precise: "wee hours / early
morning / morning / late morning / noon / mid day / aernoon /
evening / late evening / night".
• Gender Rules for Plurals: Rules for how to gender plurals. ⁵⁶
• Language Data: A list of languages and their respective scripts and
territories.
• Language Matching: Rules for how similar languages can be
interchanged, such as Kazakh and Russian.
• Likely Subtags: Given a BCP 47 language tag, the most likely subtags
to be associated.
56 In some languages, like Arabic, the addition of a single male-gendered word "taints" a collection of
female-gendered words, making the entire collection male-gendered. Other languages do not have such
a rule—or better yet, lack gendered nouns completely.
211
• Measurement Data: Which countries use metric vs. imperial units,
or A4 vs. US-Letter for paper sizes. ⁵⁷
• Metadata: ere be dragons.
• Metazones: Records establishing a regional hierarchy for localities.
• Numbering Systems: Inventory and rules for alternative numbering
systems, like Arabic, Roman, full-width CJK, and spelled out
English numerals.
• Ordinals: Rules or ordinal numbers for each language (i.e. 1st, 2nd,
3rd, etc.).
• Parent Locale: Establishes a directed graph relationship from
regions to parent locale.
• Plural Rules: Each language uses any of the 6 distinct Unicode
counting rules: zero, one, two, few, many, and other.
• Postal Code Data: Regular expressions describing the postal code
rules for each country.
• Primary Zones: e primary time zones.
• References: A bibliography of sources used to determine all of these
different rules. ⁵⁸
• Telephone Code Data: International dialing codes for each country.
• Territory Containment: Establishes the spatial relationships for the
geographic areas of territories.
57 e rule "001", the UN geographic region code for the world, is used as a catch-all, such that only the
exceptions need be defined.
58 A significant percentage of citations are for Wikipedia or the CIA World Factbook.
212
• Territory Information: A breakdown of regional statistics, including
population, GDP, literacy rate, and language populations.
• Time Data: tl;dr - {"_allowed":"H h", "_preferred":"h"}.
• Week Data: For each locale, the minimum number of days in a
week, and which day is the start of the week.
• Windows Zones: Legacy mapping of timezone information to
however Microso did things in the past.
Transforms
Originally designed to convert text in one script to another, ICU
transforms have evolved into a powerful tool for working with
Unicode text, with case and width conversion, composite character
sequence normalization, and removal of accents and diacritics.
213
An ICU transform consists of 1 or more semicolon-delimited
mappings. Each mapping is either unidirectional or bidirectional
between the le-hand side and right-hand side values.
(c) <> ©;
214
ICU provides a number of built-in transliterations for common and
useful operations, which can be combined with other rules to
accomplish virtually any automated text transformation task.
Text Processing
ICU has transliterations for basic text processing tasks like changing
case or normalization:
Text Processing Transforms
215
Accent and Diacritic Stripping
Normalization Transforms
Every code point in the Unicode standard has an official name, which
can be retrieved using an ICU transform:
216
Unicode Symbol Naming Transforms
Script Transliteration
217
• Indic <-> Indic
• Hiragana <-> Katakana
• Simplified Chinese (Hans) <-> Traditional Chinese (Hant)
218
(continued)
219
Chapter 13
Dictionary
Services
ough widely usurped of their "go-to reference" status by the
Internet, dictionaries and word lists serve an important role behind
the scenes. A vast array of functionality relies on this information,
ranging from spell check, grammar check, and auto-correct to
auto-summarization and semantic analysis.
Unix
Nearly all Unix distributions include a small collection of
newline-delimited lists of words. On OS X, these can be found at /
usr/share/dict:
$ ls /usr/share/dict
README
connectives
propernames
web2
web2a
words@ -> web2
221
$ wc /usr/share/dict/words
235886 235886 2493109
Skimming with head shows what fun lies herein. Such excitement is
rarely so palpable as it is among words beginning with "a":
$ head /usr/share/dict/words
A
a
aa
aal
aalii
aam
Aani
aardvark
aardwolf
Aaron
OS X builds upon this with its own system dictionaries. Never one to
disappoint, the operating system’s penchant for extending Unix
222
functionality through strategically placed bundles and plist files is in
full force with how dictionaries are distributed.
OS X
e OS X analog to /usr/share/dict can be found in /Library/
Dictionaries. A quick peek into the directory demonstrates one
immediate improvement over Unix, by acknowledging the existence of
languages other than English:
$ ls /Library/Dictionaries/
Apple Dictionary.dictionary/
Diccionario General de la Lengua Española Vox.dictionary/
Duden Dictionary Data Set I.dictionary/
Dutch.dictionary/
Italian.dictionary/
Korean - English.dictionary/
Korean.dictionary/
Multidictionnaire de la langue francaise.dictionary/
New Oxford American Dictionary.dictionary/
Oxford American Writer's Thesaurus.dictionary/
Oxford Dictionary of English.dictionary/
Oxford Thesaurus of English.dictionary/
Sanseido Super Daijirin.dictionary/
Sanseido The WISDOM English-Japanese Japanese-English Dictionary.dictionary/
Simplified Chinese - English.dictionary/
The Standard Dictionary of Contemporary Chinese.dictionary/
223
OS X ships with dictionaries in Chinese, English, French, Dutch,
Italian, Japanese, and Korean, as well as an English thesaurus and a
special dictionary for Apple-specific terminology.
Body.data
DefaultStyle.css
EntryID.data
EntryID.index
Images/
Info.plist
KeyText.data
KeyText.index
Resources/
_CodeSignature/
version.plist
224
• Preference to switch between US English Diacritical Pronunciation
and International Phonetic Alphabet (IPA)
• Manifest & signature for dictionary contents
#import <CoreServices/CoreServices.h>
Well, they all disappeared into that first NULL argument. One might
expect to provide a DCSCopyTextDefinition type here, as prescribed
225
by the function definition. However, there are no public functions to
construct or copy such a type, making NULL the only available option.
e documentation is as clear as it is stern:
Now, there’s nothing programmers love to hate to love more than the
practice of exploiting loopholes to side-step Apple platform
restrictions. Behold: an entirely error-prone approach to getting, say,
thesaurus results instead of the first definition available in the standard
dictionary:
226
[userDefaults setPersistentDomain:dictionaryPreferences forName:@"com.apple. ←-
DictionaryServices"];
{
NSString *word = @"apple";
NSString *definition = (__bridge_transfer NSString *)DCSCopyTextDefinition( ←-
NULL, (__bridge CFStringRef)word, CFRangeMake(0, [word length]));
NSLog(@"%@", definition);
}
dictionaryPreferences[@"DCSActiveDictionaries"] = activeDictionaries;
[userDefaults setPersistentDomain:dictionaryPreferences forName:@"com.apple. ←-
DictionaryServices"];
Private APIs
Not publicly exposed, but still available through Core Services are a
number of functions that cut closer to the dictionary services that we
crave:
227
extern CFStringRef DCSDictionaryGetShortName(DCSDictionaryRef dictionary);
extern DCSDictionaryRef DCSDictionaryCreate(CFURLRef url);
extern CFStringRef DCSDictionaryGetName(DCSDictionaryRef dictionary);
extern CFArrayRef DCSCopyRecordsForSearchString(DCSDictionaryRef dictionary, ←-
CFStringRef string, void *, void *);
NSMapTable *availableDictionariesKeyedByName =
[NSMapTable mapTableWithKeyOptions:NSPointerFunctionsCopyIn
valueOptions:NSPointerFunctionsObjectPointerPersonality];
228
NSString *name = (__bridge NSString *)DCSDictionaryGetName((__bridge ←-
DCSDictionaryRef)dictionary);
[availableDictionariesKeyedByName setObject:dictionary forKey:name];
}
229
for (id record in records) {
NSString *headword = (__bridge NSString *)DCSRecordGetHeadword(( ←-
__bridge CFTypeRef)record);
if (headword) {
NSString *definition = (__bridge_transfer NSString*) ←-
DCSCopyTextDefinition((__bridge DCSDictionaryRef)dictionary, (__bridge ←-
CFStringRef)headword, CFRangeMake(0, [headword length]));
NSLog(@"%@: %@", name, definition);
iOS
iOS development is a decidedly more by-the-books affair, so
attempting to reverse-engineer the platform would be little more than
an academic exercise. Fortunately, a good chunk of functionality is
230
available (as of iOS 5) through the obscure UIKit class
UIReferenceLibraryViewController.
UIReferenceLibraryViewController is similar to an
MFMessageComposeViewController, in that provides a
minimally-configurable view controller around system functionality,
intended to be presented modally.
UIReferenceLibraryViewController *referenceLibraryViewController =
[[UIReferenceLibraryViewController alloc] initWithTerm:@"apple"];
[viewController presentViewController:referenceLibraryViewController
animated:YES
completion:nil];
is is the same behavior that one might encounter by tapping the
"Define" UIMenuItem on a highlighted word in a UITextView.
231
[UIReferenceLibraryViewController dictionaryHasDefinitionForTerm:@"apple"];
232
Chapter 14
Xcode
Toolchain
ough we all come from different backgrounds, with different
perspectives that shape our experiences; though we do what we do
with various motivations, with beliefs and biases and opinions that sets
us apart from one another, there is one thing that brings us together:
We all have to use Xcode. One way or another. For better or worse.
But of course, Xcode is not really just one application. Underneath the
GUI lies a confederation of applications and command line tools,
which are just as central to a developer’s workflow as the editor itself.
Xcode Tools
xcode-select
234
As of Mavericks, getting started as a developer on the Mac takes a
single command:
xcode-select --install
is will install the Command Line Tools, which are necessary for
compiling Objective-C code.
xcrun
xcrun is the fundamental Xcode command line tool. With it, all other
tools are invoked.
$ xcrun xcodebuild
235
Because xcrun executes in the context of the active Xcode version (as
set by xcode-select), it is easy to have multiple versions of the Xcode
toolchain co-exist on a single system. ⁶²
Using xcrun in scripts and other external tools has the advantage of
ensuring consistency across different environments. For example,
Xcode ships with a custom distribution of Git. By invoking $ xcrun
git rather than just $ git, a build system can guarantee that the
correct distribution is run.
xcodebuild
$ xcodebuild
236
$ xcodebuild -workspace NSHipster.xcworkspace \
-scheme "NSHipster"
237
genstrings
$ genstrings -a \
/path/to/source/files/*.m
fr.lproj/Localizable.strings
63 https://developer.apple.com/library/mac/documentation/general/conceptual/devpedia-cocoacore/-
Internationalization.html
238
ibtool
$ ibtool --generate-strings-file \
Localizable.strings \
en.lpoj/Interface.xib
iprofiler
239
$ iprofiler -allocations \
-leaks \
-T 15s \
-o perf \
-a NSHipster
xed
$ xed NSHipster.xcworkspace
By passing the -w flag, xed will wait until all opened windows are
closed. is is useful for scripting user interactions, such as prompting
a user to edit a file and continuing once finished.
240
agvtool
$ agvtool what-version
$ agvtool next-version
Other Tools
In addition to the aforementioned Xcode tools, there are a score of
other executables that can be invoked with xcrun:
65 Integrate agvtool into a build system to automatically track consecutive builds.
241
Compilation & Assembly
Processors
Libraries
242
• otool: Displays specified parts of object files or libraries.
• ar: Creates and maintains library archives.
• libtool: Creates a library for use with the link editor, ld.
• ranlib: Updates the table of contents of archive libraries.
• mksdk: Makes and updates SDKs.
• lorder: Lists dependencies for object files.
Scripting
Packages
243
Documentation
Core Data
244
Chapter 15
Third-Party
Tools
appledoc
ere’s an adage among Cocoa developers that Objective-C’s verbosity
lends itself to self-documenting code. Between
longMethodNamesWithNamedParameters: and the explicit typing of
those parameters, Objective-C methods don’t leave much to the
imagination.
246
• @param [param] [Description]: Describes what value should be
passed or this parameter
• @return [Description]: Describes the return value of a method
• @see [selector]: Provide "see also" reference to related item
• @discussion [Discussion]: Provide additional background
• @warning [description]: Call out exceptional or potentially
dangerous behavior
67 http://brew.sh
247
is will generate and install an Xcode .docset file from the
documentation in the headers found within the target directory.
$ appledoc --help
xctool
xctool is a drop-in replacement for xcodebuild, the utility
underlying Xcode.app itself.
248
• plain:like pretty, but with with no colors or Unicode.
• phabricator: outputs a JSON array of build/test results which can
be fed into the Phabricator code-review tool.
• junit: produces a JUnit / xUnit compatible XML -ile with test
results.
• json-stream: a stream of build/test events as JSON dictionaries,
one -er line (example output).
• json-compilation-database: outputs a JSON Compilation
Database of build events which can be used by Clang Tooling based
tools, e.g. OCLint.
For this reason alone, xctool has great implications for the emerging
discipline of continuous integration testing within the Objective-C
community.
68 xcodebuild can’t discern which targets in your scheme are test targets, let alone run them in the simu-
lator
249
OCLint
$ oclint-json-compilation-database
69 http://caskroom.io
250
xcpretty
xcpretty is similar to xctool in that it improves on xcodebuild build
output, but instead of attempting to replace xcodebuild, xcpretty
augments and improves it.
251
xcpretty can be installed using RubyGems ⁷⁰, which is installed by
default on OS X:
Nomad
Nomad is a collection of world-class command-line utilities for iOS
and OS X development. It automates the common administrative
tasks, so that developers can focus on building and shipping soware.
Cupertino ⁷¹
252
Aside from the entire process being a nightmare from start to finish,
many of the operations require interacting through a web interface.
is not only requires a lot of extra clicking, but makes it very difficult
to automate.
$ ios devices:list
+------------------------------+---------------------------------------+
| Listing 2 devices. You can register 98 additional devices. |
+---------------------------+------------------------------------------+
| Device Name | Device Identifier |
+---------------------------+------------------------------------------+
| Johnny Appleseed iPad | 0123456789012345678901234567890123abcdef |
| Johnny Appleseed iPhone | abcdef0123456789012345678901234567890123 |
+---------------------------+------------------------------------------+
Shenzhen ⁷⁴
73 In lieu of an actual API for the Apple Provisioning Profile, Cupertino accomplishes this by mechanizing
the browser actions and scraping the results. It’s a messy business, but it gets the job done.
74 Named for the Chinese city famous for its role as the center of manufacturing for a majority of consumer
electronics, including iPhones and iPads.
253
One thing web developers have on their iOS counterparts is the ability
to continuously deploy code within seconds, as opposed to waiting a
few days for Cupertino to approve (and sometimes reject!) an update.
$ cd /path/to/iOS Project/
$ ipa build
$ ipa distribute:sftp --host HOST -u USER -p PASSWORD -P FTP_PATH
Houston ⁷⁶
254
$ apn push "<token>" \
-c /path/to/apple_push_notification.pem \
-m "Hello from the command line!"
Venice ⁷⁷
In-app purchases have, for better or worse, become the most profitable
business model for app developers. With so much on the line, ensuring
the validity of these purchases is paramount to one’s livelihood.
+-----------------------------+-------------------------------+
| Receipt |
+-----------------------------+-------------------------------+
| app_item_id | |
| bid | com.foo.bar |
| bvrs | 20120427 |
77 Venice is named for Venice, Italy—or more specifically, Shakespeare’s e Merchant of Venice.
255
| original_purchase_date | Sun, 01 Jan 2013 12:00:00 GMT |
| original_transaction_id | 1000000000000001 |
| product_id | com.example.product |
| purchase_date | Sun, 01 Jan 2013 12:00:00 GMT |
| quantity | 1 |
| transaction_id | 1000000000000001 |
| version_external_identifier | |
+-----------------------------+-------------------------------+
Dubai ⁷⁸
256
$ pk generate Example.pass -T boarding-pass
257
Chapter 16
CocoaPods
Civilization is built on infrastructure: roads, bridges, canals, sewers,
pipes, wires, fiber. When well thought-out and implemented,
infrastructure is a multiplying force that drives growth and
development. But when such formative structures are absent or ad
hoc, it feels as if progress is made in spite of the situation.
A Look Back
For the first twenty or so years of its existence, Objective-C was not a
widely known language—NeXT and later OS X were marginal
platforms, with a comparatively small user base and developer
community. Like any community, there were local user groups and
259
mailing lists and websites, but open source collaboration was not a
widespread phenomenon. Granted, Open Source was only just
starting to pick up steam at that time, but there was no contemporary
Objective-C equivalent to, for example, CPAN, the Comprehensive
Perl Archive Network. Everyone took SDKs from Redwood City and
Cupertino as far as they could, (maybe sprinkling in some code
salvaged from a forum thread), but ultimately rolling their own
solutions to pretty much everything else.
Around this same time, GitHub had just launched, and was starting to
change the way we thought about open source by enabling a new
distributed, collaborative workflow.
In those early years of iPhone OS, we started to see the first massively
adopted open source projects, like ASIHTTPRequest and Facebook’s
260
ree20. ese first libraries and frameworks were built to fill in the
gaps of app development on iPhone OS 2.0 and 3.0, and although
largely made obsolete by subsequent OS releases or other projects,
they demonstrated a significant break from the tradition of "every
developer for themselves".
Another approach was to use Git submodules, and include the source
directly in the project. But getting everything working, with linked
frameworks and build flags configured, was not great
261
either—especially at a time when the body of code was split between
ARC and non-ARC.
Enter CocoaPods
CocoaPods was created by Eloy Durán on August 12, 2011.
Since its initial proof-of-concept, the project has grown to include over
a dozen core contributors along with over 100 additional contributors.
ere are thousands of open source projects available for anyone to
add to their project.
262
A significant portion of these prolific contributions from the open
source community for Objective-C has been directly enabled and
encouraged by increased ownership around tooling. Everyone
involved should be commended for their hard work and dedication.
Using CocoaPods
CocoaPods is easy to get started with both as a consumer and a library
author. It should only take a few minutes to get set up.
Installing CocoaPods
263
Now you should have the pod command available in the terminal.
Managing Dependencies
A dependency manager resolves a list of soware requirements into a
list of specific tags to download and integrate into a project.
Podfile
264
$ pod init
Podfile
target "AppName" do
end
Once all of the dependencies have been specified, they can be installed
with:
265
$ pod install
CocoaPods will create a new Xcode project that creates static library
targets for each dependency, and then links them all together into a
libPods.a target. is static library becomes a dependency for your
original application target. An xcworkspace file is created, and should
be used from that point onward. is allows the original xcodeproj
file to remain unchanged.
$ pod update
266
your project.
Invoking $ pod try with the name of a project in the public specs
database opens up any example projects for the library:
Creating a CocoaPod
Being the de facto standard for Objective-C soware distribution,
CocoaPods is pretty much a requirement for open source projects with
the intention of being used by others
Yes, it raises the barrier to entry for sharing your work, but the effort is
minimal, and more than justifies itself. Taking a couple minutes to
create a .podspec file saves every user at least that much time
attempting to integrate it into their own projects.
Specification
A .podspec file is the atomic unit of a CocoaPods dependency. It
specifies the name, version, license, and source files for a library, along
267
with other metadata.
NSHipsterKit.podspec
Pod::Spec.new do |s|
s.name = 'NSHipsterKit'
s.version = '1.0.0'
s.license = 'MIT'
s.summary = "A pretty obscure library.
You've probably never heard of it."
s.homepage = 'http://nshipster.com'
s.authors = { 'Mattt Thompson' =>
'mattt@nshipster.com' }
s.social_media_url = "https://twitter.com/mattt"
s.source = { :git => 'https://github.com/nshipster/NSHipsterKit.git', :tag ←-
=> '1.0.0' }
s.source_files = 'NSHipsterKit'
end
Podfile
268
pod 'Z', :path => 'path/to/directory/with/podspec'
Publishing a CocoaPod
Although it worked brilliantly at first, the process of using Pull
Requests on GitHub for managing new pods became something of a
chore, both for library authors and spec organizers. Sometimes
podspecs would be submitted without passing $ pod lint, causing
the specs repo build to break. Other times, rogue commits from
people other than the original library author would break things
unexpectedly.
To get started, you must first register your machine with the Trunk
service. is is easy enough, just specify your email address (the one
you use for committing library code) along with your name.
269
Now, all it takes to publish your code to CocoaPods is a single
command. e same command works for creating a new library or
adding a new version to an existing one:
A Look Forward
CocoaPods exemplifies the compounding effect of infrastructure on a
community. In a few short years, the Objective-C community has
turned into something that we can feel proud to be part of.
CocoaPods is a good thing for Objective-C. And it’s only getting better.
270
About NSHipster
NSHipster is a journal of the overlooked bits in Swift, Objective-C, and
Cocoa. Updated weekly.
Colophon
The text is set in Minion Pro, by Robert Slimbach, with code excerpts set in
Source Code Pro, by Paul D. Hunt.
His work has taken him across the United States and around the world, to
speak at conferences and meetups about topics in Swift, Objective-C, Ruby,
Javascript, web development, design, linguistics, and philosophy.