Swift Regex Deep Dive
iOS MacOur introductory guide to Swift Regex. Learn regular expressions in Swift including RegexBuilder examples and strongly-typed captures.
Update : Official Documentation now
Looks like the cat is out of the bag, the new Objective-C literals have landed into the clang trunk. This change introduces additional Objective-C Literals.
What is an “Objective-C Literal”? It’s a chunk of code that references a specific Cocoa object, creating it if necessary. Objective-C has had literals for NSStrings
since the beginning of time:
NSString *greeble = @"Bork Bork Bork";
The syntax is an at-sign, the indication that Objective-C magic is approaching, in front of a C-string literal. This makes an NSString
that can be used just like any other NSString
. In fact, greeble
points to an actual NSString
, it’s not any kind of second-class citizen. The new literals for NSNumbers
, NSArrays
, and NSDictionaries
take a similar approach
You can’t put scalar types like int
s or float
s directly into Foundation collections. You have to “box” them first by wrapping them in an NSNumber
object. @-prefixing a numerical literal value will automatically create an NSNumber
object that wraps (or boxes) that number:
NSArray *array =
[NSArray arrayWithObjects: @0, @23, @3.1415, @25.101, nil];
NSLog (@"array: %@", array);
Will print out
array: (
0,
23,
"3.1415",
"25.101"
)
You can see that integers and floats are wrapped. You can also wrap the Objective-C symbols YES
and NO
:
NSLog (@"YES: %@ NO: %@", @YES, @NO);
And it prints out what you’d expect:
YES: 1 NO: 0
You cannot wrap NULL
, which gives an "error: unexpected '@' in program"
error instead. It would be nice if @NULL
would be equivalent to [NSNull null]
, so dupe Radar 10887680 if you agree.
Even with this new syntax, there is no “autoboxing” in Cocoa. Autoboxing in languages like Java means that primitive scalar types get automatically converted to NSNumber
equivalents and back. The Objective-C number literals are just syntactic sugar for making NSNumbers
. If you get a numeric value from somewhere other than directly from your source code you’ll need to box it up yourself before you do something with it, like putting it into a Cocoa collection, sending it through Key-Value Coding, or passing it to a method or function that’s expecting an NSNumber
.
Most scripting languages have ways of creating arrays directly in source code or via the language’s REPL (run-evaluate-print-loop), usually by specifying the array contents in square brackets. Objective-C can now do that with similar syntax, square-brackets preceded by an at-sign:
NSArray *gronk = @[ @"hi", @"bork", @23, @YES ];
And printing it yields contents of what you expect:
(
hi,
bork,
23,
1
)
Which is more concise than the usual NSArray
creation methods.
Notice you don’t need a trailing nil
here. The compiler knows how many objects you’re using to create the array so there’s no need for a sentinel value at the end. nil
is not an object, so the compiler will happily give you an error if you try to put one there anyway.
You’re not limited to literal objects inside of the array:
NSString *thing1 = [NSString stringWithFormat: @"thing1"];
NSString *thing2 = [NSMutableString stringWithString: @"thing2"];
NSOperation *op = [NSBlockOperation blockOperationWithBlock: ^{ }];
NSArray *stuff = @[ thing1, thing2, op ];
NSLog (@"%@", stuff);
Prints out
(
thing1,
thing2,
"<NSBlockOperation: 0x7fb60ae009f0>"
)
Under the hood it’s just calling -arrayWithObjects:count:
to create the array, so you will get an exception if you try to sneak in a nil
value through an object pointer.
There is new syntax for accessing the contents of an array. Take an NSArray
or NSMutableArray
pointer and subscript it with square brackets:
// Make a new array
NSArray *gronk = @[ @"hi", @"bork", @23, @YES ];
NSLog (@"gronk 0: %@", gronk[0]);
NSLog (@"gronk 2: %@", gronk[2]);
NSLog (@"gronk 37: %@", gronk[37]);
This prints out two array elements:
gronk 0: hi
gronk 2: 23
And generates a range exception for the third one:
*** Terminating app due to uncaught exception 'NSRangeException', reason: '***
-[__NSArrayI objectAtIndex:]: index 37 beyond bounds [0 .. 3]'
You can chain the indexing as well if you have arrays inside of arrays:
NSArray *nesting = @[ @"hi", @[ @"ohai" ] ];
NSLog (@"nesting: %@", nesting[1][0]);
This gets the second element of the nesting array, which is an array with a single element. And then it gets the first element of that interior array:
nesting: ohai
This new indexing syntax also works with NSOrderedSet
.
The indexing works for mutable arrays on the other side of the assignment operator. You can use it to change existing arrays:
NSMutableArray *mutt =
[NSMutableArray arrayWithArray: @[ @"A", @"B", @"C"] ];
NSLog (@"before %@", mutt);
Our original array is:
before (
A,
B,
C
)

And then make some changes:
mutt[0] = @"aaa";
mutt[3] = @"d";
mutt[4] = @"e";
[mutt insertObject: @"f" atIndex: 5];
NSLog (@"after %@", mutt);
Which prints out:
after (
aaa,
B,
C,
d,
e,
f
)
You can see that some elements (d-f
) were inserted at the end of the array. You can insert new objects this way by setting the value one past the very last element, just like you saw with -insertObject:atIndex:
NSDictionary
is the final object to get new literal syntax. Instead of wrapping a set of objects with square brackets, you wrap them with braces preceded by an at-sign. The key is listed first, then a colon, then the value.
NSDictionary *splunge = @{ @"hi" : @"bork", @"greeble" : @"bork" };
NSLog (@"splunge %@", splunge);
This creates a new dictionary with the keys hi
and greeble
, each with the value bork
:
splunge {
greeble = bork;
hi = bork;
}
The key/value order is reverse from what NSDictionary
’s -dictionaryWithObjectsAndKeys:
uses, so be careful if you decide to mechanically update existing code to use the new syntax.
You can of course nest dictionaries arbitrarily deep, and include literal arrays as values.
It’s easy to imagine that this can lead to concise creation of dictionaries. Suppose you had an NSView
prints some text in its -drawRect:
with various text attributes. Here is the old-style of defining the attributes:
NSDictionary *attributes = [NSDictionary dictionaryWithObjectsAndKeys:
[NSFont userFontOfSize: 150.0], NSFontAttributeName,
[NSNumber numberWithFloat: 3.0], NSStrokeWidthAttributeName,
[NSColor blueColor], NSStrokeColorAttributeName,
[NSNumber numberWithInt: 10], NSKernAttributeName,
[NSNumber numberWithFloat: 0.5], NSObliquenessAttributeName,
nil];
and the new-style, in with the rest of the drawing code:
- (void) drawRect: (CGRect) rect {
NSString *text = @"Ponies!";
NSDictionary *attributes = @{
NSFontAttributeName : [NSFont userFontOfSize: 150.0],
NSStrokeWidthAttributeName : @3.0,
NSStrokeColorAttributeName : [NSColor blueColor],
NSKernAttributeName : @10,
NSObliquenessAttributeName : @0.5
};
[text drawAtPoint: CGPointMake(10.0, 100.0) withAttributes: attributes];
} // drawRect
And the resulting view drawing:
NSDictionary
also grows the same accessor syntax that arrays have: square brackets after a dictionary pointer lets you access stuff. Instead of passing in a scalar index, you pass in an object to be used as the lookup key.
Given our splunge
dictionary from earlier:
NSDictionary *splunge = @{ @"hi" : @"bork", @"greeble" : @"bork" };
You can dig into it with the square brackets:
NSLog (@"%@", splunge[@"greeble"]);
NSLog (@"%@", splunge[@"ACK"]);
which prints out:
bork
(null)
The value for the key greeble
is "bork
”, and there is no value for the key ACK
, so nil
(printed out (null)
) is returned.
You can of course nest dictionaries and access them:
NSDictionary *nest = @{ @"hi" : @"ohai",
@"ichc" : @{ @"oop" : @"ack"} };
NSLog (@"nest: %@", nest);
NSLog (@"noost: %@", nest[@"ichc"][@"oop"]);
NSLog (@"nist: %@", nest[@"ichc.oop"]); // try a key path
which prints out:
nest: {
hi = ohai;
ichc = {
oop = ack;
};
}
noost: ack
nist: (null)
You can see the nested dictionaries in the first lines of output. Then there are stacked square brackets that first grab the ichc
dictionary, and then the next square brackets get the oop
key out of that.
The key used for dictionary lookup is not a key path. Key-Value Coding is not happening under the hood, which you can by see it returning nil
when trying to treat the two keys used in the square brackets as a key path.
Like with arrays, you can change mutable dictionaries using the array syntax.
NSDictionary *flarn = @{ @"name" : @"Hoover", @"age" : @42 };
NSMutableDictionary *mutaflarn = [flarn mutableCopy];
mutaflarn[@"name"] = @"Dr. Manhattan";
NSLog (@"mutaflarn: %@", mutaflarn);
You can see from the output that the name
has changed:
mutaflarn: {
age = 42;
name = "Dr. Manhattan";
}
There are some limitations with Objective-C literals. I don’t know if these are permanent limitations, or just an in-progress implementation.
Just like with NSString
s, collection objects made via literal arrays and dictionaries are immutable. You cannot create mutable collections with the literal syntax, so you will have to make a mutable copy after making the immutable dictionary or array.
You also cannot have static initializers like you can with @"NSStrings"
. For example, check out the start of this source file:
#import <Foundation/Foundation.h>
NSString *thing = @"thing 1";
NSArray *array = @[ @"thing 2" ];
@implementation BNRGroovyObject
...
The static NSString
initializer works as always, but the array initializer gives an error because the array is being created outside of a function or a method body:
literal.m:4:18: error: initializer element is not a compile-time constant
NSArray *array = @[ @"thing 2" ];
^~~~~~~~~~~~~~~
Of the collection classes, only arrays and dictionaries can be created literally. Literal NSSets
aren’t supported.
Finally, you can’t use negative indexes for arrays. A typical scripting language trick of using array[-1]
to access the last element throws an out-of-range exception with a huge index, which is understandable because a negative signed-value being treated as an unsigned-value by NSArray
’s -objectAtIndex:
.
Be sure to come back Monday when we’ll resume our tour of Objective-C literalness.
Our introductory guide to Swift Regex. Learn regular expressions in Swift including RegexBuilder examples and strongly-typed captures.
The Combine framework in Swift is a powerful declarative API for the asynchronous processing of values over time. It takes full advantage of Swift...
SwiftUI has changed a great many things about how developers create applications for iOS, and not just in the way we lay out our...