diff --git a/2012-07-24-nssortdescriptor.md b/2012-07-24-nssortdescriptor.md index 6cf4f94c..41d15714 100644 --- a/2012-07-24-nssortdescriptor.md +++ b/2012-07-24-nssortdescriptor.md @@ -26,22 +26,6 @@ Collection classes like `NSArray` and `NSSet` have methods to return sorted arra To put that into more practical terms, consider a `Person` class with properties for `firstName` & `lastName` of type `NSString *`, and `age`, which is an `NSUInteger`. -~~~{objective-c} -@interface Person : NSObject -@property NSString *firstName; -@property NSString *lastName; -@property NSNumber *age; -@end - -@implementation Person - -- (NSString *)description { - return [NSString stringWithFormat:@"%@ %@", self.firstName, self.lastName]; -} - -@end -~~~ - ~~~{swift} class Person: NSObject { let firstName: String @@ -60,6 +44,22 @@ class Person: NSObject { } ~~~ +~~~{objective-c} +@interface Person : NSObject +@property NSString *firstName; +@property NSString *lastName; +@property NSNumber *age; +@end + +@implementation Person + +- (NSString *)description { + return [NSString stringWithFormat:@"%@ %@", self.firstName, self.lastName]; +} + +@end +~~~ + Given the following dataset: | `firstName` | `lastName` | `age` | @@ -71,6 +71,27 @@ Given the following dataset: Here are some of the different ways they can be sorted by combinations of `NSSortDescriptor`: +~~~{swift} +let alice = Person(firstName: "Alice", lastName: "Smith", age: 24) +let bob = Person(firstName: "Bob", lastName: "Jones", age: 27) +let charlie = Person(firstName: "Charlie", lastName: "Smith", age: 33) +let quentin = Person(firstName: "Quentin", lastName: "Alberts", age: 31) +let people = [alice, bob, charlie, quentin] + +let firstNameSortDescriptor = NSSortDescriptor(key: "firstName", ascending: true, selector: "localizedStandardCompare:") +let lastNameSortDescriptor = NSSortDescriptor(key: "lastName", ascending: true, selector: "localizedStandardCompare:") +let ageSortDescriptor = NSSortDescriptor(key: "age", ascending: false) + +let sortedByAge = (people as NSArray).sortedArrayUsingDescriptors([ageSortDescriptor]) +// "Charlie Smith", "Quentin Alberts", "Bob Jones", "Alice Smith" + +let sortedByFirstName = (people as NSArray).sortedArrayUsingDescriptors([firstNameSortDescriptor]) +// "Alice Smith", "Bob Jones", "Charlie Smith", "Quentin Alberts" + +let sortedByLastNameFirstName = (people as NSArray).sortedArrayUsingDescriptors([lastNameSortDescriptor, firstNameSortDescriptor]) +// "Quentin Alberts", "Bob Jones", "Alice Smith", "Charlie Smith" +~~~ + ~~~{objective-c} NSArray *firstNames = @[ @"Alice", @"Bob", @"Charlie", @"Quentin" ]; NSArray *lastNames = @[ @"Smith", @"Jones", @"Smith", @"Alberts" ]; @@ -106,27 +127,6 @@ NSLog(@"By last name, first name: %@", [people sortedArrayUsingDescriptors:@[las // "Quentin Alberts", "Bob Jones", "Alice Smith", "Charlie Smith" ~~~ -~~~{swift} -let alice = Person(firstName: "Alice", lastName: "Smith", age: 24) -let bob = Person(firstName: "Bob", lastName: "Jones", age: 27) -let charlie = Person(firstName: "Charlie", lastName: "Smith", age: 33) -let quentin = Person(firstName: "Quentin", lastName: "Alberts", age: 31) -let people = [alice, bob, charlie, quentin] - -let firstNameSortDescriptor = NSSortDescriptor(key: "firstName", ascending: true, selector: "localizedStandardCompare:") -let lastNameSortDescriptor = NSSortDescriptor(key: "lastName", ascending: true, selector: "localizedStandardCompare:") -let ageSortDescriptor = NSSortDescriptor(key: "age", ascending: false) - -let sortedByAge = (people as NSArray).sortedArrayUsingDescriptors([ageSortDescriptor]) -// "Charlie Smith", "Quentin Alberts", "Bob Jones", "Alice Smith" - -let sortedByFirstName = (people as NSArray).sortedArrayUsingDescriptors([firstNameSortDescriptor]) -// "Alice Smith", "Bob Jones", "Charlie Smith", "Quentin Alberts" - -let sortedByLastNameFirstName = (people as NSArray).sortedArrayUsingDescriptors([lastNameSortDescriptor, firstNameSortDescriptor]) -// "Quentin Alberts", "Bob Jones", "Alice Smith", "Charlie Smith" -~~~ - * * * `NSSortDescriptor` can be found throughout Foundation and other system frameworks, playing an especially prominent role in Core Data. Anytime your own classes need to define sort ordering, follow the convention of specifying a `sortDescriptors` parameter as appropriate. diff --git a/2012-07-31-nsdatecomponents.md b/2012-07-31-nsdatecomponents.md index 72f1c75a..49f5c888 100644 --- a/2012-07-31-nsdatecomponents.md +++ b/2012-07-31-nsdatecomponents.md @@ -14,49 +14,55 @@ Whereas dates represent a particular moment in time, date components depend on w `NSDateComponents` can be initialized and manipulated manually, but most often, they're extracted from a specified date, using `NSCalendar -components:fromDate:`: -~~~{objective-c} -NSCalendar *calendar = [NSCalendar currentCalendar]; -NSDate *date = [NSDate date]; -[calendar components:(NSDayCalendarUnit | NSMonthCalendarUnit) fromDate:date]; -~~~ ~~~{swift} let calendar = NSCalendar.currentCalendar() let date = NSDate() let components = calendar.components(.MonthCalendarUnit | .DayCalendarUnit, 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, with many to choose from: - -- `.CalendarUnitEra` -- `.CalendarUnitYear` -- `.CalendarUnitMonth` -- `.CalendarUnitDay` -- `.CalendarUnitHour` -- `.CalendarUnitMinute` -- `.CalendarUnitSecond` -- `.CalendarUnitWeekday` -- `.CalendarUnitQuarter` -- `.CalendarUnitWeekOfMonth` -- `.CalendarUnitWeekOfYear` -- `.CalendarUnitYearForWeekOfYear` -- `.CalendarUnitCalendar` -- `.CalendarUnitTimeZone` +~~~{swift} +.CalendarUnitEra +.CalendarUnitYear +.CalendarUnitMonth +.CalendarUnitDay +.CalendarUnitHour +.CalendarUnitMinute +.CalendarUnitSecond +.CalendarUnitWeekday +.CalendarUnitQuarter +.CalendarUnitWeekOfMonth +.CalendarUnitWeekOfYear +.CalendarUnitYearForWeekOfYear +.CalendarUnitCalendar +.CalendarUnitTimeZone +~~~ + +~~~{objective-c} +NSEraCalendarUnit +NSYearCalendarUnit +NSMonthCalendarUnit +NSDayCalendarUnit +NSHourCalendarUnit +NSMinuteCalendarUnit +NSSecondCalendarUnit +NSWeekCalendarUnit +NSWeekdayCalendarUnit +NSWeekdayOrdinalCalendarUnit +NSQuarterCalendarUnit +NSWeekOfMonthCalendarUnit +NSWeekOfYearCalendarUnit +NSYearForWeekOfYearCalendarUnit +NSCalendarCalendarUnit +NSTimeZoneCalendarUnit +~~~ > Since it would be expensive to compute all of the possible values, specify only the components that will be used in subsequent calculations (joining with `|`, the bitwise `OR` operator). @@ -64,17 +70,6 @@ The `components` parameter is a [bitmask](http://en.wikipedia.org/wiki/Bitmask) `NSDateComponents` objects can be used to do relative date calculations. To 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]; - -NSDateComponents *components = [[NSDateComponents alloc] init]; -[components setWeekOfYear:1]; -[components setHour:12]; - -NSLog(@"1 week and twelve hours from now: %@", [calendar dateByAddingComponents:components toDate:date options:0]); -~~~ - ~~~{swift} let calendar = NSCalendar.currentCalendar() let date = NSDate() @@ -86,24 +81,21 @@ components.hour = 12 println("1 week and 12 hours from now: \(calendar.dateByAddingComponents(components, toDate: date, options: nil))") ~~~ -## Creating Dates from Components - -Perhaps the most powerful feature of `NSDateComponents`, however, is the ability to go the opposite direction—creating an `NSDate` object from components. `NSCalendar -dateFromComponents:` is the method used for this purpose: - ~~~{objective-c} NSCalendar *calendar = [NSCalendar currentCalendar]; +NSDate *date = [NSDate date]; NSDateComponents *components = [[NSDateComponents alloc] init]; -[components setYear:1987]; -[components setMonth:3]; -[components setDay:17]; -[components setHour:14]; -[components setMinute:20]; -[components setSecond:0]; +[components setWeekOfYear:1]; +[components setHour:12]; -NSDate *date = [calendar dateFromComponents:components]; +NSLog(@"1 week and twelve hours from now: %@", [calendar dateByAddingComponents:components toDate:date options:0]); ~~~ +## Creating Dates from Components + +Perhaps the most powerful feature of `NSDateComponents`, however, is the ability to go the opposite direction—creating an `NSDate` object from components. `NSCalendar -dateFromComponents:` is the method used for this purpose: + ~~~{swift} let calendar = NSCalendar(identifier: NSGregorianCalendar) @@ -118,6 +110,20 @@ components.second = 0 let date = calendar.dateFromComponents(components) ~~~ +~~~{objective-c} +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]; + +NSDate *date = [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. So long as a date can be uniquely determined from the provided information, you'll get a result. For example, specifying the year 2013, and the 316th day of the year would return an `NSDate` for 11/12/2013 at midnight (because no time was specified, all time components default to 0). > Note that passing inconsistent components will either result in some information being discarded, or `nil` being returned. diff --git a/2012-08-13-nsincrementalstore.md b/2012-08-13-nsincrementalstore.md index 60200e9d..a6cd6518 100644 --- a/2012-08-13-nsincrementalstore.md +++ b/2012-08-13-nsincrementalstore.md @@ -38,16 +38,6 @@ 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`: -~~~{objective-c} -+ (void)initialize { - [NSPersistentStoreCoordinator registerStoreClass:self forStoreType:[self type]]; -} - -+ (NSString *)type { - return NSStringFromClass(self); -} -~~~ - ~~~{swift} class CustomIncrementalStore: NSIncrementalStore { override class func initialize() { @@ -60,17 +50,21 @@ class CustomIncrementalStore: NSIncrementalStore { } ~~~ -### `-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: ~~~{objective-c} -NSMutableDictionary *mutableMetadata = [NSMutableDictionary dictionary]; -[mutableMetadata setValue:[[NSProcessInfo processInfo] globallyUniqueString] forKey:NSStoreUUIDKey]; -[mutableMetadata setValue:[[self class] type] forKey:NSStoreTypeKey]; -[self setMetadata:mutableMetadata]; ++ (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: + ~~~{swift} override func loadMetadata(error: NSErrorPointer) -> Bool { self.metadata = [ @@ -82,6 +76,13 @@ override func loadMetadata(error: NSErrorPointer) -> Bool { } ~~~ +~~~{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:` Here's where things get interesting, from an implementation standpoint. (And where it all goes to hell, from an API design standpoint) @@ -132,14 +133,14 @@ Finally, this method is called before `executeRequest:withContext:error:` with a 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: -~~~{objective-c} -[self newObjectIDForEntity:entity referenceObject:[NSNumber numberWithUnsignedInteger:rowID]]; -~~~ - ~~~{swift} self.newObjectIDForEntity(entity, referenceObject: rowID) ~~~ +~~~{objective-c} +[self newObjectIDForEntity:entity referenceObject:[NSNumber numberWithUnsignedInteger:rowID]]; +~~~ + ## Roll Your Own Core Data Backend Going through all of the necessary methods to override in an `NSIncrementalStore` subclass, you may have found your mind racing with ideas about how you might implement a SQL or NoSQL store, or maybe something new altogether. diff --git a/2012-09-03-nslocale.md b/2012-09-03-nslocale.md index 7f342cd9..6384141d 100644 --- a/2012-09-03-nslocale.md +++ b/2012-09-03-nslocale.md @@ -93,12 +93,6 @@ 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`): -~~~{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"]); -~~~ - ~~~{swift} let locale = NSLocale(localeIdentifier: "fr_FR") @@ -106,6 +100,12 @@ let fr_FR = locale.displayNameForKey(NSLocaleIdentifier, value: "fr_FR") let en_US = locale.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)" @@ -119,11 +119,6 @@ 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: -~~~{objective-c} -NSMutableURLRequest *request = ...; -[request setValue:[NSString stringWithFormat:@"%@", [[NSLocale preferredLanguages] componentsJoinedByString:@", "]], forHTTPHeaderField:@"Accept-Language"]; -~~~ - ~~~{swift} // Accept-Language HTTP Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4 let acceptLanguage: String = { @@ -145,6 +140,11 @@ var mutableRequest = NSMutableURLRequest(URL: URL) mutableRequest.setValue(acceptLanguage, 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! --- diff --git a/2012-09-17-nscharacterset.md b/2012-09-17-nscharacterset.md index 863baf03..6f577d55 100644 --- a/2012-09-17-nscharacterset.md +++ b/2012-09-17-nscharacterset.md @@ -48,6 +48,14 @@ 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: +~~~{swift} +var string = " Lorem ipsum dolar sit amet. " + +let components = string.componentsSeparatedByCharactersInSet(NSCharacterSet.whitespaceCharacterSet()).filter({!isEmpty($0)}) + +string = join(" ", components) +~~~ + ~~~{objective-c} NSString *string = @"Lorem ipsum dolar sit amet."; string = [string stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; @@ -58,14 +66,6 @@ components = [components filteredArrayUsingPredicate:[NSPredicate predicateWithF string = [components componentsJoinedByString:@" "]; ~~~ -~~~{swift} -var string = " Lorem ipsum dolar sit amet. " - -let components = string.componentsSeparatedByCharactersInSet(NSCharacterSet.whitespaceCharacterSet()).filter({!isEmpty($0)}) - -string = join(" ", components) -~~~ - 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. And now for the anti-patterns. Take a gander at [the answers to this question on StackOverflow](http://stackoverflow.com/questions/758212/how-can-i-strip-all-the-whitespaces-from-a-string-in-objective-c). @@ -115,27 +115,6 @@ Sat-Sun: 10:00 - 15:00 You might `enumerateLinesUsingBlock:` and parse with an `NSScanner` like so: -~~~{objective-c} -NSMutableCharacterSet *skippedCharacters = [NSMutableCharacterSet punctuationCharacterSet]; -[skippedCharacters formUnionWithCharacterSet:[NSCharacterSet whitespaceCharacterSet]]; - -[hours enumerateLinesUsingBlock:^(NSString *line, BOOL *stop) { - NSScanner *scanner = [NSScanner scannerWithString:line]; - [scanner setCharactersToBeSkipped:skippedCharacters]; - - NSString *startDay, *endDay; - NSUInteger startHour, startMinute, endHour, endMinute; - - [scanner scanCharactersFromSet:[NSCharacterSet letterCharacterSet] intoString:&startDay]; - [scanner scanCharactersFromSet:[NSCharacterSet letterCharacterSet] intoString:&endDay]; - - [scanner scanInteger:&startHour]; - [scanner scanInteger:&startMinute]; - [scanner scanInteger:&endHour]; - [scanner scanInteger:&endMinute]; -}]; -~~~ - ~~~{swift} let skippedCharacters = NSMutableCharacterSet() skippedCharacters.formIntersectionWithCharacterSet(NSCharacterSet.punctuationCharacterSet()) @@ -161,6 +140,27 @@ string.enumerateLines { (line, _) in } ~~~ +~~~{objective-c} +NSMutableCharacterSet *skippedCharacters = [NSMutableCharacterSet punctuationCharacterSet]; +[skippedCharacters formUnionWithCharacterSet:[NSCharacterSet whitespaceCharacterSet]]; + +[hours enumerateLinesUsingBlock:^(NSString *line, BOOL *stop) { + NSScanner *scanner = [NSScanner scannerWithString:line]; + [scanner setCharactersToBeSkipped:skippedCharacters]; + + NSString *startDay, *endDay; + NSUInteger startHour, startMinute, endHour, endMinute; + + [scanner scanCharactersFromSet:[NSCharacterSet letterCharacterSet] intoString:&startDay]; + [scanner scanCharactersFromSet:[NSCharacterSet letterCharacterSet] intoString:&endDay]; + + [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. `scanCharactersFromSet:` with the letters character set captures the start and (optional) end day of the week for each entry. `scanInteger` similarly captures the next contiguous integer value. diff --git a/2012-10-15-addressbookui.md b/2012-10-15-addressbookui.md index f06fd606..d0b8575d 100644 --- a/2012-10-15-addressbookui.md +++ b/2012-10-15-addressbookui.md @@ -27,14 +27,14 @@ The first argument for the function is a dictionary containing the address compo > `kABPersonAddressCountryCodeKey` is an especially important attribute, as it determines which locale used to format the address string. If you are unsure of the country code, or one isn't provided with your particular data set, `NSLocale` may be able to help you out: -~~~{objective-c} -[mutableAddressComponents setValue:[[[NSLocale alloc] initWithLocaleIdentifier:@"en_US"] objectForKey:NSLocaleCountryCode] forKey:(__bridge NSString *)kABPersonAddressCountryCodeKey]; -~~~ - ~~~{swift} let countryCode: String = NSLocale(localeIdentifier: "en_US").objectForKey(NSLocaleCountryCode) as String ~~~ +~~~{objective-c} +[mutableAddressComponents setValue:[[[NSLocale alloc] initWithLocaleIdentifier:@"en_US"] objectForKey:NSLocaleCountryCode] forKey:(__bridge NSString *)kABPersonAddressCountryCodeKey]; +~~~ + The second argument is a boolean flag, `addCountryName`. When `YES`, the name of the country corresponding to the specified country code will be automatically appended to the address. This should only used when the country code is known. ~~~{swift} diff --git a/2012-10-22-nslinguistictagger.md b/2012-10-22-nslinguistictagger.md index f89e8aeb..e61bd599 100644 --- a/2012-10-22-nslinguistictagger.md +++ b/2012-10-22-nslinguistictagger.md @@ -21,17 +21,6 @@ Consider a typical question we might ask Siri: Computers are a long ways off from "understanding" this question literally, but with a few simple tricks, we can do a reasonable job understanding the _intention_ of the question: -~~~{objective-c} -NSString *question = @"What is the weather in San Francisco?"; -NSLinguisticTaggerOptions options = NSLinguisticTaggerOmitWhitespace | NSLinguisticTaggerOmitPunctuation | NSLinguisticTaggerJoinNames; -NSLinguisticTagger *tagger = [[NSLinguisticTagger alloc] initWithTagSchemes: [NSLinguisticTagger availableTagSchemesForLanguage:@"en"] options:options]; -tagger.string = question; -[tagger enumerateTagsInRange:NSMakeRange(0, [question length]) scheme:NSLinguisticTagSchemeNameTypeOrLexicalClass options:options usingBlock:^(NSString *tag, NSRange tokenRange, NSRange sentenceRange, BOOL *stop) { - NSString *token = [question substringWithRange:tokenRange]; - NSLog(@"%@: %@", token, tag); -}]; -~~~ - ~~~{swift} let question = "What is the weather in San Francisco?" let options: NSLinguisticTaggerOptions = .OmitWhitespace | .OmitPunctuation | .JoinNames @@ -44,6 +33,17 @@ tagger.enumerateTagsInRange(NSMakeRange(0, (question as NSString).length), schem } ~~~ +~~~{objective-c} +NSString *question = @"What is the weather in San Francisco?"; +NSLinguisticTaggerOptions options = NSLinguisticTaggerOmitWhitespace | NSLinguisticTaggerOmitPunctuation | NSLinguisticTaggerJoinNames; +NSLinguisticTagger *tagger = [[NSLinguisticTagger alloc] initWithTagSchemes: [NSLinguisticTagger availableTagSchemesForLanguage:@"en"] options:options]; +tagger.string = question; +[tagger enumerateTagsInRange:NSMakeRange(0, [question length]) scheme:NSLinguisticTagSchemeNameTypeOrLexicalClass options:options usingBlock:^(NSString *tag, NSRange tokenRange, NSRange sentenceRange, BOOL *stop) { + NSString *token = [question substringWithRange:tokenRange]; + NSLog(@"%@: %@", token, tag); +}]; +~~~ + This code would print the following: > What: _Pronoun_ diff --git a/2012-10-29-uilocalizedindexedcollation.md b/2012-10-29-uilocalizedindexedcollation.md index a5c9a751..0bb60104 100644 --- a/2012-10-29-uilocalizedindexedcollation.md +++ b/2012-10-29-uilocalizedindexedcollation.md @@ -59,6 +59,43 @@ Finally, the table view should implement `-tableView:sectionForSectionIndexTitle All told, here's what a typical table view data source implementation looks like: +~~~{swift} +class ObjectTableViewController: UITableViewController { + let collation = UILocalizedIndexedCollation.currentCollation() as UILocalizedIndexedCollation + var sections: [[Object]] = [] + var objects: [Object] { + didSet { + let selector: Selector = "localizedTitle" + + + sections = [[Object]](count: collation.sectionTitles.count, repeatedValue: []) + + let sortedObjects = collation.sortedArrayFromArray(objects, collationStringSelector: selector) as [Object] + for object in sortedObjects { + let sectionNumber = collation.sectionForObject(object, collationStringSelector: selector) + sections[sectionNumber].append(object) + } + + self.tableView.reloadData() + } + } + + // MARK: UITableViewDelegate + + override func tableView(tableView: UITableView!, titleForHeaderInSection section: Int) -> String! { + return collation.sectionTitles![section] as String + } + + override func sectionIndexTitlesForTableView(tableView: UITableView!) -> [AnyObject]! { + return collation.sectionIndexTitles + } + + override func tableView(tableView: UITableView!, sectionForSectionIndexTitle title: String!, atIndex index: Int) -> Int { + return collation.sectionForSectionIndexTitleAtIndex(index) + } +} +~~~ + ~~~{objective-c} - (void)setObjects:(NSArray *)objects { SEL selector = @selector(localizedTitle); @@ -102,43 +139,6 @@ sectionForSectionIndexTitle:(NSString *)title } ~~~ -~~~{swift} -class ObjectTableViewController: UITableViewController { - let collation = UILocalizedIndexedCollation.currentCollation() as UILocalizedIndexedCollation - var sections: [[Object]] = [] - var objects: [Object] { - didSet { - let selector: Selector = "localizedTitle" - - - sections = [[Object]](count: collation.sectionTitles.count, repeatedValue: []) - - let sortedObjects = collation.sortedArrayFromArray(objects, collationStringSelector: selector) as [Object] - for object in sortedObjects { - let sectionNumber = collation.sectionForObject(object, collationStringSelector: selector) - sections[sectionNumber].append(object) - } - - self.tableView.reloadData() - } - } - - // MARK: UITableViewDelegate - - override func tableView(tableView: UITableView!, titleForHeaderInSection section: Int) -> String! { - return collation.sectionTitles![section] as String - } - - override func sectionIndexTitlesForTableView(tableView: UITableView!) -> [AnyObject]! { - return collation.sectionIndexTitles - } - - override func tableView(tableView: UITableView!, sectionForSectionIndexTitle title: String!, atIndex index: Int) -> Int { - return collation.sectionForSectionIndexTitleAtIndex(index) - } -} -~~~ - ## UITableViewIndexSearch There is one special section index title worth mentioning, and that's `UITableViewIndexSearch`. It's a common pattern to have both a search bar and section indexes. In equal parts convenience and visual consistency, a search icon is often included as the first section index title, which can be touched to bring up the `UISearchBar` in the header of the table view. diff --git a/2012-11-12-nsvaluetransformer.md b/2012-11-12-nsvaluetransformer.md index 238c97d5..ef05ac9f 100644 --- a/2012-11-12-nsvaluetransformer.md +++ b/2012-11-12-nsvaluetransformer.md @@ -23,6 +23,23 @@ But you know what? `NSValueTransformer` is ripe for a comeback. With a little bi A typical implementation would look something like this: +~~~{swift} +class ClassNameTransformer: NSValueTransformer { + + override class func transformedValueClass() -> AnyClass! { + return NSString.self + } + + override class func allowsReverseTransformation() -> Bool { + return false + } + + override func transformedValue(value: AnyObject!) -> AnyObject! { + return (value == nil) ? nil : NSStringFromClass(value as AnyClass) + } +} +~~~ + ~~~{objective-c} @interface ClassNameTransformer: NSValueTransformer {} @end @@ -44,25 +61,18 @@ A typical implementation would look something like this: @end ~~~ -~~~{swift} -class ClassNameTransformer: NSValueTransformer { +`NSValueTransformer` is rarely initialized directly. Instead, it follows a pattern familiar to fans of `NSPersistentStore` or `NSURLProtocol`, where a class is registered, and instances are created from a manager--except in this case, you register the _instance_ to act as a singleton (with a particular name): - override class func transformedValueClass() -> AnyClass! { - return NSString.self - } +~~~{swift} +let ClassNameTransformerName = "ClassNameTransformer" - override class func allowsReverseTransformation() -> Bool { - return false - } +// Set the value transformer +NSValueTransformer.setValueTransformer(ClassNameTransformer(), forName: ClassNameTransformerName) - override func transformedValue(value: AnyObject!) -> AnyObject! { - return (value == nil) ? nil : NSStringFromClass(value as AnyClass) - } -} +// Get the value transformer +let valueTransformer = NSValueTransformer(forName: ClassNameTransformerName) ~~~ -`NSValueTransformer` is rarely initialized directly. Instead, it follows a pattern familiar to fans of `NSPersistentStore` or `NSURLProtocol`, where a class is registered, and instances are created from a manager--except in this case, you register the _instance_ to act as a singleton (with a particular name): - ~~~{objective-c} NSString * const ClassNameTransformerName = @"ClassNameTransformer"; @@ -73,16 +83,6 @@ NSString * const ClassNameTransformerName = @"ClassNameTransformer"; NSValueTransformer *valueTransformer = [NSValueTransformer valueTransformerForName:ClassNameTransformerName]; ~~~ -~~~{swift} -let ClassNameTransformerName = "ClassNameTransformer" - -// Set the value transformer -NSValueTransformer.setValueTransformer(ClassNameTransformer(), forName: ClassNameTransformerName) - -// Get the value transformer -let valueTransformer = NSValueTransformer(forName: ClassNameTransformerName) -~~~ - Typically, the singleton instance would be registered in the `+initialize` method of the value transformer subclass, so it could be used without further setup. Now, at this point, you probably realize `NSValueTransformer`'s fatal flaw: it's a pain in the ass to set up! Create a class, implement a handful of simple methods, define a constant, _and_ register it in an `+initialize` method? No thanks. diff --git a/2012-11-26-nsorderedset.md b/2012-11-26-nsorderedset.md index 7611cb04..2e996a21 100644 --- a/2012-11-26-nsorderedset.md +++ b/2012-11-26-nsorderedset.md @@ -25,14 +25,6 @@ As expertly demonstrated by [Tom Dalling](http://tomdalling.com) in [this Stack To start, let's look at how `-mutableCopy` is supposed to work in a class cluster: -~~~{objective-c} -NSSet* immutable = [NSSet set]; -NSMutableSet* mutable = [immutable mutableCopy]; - -[mutable isKindOfClass:[NSSet class]]; // YES -[mutable isKindOfClass:[NSMutableSet class]]; // YES -~~~ - ~~~{swift} let immutable: NSSet = NSSet() var mutable: NSMutableSet = immutable.mutableCopy() as NSMutableSet @@ -41,18 +33,16 @@ mutable.isKindOfClass(NSSet.self) // true mutable.isKindOfClass(NSMutableSet.self) // true ~~~ -Now let's suppose that `NSOrderedSet` was indeed a subclass of `NSSet`: - ~~~{objective-c} -// @interface NSOrderedSet : NSSet - -NSOrderedSet* immutable = [NSOrderedSet orderedSet]; -NSMutableOrderedSet* mutable = [immutable mutableCopy]; +NSSet* immutable = [NSSet set]; +NSMutableSet* mutable = [immutable mutableCopy]; [mutable isKindOfClass:[NSSet class]]; // YES -[mutable isKindOfClass:[NSMutableSet class]]; // NO (!) +[mutable isKindOfClass:[NSMutableSet class]]; // YES ~~~ +Now let's suppose that `NSOrderedSet` was indeed a subclass of `NSSet`: + ~~~{swift} // class NSOrderedSet: NSSet {...} @@ -63,22 +53,20 @@ mutable.isKindOfClass(NSSet.self) // true mutable.isKindOfClass(NSMutableSet.self) // false (!) ~~~ - - -That's no good... since `NSMutableOrderedSet` couldn't be used as a method parameter of type `NSMutableSet`. So what happens if we make `NSMutableOrderedSet` a subclass of `NSMutableSet` as well? - ~~~{objective-c} // @interface NSOrderedSet : NSSet -// @interface NSMutableOrderedSet : NSMutableSet NSOrderedSet* immutable = [NSOrderedSet orderedSet]; NSMutableOrderedSet* mutable = [immutable mutableCopy]; [mutable isKindOfClass:[NSSet class]]; // YES -[mutable isKindOfClass:[NSMutableSet class]]; // YES -[mutable isKindOfClass:[NSOrderedSet class]]; // NO (!) +[mutable isKindOfClass:[NSMutableSet class]]; // NO (!) ~~~ + + +That's no good... since `NSMutableOrderedSet` couldn't be used as a method parameter of type `NSMutableSet`. So what happens if we make `NSMutableOrderedSet` a subclass of `NSMutableSet` as well? + ~~~{swift} // class NSOrderedSet: NSSet {...} // class NSMutableOrderedSet: NSMutableSet {...} @@ -91,6 +79,18 @@ mutable.isKindOfClass(NSMutableSet.self) // true mutable.isKindOfClass(NSOrderedSet.self) // false (!) ~~~ +~~~{objective-c} +// @interface NSOrderedSet : NSSet +// @interface NSMutableOrderedSet : NSMutableSet + +NSOrderedSet* immutable = [NSOrderedSet orderedSet]; +NSMutableOrderedSet* mutable = [immutable mutableCopy]; + +[mutable isKindOfClass:[NSSet class]]; // YES +[mutable isKindOfClass:[NSMutableSet class]]; // YES +[mutable isKindOfClass:[NSOrderedSet class]]; // NO (!) +~~~ + This is perhaps even worse, as now `NSMutableOrderedSet` couldn't be used as a method parameter expecting an `NSOrderedSet`. diff --git a/2013-02-11-nsurlcache.md b/2013-02-11-nsurlcache.md index 040aa00e..a06ffc67 100644 --- a/2013-02-11-nsurlcache.md +++ b/2013-02-11-nsurlcache.md @@ -18,6 +18,15 @@ As of iOS 5, a shared `NSURLCache` is set for the application by default. [Quoth Those having such special caching requirements can set a shared URL cache in `-application:didFinishLaunchingWithOptions:` on iOS, or `–applicationDidFinishLaunching:` on OS X: +~~~{swift} +func application(application: UIApplication!, didFinishLaunchingWithOptions launchOptions: NSDictionary!) -> Bool { + let URLCache = NSURLCache(memoryCapacity: 4 * 1024 * 1024, diskCapacity: 20 * 1024 * 1024, diskPath: nil) + NSURLCache.setSharedURLCache(URLCache) + + return true +} +~~~ + ~~~{objective-c} - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions @@ -29,15 +38,6 @@ Those having such special caching requirements can set a shared URL cache in `-a } ~~~ -~~~{swift} -func application(application: UIApplication!, didFinishLaunchingWithOptions launchOptions: NSDictionary!) -> Bool { - let URLCache = NSURLCache(memoryCapacity: 4 * 1024 * 1024, diskCapacity: 20 * 1024 * 1024, diskPath: nil) - NSURLCache.setSharedURLCache(URLCache) - - return true -} -~~~ - Caching policies are specified in both the request (by the client) and in the response (by the server). Understanding these policies and how they relate to one another is essential to finding the optimal behavior for your application. ## `NSURLRequestCachePolicy` @@ -125,6 +125,20 @@ Once the server response has been received, the `NSURLConnection` delegate has a In `-connection:willCacheResponse:`, the `cachedResponse` object has been automatically created from the result of the URL connection. Because there is no mutable counterpart to `NSCachedURLResponse`, in order to change anything about `cachedResponse`, a new object must be constructed, passing any modified values into `–initWithResponse:data:userInfo:storagePolicy:`, for instance: +~~~{swift} +// MARK: NSURLConnectionDataDelegate + +func connection(connection: NSURLConnection!, willCacheResponse cachedResponse: NSCachedURLResponse!) -> NSCachedURLResponse! { + var mutableUserInfo = NSMutableDictionary(dictionary: cachedResponse.userInfo) + var mutableData = NSMutableData(data: cachedResponse.data) + var storagePolicy: NSURLCacheStoragePolicy = .AllowedInMemoryOnly + + // ... + + return NSCachedURLResponse(response: cachedResponse.response, data: mutableData, userInfo: mutableUserInfo, storagePolicy: storagePolicy) +} +~~~ + ~~~{objective-c} - (NSCachedURLResponse *)connection:(NSURLConnection *)connection willCacheResponse:(NSCachedURLResponse *)cachedResponse @@ -142,22 +156,14 @@ In `-connection:willCacheResponse:`, the `cachedResponse` object has been automa } ~~~ -~~~{swift} -// MARK: NSURLConnectionDataDelegate +If `-connection:willCacheResponse:` returns `nil`, the response will not be cached. +~~~{swift} func connection(connection: NSURLConnection!, willCacheResponse cachedResponse: NSCachedURLResponse!) -> NSCachedURLResponse! { - var mutableUserInfo = NSMutableDictionary(dictionary: cachedResponse.userInfo) - var mutableData = NSMutableData(data: cachedResponse.data) - var storagePolicy: NSURLCacheStoragePolicy = .AllowedInMemoryOnly - - // ... - - return NSCachedURLResponse(response: cachedResponse.response, data: mutableData, userInfo: mutableUserInfo, storagePolicy: storagePolicy) + return nil } ~~~ -If `-connection:willCacheResponse:` returns `nil`, the response will not be cached. - ~~~{objective-c} - (NSCachedURLResponse *)connection:(NSURLConnection *)connection willCacheResponse:(NSCachedURLResponse *)cachedResponse @@ -166,12 +172,6 @@ If `-connection:willCacheResponse:` returns `nil`, the response will not be cach } ~~~ -~~~{swift} -func connection(connection: NSURLConnection!, willCacheResponse cachedResponse: NSCachedURLResponse!) -> NSCachedURLResponse! { - return nil -} -~~~ - When left unimplemented, `NSURLConnection` will simply use the cached response that would otherwise be passed into `-connection:willCacheResponse:`, so unless you need to change or prevent caching, this method does not need to be implemented in the delegate. ## Caveats diff --git a/2013-04-01-icloud.md b/2013-04-01-icloud.md index 17ca8d66..f8c77019 100644 --- a/2013-04-01-icloud.md +++ b/2013-04-01-icloud.md @@ -38,6 +38,17 @@ Everyone knows that key-value storage is the secret to achieving Web Scale. That Inject a healthy dose of Web Scale convenience into your app by incorporating `NSUbiquitousKeyValueStore` into your app: +~~~{swift} +func application(application: UIApplication!, didFinishLaunchingWithOptions launchOptions: NSDictionary!) -> Bool { + NSNotificationCenter.defaultCenter().addObserverForName(NSUbiquitousKeyValueStoreDidChangeExternallyNotification, object: NSUbiquitousKeyValueStore.defaultStore(), queue: NSOperationQueue.mainQueue()) { (notification) in + let ubiquitousKeyValueStore = notification.object as NSUbiquitousKeyValueStore + ubiquitousKeyValueStore.synchronize() + } + + return true +} +~~~ + ~~~{objective-c} - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions @@ -59,17 +70,6 @@ didFinishLaunchingWithOptions:(NSDictionary *)launchOptions } ~~~ -~~~{swift} -func application(application: UIApplication!, didFinishLaunchingWithOptions launchOptions: NSDictionary!) -> Bool { - NSNotificationCenter.defaultCenter().addObserverForName(NSUbiquitousKeyValueStoreDidChangeExternallyNotification, object: NSUbiquitousKeyValueStore.defaultStore(), queue: NSOperationQueue.mainQueue()) { (notification) in - let ubiquitousKeyValueStore = notification.object as NSUbiquitousKeyValueStore - ubiquitousKeyValueStore.synchronize() - } - - return true -} -~~~ - ## Document Storage... in the Cloud! > Adopting iCloud document storage makes your app’s documents available on all of a user’s devices. Documents automatically implement most of the behavior expected of iCloud apps. Specifically, a document automatically ensures that local changes are safely coordinated with iCloud-originated changes. diff --git a/2013-04-15-nssecurecoding.md b/2013-04-15-nssecurecoding.md index ed8f88b8..4a9084e2 100644 --- a/2013-04-15-nssecurecoding.md +++ b/2013-04-15-nssecurecoding.md @@ -28,6 +28,12 @@ Anyway, `NSSecureCoding` patches this vulnerability by establishing a contract f Whereas a standard, secure implementation of `-initWithCoder:` might have a check like: +~~~{swift} +if let object = decoder.decodeObjectForKey("key") as? SomeClass { + // ... +} +~~~ + ~~~{objective-c} id obj = [decoder decodeObjectForKey:@"myKey"]; if (![obj isKindOfClass:[MyClass class]]) { @@ -35,23 +41,17 @@ if (![obj isKindOfClass:[MyClass class]]) { } ~~~ +...an `NSSecureCoding`-conforming class would use: + ~~~{swift} -if let object = decoder.decodeObjectForKey("key") as? SomeClass { - // ... -} +let object = decoder.decodeObjectOfClass(SomeClass.self, forKey: "key") as SomeClass ~~~ -...an `NSSecureCoding`-conforming class would use: - ~~~{objective-c} id obj = [decoder decodeObjectOfClass:[MyClass class] forKey:@"myKey"]; ~~~ -~~~{swift} -let object = decoder.decodeObjectOfClass(SomeClass.self, forKey: "key") as SomeClass -~~~ - Sometimes, a little API change makes all of the difference. --- diff --git a/2013-04-29-mklocalsearch.md b/2013-04-29-mklocalsearch.md index 8b1c3443..9eab9e8e 100644 --- a/2013-04-29-mklocalsearch.md +++ b/2013-04-29-mklocalsearch.md @@ -18,16 +18,6 @@ In all of the hubbub of torch burning and pitchfork raising, you may have comple But before you go and rush into using `MKLocalSearch`, you'll have to know a few things about its friends. You see, `MKLocalSearch` has its functionality divided across `MKLocalSearchRequest` and `MKLocalSearchResponse`: -~~~{objective-c} -MKLocalSearchRequest *request = [[MKLocalSearchRequest alloc] init]; -request.naturalLanguageQuery = @"Restaurants"; -request.region = mapView.region; -MKLocalSearch *search = [[MKLocalSearch alloc] initWithRequest:request]; -[search startWithCompletionHandler:^(MKLocalSearchResponse *response, NSError *error) { - NSLog(@"Map Items: %@", response.mapItems); -}]; -~~~ - ~~~{swift} let request = MKLocalSearchRequest() request.naturalLanguageQuery = "Restaurants" @@ -41,6 +31,16 @@ search.startWithCompletionHandler { (response, error) in } ~~~ +~~~{objective-c} +MKLocalSearchRequest *request = [[MKLocalSearchRequest alloc] init]; +request.naturalLanguageQuery = @"Restaurants"; +request.region = mapView.region; +MKLocalSearch *search = [[MKLocalSearch alloc] initWithRequest:request]; +[search startWithCompletionHandler:^(MKLocalSearchResponse *response, NSError *error) { + NSLog(@"Map Items: %@", response.mapItems); +}]; +~~~ + `MKLocalSearchRequest` takes a `naturalLanguageQuery`, such as "Taxidermists", and an optional bounding geographic `region` to constrain results. In practice, the `region` is usually passed from an `MKMapView`. `MKLocalSearchResponse` is returned in the eponymous block handler of `MKLocalSearch -startWithCompletionHandler:`, and returns an array of `MKMapItem` objects. Each `MKMapItem` contains information like `name`, `phoneNumber`, `url` and address information via the `placemark` property. diff --git a/2013-05-13-nscoding.md b/2013-05-13-nscoding.md index f7a39e69..f362f2cd 100644 --- a/2013-05-13-nscoding.md +++ b/2013-05-13-nscoding.md @@ -94,6 +94,35 @@ This article will look at the how's, when's, and why's of `NSKeyedArchiver` and For example: +~~~{swift} +class Book: NSObject, NSCoding { + var title: String! + var author: String! + var pageCount: Int! + var categories: [String]! + var available: Bool! + + // MARK: NSCoding + + required convenience init(coder decoder: NSCoder) { + self.init() + self.title = decoder.decodeObjectForKey("title") as String? + self.author = decoder.decodeObjectForKey("author") as String? + self.pageCount = decoder.decodeIntegerForKey("pageCount") + self.categories = decoder.decodeObjectForKey("categories") as [String]! + self.available = decoder.decodeBoolForKey("available") + } + + func encodeWithCoder(coder: NSCoder) { + coder.encodeObject(self.title, forKey: "title") + coder.encodeObject(self.author, forKey: "author") + coder.encodeInt(Int32(self.pageCount), forKey: "pageCount") + coder.encodeObject(self.categories, forKey: "categories") + coder.encodeBool(self.available, forKey: "available") + } +} +~~~ + ~~~{objective-c} @interface Book : NSObject @property NSString *title; @@ -133,35 +162,6 @@ For example: @end ~~~ -~~~{swift} -class Book: NSObject, NSCoding { - var title: String! - var author: String! - var pageCount: Int! - var categories: [String]! - var available: Bool! - - // MARK: NSCoding - - required convenience init(coder decoder: NSCoder) { - self.init() - self.title = decoder.decodeObjectForKey("title") as String? - self.author = decoder.decodeObjectForKey("author") as String? - self.pageCount = decoder.decodeIntegerForKey("pageCount") - self.categories = decoder.decodeObjectForKey("categories") as [String]! - self.available = decoder.decodeBoolForKey("available") - } - - func encodeWithCoder(coder: NSCoder) { - coder.encodeObject(self.title, forKey: "title") - coder.encodeObject(self.author, forKey: "author") - coder.encodeInt(Int32(self.pageCount), forKey: "pageCount") - coder.encodeObject(self.categories, forKey: "categories") - coder.encodeBool(self.available, forKey: "available") - } -} -~~~ - As you can see, `NSCoding` is mostly boilerplate. Each property is encoded or decoded as an object or type, using the name of the property of as the key each time. (Some developers prefer to define `NSString *` constants for each keypath, but this is usually unnecessary). But boilerplate can be a good things sometimes—with direct control over the entire serialization process, it remains flexible to account for things like: @@ -183,24 +183,24 @@ An `NSCoding`-backed table view controller might, for instance, set its collecti #### Archiving -~~~{objective-c} -[NSKeyedArchiver archiveRootObject:books toFile:@"/path/to/archive"]; -~~~ - ~~~{swift} NSKeyedArchiver.archiveRootObject(books, toFile: "/path/to/archive") ~~~ -#### Unarchiving - ~~~{objective-c} -[NSKeyedUnarchiver unarchiveObjectWithFile:@"/path/to/archive"]; +[NSKeyedArchiver archiveRootObject:books toFile:@"/path/to/archive"]; ~~~ +#### Unarchiving + ~~~{swift} NSKeyedUnarchiver.unarchiveObjectWithFile("/path/to/archive") ~~~ +~~~{objective-c} +[NSKeyedUnarchiver unarchiveObjectWithFile:@"/path/to/archive"]; +~~~ + ## `NSUserDefaults` Each app has its own database of user preferences, which can store and retrieve any `NSCoding`-compatible object or C value. @@ -209,29 +209,29 @@ While it is not advisable to store an entire object graph into `NSUserDefaults`, #### Archiving -~~~{objective-c} -NSData *data = [NSKeyedArchiver archivedDataWithRootObject:books]; -[[NSUserDefaults standardUserDefaults] setObject:data forKey:@"books"]; -~~~ - ~~~{swift} let data = NSKeyedArchiver.archivedDataWithRootObject(books) NSUserDefaults.standardUserDefaults().setObject(data, forKey: "books") ~~~ -#### Unarchiving - ~~~{objective-c} -NSData *data = [[NSUserDefaults standardUserDefaults] objectForKey:@"books"]; -NSArray *books = [NSKeyedUnarchiver unarchiveObjectWithData:data]; +NSData *data = [NSKeyedArchiver archivedDataWithRootObject:books]; +[[NSUserDefaults standardUserDefaults] setObject:data forKey:@"books"]; ~~~ +#### Unarchiving + ~~~{swift} if let data = NSUserDefaults.standardUserDefaults().objectForKey("books") as? NSData { let books = NSKeyedUnarchiver.unarchiveObjectWithData(data) } ~~~ +~~~{objective-c} +NSData *data = [[NSUserDefaults standardUserDefaults] objectForKey:@"books"]; +NSArray *books = [NSKeyedUnarchiver unarchiveObjectWithData:data]; +~~~ + --- As developers, it is our responsibility to understand the goals and needs of our applications, and to resist the urge to over-engineer and prematurely optimize our solutions. diff --git a/2013-06-02-nsdatadetector.md b/2013-06-02-nsdatadetector.md index 7f49604d..c2b9e64e 100644 --- a/2013-06-02-nsdatadetector.md +++ b/2013-06-02-nsdatadetector.md @@ -25,6 +25,16 @@ You can think of it as a regexp matcher with incredibly complicated expressions `NSDataDetector` objects are initialized with a bitmask of types of information to check, and then passed strings to match on. Like `NSRegularExpression`, each match found in a string is represented by a `NSTextCheckingResult`, which has details like character range and match type. However, `NSDataDetector`-specific types may also contain metadata such as address or date components. +~~~{swift} +let string = "123 Main St. / (555) 555-5555" +let types: NSTextCheckingType = .Address | .PhoneNumber +var error: NSError? +let detector = NSDataDetector(types: types.toRaw(), error: &error) +detector.enumerateMatchesInString(string, options: nil, range: NSMakeRange(0, (string as NSString).length)) { (result, flags, _) in + println(result) +} +~~~ + ~~~{objective-c} NSError *error = nil; NSDataDetector *detector = [NSDataDetector dataDetectorWithTypes:NSTextCheckingTypeAddress @@ -41,16 +51,6 @@ NSString *string = @"123 Main St. / (555) 555-5555"; }]; ~~~ -~~~{swift} -let string = "123 Main St. / (555) 555-5555" -let types: NSTextCheckingType = .Address | .PhoneNumber -var error: NSError? -let detector = NSDataDetector(types: types.toRaw(), error: &error) -detector.enumerateMatchesInString(string, options: nil, range: NSMakeRange(0, (string as NSString).length)) { (result, flags, _) in - println(result) -} -~~~ - > When initializing `NSDataDetector`, be sure to specify only the types you're interested in. With each additional type to be checked comes a nontrivial performance cost. ## Data Detector Match Types diff --git a/2013-06-24-uuid-udid-unique-identifier.md b/2013-06-24-uuid-udid-unique-identifier.md index 7a64404f..a3a51c7b 100644 --- a/2013-06-24-uuid-udid-unique-identifier.md +++ b/2013-06-24-uuid-udid-unique-identifier.md @@ -50,26 +50,41 @@ Users can opt out of ad targeting in a Settings screen added in iOS 6.1, found a `NSUUID` was added to Foundation in iOS 6 as a way to easily create UUIDs. How easy? +~~~{swift} +let UUID = NSUUID.UUID().UUIDString +~~~ + ~~~{objective-c} NSString *UUID = [[NSUUID UUID] UUIDString]; ~~~ +If your app targets iOS 5 or earlier, however, you have to settle for Core Foundation functions on `CFUUIDRef`: + ~~~{swift} -let UUID = NSUUID.UUID().UUIDString +let UUID = CFUUIDCreateString(nil, CFUUIDCreate(nil)) ~~~ -If your app targets iOS 5 or earlier, however, you have to settle for Core Foundation functions on `CFUUIDRef`: - ~~~{objective-c} CFUUIDRef uuid = CFUUIDCreate(NULL); NSString *UUID = CFUUIDCreateString(NULL, uuid); ~~~ +For apps building against a base SDK without the vendor or advertising identifier APIs, a similar effect can be achieved—as recommended in the deprecation notes—by using [`NSUserDefaults`](http://developer.apple.com/library/ios/#documentation/cocoa/reference/foundation/Classes/NSUserDefaults_Class/Reference/Reference.html): + ~~~{swift} -let UUID = CFUUIDCreateString(nil, CFUUIDCreate(nil)) -~~~ + func application(application: UIApplication!, didFinishLaunchingWithOptions launchOptions: NSDictionary!) -> Bool { -For apps building against a base SDK without the vendor or advertising identifier APIs, a similar effect can be achieved—as recommended in the deprecation notes—by using [`NSUserDefaults`](http://developer.apple.com/library/ios/#documentation/cocoa/reference/foundation/Classes/NSUserDefaults_Class/Reference/Reference.html): + let userDefaults = NSUserDefaults.standardUserDefaults() + + if userDefaults.objectForKey("ApplicationUniqueIdentifier") == nil { + let UUID = NSUUID.UUID().UUIDString + userDefaults.setObject(UUID, forKey: "ApplicationUniqueIdentifier") + userDefaults.synchronize() + } + + return true +} +~~~ ~~~{objective-c} - (BOOL)application:(UIApplication *)application @@ -87,21 +102,6 @@ didFinishLaunchingWithOptions:(NSDictionary *)launchOptions } ~~~ -~~~{swift} - func application(application: UIApplication!, didFinishLaunchingWithOptions launchOptions: NSDictionary!) -> Bool { - - let userDefaults = NSUserDefaults.standardUserDefaults() - - if userDefaults.objectForKey("ApplicationUniqueIdentifier") == nil { - let UUID = NSUUID.UUID().UUIDString - userDefaults.setObject(UUID, forKey: "ApplicationUniqueIdentifier") - userDefaults.synchronize() - } - - return true -} -~~~ - This way, a UUID will be generated once when the app is launched for the first time, and then stored in `NSUserDefaults` to be retrieved on each subsequent app launch. Unlike advertising or vendor identifiers, these identifiers would not be shared across other apps, but for most intents and purposes, this is works just fine. --- diff --git a/2013-07-15-nspredicate.md b/2013-07-15-nspredicate.md index d153ef2a..8538d566 100644 --- a/2013-07-15-nspredicate.md +++ b/2013-07-15-nspredicate.md @@ -18,6 +18,43 @@ It's easier to show `NSPredicate` in use, rather than talk about it in the abstr | Charlie | Smith | 33 | | Quentin | Alberts | 31 | +~~~{swift} +class Person: NSObject { + let firstName: String + let lastName: String + let age: Int + + init(firstName: String, lastName: String, age: Int) { + self.firstName = firstName + self.lastName = lastName + self.age = age + } + + override var description: String { + return "\(firstName) \(lastName)" + } +} + +let alice = Person(firstName: "Alice", lastName: "Smith", age: 24) +let bob = Person(firstName: "Bob", lastName: "Jones", age: 27) +let charlie = Person(firstName: "Charlie", lastName: "Smith", age: 33) +let quentin = Person(firstName: "Quentin", lastName: "Alberts", age: 31) +let people = [alice, bob, charlie, quentin] + +let bobPredicate = NSPredicate(format: "firstName = 'Bob'") +let smithPredicate = NSPredicate(format: "lastName = %@", "Smith") +let thirtiesPredicate = NSPredicate(format: "age >= 30") + +(people as NSArray).filteredArrayUsingPredicate(bobPredicate) +// ["Bob Jones"] + +(people as NSArray).filteredArrayUsingPredicate(smithPredicate) +// ["Alice Smith", "Charlie Smith"] + +(people as NSArray).filteredArrayUsingPredicate(thirtiesPredicate) +// ["Charlie Smith", "Quentin Alberts"] +~~~ + ~~~{objective-c} @interface Person : NSObject @property NSString *firstName; @@ -62,43 +99,6 @@ NSLog(@"Smiths: %@", [people filteredArrayUsingPredicate:smithPredicate]); NSLog(@"30's: %@", [people filteredArrayUsingPredicate:thirtiesPredicate]); ~~~ -~~~{swift} -class Person: NSObject { - let firstName: String - let lastName: String - let age: Int - - init(firstName: String, lastName: String, age: Int) { - self.firstName = firstName - self.lastName = lastName - self.age = age - } - - override var description: String { - return "\(firstName) \(lastName)" - } -} - -let alice = Person(firstName: "Alice", lastName: "Smith", age: 24) -let bob = Person(firstName: "Bob", lastName: "Jones", age: 27) -let charlie = Person(firstName: "Charlie", lastName: "Smith", age: 33) -let quentin = Person(firstName: "Quentin", lastName: "Alberts", age: 31) -let people = [alice, bob, charlie, quentin] - -let bobPredicate = NSPredicate(format: "firstName = 'Bob'") -let smithPredicate = NSPredicate(format: "lastName = %@", "Smith") -let thirtiesPredicate = NSPredicate(format: "age >= 30") - -(people as NSArray).filteredArrayUsingPredicate(bobPredicate) -// ["Bob Jones"] - -(people as NSArray).filteredArrayUsingPredicate(smithPredicate) -// ["Alice Smith", "Charlie Smith"] - -(people as NSArray).filteredArrayUsingPredicate(thirtiesPredicate) -// ["Charlie Smith", "Quentin Alberts"] -~~~ - ## Using `NSPredicate` with Collections Foundation provides methods to filter `NSArray` / `NSMutableArray` & `NSSet` / `NSMutableSet` with predicates. @@ -120,13 +120,6 @@ Mutable collections, `NSMutableArray` & `NSMutableSet` have the method `filterUs > - `%@` is a var arg substitution for an object value—often a string, number, or date. > - `%K` is a var arg substitution for a key path. -~~~{objective-c} -NSPredicate *ageIs33Predicate = [NSPredicate predicateWithFormat:@"%K = %@", @"age", @33]; - -// ["Charlie Smith"] -NSLog(@"Age 33: %@", [people filteredArrayUsingPredicate:ageIs33Predicate]); -~~~ - ~~~{swift} let ageIs33Predicate = NSPredicate(format: "%K = %@", "age", "33") @@ -134,15 +127,15 @@ let ageIs33Predicate = NSPredicate(format: "%K = %@", "age", "33") // ["Charlie Smith"] ~~~ -> - `$VARIABLE_NAME` is a value that can be substituted with `NSPredicate -predicateWithSubstitutionVariables:`. - ~~~{objective-c} -NSPredicate *namesBeginningWithLetterPredicate = [NSPredicate predicateWithFormat:@"(firstName BEGINSWITH[cd] $letter) OR (lastName BEGINSWITH[cd] $letter)"]; +NSPredicate *ageIs33Predicate = [NSPredicate predicateWithFormat:@"%K = %@", @"age", @33]; -// ["Alice Smith", "Quentin Alberts"] -NSLog(@"'A' Names: %@", [people filteredArrayUsingPredicate:[namesBeginningWithLetterPredicate predicateWithSubstitutionVariables:@{@"letter": @"A"}]]); +// ["Charlie Smith"] +NSLog(@"Age 33: %@", [people filteredArrayUsingPredicate:ageIs33Predicate]); ~~~ +> - `$VARIABLE_NAME` is a value that can be substituted with `NSPredicate -predicateWithSubstitutionVariables:`. + ~~~{swift} let namesBeginningWithLetterPredicate = NSPredicate(format: "(firstName BEGINSWITH[cd] $letter) OR (lastName BEGINSWITH[cd] $letter)") @@ -150,6 +143,13 @@ let namesBeginningWithLetterPredicate = NSPredicate(format: "(firstName BEGINSWI // ["Alice Smith", "Quentin Alberts"] ~~~ +~~~{objective-c} +NSPredicate *namesBeginningWithLetterPredicate = [NSPredicate predicateWithFormat:@"(firstName BEGINSWITH[cd] $letter) OR (lastName BEGINSWITH[cd] $letter)"]; + +// ["Alice Smith", "Quentin Alberts"] +NSLog(@"'A' Names: %@", [people filteredArrayUsingPredicate:[namesBeginningWithLetterPredicate predicateWithSubstitutionVariables:@{@"letter": @"A"}]]); +~~~ + ### Basic Comparisons > - `=`, `==`: The left-hand expression is equal to the right-hand expression. @@ -203,18 +203,18 @@ We saw that `AND` & `OR` can be used in predicate format strings to create compo For example, the following predicates are equivalent: -~~~{objective-c} -[NSCompoundPredicate andPredicateWithSubpredicates:@[[NSPredicate predicateWithFormat:@"age > 25"], [NSPredicate predicateWithFormat:@"firstName = %@", @"Quentin"]]]; - -[NSPredicate predicateWithFormat:@"(age > 25) AND (firstName = %@)", @"Quentin"]; -~~~ - ~~~{swift} NSCompoundPredicate(type: .AndPredicateType, subpredicates: [NSPredicate(format: "age > 25"), NSPredicate(format: "firstName = %@", "Quentin")]) NSPredicate(format: "(age > 25) AND (firstName = %@)", "Quentin") ~~~ +~~~{objective-c} +[NSCompoundPredicate andPredicateWithSubpredicates:@[[NSPredicate predicateWithFormat:@"age > 25"], [NSPredicate predicateWithFormat:@"firstName = %@", @"Quentin"]]]; + +[NSPredicate predicateWithFormat:@"(age > 25) AND (firstName = %@)", @"Quentin"]; +~~~ + While the syntax string literal is certainly easier to type, there are occasions where you may need to combine existing predicates. In these cases, `NSCompoundPredicate -andPredicateWithSubpredicates:` & `-orPredicateWithSubpredicates:` is the way to go. ## `NSComparisonPredicate` @@ -226,10 +226,10 @@ Analyzing its class constructor provides a glimpse into the way `NSPredicate` fo ~~~{objective-c} + (NSPredicate *)predicateWithLeftExpression:(NSExpression *)lhs - rightExpression:(NSExpression *)rhs - modifier:(NSComparisonPredicateModifier)modifier - type:(NSPredicateOperatorType)type - options:(NSUInteger)options + rightExpression:(NSExpression *)rhs + modifier:(NSComparisonPredicateModifier)modifier + type:(NSPredicateOperatorType)type + options:(NSUInteger)options ~~~ #### Parameters @@ -242,7 +242,26 @@ Analyzing its class constructor provides a glimpse into the way `NSPredicate` fo ### `NSComparisonPredicate` Types -