Skip to content

Commit

Permalink
Updating article content and formatting
Browse files Browse the repository at this point in the history
  • Loading branch information
mattt committed Jan 6, 2013
1 parent 9af4d7f commit 2cbb541
Show file tree
Hide file tree
Showing 24 changed files with 568 additions and 462 deletions.
Empty file removed .gitkeep
Empty file.
1 change: 0 additions & 1 deletion 2012-07-07-nsindexset.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,3 @@ If you look hard enough, you may start to find aspects of your data model that c
- Filtering a list of items by a set of composable conditions? Ditch the `NSPredicate`; instead, cache the indexes of objects that fulfill each condition, and then get the union of those indexes as conditions are added and removed.

Overall, `NSIndex` is a solid class. A fair bit nerdier than its collection class siblings, but it has its place. It's at least a good reminder the useful things that you find by paying attention to what Foundation uses in its own APIs.

69 changes: 38 additions & 31 deletions 2012-07-24-nssortdescriptor.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,49 +57,56 @@ Let's say that we have a `Person` object that has properties for `firstName` & `
</tbody>
</table>

@interface Person : NSObject
@property NSString *firstName;
@property NSString *lastName;
@property NSNumber *age;
@end
~~~{objective-c}
@interface Person : NSObject
@property NSString *firstName;
@property NSString *lastName;
@property NSNumber *age;
@end
@implementation Person
@implementation Person
- (NSString *)description {
return [NSString stringWithFormat:@"%@ %@", self.firstName, self.lastName];
}
- (NSString *)description {
return [NSString stringWithFormat:@"%@ %@", self.firstName, self.lastName];
}
@end
@end
#pragma mark -
#pragma mark -
NSArray *firstNames = @[ @"Alice", @"Bob", @"Charlie", @"Quentin" ];
NSArray *lastNames = @[ @"Smith", @"Jones", @"Smith", @"Alberts" ];
NSArray *ages = @[ @24, @27, @33, @31 ];
NSArray *firstNames = @[ @"Alice", @"Bob", @"Charlie", @"Quentin" ];
NSArray *lastNames = @[ @"Smith", @"Jones", @"Smith", @"Alberts" ];
NSArray *ages = @[ @24, @27, @33, @31 ];
NSMutableArray *people = [NSMutableArray array];
[firstNames enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
Person *person = [[Person alloc] init];
person.firstName = [firstNames objectAtIndex:idx];
person.lastName = [lastNames objectAtIndex:idx];
person.age = [ages objectAtIndex:idx];
[people addObject:person];
}];
NSMutableArray *people = [NSMutableArray array];
[firstNames enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
Person *person = [[Person alloc] init];
person.firstName = [firstNames objectAtIndex:idx];
person.lastName = [lastNames objectAtIndex:idx];
person.age = [ages objectAtIndex:idx];
[people addObject:person];
}];
NSSortDescriptor *firstNameSortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"firstName" ascending:YES selector:@selector(localizedStandardCompare:)];
NSSortDescriptor *lastNameSortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"lastName" ascending:YES selector:@selector(localizedStandardCompare:)];
NSSortDescriptor *ageSortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"age" ascending:NO];
NSSortDescriptor *firstNameSortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"firstName"
ascending:YES
selector:@selector(localizedStandardCompare:)];
NSSortDescriptor *lastNameSortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"lastName"
ascending:YES
selector:@selector(localizedStandardCompare:)];
NSSortDescriptor *ageSortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"age"
ascending:NO];
NSLog(@"By age: %@", [people sortedArrayUsingDescriptors:@[ ageSortDescriptor ]]);
// "Charlie Smith", "Quentin Alberts", "Bob Jones", "Alice Smith"
NSLog(@"By age: %@", [people sortedArrayUsingDescriptors:@[ ageSortDescriptor ]]);
// "Charlie Smith", "Quentin Alberts", "Bob Jones", "Alice Smith"
NSLog(@"By first name: %@", [people sortedArrayUsingDescriptors:@[ firstNameSortDescriptor ]]);
// "Alice Smith", "Bob Jones", "Charlie Smith", "Quentin Alberts"
NSLog(@"By first name: %@", [people sortedArrayUsingDescriptors:@[ firstNameSortDescriptor ]]);
// "Alice Smith", "Bob Jones", "Charlie Smith", "Quentin Alberts"
NSLog(@"By last name, first name: %@", [people sortedArrayUsingDescriptors:@[ lastNameSortDescriptor, firstNameSortDescriptor ]]);
// "Quentin Alberts", "Bob Jones", "Alice Smith", "Charlie Smith"
NSLog(@"By last name, first name: %@", [people sortedArrayUsingDescriptors:@[ lastNameSortDescriptor, firstNameSortDescriptor ]]);
// "Quentin Alberts", "Bob Jones", "Alice Smith", "Charlie Smith"
~~~

Sort descriptors can be found throughout Foundation as well as most other frameworks--including a prominent role in Core Data. Anytime your own classes need to define sort ordering, follow the convention of specifying a `sortDescriptors` parameter as appropriate.

Expand Down
50 changes: 25 additions & 25 deletions 2012-07-31-nsdatecomponents.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ Whereas dates represent a particular moment in time, date component depend on wh

The most common use of `NSDateComponents` is to determine information about a given date, which may be used to group events by day or week, for instance. To do this, get the calendar for the current locale, and then use `NSCalendar -components:fromDate:`.


NSCalendar *calendar = [NSCalendar currentCalendar];
NSDate *date = [NSDate date];
[calendar components:(NSDayCalendarUnit | NSMonthCalendarUnit) fromDate:date];

~~~{objective-c}
NSCalendar *calendar = [NSCalendar currentCalendar];
NSDate *date = [NSDate date];
[calendar components:(NSDayCalendarUnit | NSMonthCalendarUnit) fromDate:date];
~~~

The `components` parameter is a [bitmask](http://en.wikipedia.org/wiki/Bitmask) of the date component values to retrieve:

Expand All @@ -42,32 +42,32 @@ Since it would be expensive to compute all of the possible values, specify only

Another way you may use `NSDateComponents` would be to make relative date calculations, such as determining the date yesterday, next week, or 5 hours and 30 minutes from now. Use `NSCalendar -dateByAddingComponents:toDate:options:`:

~~~{objective-c}
NSCalendar *calendar = [NSCalendar currentCalendar];
NSDate *date = [NSDate date];
NSCalendar *calendar = [NSCalendar currentCalendar];
NSDate *date = [NSDate date];

NSDateComponents *components = [[NSDateComponents alloc] init];
[components setWeek:1];
[components setHour:12];

NSLog(@"1 week and twelve hours from now: %@", [calendar dateByAddingComponents:components toDate:date options:0]);
NSDateComponents *components = [[NSDateComponents alloc] init];
[components setWeek:1];
[components setHour:12];
NSLog(@"1 week and twelve hours from now: %@", [calendar dateByAddingComponents:components toDate:date options:0]);
~~~

One last example of how you can use `NSDateComponents` would be to use them to create an `NSDate` object from components. `NSCalendar -dateFromComponents:` is the method you'll use here:


NSCalendar *calendar = [NSCalendar currentCalendar];
NSDateComponents *components = [[NSDateComponents alloc] init];
[components setYear:1987];
[components setMonth:3];
[components setDay:17];
[components setHour:14];
[components setMinute:20];
[components setSecond:0];
~~~{objective-c}
NSCalendar *calendar = [NSCalendar currentCalendar];
NSLog(@"Awesome time: %@", [calendar dateFromComponents:components]);

NSDateComponents *components = [[NSDateComponents alloc] init];
[components setYear:1987];
[components setMonth:3];
[components setDay:17];
[components setHour:14];
[components setMinute:20];
[components setSecond:0];
NSLog(@"Awesome time: %@", [calendar dateFromComponents:components]);
~~~

What's particularly interesting about this approach is that a date can be determined by information other than the normal month/day/year approach. If you pass enough information to uniquely determine the date, such as the year (e.g. 2012), and day of the the year (e.g. 213 of 365) you would get 7/31/2012 at midnight (because no time was specified, it defaults to 0). Note that passing inconsistent components will either result in some information being discarded, or `nil` being returned.

Expand Down
11 changes: 0 additions & 11 deletions 2012-08-06-nsindexpath.md

This file was deleted.

32 changes: 19 additions & 13 deletions 2012-08-13-nsincrementalstore.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,22 +41,26 @@ With `NSIncrementalStore`, developers now have a sanctioned, reasonable means to

`+initialize` is automatically called the first time a class is loaded, so this is a good place to register with `NSPersistentStoreCoordinator`:

+ (void)initialize {
[NSPersistentStoreCoordinator registerStoreClass:self forStoreType:[self type]];
}

+ (NSString *)type {
return NSStringFromClass(self);
}
~~~{objective-c}
+ (void)initialize {
[NSPersistentStoreCoordinator registerStoreClass:self forStoreType:[self type]];
}
+ (NSString *)type {
return NSStringFromClass(self);
}
~~~

### `-loadMetadata:`

`loadMetadata:` is where the incremental store has a chance to configure itself. There is, however, a bit of Kabuki theater boilerplate that's necessary to get everything set up. Specifically, you need to set a UUID for the store, as well as the store type. Here's what that looks like:

NSMutableDictionary *mutableMetadata = [NSMutableDictionary dictionary];
[mutableMetadata setValue:[[NSProcessInfo processInfo] globallyUniqueString] forKey:NSStoreUUIDKey];
[mutableMetadata setValue:[[self class] type] forKey:NSStoreTypeKey];
[self setMetadata:mutableMetadata];
~~~{objective-c}
NSMutableDictionary *mutableMetadata = [NSMutableDictionary dictionary];
[mutableMetadata setValue:[[NSProcessInfo processInfo] globallyUniqueString] forKey:NSStoreUUIDKey];
[mutableMetadata setValue:[[self class] type] forKey:NSStoreTypeKey];
[self setMetadata:mutableMetadata];
~~~

### `-executeRequest:withContext:error:`

Expand Down Expand Up @@ -107,8 +111,10 @@ Unlike the previous method, the return value will be just the current value for
Finally, this method is called before `executeRequest:withContext:error:` with a save request, where permanent IDs should be assigned to newly-inserted objects. As you might expect, the array of permanent IDs should match up with the array of objects passed into this method.

This usually corresponds with a write to the persistence layer, such as an `INSERT` statement in SQL. If, for example, the row corresponding to the object had an auto-incrementing `id` column, you could generate an objectID with:

[self newObjectIDForEntity:entity referenceObject:[NSNumber numberWithUnsignedInteger:rowID]];

~~~{objective-c}
[self newObjectIDForEntity:entity referenceObject:[NSNumber numberWithUnsignedInteger:rowID]];
~~~

## Roll Your Own Core Data Backend

Expand Down
8 changes: 5 additions & 3 deletions 2012-08-20-nsoperation.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,11 @@ Depending on the complexity of your application, it may make sense to divide up

For example, to describe the process of of downloading and resizing an image from a server, you would probably want to divide up the networking into one operation, and resizing into another (perhaps to reuse the networking operation to download other resources, or reuse the resizing operation for images already on-disk). However, an image can't be resized until its downloaded. Therefore, we say that the the networking operation is a _dependency_ of the resizing operation, and must be finished before the resizing operation can be started. Expressed in code:

[resizingOperation addDependency:networkingOperation];
[operationQueue addOperation:networkingOperation];
[operationQueue addOperation:resizingOperation];
~~~{objective-c}
[resizingOperation addDependency:networkingOperation];
[operationQueue addOperation:networkingOperation];
[operationQueue addOperation:resizingOperation];
~~~

An operation will not be started until all of its dependencies return `YES` to `isFinished`. It's important to remember to add all of the operations involved in a dependency graph to the operation queue, lest there be a gap somewhere along the way.

Expand Down
20 changes: 11 additions & 9 deletions 2012-08-27-cfbag.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,15 +109,17 @@ As an implementation of the bag data type, `CFBag` and its mutable counterpart,

Although it lacks the object-oriented convenience of `NSCountedSet`, it makes up for it with a number of ways to customize its behavior. When `CFBag` is created, it can be initialized with a number of callbacks, defined by the `CFBagCallBacks` struct, which specify the way values are inserted, removed, and compared:

struct CFBagCallBacks {
CFIndex version;
CFBagRetainCallBack retain;
CFBagReleaseCallBack release;
CFBagCopyDescriptionCallBack copyDescription;
CFBagEqualCallBack equal;
CFBagHashCallBack hash;
};
typedef struct CFBagCallBacks CFBagCallBacks;
~~~{objective-c}
struct CFBagCallBacks {
CFIndex version;
CFBagRetainCallBack retain;
CFBagReleaseCallBack release;
CFBagCopyDescriptionCallBack copyDescription;
CFBagEqualCallBack equal;
CFBagHashCallBack hash;
};
typedef struct CFBagCallBacks CFBagCallBacks;
~~~

- `retain`: callback used to retain values as they're added to the collection
- `release`: callback used to release values as they're removed from the collection
Expand Down
14 changes: 9 additions & 5 deletions 2012-09-03-nslocale.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,9 +95,11 @@ So if you were building a component that added quotations around arbitrary text,

Another impressive, albeit mostly-useless method is `-displayNameForKey:value:`, which can return the display name of a locale identifier (`NSLocaleIdentifier`):

NSLocale *frLocale = [[NSLocale alloc] initWithLocaleIdentifier:@"fr_FR"];
NSLog(@"fr_FR: %@", [frLocale displayNameForKey:NSLocaleIdentifier value:@"fr_FR"]);
NSLog(@"en_US: %@", [frLocale displayNameForKey:NSLocaleIdentifier value:@"en_US"]);
~~~{objective-c}
NSLocale *frLocale = [[NSLocale alloc] initWithLocaleIdentifier:@"fr_FR"];
NSLog(@"fr_FR: %@", [frLocale displayNameForKey:NSLocaleIdentifier value:@"fr_FR"]);
NSLog(@"en_US: %@", [frLocale displayNameForKey:NSLocaleIdentifier value:@"en_US"]);
~~~

> fr_FR: français (France)
> en_US: anglais (États-Unis)
Expand All @@ -112,8 +114,10 @@ One final method worth mentioning is `NSLocale +preferredLanguages`, which retur

An app that communicates with a web server can use these values to define the `Accept-Language` HTTP header, such that the server has the option to return localized resources:

NSMutableURLRequest *request = ...;
[request setValue:[NSString stringWithFormat:@"%@", [[NSLocale preferredLanguages] componentsJoinedByString:@", "]], forHTTPHeaderField:@"Accept-Language"];
~~~{objective-c}
NSMutableURLRequest *request = ...;
[request setValue:[NSString stringWithFormat:@"%@", [[NSLocale preferredLanguages] componentsJoinedByString:@", "]], forHTTPHeaderField:@"Accept-Language"];
~~~

Even if your server doesn't yet localize its resources, putting this in place now will allow you to flip the switch when the time comes, without having to push an update to the client. Neat!

Expand Down
44 changes: 24 additions & 20 deletions 2012-09-17-nscharacterset.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,15 @@ It's important to note that this method _only_ strips the _first_ and _last_ con

So let's say you do want to get rid of excessive inter-word spacing for that string you just stripped of whitespace. Here's a really easy way to do that:

NSString *string = @"Lorem ipsum dolar sit amet.";
string = [string stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
~~~{objective-c}
NSString *string = @"Lorem ipsum dolar sit amet.";
string = [string stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
NSArray *components = [string componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
components = [components filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"self <> ''"]];

string = [components componentsJoinedByString:@" "];
NSArray *components = [string componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
components = [components filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"self <> ''"]];
string = [components componentsJoinedByString:@" "];
~~~

First, trim the string of leading and trailing whitespace. Next, use `NSString -componentsSeparatedByCharactersInSet:` to split on the remaining whitespace to create an `NSArray`. Next, filter out the blank string components with an `NSPredicate`. Finally, use `NSArray -componentsJoinedByString:` to re-join the components with a single space. Note that this only works for languages like English that delimit words with whitespace.

Expand Down Expand Up @@ -106,24 +108,26 @@ For example, let's say you have a string that parses opening hours in the follow

You might `enumerateLinesUsingBlock:` and parse with an `NSScanner` like so:

NSMutableCharacterSet *skippedCharacters = [NSMutableCharacterSet punctuationCharacterSet];
[skippedCharacters formUnionWithCharacterSet:[NSCharacterSet whitespaceCharacterSet]];
~~~{objective-c}
NSMutableCharacterSet *skippedCharacters = [NSMutableCharacterSet punctuationCharacterSet];
[skippedCharacters formUnionWithCharacterSet:[NSCharacterSet whitespaceCharacterSet]];
[hours enumerateLinesUsingBlock:^(NSString *line, BOOL *stop) {
NSScanner *scanner = [NSScanner scannerWithString:line];
[scanner setCharactersToBeSkipped:skippedCharacters];
[hours enumerateLinesUsingBlock:^(NSString *line, BOOL *stop) {
NSScanner *scanner = [NSScanner scannerWithString:line];
[scanner setCharactersToBeSkipped:skippedCharacters];
NSString *startDay, *endDay;
NSUInteger startHour, startMinute, endHour, endMinute;
NSString *startDay, *endDay;
NSUInteger startHour, startMinute, endHour, endMinute;
[scanner scanCharactersFromSet:[NSCharacterSet letterCharacterSet] intoString:&startDay];
[scanner scanCharactersFromSet:[NSCharacterSet letterCharacterSet] intoString:&endDay];
[scanner scanCharactersFromSet:[NSCharacterSet letterCharacterSet] intoString:&startDay];
[scanner scanCharactersFromSet:[NSCharacterSet letterCharacterSet] intoString:&endDay];
[scanner scanInteger:&startHour];
[scanner scanInteger:&startMinute];
[scanner scanInteger:&endHour];
[scanner scanInteger:&endMinute];
}];
[scanner scanInteger:&startHour];
[scanner scanInteger:&startMinute];
[scanner scanInteger:&endHour];
[scanner scanInteger:&endMinute];
}];
~~~

We first construct an `NSMutableCharacterSet` from the union of whitespace and punctuation characters. Telling `NSScanner` to skip these characters greatly reduces the logic necessary to parse values from the string.

Expand Down
10 changes: 6 additions & 4 deletions 2012-09-24-uicollectionview.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,12 @@ In another departure from the old-school table view way of doing things, the pro

In `-tableView:cellForRowAtIndexPath:`, a developer had to invoke the familiar incantation:

UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:...];
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:... reuseIdentifier:...];
}
~~~{objective-c}
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:...];
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:... reuseIdentifier:...];
}
~~~

`UICollectionView` thankfully does away with this. `-dequeueReusableCellWithReuseIdentifier:forIndexPath:` is guaranteed to return a valid object, by creating a new cell if there are no cells to reuse. Simply register a `UICollectionReusableView` subclass for a particular reuse identifier, and everything will work automatically. Thankfully, this behavior has been backported to `UITableView` as well with iOS 6.

Expand Down
Loading

0 comments on commit 2cbb541

Please sign in to comment.