diff --git a/2012-07-07-nsindexset.md b/2012-07-07-nsindexset.md index 9a34fb3e..ffa692e8 100644 --- a/2012-07-07-nsindexset.md +++ b/2012-07-07-nsindexset.md @@ -1,6 +1,6 @@ --- title: NSIndexSet -author: Mattt Thompson +author: Mattt category: Cocoa tags: nshipster excerpt: "NSIndexSet (like its mutable counterpart, NSMutableIndexSet) is a sorted collection of unique unsigned integers. Think of it like an NSRange that supports non-contiguous series. It has wicked fast operations for finding indexes in ranges or set intersections, and comes with all of the convenience methods you'd expect in a Foundation collection class." diff --git a/2012-07-14-nscache.md b/2012-07-14-nscache.md index 7f816b3b..513fa7f8 100644 --- a/2012-07-14-nscache.md +++ b/2012-07-14-nscache.md @@ -1,6 +1,6 @@ --- title: NSCache -author: Mattt Thompson +author: Mattt category: Cocoa tags: nshipster excerpt: "Poor NSCache, always being overshadowed by NSMutableDictionary. It's as if no one knew how it provides all of that garbage collection behavior that developers take great pains to re-implement themselves." @@ -41,7 +41,7 @@ There's also a whole part about controlling whether objects are automatically ev Despite all of this, developers should be using `NSCache` a lot more than they currently are. Anything in your project that you call a "cache", but isn't `NSCache` would be prime candidates for replacement. But if you do, just be sure to stick to the classics: `objectForKey:`, `setObject:forKey:` & `removeObjectForKey:`. -Still not convinved? As a parting gift, we'll even make it easier, via a little subscripting majick: +Still not convinced? As a parting gift, we'll even make it easier, via a little subscripting majick: ```swift extension NSCache { @@ -58,4 +58,7 @@ extension NSCache { } } } -``` \ No newline at end of file +``` + +> **Note:** Due to changes in Objective-C generics in Swift 3, the +> subscript given above will only work in Swift 2.3 and earlier. diff --git a/2012-07-24-nssortdescriptor.md b/2012-07-24-nssortdescriptor.md index 77037028..16692052 100644 --- a/2012-07-24-nssortdescriptor.md +++ b/2012-07-24-nssortdescriptor.md @@ -1,19 +1,18 @@ --- title: NSSortDescriptor -author: Mattt Thompson +author: Mattt category: Cocoa tags: nshipster excerpt: "Sorting: it's the mainstay of Computer Science 101 exams and whiteboarding interview questions. But when was the last time you actually needed to know how to implement Quicksort yourself?" status: - swift: 2.2 - reviewed: April 10, 2016 + swift: 1.1 --- Sorting: it's the mainstay of Computer Science 101 exams and whiteboarding interview questions. But when was the last time you actually needed to know how to implement Quicksort yourself? When making apps, sorting is just something you can assume to be fast, and utility is a function of convenience and clarity of intention. And when it comes to that, you'd be hard-pressed to find a better implementation than Foundation's `NSSortDescriptor`. -* * * +--- `NSSortDescriptor` objects are constructed with the following parameters: @@ -28,7 +27,7 @@ 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`. -~~~{swift} +```swift class Person: NSObject { let firstName: String let lastName: String @@ -44,9 +43,9 @@ class Person: NSObject { return "\(firstName) \(lastName)" } } -~~~ +``` -~~~{objective-c} +```objc @interface Person : NSObject @property NSString *firstName; @property NSString *lastName; @@ -60,12 +59,12 @@ class Person: NSObject { } @end -~~~ +``` Given the following dataset: | `firstName` | `lastName` | `age` | -|-------------|------------|-------| +| ----------- | ---------- | ----- | | Alice | Smith | 24 | | Bob | Jones | 27 | | Charlie | Smith | 33 | @@ -73,15 +72,15 @@ Given the following dataset: Here are some of the different ways they can be sorted by combinations of `NSSortDescriptor`: -~~~{swift} +```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: #selector(NSString.localizedStandardCompare(_:))) -let lastNameSortDescriptor = NSSortDescriptor(key: "lastName", ascending: true, selector: #selector(NSString.localizedStandardCompare(_:))) +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]) @@ -92,9 +91,9 @@ let sortedByFirstName = (people as NSArray).sortedArrayUsingDescriptors([firstNa let sortedByLastNameFirstName = (people as NSArray).sortedArrayUsingDescriptors([lastNameSortDescriptor, firstNameSortDescriptor]) // "Quentin Alberts", "Bob Jones", "Alice Smith", "Charlie Smith" -~~~ +``` -~~~{objective-c} +```objc NSArray *firstNames = @[ @"Alice", @"Bob", @"Charlie", @"Quentin" ]; NSArray *lastNames = @[ @"Smith", @"Jones", @"Smith", @"Alberts" ]; NSArray *ages = @[ @24, @27, @33, @31 ]; @@ -127,9 +126,9 @@ NSLog(@"By first name: %@", [people sortedArrayUsingDescriptors:@[firstNameSortD NSLog(@"By last name, first name: %@", [people 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-datecomponents.md b/2012-07-31-datecomponents.md new file mode 100644 index 00000000..32d098d9 --- /dev/null +++ b/2012-07-31-datecomponents.md @@ -0,0 +1,428 @@ +--- +title: DateComponents +author: Mattt +category: Cocoa +excerpt: >- + `DateComponents` is a useful, but ambiguous type. + Taken in one context, + date components can be used to represent a specific calendar date. + But in another context, + the same object might instead be used as a duration of time. +revisions: + "2012-07-31": Original Publication + "2018-10-10": Expanded details +status: + swift: 4.2 + reviewed: October 10, 2018 +--- + +There are as many mnemonic devices for making sense of time +as the day is long. +["Spring ahead, Fall back"](https://en.wikipedia.org/wiki/Daylight_saving_time). +[That knuckle trick for remembering the lengths of months.](https://en.wikipedia.org/wiki/Knuckle_mnemonic) +Musical theater aficionados can tell you in quick measure +[the length of a year in minutes](https://en.wikipedia.org/wiki/Seasons_of_Love). +Mathematicians, though, have the best ones of all: +Did you know that the fifth hyperfactorial (5⁵ × 4⁴ × 3³ × 2² × 1¹) +is equal to 86400000, or exactly 1 (civil) day in milliseconds? +Or that ten factorial (10! = 10 × 9 × 8… = 3628800) seconds +is equal to 6 weeks? + +Amazing, right? +But I want you to forget all of those, +at least for the purposes of programming. + +As we discussed in +[our article about `Date`, et al.](/timeinterval-date-dateinterval), +the only unit of time with a constant duration is the second +(and its subdivisions). +When you want to express the duration of, 1 day, +don't write `60 * 60 * 24`. +Instead, write `DateComponents(day: 1)`. + +"What is `DateComponents`", you ask? +It's a relatively recent addition to Foundation +for representing a date or duration of time, +and it's the subject of this article. + +--- + +`DateComponents` is a useful, but ambiguous type. + +Taken in one context, +date components can be used to represent a specific calendar date. +But in another context, +the same object might instead be used as a duration of time. +For example, a date components object with +`year` set to `2018`, +`month` set to `10`, and +`day` set to `10` +could represent a period of 2018 years, 10 months, and 10 days +or the tenth day of the tenth month in the year 2018: + +```swift +import Foundation + +let calendar = Calendar.current +let dateComponents = DateComponents(calendar: calendar, + year: 2018, + month: 10, + day: 10) + +// DateComponents as a date specifier +let date = calendar.date(from: dateComponents)! // 2018-10-10 + +// DateComponents as a duration of time +calendar.date(byAdding: dateComponents, to: date) // 4037-08-20 +``` + +Let's explore both of these contexts individually, +starting with date components as a representation of a calendar date: + +--- + +## Date Components as a Representation of a Calendar Date + +### Extracting Components from a Date + +`DateComponents` objects can be created for a particular date +using the `Calendar` method `components(_:from:)`: + +```swift +let date = Date() // 2018-10-10T10:00:00+00:00 +let calendar = Calendar.current +calendar.dateComponents([.year, .month, .day], from: date) +// {{ page.updated_on | date: '(year: %Y, month: %-M, day: %-d)' }} +``` + +Each property in `DateComponents` +has a corresponding entry in the +[`Calendar.Component` enumeration](https://developer.apple.com/documentation/foundation/calendar/component). + +{% info %} +For best results, +specify only the date components / calendar units that you're interested in. +{% endinfo %} + +For reference, +here's what the `dateComponents(_:from:)` method produces +when you specify all of the available calendar units: + +```swift +import Foundation + +let date = Date() // 2018-10-10T10:00:00+00:00 +let calendar = Calendar.current +let dateComponents = calendar.dateComponents( + [.calendar, .timeZone, + .era, .quarter, + .year, .month, .day, + .hour, .minute, .second, .nanosecond, + .weekday, .weekdayOrdinal, + .weekOfMonth, .weekOfYear, .yearForWeekOfYear], + from: date) +``` + +| Component | Value | +| ------------------- | ------------------- | +| `calendar` | gregorian | +| `timeZone` | America/Los_Angeles | +| `era` | 1 | +| `quarter` | 0 | +| `year` | 2018 | +| `month` | 10 | +| `day` | 10 | +| `hour` | 10 | +| `minute` | 0 | +| `second` | 0 | +| `nanosecond` | 0 | +| `weekday` | 4 | +| `weekdayOrdinal` | 2 | +| `weekOfMonth` | 2 | +| `weekOfYear` | 41 | +| `yearForWeekOfYear` | 2018 | +| `isLeapMonth` | false | + +One of the advantages of learning Foundation APIs +is that you gain a deeper understanding of the domains that it models. +Unless you're a horologist or ISO 8601 enthusiast, +there are probably a few of these components that you're less familiar with, +so let's take a look at some of the more obscure ones: + +### Era and Year + +The Gregorian calendar has two [eras](https://en.wikipedia.org/wiki/Calendar_era): +BC and AD (alternatively, C.E. and B.C.E). +Their respective integer date component values are `0` and `1`. +No matter what the era is, the `year` component is always a positive number. + +### Quarter + +In academia and business, +calendar years are often divided up into +[quarter](https://en.wikipedia.org/wiki/Calendar_year#Quarters) +(Q1, Q2, Q3, Q4). + +{% error %} + +In iOS 12 and macOS Mojave, +the `dateComponents(_:from:)` method +doesn't populate the `quarter` property +for the returned value, even with the unit is specified. +See [rdar://35247464](http://www.openradar.me/35247464). + +As a workaround, +you can use `DateFormatter` to generate a string +using the date format `"Q"` +and parse its integer value: + +```swift +let formatter = DateFormatter() +formatter.dateFormat = "Q" +Int(formatter.string(from: Date())) // 4 +``` + +{% enderror %} + +### Weekday, Weekday Ordinal, and Week of Month + +Weekdays are given integer values starting with +1 for Sunday +and ending with 7 for Saturday. + +But the first weekday varies across different locales. +The first weekday in the calendar depends on your current locale. +The United States, China, and other countries begin their weeks on Sunday. +Most countries in Europe, as well as India, Australia, and elsewhere +typically designate Monday as their first weekday. +Certain locales in the Middle East and North Africa +use Saturday as the start of their week. + +The locale also affects the values returned for +the `weekdayOrdinal` and `weekOfMonth` components. +In the `en-US` locale, +the date components returned for October 7th, 2018 +would have `weekdayOrdinal` equal to 1 +(meaning "the first Sunday of the month") +and a `weekOfMonth` value of 2 +(meaning "the second week of the month"). + +### Week of Year and Year for Week of Year + +These two are probably the most confusing of all the date components. +Part of that has to do with the ridiculous API name `yearForWeekOfYear`, +but it mostly comes down to the lack of general awareness for +[ISO week dates](https://en.wikipedia.org/wiki/ISO_week_date). + +The `weekOfYear` component +returns the ISO week number for the date in question. +For example, October 10th, 2018 occurs on the 41st ISO week. + +The `yearForWeekOfYear` component +is helpful for weeks that span two calendar years. +For example, New Years Eve this year --- December 31st, 2018 --- +falls on a Monday. +Because occurs in the first week of 2019, +its `weekOfYear` value is `1`, +its `yearForWeekOfYear` value is `2019`, +and its `year` value is `2018` + +{% warning %} + +In contrast to the `year` date component, +`yearForWeekOfYear` has a negative value +for years before the common era. +For example, +a date in the year 47 BC +has a `yearForWeekOfYear` equal to `-46` +(the off-by-one value is a consequence of how the year 0 is handled). + +{% endwarning %} + +### Creating a Date from Date Components + +In addition to extracting components from a date, +we can go the opposite direction to create a date from components +using the `Calendar` method `date(from:)`. + +Use it the next time you need to initialize a static date +as a more performant and reliable way +than parsing a timestamp with a date formatter. + +```swift +var date: Date? + +// Bad +let timestamp = "2018-10-03" +let formatter = ISO8601DateFormatter() +formatter.formatOptions = + [.withFullDate, .withDashSeparatorInDate] +date = formatter.date(from: timestamp) + +// Good +let calendar = Calendar.current +let dateComponents = + DateComponents(calendar: calendar, + year: 2018, month: 10, day: 3) +date = calendar.date(from: dateComponents) +``` + +When date components are used to represent a date, +there's still some ambiguity. +Date components can be (and often are) under-specified, +such that the values of components like `era` or `hour` are inferred +from additional context. +When you use the `date(from:)` method, +what you're really doing is telling `Calendar` +to search for the next date that satisfies the criteria you specified. + +Sometimes this isn't possible, +like if date components have contradictory values +(such as `weekOfYear = 1` and `weekOfMonth = 3`), +or a value in excess of what a calendar allows +(such as an `hour = 127`). +In these cases, `date(from:)` returns `nil`. + +{% info %} +Ranges for some units can also vary between calendars; +for example, a `month` value of `13` is valid in the Coptic calendar, +but invalid in the Gregorian calendar. +{% endinfo %} + +### Getting the Range of a Calendar Unit + +A common task when working with dates +is to get the start of day, week, month, or year. +Although it's possible to do this with `DateComponents` +creating a new date with a subset of date component values, +a better way would be to use the `Calendar` method `dateInterval(of:for:)`: + +```swift +let date = Date() // 2018-10-10T10:00:00+00:00 +let calendar = Calendar.current + +var beginningOfMonth: Date? + +// OK +let dateComponents = + calendar.dateComponents([.year, .month], from: date) +beginningOfMonth = calendar.date(from: dateComponents) + +// Better +beginningOfMonth = + calendar.dateInterval(of: .month, for: date)?.start +``` + +--- + +## Date Components as a Representation of a Duration of Time + +## Calculating the Distance Between Two Dates + +Picking up from the previous example --- +you can use the `Calendar` method `dateComponents(_:from:to:)` +to calculate the time between two dates +in terms of your desired units. + +How long is the month of October in hours? + +```swift +let date = Date() // 2018-10-10T10:00:00+00:00 +let calendar = Calendar.current + +let monthInterval = + calendar.dateInterval(of: .month, for: date)! + +calendar.dateComponents([.hour], + from: monthInterval.start, + to: monthInterval.end) + .hour // 744 +``` + +## Adding Components to Dates + +Another frequent programming task +is to calculate a date from an offset +like "tomorrow" or "next week". + +If you're adding a single calendar component value, +you can use the `Calendar` method `date(byAdding:value:to:)`: + +```swift +let date = Date() // 2018-10-10T10:00:00+00:00 +let calendar = Calendar.current + +var tomorrow: Date? + +// Bad +tomorrow = date.addingTimeInterval(60 * 60 * 24) + +// Good +tomorrow = calendar.date(byAdding: .day, + value: 1, + to: date) +``` + +For more than one calendar component value, +use the `date(byAdding:to:)` method instead, +passing a `DateComponents` object. + +```swift +let date = Date() +let calendar = Calendar.current + +// Adding a year +calendar.date(byAdding: .year, value: 1, to: date) + +// Adding a year and a day +let dateComponents = DateComponents(year: 1, day: 1) +calendar.date(byAdding: dateComponents, to: date) +``` + +If you _really_ want to be pedantic when time traveling, though, +the method you're looking for is +`nextDate(after:matching:matchingPolicy:repeatedTimePolicy:direction:)`. +For example, +if you wanted to find the date corresponding to the next time +with the same time components (hour, minute, second, nanosecond) +and wanted to be specific about how to handle phenomena like +2:59AM occurring twice on November 4th, 2018, +here's how you might do that: + +```swift +let dateComponents = + calendar.dateComponents([.hour, + .minute, + .second, + .nanosecond], + from: date) + +tomorrow = calendar.nextDate(after: date, + matching: dateComponents, + matchingPolicy: .nextTime, + repeatedTimePolicy: .first, + direction: .forward) +``` + +{% info %} + +If this seems like a lot of work, +remember that time is complicated and requires precision. +There's a great explanation of the matching and repeated time policies +[buried in the official documentation](https://developer.apple.com/documentation/foundation/nscalendar/1413938-enumeratedates), +so be sure to check that out. + +{% endinfo %} + +--- + +So there you have it! +Now you know how to do calendar arithmetic correctly +using `Calendar` and `DateComponents`. + +To help you remember, we humbly offer the following mnemonic: + +> Are you multiplying seconds? Don't! /
+> Instead, use `(NS)DateComponents`\* + +\* `NS` prefix added to make the meter work. Thanks, Swift 3. diff --git a/2012-07-31-nsdatecomponents.md b/2012-07-31-nsdatecomponents.md deleted file mode 100644 index 7ad1567e..00000000 --- a/2012-07-31-nsdatecomponents.md +++ /dev/null @@ -1,135 +0,0 @@ ---- -title: NSDateComponents -author: Mattt Thompson -category: Cocoa -excerpt: "NSDateComponents serves an important role in Foundation's date and time APIs. By itself, it's nothing impressive—just a container for information about a date (its month, year, day of month, week of year, or whether that month is a leap month). However, combined with NSCalendar, NSDateComponents becomes a remarkably convenient interchange format for calendar calculations." -status: - swift: 2.0 - reviewed: September 19, 2015 ---- - -`NSDateComponents` serves an important role in Foundation's date and time APIs. By itself, it's nothing impressive—just a container for information about a date (its month, year, day of month, week of year, or whether that month is a leap month). However, combined with `NSCalendar`, `NSDateComponents` becomes a remarkably convenient interchange format for calendar calculations. - -Whereas dates represent a particular moment in time, date components depend on which calendar system is being used to represent them. Very often, this will differ wildly from what many of us may be used to with the [Gregorian Calendar](http://en.wikipedia.org/wiki/Gregorian_calendar). For example, the [Islamic Calendar](http://en.wikipedia.org/wiki/Islamic_calendar) has 354 or 355 days in a year, whereas the [Buddhist calendar](http://en.wikipedia.org/wiki/Buddhist_calendar) may have 354, 355, 384, or 385 days, depending on the year. - -## Extracting Components From Dates - -`NSDateComponents` can be initialized and manipulated manually, but most often, they're extracted from a specified date, using `NSCalendar -components:fromDate:`: - -~~~{swift} -let calendar = NSCalendar.currentCalendar() -let date = NSDate() -let components = calendar.components([.Month, .Day], fromDate: date) -~~~ - -~~~{objective-c} -NSCalendar *calendar = [NSCalendar currentCalendar]; -NSDate *date = [NSDate date]; -[calendar components:(NSCalendarUnitDay | NSCalendarUnitMonth) 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: - -~~~{swift} -NSCalendarUnit.Era -NSCalendarUnit.Year -NSCalendarUnit.Month -NSCalendarUnit.Day -NSCalendarUnit.Hour -NSCalendarUnit.Minute -NSCalendarUnit.Second -NSCalendarUnit.Weekday -NSCalendarUnit.WeekdayOrdinal -NSCalendarUnit.Quarter -NSCalendarUnit.WeekOfMonth -NSCalendarUnit.WeekOfYear -NSCalendarUnit.YearForWeekOfYear -NSCalendarUnit.Calendar -NSCalendarUnit.TimeZone -~~~ - -~~~{objective-c} -NSCalendarUnitEra -NSCalendarUnitYear -NSCalendarUnitMonth -NSCalendarUnitDay -NSCalendarUnitHour -NSCalendarUnitMinute -NSCalendarUnitSecond -NSCalendarUnitWeekday -NSCalendarUnitWeekdayOrdinal -NSCalendarUnitQuarter -NSCalendarUnitWeekOfMonth -NSCalendarUnitWeekOfYear -NSCalendarUnitYearForWeekOfYear -NSCalendarUnitCalendar -NSCalendarUnitTimeZone -~~~ - -> 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). - -## Relative Date Calculations - -`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:`: - -~~~{swift} -let calendar = NSCalendar.currentCalendar() -let date = NSDate() - -let components = NSDateComponents() -components.weekOfYear = 1 -components.hour = 12 - -print("1 week and 12 hours from now: \(calendar.dateByAddingComponents(components, toDate: date, 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]); -~~~ - -## 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: NSCalendarIdentifierGregorian) - -let components = NSDateComponents() -components.year = 1987 -components.month = 3 -components.day = 17 -components.hour = 14 -components.minute = 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. - -* * * - -`NSDateComponents` and its relationship to `NSCalendar` highlight the distinct advantage having a pedantically-engineered framework like Foundation at your disposal. You may not be doing calendar calculations every day, but when the time comes, knowing how to use `NSDateComponents` will save you eons of frustration. diff --git a/2012-08-06-cfstringtransform.md b/2012-08-06-cfstringtransform.md index cc89b8f5..08bd46ad 100644 --- a/2012-08-06-cfstringtransform.md +++ b/2012-08-06-cfstringtransform.md @@ -1,6 +1,6 @@ --- title: CFStringTransform -author: Mattt Thompson +author: Mattt category: Cocoa tags: nshipster, popular excerpt: "NSString is the crown jewel of Foundation. But as powerful as it is, one would be remiss not to mention its toll-free bridged cousin, CFMutableString—or more specifically, CFStringTransform." @@ -15,7 +15,7 @@ There are two indicators that tell you everything you need to know about how nic `NSString` is the crown jewel of Foundation. In an age where other languages _still_ struggle to handle Unicode correctly, `NSString` is especially impressive. Not content to _just work_ with whatever is thrown at it, `NSString` can parse strings into linguistic tags, determine the dominant language of the content, and convert between every string encoding imaginable. It's unfairly good. -But as powerful as `NSString` / `NSMutableString` are, one would be remiss not to mention their [toll-free bridged](http://developer.apple.com/library/ios/#documentation/CoreFoundation/Conceptual/CFDesignConcepts/Articles/tollFreeBridgedTypes.html) cousin, `CFMutableString`—or more specifically, `CFStringTransform`. +But as powerful as `NSString` / `NSMutableString` are, one would be remiss not to mention their [toll-free bridged](https://developer.apple.com/library/ios/#documentation/CoreFoundation/Conceptual/CFDesignConcepts/Articles/tollFreeBridgedTypes.html) cousin, `CFMutableString`—or more specifically, `CFStringTransform`. As denoted by the `CF` prefix, `CFStringTransform` is part of Core Foundation. The function takes the following arguments, and returns a `Boolean` for whether or not the transform was successful: @@ -38,7 +38,7 @@ As denoted by the `CF` prefix, `CFStringTransform` is part of Core Foundation. T With the notable exception of English (and its delightful spelling inconsistencies), writing systems generally encode speech sounds into a consistent written representation. European languages generally use the Latin alphabet (with a few added diacritics), Russian uses Cyrillic, Japanese uses Hiragana & Katakana, and Thai, Korean, & Arabic each have their own scripts. -Although each language has a particular inventory of sounds, some of which other languages may lack, the overlap across all of the major writing systems is remarkably high—enough so that one can rather effectively [transliterate](http://en.wikipedia.org/wiki/Transliteration) (not to be confused with [translation](http://en.wikipedia.org/wiki/Translation)) from one script to another. +Although each language has a particular inventory of sounds, some of which other languages may lack, the overlap across all of the major writing systems is remarkably high—enough so that one can rather effectively [transliterate](https://en.wikipedia.org/wiki/Transliteration) (not to be confused with [translation](https://en.wikipedia.org/wiki/Translation)) from one script to another. `CFStringTransform` can transliterate back and forth between Latin and Arabic, Cyrillic, Greek, Korean (Hangul), Hebrew, Japanese (Hiragana & Katakana), Mandarin Chinese, and Thai. @@ -52,52 +52,52 @@ Although each language has a particular inventory of sounds, some of which other - kCFStringTransformLatinArabic + kCFStringTransformLatinArabic mrḥbạ مرحبا - kCFStringTransformLatinCyrillic + kCFStringTransformLatinCyrillic privet привет - kCFStringTransformLatinGreek + kCFStringTransformLatinGreek geiá sou γειά σου - kCFStringTransformLatinHangul + kCFStringTransformLatinHangul annyeonghaseyo 안녕하세요 - kCFStringTransformLatinHebrew + kCFStringTransformLatinHebrew şlwm שלום - kCFStringTransformLatinHiragana + kCFStringTransformLatinHiragana hiragana ひらがな - kCFStringTransformLatinKatakana + kCFStringTransformLatinKatakana katakana カタカナ - kCFStringTransformLatinThai + kCFStringTransformLatinThai s̄wạs̄dī สวัสดี - kCFStringTransformHiraganaKatakana + kCFStringTransformHiraganaKatakana にほんご ニホンゴ - kCFStringTransformMandarinLatin + kCFStringTransformMandarinLatin 中文 zhōng wén @@ -112,31 +112,31 @@ One of the more practical applications for string transformation is to normalize For example, let's say you want to build a searchable index of movies on the device, which includes greetings from around the world: -~~~{swift} +```swift var mutableString = NSMutableString(string: "Hello! こんにちは! สวัสดี! مرحبا! 您好!") as CFMutableStringRef -~~~ +``` - First, apply the `kCFStringTransformToLatin` transform to transliterate all non-English text into a Latin alphabetic representation. -~~~{swift} +```swift CFStringTransform(mutableString, nil, kCFStringTransformToLatin, Boolean(0)) -~~~ +``` > Hello! こんにちは! สวัสดี! مرحبا! 您好! → > Hello! kon'nichiha! s̄wạs̄dī! mrḥbạ! nín hǎo! - Next, apply the `kCFStringTransformStripCombiningMarks` transform to remove any diacritics or accents. -~~~{swift} +```swift CFStringTransform(mutableString, nil, kCFStringTransformStripCombiningMarks, Boolean(0)) -~~~ +``` > Hello! kon'nichiha! s̄wạs̄dī! mrḥbạ! nín hǎo! → > Hello! kon'nichiha! swasdi! mrhba! nin hao! - Finally, downcase the text with `CFStringLowercase`, and split the text into tokens with [`CFStringTokenizer`](https://developer.apple.com/library/mac/#documentation/CoreFoundation/Reference/CFStringTokenizerRef/Reference/reference.html) to use as an index for the text. -~~~{swift} +```swift let tokenizer = CFStringTokenizerCreate(nil, mutableString, CFRangeMake(0, CFStringGetLength(mutableString)), 0, CFLocaleCopyCurrent()) var mutableTokens: [String] = [] @@ -147,7 +147,7 @@ do { let token = CFStringCreateWithSubstring(nil, mutableString, range) as NSString mutableTokens.append(token) } while type != .None -~~~ +``` > (hello, kon'nichiha, swasdi, mrhba, nin, hao) diff --git a/2012-08-13-nsincrementalstore.md b/2012-08-13-nsincrementalstore.md index 665c31c2..23bde340 100644 --- a/2012-08-13-nsincrementalstore.md +++ b/2012-08-13-nsincrementalstore.md @@ -1,11 +1,12 @@ --- title: NSIncrementalStore -author: Mattt Thompson +author: Mattt category: Cocoa excerpt: Even for a blog dedicated to obscure APIs, `NSIncrementalStore` sets a new standard. It was introduced in iOS 5, with no more fanfare than the requisite entry in the SDK changelog. Ironically, it is arguably the most important thing to come out of iOS 5, which will completely change the way we build apps from here on out. +retired: true status: - swift: 1.1 - reviewed: September 8, 2015 + swift: 1.1 + reviewed: September 8, 2015 --- Even for a blog dedicated to obscure APIs, `NSIncrementalStore` brings a new meaning to the word "obscure". @@ -24,7 +25,7 @@ And yet, `NSIncrementalStore` is arguably the most important thing to come out o For those of you not as well-versed in Core Data, here's some background: -[Core Data](http://developer.apple.com/library/mac/#documentation/cocoa/Conceptual/CoreData/cdProgrammingGuide.html) is Apple's framework for object relational mapping. It's used in at least half of all of the first-party apps on Mac and iOS, as well as thousands of other third-party apps. Core Data is complex, but that's because it solves complex problems, covering a decades-worth of one-offs and edge cases. +[Core Data](https://developer.apple.com/library/mac/#documentation/cocoa/Conceptual/CoreData/cdProgrammingGuide.html) is Apple's framework for object relational mapping. It's used in at least half of all of the first-party apps on Mac and iOS, as well as thousands of other third-party apps. Core Data is complex, but that's because it solves complex problems, covering a decades-worth of one-offs and edge cases. This is all to say that Core Data is something you should probably use in your application. @@ -40,7 +41,7 @@ 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`: -~~~{swift} +```swift class CustomIncrementalStore: NSIncrementalStore { override class func initialize() { NSPersistentStoreCoordinator.registerStoreClass(self, forStoreType:self.type) @@ -50,10 +51,9 @@ class CustomIncrementalStore: NSIncrementalStore { return NSStringFromClass(self) } } -~~~ +``` - -~~~{objective-c} +```objc + (void)initialize { [NSPersistentStoreCoordinator registerStoreClass:self forStoreType:[self type]]; } @@ -61,13 +61,13 @@ class CustomIncrementalStore: NSIncrementalStore { + (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} +```swift override func loadMetadata(error: NSErrorPointer) -> Bool { self.metadata = [ NSStoreUUIDKey: NSProcessInfo().globallyUniqueString, @@ -76,14 +76,14 @@ override func loadMetadata(error: NSErrorPointer) -> Bool { return true } -~~~ +``` -~~~{objective-c} +```objc NSMutableDictionary *mutableMetadata = [NSMutableDictionary dictionary]; [mutableMetadata setValue:[[NSProcessInfo processInfo] globallyUniqueString] forKey:NSStoreUUIDKey]; [mutableMetadata setValue:[[self class] type] forKey:NSStoreTypeKey]; [self setMetadata:mutableMetadata]; -~~~ +``` ### `-executeRequest:withContext:error:` @@ -103,7 +103,7 @@ This method requires very specific and very different return values depending on - Result Type: `NSCountResultType` -> **Return**: NSNumberNSArray containing one NSNumber of count of objects matching request +> **Return**: NSNumberNSArray containing one NSNumber of count of objects matching request #### Request Type: `NSSaveRequestType` @@ -135,13 +135,13 @@ 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: -~~~{swift} +```swift self.newObjectIDForEntity(entity, referenceObject: rowID) -~~~ +``` -~~~{objective-c} +```objc [self newObjectIDForEntity:entity referenceObject:[NSNumber numberWithUnsignedInteger:rowID]]; -~~~ +``` ## Roll Your Own Core Data Backend @@ -149,16 +149,6 @@ Going through all of the necessary methods to override in an `NSIncrementalStore What makes `NSIncrementalStore` so exciting is that you _can_ build a store on your favorite technology, and drop that into any existing Core Data stack with little to no additional configuration. -So imagine if, instead SQL or NoSQL, we wrote a Core Data store that connected to a webservice. Allow me to introduce [AFIncrementalStore](https://github.com/AFNetworking/AFIncrementalStore). - -## AFIncrementalStore - -[`AFIncrementalStore`](https://github.com/AFNetworking/AFIncrementalStore) is an NSIncrementalStore subclass that uses [AFNetworking](https://github.com/afnetworking/afnetworking) to automatically request resources as properties and relationships are needed. - -What this means is that you can now write apps that communicate with a webservice _without exposing any of the details about the underlying API_. Any time a fetch request is made or an attribute or relationship faults, an asynchronous network request will fetch that information from the webservice. - -Since the store abstracts all of the implementation details of the API away, you can write expressive fetch requests and object relationships from the start. No matter how bad or incomplete an API may be, you can change all of that mapping independently of the business logic of the client. - -* * * +--- Even though `NSIncrementalStore` has been around since iOS 5, we're still a long way from even beginning to realize its full potential. The future is insanely bright, so you best don your aviators, grab an iced latte and start coding something amazing. diff --git a/2012-08-27-cfbag.md b/2012-08-27-cfbag.md index ebad39c5..7869c9ec 100644 --- a/2012-08-27-cfbag.md +++ b/2012-08-27-cfbag.md @@ -1,6 +1,6 @@ --- title: CFBag -author: Mattt Thompson +author: Mattt category: Cocoa tags: nshipster excerpt: "In the pantheon of collection data types in computer science, bag doesn't really have the same clout as lists, sets, associative arrays, trees, graphs, or priority queues. In fact, it's pretty obscure. You've probably never heard of it." @@ -10,7 +10,7 @@ status: Objective-C is a language caught between two worlds. -On one side, it follows the thoughtful, object-oriented philosophy of [Smalltalk](http://en.wikipedia.org/wiki/Smalltalk), which brings ideas like message sending and named parameters. On the other, are the inescapable vestiges of C, which brings it power and a dash of chaos. +On one side, it follows the thoughtful, object-oriented philosophy of [Smalltalk](https://en.wikipedia.org/wiki/Smalltalk), which brings ideas like message sending and named parameters. On the other, are the inescapable vestiges of C, which brings it power and a dash of chaos. It's an identity crisis borne out through an ever-increasing prevalence of the `@` symbol. @@ -28,58 +28,58 @@ However, toll-free bridging is the exception when it comes to collection classes - NSArray* - CFArray* + NSArray* + CFArray* ✓ - NSCountedSet - CFBag* + NSCountedSet + CFBag* N/A - CFBinaryHeap + CFBinaryHeap N/A - CFBitVector* + CFBitVector* - NSDictionary* - CFDictionary* + NSDictionary* + CFDictionary* ✓ - NSIndexSet* + NSIndexSet* N/A - NSMapTable + NSMapTable N/A - NSOrderedSet + NSOrderedSet N/A - NSPointerArray + NSPointerArray N/A - NSPointerFunctions + NSPointerFunctions N/A - NSSet* - CFSet* + NSSet* + CFSet* ✓ @@ -94,11 +94,11 @@ Take a look at the second row, with `NSCountedSet` and `CFBag`. Notice that unli ## Bags, in the Abstract -In the pantheon of collection data types in computer science, bag doesn't really have the same clout as lists, sets, associative arrays, trees, graphs, or priority queues. +In the pantheon of collection data types in computer science, bag doesn't really have the same clout as lists, sets, associative arrays, trees, graphs, or priority queues. In fact, it's pretty obscure. You've probably never heard of it. -A bag, or [multiset](http://en.wikipedia.org/wiki/Multiset) is a variant of a set, where members can appear more than once. A count is associated with each unique member of the collection, representing the number of times it has been added. Like with sets, order does not matter. +A bag, or [multiset](https://en.wikipedia.org/wiki/Multiset) is a variant of a set, where members can appear more than once. A count is associated with each unique member of the collection, representing the number of times it has been added. Like with sets, order does not matter. Its practical applications are... limited, but you'll know one when it comes up. Tallying votes in a general election? Simulating homework problems an intro probability class? Implementing a game of Yahtzee? Bag is your new bicycle! @@ -108,7 +108,7 @@ 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: -~~~{objective-c} +```objc struct CFBagCallBacks { CFIndex version; CFBagRetainCallBack retain; @@ -118,7 +118,7 @@ struct CFBagCallBacks { 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 diff --git a/2012-09-03-locale.md b/2012-09-03-locale.md new file mode 100644 index 00000000..0d23167a --- /dev/null +++ b/2012-09-03-locale.md @@ -0,0 +1,285 @@ +--- +title: Locale +author: Mattt +category: Cocoa +tags: nshipster +excerpt: >- + The hardest thing about internationalization + _(aside from saying the word itself)_ + is learning how to think outside of your cultural context. + Unless you've had the privelage to travel + or to meet people from other places, + you may not even be aware that things could _be_ any other way. +revisions: + "2012-09-03": First Publication + "2018-11-28": Updated for Swift 4.2 +status: + swift: 4.2 + reviewed: November 28, 2018 +--- + +> “You take delight not in a city's seven or seventy wonders, \\ +> but in the answer it gives to a question of yours.” +> Italo Calvino, Invisible Cities + +Localization +(l10n) +is the process of adapting your application for a specific market, +or locale. +Internationalization +(i18n) +is the process of preparing your app to be localized. +Internationalization is a _necessary_, +but not _sufficient_ condition for localization. +And whether or not you decide to localize your app for other markets, +it's always a good idea to do everything with internationalization in mind. + +The hardest thing about internationalization +_(aside from saying the word itself)_ +is learning how to think outside of your cultural context. +Unless you've had the privilege to travel +or to meet people from other places, +you may not even be aware that things could _be_ any other way. +But when you venture out into the world, +everything from the price of bread to +the letters in the alphabet to +the numbers of hours in a day --- +all of that is up in the air. + +It can be absolutely overwhelming without a guide. +Fortunately for us, we have `Locale` to show us the ways of the world. + +--- + +Foundation's `Locale` type encapsulates +the linguistic and cultural conventions of a user, +which include: + +- Language and Orthography +- Text Direction and Layout +- Keyboards and Input Methods +- Collation Ordering +- Personal Name Formatting +- Calendar and Date Formatting +- Currency and Number Formatting +- Units and Measurement Formatting +- Use of Symbols, Colors, and Iconography + +`Locale` objects are often used as parameters +for methods that perform localized operations +like sorting a list of strings or formatting a date. +Most of the time, +you'll access the `Locale.current` type property +to use the user's current locale. + +```swift +import Foundation + +let units = ["meter", "smoot", "agate", "ångström"] + +units.sorted { (lhs, rhs) in + lhs.compare(rhs, locale: .current) == .orderedAscending +} +// => ["agate", "ångström", "meter", "smoot"] +``` + +You can also construct a `Locale` +that corresponds to a particular locale identifier. +A locale identifier typically comprises +an ISO 639-1 language code (such as `en` for English) and +an ISO 3166-2 region code (such as `US` for the United States). + +```swift +let 🇸🇪 = Locale(identifier: "sv_SE") +units.sorted { (lhs, rhs) in + lhs.compare(rhs, locale: 🇸🇪) == .orderedAscending +} +// => ["agate", "meter", "smoot", "ångström"] +``` + +{% info %} + +Most methods and properties that take a `Locale` +do so optionally or with a default value +such that --- +if left unspecified --- +the current locale is used. + +{% endinfo %} + +Locale identifiers may also specify +an explicit character encoding or +other preferences like currency, calendar system, or number format +with the following syntax: + +``` +language[_region][.character-encoding][@modifier=value[, ...]] +``` + +For example, +the locale identifier `de_DE.UTF8@collation=phonebook,currency=DEM` +specifies a German language locale in Germany +that uses UTF-8 text encoding, +[phonebook collation](http://developer.mimer.com/charts/german_phonebook.htm), +and the pre-Euro [Deutsche Mark](https://en.wikipedia.org/wiki/Deutsche_Mark) currency. + +{% warning %} + +Support for identifiers specifying other than language or region is inconsistent +across system APIs and different platforms, +so you shouldn't rely on specific behavior. + +{% endwarning %} + +Users can change their locale settings +in the "Language & Text" system preference on macOS and +in "General > International" in the iOS Settings app. + +{% asset locale-preferences.png alt="Locale Preferences" %} + +## _Terra Cognita_ + +`Locale` often takes a passive role, +being passed into a property here and a method there. +But if you give it a chance, +it can tell you more about a place than you ever knew there was to know: + +{::nomarkdown} + +
+
Language and Script
+
languageCode, scriptCode, exemplarCharacterSet
+ +
Region
+
regionCode
+ +
Collation
+
collationIdentifier, collatorIdentifier
+ +
Calendar
+
calendar
+ +
Currency
+
currencyCode, currencySymbol
+ +
Numbers and Units
+
decimalSeparator, usesMetricSystem
+ +
Quotation Delimiters
+
quotationBeginDelimiter / quotationEndDelimiter, +alternateQuotationBeginDelimiter / alternateQuotationEndDelimiter
+
+ +{:/} + +While this all may seem like fairly esoteric stuff, +you'd be surprised by how many opportunities this kind of information unlocks +for making a world-class app. + +It's the small things, like knowing that quotation marks vary between locales: + +| **English** | “I can eat glass, it doesn't harm me.” | +| **German** | „Ich kann Glas essen, das tut mir nicht weh.“ | +| **Japanese** | 「私はガラスを食べられます。それは私を傷つけません。」| + +So if you were building a component that added quotations around arbitrary text, +you should use these properties +rather than hard-coding English quotation marks. + +## _Si Fueris Romae…_ + +As if it weren't enough to know, among other things, +the preferred currency for every region on the planet, +`Locale` can also tell you how you'd refer to it in a particular locale. +For example, +here in the USA, we call our country the United States. +But that's just an +endonym; +elsewhere, American has other names --- +exonyms. +Like in France, it's... + +```swift +import Foundation + +let 🇺🇸 = Locale(identifier: "en_US") +let 🇫🇷 = Locale(identifier: "fr_FR") + +🇺🇸.localizedString(forIdentifier: 🇺🇸.identifier) +// => "English (United States)" + +🇫🇷.localizedString(forIdentifier: 🇺🇸.identifier) +// => "anglais (États-Unis)" +``` + +Use this technique whenever you're displaying locale, +like in this screen from the Settings app, +which shows language endonyms alongside exonyms: + +{% asset locale-languages.png alt="Locale Languages Menu on iOS" %} + +{% info %} + +Behind the scenes, +all of this locale information is provided by the +[Common Locale Data Repository](http://cldr.unicode.org) +CLDR, +a companion project to the Unicode standard. + +{% endinfo %} + +## _Vox Populi_ + +The last stop in our tour of `Locale` +is the `preferredLanguages` property. +Contrary to what you might expect for a type (rather than an instance) property, +the returned value depends on the current language preferences of the user. +Each of the array elements is +[IETF BCP 47 language identifier](http://tools.ietf.org/html/bcp47) +and is returned in order of user preference. + +If your app communicates with a web app over HTTP, +you might use this property to set the +[`Accept-Language` header field](http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4) +to give the server an opportunity to return localized resources: + +```swift +import Foundation + +let url = URL(string: "https://nshipster.com")! +var request = URLRequest(url: url) + +let acceptLanguage = Locale.preferredLanguages.joined(separator: ", ") +request.setValue(acceptLanguage, forHTTPHeaderField: "Accept-Language") +``` + +Even if your server doesn't yet localize its resources, +putting this in place now allows you to flip the switch when the time comes --- +without having to push an update to the client. +_Neat!_ + +{% info %} +[HTTP content negotiation](https://tools.ietf.org/html/rfc7231#section-3.4) +is a complex topic, +and apps interacting with localized, remote resources +should use `Locale.preferredLanguages` as a starting point +for generating a more precise `Accept-Language` field value. +For example, +you might specify an associated quality value +like in `en-SG, en;q=0.9` +to tell the server +_"I prefer Singaporean English, but will accept other types of English"_. + +For more information, +see [RFC 7231](https://tools.ietf.org/html/rfc7231#section-5.3.5). +{% endinfo %} + +--- + +Travel is like internationalization: +it's something that isn't exactly fun at the time, +but we do it because it makes us better people. +Both activities expand our cultural horizons +and allow us to better know people we'll never actually meet. + +With `Locale` you can travel the world without going AFK. diff --git a/2012-09-03-nslocale.md b/2012-09-03-nslocale.md deleted file mode 100644 index 50bf828b..00000000 --- a/2012-09-03-nslocale.md +++ /dev/null @@ -1,157 +0,0 @@ ---- -title: NSLocale -author: Mattt Thompson -category: Cocoa -tags: nshipster -excerpt: "Internationalization is like flossing: everyone knows they should do it, but probably don't." -status: - swift: 1.1 ---- - -Internationalization is like flossing: everyone knows they should do it, but probably don't. - -And like any habit, it becomes second-nature with practice, to the point that you couldn't imagine _not_ doing it. All it takes is for someone to show you the way. - -Let NSHipster be your dental hygienist Virgil through these foreign lands.. without all of the lecturing about tooth decay (promsies!) - -## i18n versus l10n - -As is necessary in any discussion about Internationalization (i18n) or Localization (l10n), we must take some time to differentiate the two: - -- **Localization** is the process of adapting your application for a specific market, or _locale_. -- **Internationalization** is the process of preparing your app to be localized. - -> Internationalization is a necessary, but not sufficient condition for localization, and will be the focus of this article. Localization, which involves the translation of text and assets into a particular language, will be covered in a future edition of NSHipster. - -What makes internationalization difficult is having to think outside of your cultural context. All of the assumptions you have about the way things are supposed to work must be acknowledged and reconsidered. You have to fight the urge to write off things that may seem trivial, like sorting and collation, and empathize with the pain and confusion even minor differences may cause. - -Fortunately for us, we don't have to do this alone. Meet `NSLocale`: - -## `NSLocale` - -`NSLocale` is a Foundation class that encapsulates all of the conventions about language and culture for a particular locale. A locale encompasses all of the linguistic and cultural norms of a particular group of people, including: - -- Language -- Keyboards -- Number, Date, and Time Formats -- Currency -- Collation and Sorting -- Use of Symbols, Colors, and Iconography - -Each locale corresponds to a _locale identifier_, such as `en_US`, `fr_FR`, `ja_JP`, and `en_GB`, which include a language code (e.g. `en` for English) and a region code (e.g. `US` for United States). - -Locale identifiers can encode more explicit preferences about currency, calendar system, or number formats, such as in the case of `de_DE@collation=phonebook,currency=DDM`, which specifies German spoken in Germany, using [phonebook collation](http://developer.mimer.com/charts/german_phonebook.htm), and using the pre-Euro [Deutsche Mark](http://en.wikipedia.org/wiki/Deutsche_Mark). - -Users can change their locale settings in the "Language & Text" (or "International" on older versions of OS X) System Preferences on the Mac, or "General > International" in iOS Settings. - -![Language & Text System Preferences]({{ site.asseturl }}/nslocale-international-system-preferences.png) - -## Formatting Dates & Numbers - -Although `NSLocale` encapsulates a rich set of domain-specific information, its typical usage is rather understated. - -If there's just one thing you should learn about `NSLocale`, it's that you should always pass `[NSLocale currentLocale]` into your `NSDateFormatter` and `NSNumberFormatter` instances. Doing this will ensure that dates, numbers, and currencies will be formatted according to the localization preferences of the user. - -Actually, make that a meta lesson about locales: always use `NSDateFormatter` and `NSNumberFormatter` when displaying anything to do with dates or numbers, respectively. - -But let's get back to some of the cool features of `NSLocale` itself, shall we? - -## `-objectForKey:` - -`NSLocale` typifies Foundation's obsession with domain-specific pedantry, and nowhere is this more visible than in `-objectForKey:`. Cue the list of available constants: - -- `NSLocaleIdentifier` -- `NSLocaleLanguageCode` -- `NSLocaleCountryCode` -- `NSLocaleScriptCode` -- `NSLocaleVariantCode` -- `NSLocaleExemplarCharacterSet` -- `NSLocaleCalendar` -- `NSLocaleCollationIdentifier` -- `NSLocaleUsesMetricSystem` -- `NSLocaleMeasurementSystem` -- `NSLocaleDecimalSeparator` -- `NSLocaleGroupingSeparator` -- `NSLocaleCurrencySymbol` -- `NSLocaleCurrencyCode` -- `NSLocaleCollatorIdentifier` -- `NSLocaleQuotationBeginDelimiterKey` -- `NSLocaleQuotationEndDelimiterKey` -- `NSLocaleAlternateQuotationBeginDelimiterKey` -- `NSLocaleAlternateQuotationEndDelimiterKey` - -While this all may seem like fairly esoteric stuff, you may be surprised by the number of opportunities your application has to use this information to make for a better user experience. - -It's the small things, like knowing that quotation marks vary between locales: - -- English: “I can eat glass, it doesn't harm me.” -- German: „Ich kann Glas essen, das tut mir nicht weh.“ -- Japanese:「私はガラスを食べられます。それは私を傷つけません。」 - -So if you were building a component that added quotations around arbitrary text, you should use `NSLocaleQuotationBeginDelimiterKey` and `NSLocaleAlternateQuotationEndDelimiterKey` rather than assuming `@"\""` for English quotation marks. - -## `-displayNameForKey:value:` - -Another impressive, albeit mostly-useless method is `-displayNameForKey:value:`, which can return the display name of a locale identifier (`NSLocaleIdentifier`): - -~~~{swift} -let locale = NSLocale(localeIdentifier: "fr_FR") - -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)" - -You should use this method any time you need to display information about the user's current locale, or any alternative locales available to them, like in this screen from the Settings app: - -![Languages Settings]({{ site.asseturl }}/nslocale-languages-settings.png) - -## `+preferredLanguages` - -One final method worth mentioning is `NSLocale +preferredLanguages`, which returns an array of [IETF BCP 47 language identifier](http://tools.ietf.org/html/bcp47) strings, in order of user preference. - -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: - -~~~{swift} -// Accept-Language HTTP Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4 -let acceptLanguage: String = { - var components: [String] = [] - for (index, languageCode) in enumerate(NSLocale.preferredLanguages() as [String]) { - let q = 1.0 - (Double(index) * 0.1) - components.append("\(languageCode);q=\(q)") - if q <= 0.5 { - break - } - } - - return join(",", components) -}() - -let URL = NSURL(string: "http://nshipster.com") -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! - ---- - -Internationalization is often considered to be an un-sexy topic in programming--just another chore that most projects don't have to worry about. In actuality, designing software for other locales is a valuable exercise (and not just for the economic benefits of expanding your software into other markets). - -One of the greatest joys and challenges in programming is in designing systems that can withstand change. The only way designs can survive this level of change is to identify and refactor assumptions about the system that may not always hold. In this way, internationalization represents the greatest challenge, making us question everything about our cultural identity. And in doing so, we become not just better programmers, but better people, too. - -So go and be a better person: make `NSLocale` part of your daily ritual. diff --git a/2012-09-10-uiaccessibility.md b/2012-09-10-uiaccessibility.md index 79393d56..eef60f1b 100644 --- a/2012-09-10-uiaccessibility.md +++ b/2012-09-10-uiaccessibility.md @@ -1,6 +1,6 @@ --- title: UIAccessibility -author: Mattt Thompson +author: Mattt category: Cocoa tags: nshipster excerpt: "Accessibility, like internationalization, is one of those topics that's difficult to get developers excited about. But as you know, NSHipster is all about getting developers excited about this kind of stuff." @@ -9,13 +9,14 @@ status: --- > We all want to help one another, human beings are like that. +> > - [Charlie Chaplin](http://en.wikiquote.org/wiki/Charlie_Chaplin) You know what I wish everyone would copy from Apple? Their assistive technologies. -iPhones and iPads--magical as they are--become downright _life-changing_ for individuals with disabilities and their families because of Apple's commitment to accessibility. Look no further than the [WWDC 2012 Introduction Video](http://www.youtube.com/watch?v=MbP_pxR5cMk), which opens with Per Busch, a blind man who walks the woods of Kassel, Germany with the aid of [Ariadne GPS](http://www.ariadnegps.eu). It's a lovely reminder of the kind of impact our work can have on others. +iPhones and iPads--magical as they are--become downright _life-changing_ for individuals with disabilities and their families because of Apple's commitment to accessibility. Look no further than the [WWDC 2012 Introduction Video](https://www.youtube.com/watch?v=MbP_pxR5cMk), which opens with Per Busch, a blind man who walks the woods of Kassel, Germany with the aid of [Ariadne GPS](http://www.ariadnegps.eu). It's a lovely reminder of the kind of impact our work can have on others. -Accessibility, like [internationalization](http://nshipster.com/nslocale/), is one of those topics that's difficult to get developers excited about. But as you know, NSHipster is _all about_ getting developers excited about this kind of stuff. Let's get started: +Accessibility, like [internationalization](https://nshipster.com/nslocale/), is one of those topics that's difficult to get developers excited about. But as you know, NSHipster is _all about_ getting developers excited about this kind of stuff. Let's get started: --- @@ -43,7 +44,7 @@ Tap VoiceOver, and then tap the VoiceOver switch to turn it on. An alert will po Don't Panic--unlike setting your device to another language, there's no real risk of not being able to figure out how to turn VoiceOver off. -![VoiceOver Settings]({{ site.asseturl }}/uiaccessibility-voiceover.png) +![VoiceOver Settings]({% asset uiaccessibility-voiceover.png @path %}) Using the device in VoiceOver mode is a bit different than you're used to: @@ -68,20 +69,20 @@ Accessibility labels and hints tell VoiceOver what to say when selecting user in - **`accessibilityLabel`** identifies a user interface element. Every accessible view and control _must_ supply a label. - **`accessibilityHint`** describes the results of interacting with a user interface element. A hint should be supplied _only_ if the result of an interaction is not obvious from the element's label. -The [Accessibility Programming Guide](http://developer.apple.com/library/ios/#documentation/UserExperience/Conceptual/iPhoneAccessibility/Making_Application_Accessible/Making_Application_Accessible.html) provides the following guidelines for labels and hints: +The [Accessibility Programming Guide](https://developer.apple.com/library/ios/#documentation/UserExperience/Conceptual/iPhoneAccessibility/Making_Application_Accessible/Making_Application_Accessible.html) provides the following guidelines for labels and hints: > ### Guidelines for Creating Labels -> If you provide a custom control or view, or if you display a custom icon in a standard control or view, you need to provide a label that: > +> If you provide a custom control or view, or if you display a custom icon in a standard control or view, you need to provide a label that: + - **Very briefly describes the element.** Ideally, the label consists of a single word, such as Add, Play, Delete, Search, Favorites, or Volume. - **Does not include the type of the control or view.** The type information is contained in the traits attribute of the element and should never be repeated in the label. - **Begins with a capitalized word.** This helps VoiceOver read the label with the appropriate inflection. - **Does not end with a period.** The label is not a sentence and therefore should not end with a period. - **Is localized.** Be sure to make your application available to as wide an audience as possible by localizing all strings, including accessibility attribute strings. In general, VoiceOver speaks in the language that the user specifies in International settings. -> -> ### Guidelines for Creating Hints -> The hint attribute describes the results of performing an action on a control or view. You should provide a hint only when the results of an action are not obvious from the element’s label. -> + > ### Guidelines for Creating Hints + > + > The hint attribute describes the results of performing an action on a control or view. You should provide a hint only when the results of an action are not obvious from the element’s label. - **Very briefly describes the results.** Even though few controls and views need hints, strive to make the hints you do need to provide as brief as possible. Doing so decreases the amount of time users must spend listening before they can use the element. - **Begins with a verb and omits the subject.** Be sure to use the third-person singular declarative form of a verb, such as “Plays,” and not the imperative, such as “Play.” You want to avoid using the imperative, because using it can make the hint sound like a command. - **Begins with a capitalized word and ends with a period.** Even though a hint is a phrase, not a sentence, ending the hint with a period helps VoiceOver speak it with the appropriate inflection. @@ -133,6 +134,6 @@ Want to know a quick way to improve the accessibility of your table views? Try s Apple has done a great service to humanity in making accessibility a first-class citizen in its hardware and software. You're missing out on some of the best engineering, design, and technical writing that Apple has ever done if you ignore `UIAccessibility`. -Do yourself a favor and read the _excellent_ [Accessibility Programming Guide for iOS](http://developer.apple.com/library/ios/#documentation/UserExperience/Conceptual/iPhoneAccessibility/Introduction/Introduction.html). It only takes an hour or two to get the hang of everything. +Do yourself a favor and read the _excellent_ [Accessibility Programming Guide for iOS](https://developer.apple.com/library/ios/#documentation/UserExperience/Conceptual/iPhoneAccessibility/Introduction/Introduction.html). It only takes an hour or two to get the hang of everything. Who knows? You may end up changing someone's life because of it. diff --git a/2012-09-17-characterset.md b/2012-09-17-characterset.md new file mode 100644 index 00000000..71593f41 --- /dev/null +++ b/2012-09-17-characterset.md @@ -0,0 +1,350 @@ +--- +title: CharacterSet +author: Mattt +category: Cocoa +excerpt: >- + `CharacterSet` isn't a set and it doesn't contain `Character` values. + Before we use it to trim, filter, and search through text, + we should take a closer look to see what's actually going on. +revisions: + "2012-09-17": Original publication + "2018-12-12": Updated for Swift 4.2 +status: + swift: 4.2 + reviewed: December 12, 2018 +--- + +In Japan, +there's a comedy tradition known as +[Manzai (漫才)](https://en.wikipedia.org/wiki/Manzai). +It's kind of a cross between stand up and vaudeville, +with a straight man and a funny man +delivering rapid-fire jokes that revolve around miscommunication and wordplay. + +As it were, we've been working on a new routine +as a way to introduce the subject for this week's article, `CharacterSet`, +and wanted to see what you thought: + +{::nomarkdown} + +
+ + +Is CharacterSet a Set<Character>? + +キャラクターセットではないキャラクターセット? + + + + +Of course not! + +もちろん違います! + + + + +What about NSCharacterSet? + +何エンエスキャラクタセットは? + + + + +That's an old reference. + +それは古いリファレンスです。 + + + + +Then what do you call a collection of characters? + +何と呼ばれる文字の集合ですか? + + + + +That would be a String! + +それは文字列でしょ! + + + + +(╯° 益 °)╯ 彡 ┻━┻ + +無駄無駄無駄無駄無駄無駄無駄 + + + +
+{:/} + +_(Yeah, we might need to workshop this one a bit more.)_ + +All kidding aside, +`CharacterSet` is indeed ripe for miscommunication and wordplay (so to speak): +it doesn't store `Character` values, +and it's not a `Set` in the literal sense. + +So what is `CharacterSet` and how can we use it? +Let's find out! (行きましょう!) + +--- + +`CharacterSet` (and its reference type counterpart, `NSCharacterSet`) +is a Foundation type used to trim, filter, and search for +characters in text. + +In Swift, +a `Character` is an extended grapheme cluster +(really just a `String` with a length of 1) +that comprises one or more scalar values. +`CharacterSet` stores those underlying `Unicode.Scalar` values, +rather than `Character` values, as the name might imply. + +The "set" part of `CharacterSet` +refers not to `Set` from the Swift standard library, +but instead to the `SetAlgebra` protocol, +which bestows the type with the same interface: +`contains(_:)`, `insert(_:)`, `union(_:)`, `intersection(_:)`, and so on. + +## Predefined Character Sets + +`CharacterSet` defines constants +for sets of characters that you're likely to work with, +such as letters, numbers, punctuation, and whitespace. +Most of them are self-explanatory and, +with only a few exceptions, +correspond to one or more +[Unicode General Categories](https://unicode.org/reports/tr44/#General_Category_Values). + +| Type Property | Unicode General Categories & Code Points | +| --------------------------------- | ---------------------------------------- | +| `alphanumerics` | L\*, M\*, N\* | +| `letters` | L\*, M\* | +| `capitalizedLetters`\* | Lt | +| `lowercaseLetters` | Ll | +| `uppercaseLetters` | Lu, Lt | +| `nonBaseCharacters` | M\* | +| `decimalDigits` | Nd | +| `punctuationCharacters` | P\* | +| `symbols` | S\* | +| `whitespaces` | Zs, U+0009 | +| `newlines` | U+000A – U+000D, U+0085, U+2028, U+2029 | +| `whitespacesAndNewlines` | Z\*, U+000A – U+000D, U+0085 | +| `controlCharacters` | Cc, Cf | +| `illegalCharacters` | Cn | + +{% info %} +A common mistake is to use `capitalizedLetters` +when what you actually want is `uppercaseLetters`. +Unicode actually defines three cases: +lowercase, uppercase, and titlecase. +You can see this in the Latin script used for +Serbo-Croatian and other South Slavic languages, +in which digraphs like "dž" are considered single letters, +and have separate forms for +lowercase (dž), uppercase (DŽ), and titlecase (Dž). +The `capitalizedLetters` character set contains only +a few dozen of those titlecase digraphs. +{% endinfo %} + +The remaining predefined character set, `decomposables`, +is derived from the +[decomposition type and mapping](https://unicode.org/reports/tr44/#Character_Decomposition_Mappings) +of characters. + +### Trimming Leading and Trailing Whitespace + +Perhaps the most common use for `CharacterSet` +is to remove leading and trailing whitespace from text. + +```swift +""" + + 😴 + +""".trimmingCharacters(in: .whitespacesAndNewlines) // "😴" +``` + +You can use this, for example, +when sanitizing user input or preprocessing text. + +## Predefined URL Component Character Sets + +In addition to the aforementioned constants, +`CharacterSet` provides predefined values +that correspond to the characters allowed in various +[components of a URL](https://nshipster.com/nsurl/): + +- `urlUserAllowed` +- `urlPasswordAllowed` +- `urlHostAllowed` +- `urlPathAllowed` +- `urlQueryAllowed` +- `urlFragmentAllowed` + +### Escaping Special Characters in URLs + +Only certain characters are allowed in certain parts of a URL +without first being escaped. +For example, spaces must be percent-encoded as `%20` (or `+`) +when part of a query string like +`https://nshipster.com/search/?q=character%20set`. + +`URLComponents` takes care of percent-encoding components automatically, +but you can replicate this functionality yourself +using the `addingPercentEncoding(withAllowedCharacters:)` method +and passing the appropriate character set: + +```swift +let query = "character set" +query.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) +// "character%20set" +``` + +{% warning %} +[Internationalized domain names](https://en.wikipedia.org/wiki/Internationalized_domain_name) +encode non-ASCII characters using +[Punycode](https://en.wikipedia.org/wiki/Punycode) +instead of percent-encoding +(for example, +[NSHipster.中国](https://nshipster.cn) would be +[NSHipster.xn--fiqy6j](https://nshipster.cn)) +Punycode encoding / decoding isn't currently provided by Apple SDKs. +{% endwarning %} + +## Building Your Own + +In addition to these predefined character sets, +you can create your own. +Build them up character by character, +inserting multiple characters at a time by passing a string, +or by mixing and matching any of the predefined sets. + +### Validating User Input + +You might create a `CharacterSet` to validate some user input to, for example, +allow only lowercase and uppercase letters, digits, and certain punctuation. + +```swift +var allowed = CharacterSet() +allowed.formUnion(.lowercaseLetters) +allowed.formUnion(.uppercaseLetters) +allowed.formUnion(.decimalDigits) +allowed.insert(charactersIn: "!@#$%&") + +func validate(_ input: String) -> Bool { + return input.unicodeScalars.allSatisfy { allowed.contains($0) } +} +``` + +Depending on your use case, +you might find it easier to think in terms of what shouldn't be allowed, +in which case you can compute the inverse character set +using the `inverted` property: + +```swift +let disallowed = allowed.inverted +func validate(_ input: String) -> Bool { + return input.rangeOfCharacter(from: disallowed) == nil +} +``` + +### Caching Character Sets + +If a `CharacterSet` is created as the result of an expensive operation, +you may consider caching its `bitmapRepresentation` +for later reuse. + +For example, +if you wanted to create `CharacterSet` for Emoji, +you might do so by enumerating over the Unicode code space (U+0000 – U+1F0000) +and inserting the scalar values for any characters with +[Emoji properties](https://www.unicode.org/reports/tr51/#Emoji_Properties) +using the `properties` property added in Swift 5 by +[SE-0221 "Character Properties"](https://github.com/apple/swift-evolution/blob/master/proposals/0221-character-properties.md): + +```swift +import Foundation + +var emoji = CharacterSet() + +for codePoint in 0x0000...0x1F0000 { + guard let scalarValue = Unicode.Scalar(codePoint) else { + continue + } + + // Implemented in Swift 5 (SE-0221) + // https://github.com/apple/swift-evolution/blob/master/proposals/0221-character-properties.md + if scalarValue.properties.isEmoji { + emoji.insert(scalarValue) + } +} +``` + +The resulting `bitmapRepresentation` is a 16KB `Data` object. + +```swift +emoji.bitmapRepresentation // 16385 bytes +``` + +You could store that in a file somewhere in your app bundle, +or embed its [Base64 encoding](https://en.wikipedia.org/wiki/Base64) +as a string literal directly in the source code itself. + +```swift +extension CharacterSet { + static var emoji: CharacterSet { + let base64Encoded = """ + AAAAAAgE/wMAAAAAAAAAAAAAAAAA... + """ + let data = Data(base64Encoded: base64Encoded)! + + return CharacterSet(bitmapRepresentation: data) + } +} + +CharacterSet.emoji.contains("👺") // true +``` + +{% info %} +Because the Unicode code space is a closed range, +`CharacterSet` can express the membership of a given scalar value +using a single bit in a [bit map](https://en.wikipedia.org/wiki/Bit_array), +rather than using a +[universal hashing function](https://en.wikipedia.org/wiki/Universal_hashing) +like a conventional `Set`. +On top of that, `CharacterSet` does some clever optimizations, like +allocating on a per-[plane](https://www.unicode.org/glossary/#plane) basis +and representing sets of contiguous scalar values as ranges, if possible. +{% endinfo %} + +--- + +Much like our attempt at a Manzai routine at the top of the article, +some of the meaning behind `CharacterSet` is lost in translation. + +`NSCharacterSet` was designed for `NSString` +at a time when characters were equivalent to 16-bit UCS-2 code units +and text rarely had occasion to leave the Basic Multilingual Plane. +But with Swift's modern, +Unicode-compliant implementations of `String` and `Character`, +the definition of terms has drifted slightly; +along with its `NS` prefix, +`CharacterSet` lost some essential understanding along the way. + +Nevertheless, +`CharacterSet` remains a performant, specialized container type +for working with collections of scalar values. + + +FIN + +おしまい。 + + + +{% asset articles/characterset.css %} diff --git a/2012-09-17-nscharacterset.md b/2012-09-17-nscharacterset.md deleted file mode 100644 index 289f53cd..00000000 --- a/2012-09-17-nscharacterset.md +++ /dev/null @@ -1,177 +0,0 @@ ---- -title: NSCharacterSet -author: Mattt Thompson -category: Cocoa -excerpt: "Foundation boasts one of the best, most complete implementations of strings around. But a string implementation is only as good as the programmer who wields it. So this week, we're going to explore some common uses--and misuses--of an important part of the Foundation string ecosystem: NSCharacterSet." -status: - swift: 2.0 - reviewed: September 9, 2015 ---- - -As mentioned [previously](http://nshipster.com/cfstringtransform/), Foundation boasts one of the best, most complete implementations of strings around. - -But a string implementation is only as good as the programmer who wields it. So this week, we're going to explore some common uses--and misuses--of an important part of the Foundation string ecosystem: `NSCharacterSet`. - ---- - -> If you're fuzzy on what character encodings are (or even if you have a pretty good working knowledge), you should take this opportunity to read / re-read / skim and read later Joel Spolsky's classic essay ["The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!)"](http://www.joelonsoftware.com/articles/Unicode.html). Having that fresh in your mind will give you a much better appreciation of everything we're about to cover. - -`NSCharacterSet` and its mutable counterpart, `NSMutableCharacterSet`, provide an object-oriented way of representing sets of Unicode characters. It's most often used with `NSString` & `NSScanner` to filter, remove, or split on different kinds of characters. To give you an idea of what those kinds of characters can be, take a look at the class methods provided by `NSCharacterSet`: - -- `alphanumericCharacterSet` -- `capitalizedLetterCharacterSet` -- `controlCharacterSet` -- `decimalDigitCharacterSet` -- `decomposableCharacterSet` -- `illegalCharacterSet` -- `letterCharacterSet` -- `lowercaseLetterCharacterSet` -- `newlineCharacterSet` -- `nonBaseCharacterSet` -- `punctuationCharacterSet` -- `symbolCharacterSet` -- `uppercaseLetterCharacterSet` -- `whitespaceAndNewlineCharacterSet` -- `whitespaceCharacterSet` - -Contrary to what its name might suggest, `NSCharacterSet` has _nothing_ to do with `NSSet`. - -However, `NSCharacterSet` _does_ have quite a bit in common with `NSIndexSet`, conceptually if not also in its underlying implementation. `NSIndexSet`, covered [previously](http://nshipster.com/nsindexset/), represents a sorted collection of unique unsigned integers. Unicode characters are likewise unique unsigned integers that roughly correspond to some orthographic representation. Thus, a character set like `NSCharacterSet +lowercaseCharacterSet` is analogous to the `NSIndexSet` of the integers 97 to 122. - -Now that we're comfortable with the basic concepts of `NSCharacterSet`, let's see some of those patterns and anti-patterns: - -## Stripping Whitespace - -`NSString -stringByTrimmingCharactersInSet:` is a method you should know by heart. It's most often passed `NSCharacterSet +whitespaceCharacterSet` or `+whitespaceAndNewlineCharacterSet` in order to remove the leading and trailing whitespace of string input. - -It's important to note that this method _only_ strips the _first_ and _last_ contiguous sequences of characters in the specified set. That is to say, if you want to remove excess whitespace between words, you need to go a step further. - -## Squashing Whitespace - -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 { !$0.isEmpty } - -string = components.joinWithSeparator(" ") -~~~ - -~~~{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:@" "]; -~~~ - -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). - -At the time of writing, the correct answer ranks second by number of votes, with 58 up and 2 down. The top answer edges it out with 84 up and 24 down. - -Now, it's not uncommon for the top-voted / accepted answer to not be the correct one, but this question may set records for number of completely distinct answers (10), and number of unique, completely incorrect answers (9). - -Without further ado, here are the 9 _incorrect_ answers: - -- "Use `stringByTrimmingCharactersInSet`" - _Only strips the leading and trailing whitespace, as you know._ -- "Replace ' ' with ''" - _This removes **all** of the spaces. Swing and a miss._ -- "Use a regular expression" - _Kinda works, except it doesn't handle leading and trailing whitespace. A regular expression is overkill anyway._ -- "Use Regexp Lite" - _No seriously, regular expressions are completely unnecessary. And it's definitely not worth the external dependency._ -- "Use OgreKit" - _Ditto any other third-party regexp library._ -- "Split the string into components, iterate over them to find components with non-zero length, and then re-combine" - _So close, but `componentsSeparatedByCharactersInSet:` already makes the iteration unnecessary._ -- "Replace two-space strings with single-space strings in a while loop" - _Wrong and oh-so computationally wasteful_. -- "Manually iterate over each `unichar` in the string and use `NSCharacterSet -characterIsMember:`" - _Shows a surprising level of sophistication for missing the method that does this in the standard library._ -- "Find and remove all of the tabs" - _Thanks all the same, but who said anything about tabs?_ - -I don't mean to rag on any of the answerers personally--this is all to point out how many ways there are to approach these kinds of tasks, and how many of those ways are totally wrong. - -## String Tokenization - -**Do not use `NSCharacterSet` to tokenize strings.** -**Use `CFStringTokenizer` instead.** - -You can be forgiven for using `componentsSeparatedByCharactersInSet:` to clean up user input, but do this for anything more complex, and you'll be in a world of pain. - -Why? Well, remember that bit about languages not always having whitespace word boundaries? As it turns out, those languages are rather widely used. Just Chinese and Japanese--#1 and #9 in terms of number of speakers, respectively--alone account for 16% of the world population, or well over a billion people. - -...and even for languages that do have whitespace word boundaries, tokenization has some obscure edge cases, particularly with compound words and punctuation. - -This is all to say: use `CFStringTokenizer` (or `enumerateSubstringsInRange:options:usingBlock:`) if you ever intend to split a string by words in any meaningful way. - -## Parse Data From Strings - -`NSScanner` is a class that helps to parse data out of arbitrary or semi-structured strings. When you create a scanner for a string, you can specify a set of characters to skip, thus preventing any of those characters from somehow being included in the values parsed from the string. - -For example, let's say you have a string that parses opening hours in the following form: - -~~~ -Mon-Thurs: 8:00 - 18:00 -Fri: 7:00 - 17:00 -Sat-Sun: 10:00 - 15:00 -~~~ - -You might `enumerateLinesUsingBlock:` and parse with an `NSScanner` like so: - -~~~{swift} -let skippedCharacters = NSMutableCharacterSet.punctuationCharacterSet() -skippedCharacters.formUnionWithCharacterSet(NSCharacterSet.whitespaceCharacterSet()) - -hours.enumerateLines { (line, _) in - let scanner = NSScanner(string: line) - scanner.charactersToBeSkipped = skippedCharacters - - var startDay, endDay: NSString? - var startHour: Int = 0 - var startMinute: Int = 0 - var endHour: Int = 0 - var endMinute: Int = 0 - - scanner.scanCharactersFromSet(NSCharacterSet.letterCharacterSet(), intoString: &startDay) - scanner.scanCharactersFromSet(NSCharacterSet.letterCharacterSet(), intoString: &endDay) - - scanner.scanInteger(&startHour) - scanner.scanInteger(&startMinute) - scanner.scanInteger(&endHour) - scanner.scanInteger(&endMinute) -} -~~~ - -~~~{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. - -`NSCharacterSet` and `NSScanner` allow you to code quickly and confidently. They're really a great combination, those two. - ---- - -`NSCharacterSet` is but one piece to the Foundation string ecosystem, and perhaps the most misused and misunderstood of them all. By keeping these patterns and anti-patterns in mind, however, not only will you be able to do useful things like manage whitespace and scan information from strings, but--more importantly--you'll be able to avoid all of the wrong ways to do it. - -And if not being wrong isn't the most important thing about being an NSHipster, then I don't want to be right! - -> Ed. Speaking of (not) being wrong, the original version of this article contained errors in both code samples. These have since been corrected. diff --git a/2012-09-24-uicollectionview.md b/2012-09-24-uicollectionview.md index ec04b9e3..3df5e333 100644 --- a/2012-09-24-uicollectionview.md +++ b/2012-09-24-uicollectionview.md @@ -1,6 +1,6 @@ --- title: UICollectionView -author: Mattt Thompson +author: Mattt category: Cocoa excerpt: "UICollectionView single-handedly changes the way we will design and develop iOS apps from here on out. This is not to say that collection views are in any way unknown or obscure. But being an NSHipster isn't just about knowing obscure gems in the rough. Sometimes, it's about knowing about up-and-comers before they become popular and sell out." status: @@ -28,19 +28,19 @@ 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: -~~~{swift} +```swift let identifier = "Cell" var cell = tableView.dequeueReusableCellWithIdentifier(identifier) if cell == nil { cell = UITableViewCell(...) } -~~~ -~~~{objective-c} +``` +```objc 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. @@ -52,7 +52,7 @@ Because collection views aren't relegated to any particular structure, the conve Each cell can have multiple supplementary views associated with it--one for each named "kind". As such, headers and footers are just the beginning of what can be done with supplementary views. -The whole point is that with supplementary views, even the most complex layout can be accomplished without compromising the semantic integrity of cells. `UITableView` hacks are to [`spacer.gif`](http://en.wikipedia.org/wiki/Spacer_GIF) as `UICollectionView` cells are to [semantic HTML](http://en.wikipedia.org/wiki/Semantic_HTML). +The whole point is that with supplementary views, even the most complex layout can be accomplished without compromising the semantic integrity of cells. `UITableView` hacks are to [`spacer.gif`](https://en.wikipedia.org/wiki/Spacer_GIF) as `UICollectionView` cells are to [semantic HTML](https://en.wikipedia.org/wiki/Semantic_HTML). ### Decoration Views @@ -88,7 +88,7 @@ What's _extremely_ cool is this method here: - `-layoutAttributesForElementsInRect:` -Using this, you could, for example, fade out items as they approach the edge of the screen. Or, since all of the layout attribute properties are automatically animated, you could create a poor-man's [cover flow](http://en.wikipedia.org/wiki/Cover_Flow) layout in just a couple lines of code with the right set of 3D transforms. +Using this, you could, for example, fade out items as they approach the edge of the screen. Or, since all of the layout attribute properties are automatically animated, you could create a poor-man's [cover flow](https://en.wikipedia.org/wiki/Cover_Flow) layout in just a couple lines of code with the right set of 3D transforms. In fact, collection views can even swap out layouts wholesale, allowing views to transition seamlessly between different modes--all without changing the underlying data. diff --git a/2012-10-01-pragma.md b/2012-10-01-pragma.md index 258d11b1..ed849f26 100644 --- a/2012-10-01-pragma.md +++ b/2012-10-01-pragma.md @@ -1,95 +1,206 @@ --- title: "#pragma" -author: Mattt Thompson +author: Mattt category: Objective-C tags: nshipster -excerpt: "`#pragma` declarations are a mark of craftsmanship in Objective-C. Although originally purposed for compiling source code across many different compilers, the modern Xcode-savvy programmer uses #pragma declarations to very different ends." +excerpt: >- + `#pragma` declarations are a mark of craftsmanship in Objective-C. + Although originally used to make source code compatible across different compilers, + Xcode-savvy programmers use `#pragma` to very different ends. +revisions: + 2019-12-13: Updated for Xcode 11 status: - swift: n/a + swift: n/a --- -`#pragma` declarations are a mark of craftsmanship in Objective-C. Although originally used to make source code compatible between different compilers, the Xcode-savvy coder uses `#pragma` declarations to very different ends. +`#pragma` declarations are a mark of craftsmanship in Objective-C. +Although originally used to make source code compatible across different compilers, +Xcode-savvy programmers use `#pragma` declarations to very different ends. -In this modern context, `#pragma` skirts the line between comment and code. As a preprocessor directive, `#pragma` evaluates at compile-time. But unlike other macros, such as `#ifdef...#endif`, the way `#pragma` is used will not change the runtime behavior of your application. Instead, `#pragma` declarations are used by Xcode to accomplish two primary tasks: organizing code and inhibiting compiler warnings. +Whereas other preprocessor directives +allow you to define behavior when code is executed, +`#pragma` is unique in that it gives you control +_at the time code is being written_ --- +specifically, +by organizing code under named headings +and inhibiting warnings. -> In addition to the `#pragma` syntax, both [GCC](http://gcc.gnu.org/onlinedocs/cpp/Pragmas.html) and [Clang](http://clang.llvm.org/docs/UsersManual.html#controlling-diagnostics-via-pragmas) have added the C99 `_Pragma` operator. +As we'll see in this week's article, +good developer habits start with `#pragma mark`. ## Organizing Your Code -Code organization is a matter of hygiene. How you structure your code is a reflection on you and your work. A lack of convention and internal consistency indicates either carelessness or incompetence--and worse, makes a project difficult to maintain and collaborate on. - -Good habits start with `#pragma mark`. Like so: - -~~~{objective-c} +Code organization is a matter of hygiene. +How you structure your code is a reflection of you and your work. +A lack of convention and internal consistency indicates +either carelessness or incompetence --- +and worse, +it makes a project more challenging to maintain over time. + +We here at NSHipster believe that +code should be clean enough to eat off of. +That's why we use `#pragma mark` to divide code into logical sections. + +If your class overrides any inherited methods, +organize them under common headings according to their superclass. +This has the added benefit of +describing the responsibilities of each ancestor in the class hierarchy. +An `NSInputStream` subclass, for instance, +might have a group marked `NSInputStream`, +followed by `NSStream`, +and then finally `NSObject`. + +If your class adopts any protocols, +it makes sense to group each of their respective methods +using a `#pragma mark` header with the name of that protocol +_(bonus points for following the same order as the original declarations)_. + +Finding it difficult to make sense of your app's MVC architecture? +_("Massive View Controller", that is.)_ +`#pragma` marks allow you to divide-and-conquer! +With a little practice, +you'll be able to identify and organize around common concerns with ease. +Some headings that tend to come up regularly include +initializers, +helper methods, +`@dynamic` properties, +Interface Builder outlets or actions, +and handlers for notification or +Key-Value Observing (KVO) selectors. + +Putting all of these techniques together, +the `@implementation` for +a `ViewController` class that inherits from `UITableViewController` +might organize Interface Builder actions together at the top, +followed by overridden view controller life-cycle methods, +and then methods for each adopted protocol, +each under their respective heading. + +```objc @implementation ViewController -- (id)init { - ... -} +- (instancetype)init { <#...#> } -#pragma mark - UIViewController +#pragma mark - IBAction -- (void)viewDidLoad { - ... -} +- (IBAction)confirm:(id)sender { <#...#> } +- (IBAction)cancel:(id)sender { <#...#> } -#pragma mark - IBAction +#pragma mark - UIViewController -- (IBAction)cancel:(id)sender { - ... -} +- (void)viewDidLoad { <#...#> } +- (void)viewDidAppear:(BOOL)animated { <#...#> } #pragma mark - UITableViewDataSource -- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { - ... -} +- (NSInteger)tableView:(UITableView *)tableView + numberOfRowsInSection:(NSInteger)section { <#...#> } #pragma mark - UITableViewDelegate -- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { - ... -} -~~~ +- (void)tableView:(UITableView *)tableView +didSelectRowAtIndexPath:(NSIndexPath *)indexPath { <#...#> } -Use `#pragma mark` in your `@implementation` to divide code into logical sections. Not only do these sections make it easier to read through the code itself, but it also adds visual cues to the Xcode source navigator (`#pragma mark` declarations starting with a dash (`-`) are preceded with a horizontal divider). +@end +``` -![Xcode Sections]({{ site.asseturl }}/pragma-xcode-sections.png) +Not only do these sections make for easier reading in the code itself, +but they also create visual cues to +the Xcode source navigator and minimap. -If your class conforms to any `@protocols`, start by grouping all of the methods within each protocol together, and adding a `#pragma mark` header with the name of that protocol. Another good convention is to group subclassed methods according to their respective superclass. For example, an `NSInputStream` subclass should have a group marked `NSInputStream`, followed by a group marked `NSStream`. Things like `IBAction` outlets, or methods corresponding to target / action, notification, or KVO selectors probably deserve their own sections as well. +{% asset pragma-xcode-sections.png alt="#pragma headings in the Xcode source navigator"%} -Your code should be clean enough to eat off of. So take the time to leave your `.m` files better than how you found them. +{% info %} -## Inhibiting Warnings - -`#pragma mark` is pretty mainstream. On the other hand, `#pragma` declarations to inhibit warnings from the compiler & static analyzer--now that's pretty fresh. - -You know what's even more annoying than poorly-formatted code? Code that generates warnings. Especially 3rd-party code. There are few things as irksome as that one vendor library that takes forever to compile, and finishes with 200+ warnings. Even shipping code with 1 warning is in poor form. +`#pragma mark` declarations beginning with a dash (`-`) +are preceded with a horizontal divider in Xcode. -> Pro tip: Try setting the `-Weverything` flag and checking the "Treat Warnings as Errors" box your build settings. This turns on Hard Mode in Xcode. +{% endinfo %} -But sometimes there's no avoiding compiler warnings. Deprecation notices and retain-cycle false positives are two common situations where this might happen. In those rare cases where you are _absolutely_ certain that a particular compiler or static analyzer warning should be inhibited, `#pragma` comes to the rescue: +## Inhibiting Warnings -~~~{objective-c} -// completionBlock is manually nilled out in AFURLConnectionOperation to break the retain cycle. +Do you know what's even more annoying than poorly-formatted code? +Code that generates warnings --- +especially 3rd-party code. +Is there anything more irksome than a vendor SDK that generates dozens of warnings +each time you hit R? +Heck, +even a single warning is one too many for many developers. + +Warnings are almost always warranted, +but sometimes they're unavoidable. +In those rare circumstances where you are _absolutely_ certain that +a particular warning from the compiler or static analyzer is errant, +`#pragma` comes to the rescue. + +Let's say you've deprecated a class: + +```objc +@interface DeprecatedClass __attribute__ ((deprecated)) +<#...#> +@end +``` + +Now, +deprecation is something to be celebrated. +It's a way to responsibly communicate future intent to API consumers +in a way that provides enough time for them to +migrate to an alternative solution. +But you might not feel so appreciated if you have +`CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS` enabled in your project. +Rather than being praised for your consideration, +you'd be admonished for implementing a deprecated class. + +One way to silence the compiler would be to +disable the warnings by setting `-Wno-deprecated-implementations`. +However, +doing this for the entire project would be too coarse an adjustment, +and doing this for this file only would be too much work. +A better option would be to use `#pragma` +to inhibit the unhelpful warning +around the problematic code. + +Using `#pragma clang diagnostic push/pop`, +you can tell the compiler to ignore certain warnings +for a particular section of code: + +```objc #pragma clang diagnostic push -#pragma clang diagnostic ignored "-Warc-retain-cycles" - self.completionBlock = ^ { - ... - }; +#pragma clang diagnostic ignored "-Wdeprecated-implementations" +@implementation DeprecatedClass +<#...#> +@end #pragma clang diagnostic pop -~~~ +``` -This code sample [from AFNetworking](https://github.com/AFNetworking/AFNetworking/blob/master/AFNetworking/AFHTTPRequestOperation.m#L247) (contributed by [Peter Steinberger](https://github.com/steipete)) is an example of an otherwise unavoidable warning from the static analyzer. Clang notices a strong reference to `self` within the block, and warns about a possible [retain cycle](http://www.quora.com/What-is-a-retain-cycle). However, the `super` implementation of `setCompletionBlock` takes care of this by `nil`-ing out the strong reference after the completion block is finished. +Before you start copy-pasting this across your project +in a rush to zero out your warnings, +remember that, +in the overwhelming majority of cases, +Clang is going to be right. +Fixing an analyzer warning is _strongly_ preferred to ignoring it, +so use `#pragma clang diagnostic ignored` as a method of last resort. -Fortunately, Clang provides a convenient way to get around all of this. Using `#pragma clang diagnostic push/pop`, you can tell the compiler to ignore certain warnings, _only_ for a particular section of code (the original diagnostic settings are restored with the final `pop`). +{% warning %} -> You can read more about the LLVM's use of `#pragma` in the [Clang Compiler User's Manual](http://clang.llvm.org/docs/UsersManual.html#diagnostics_pragmas). +Finding app development to be _too_ easy for you? +Enable all diagnostics with the +[`-Weverything`](https://clang.llvm.org/docs/UsersManual.html#diagnostics-enable-everything) flag +and enable "Treat Warnings as Errors". +This turns on "Hard Mode" in Xcode. -Just don't use this as a way to sweep legitimate warnings under the rug--that will only come back to bite you later. +{% endwarning %} --- -So there you go: two ways you can markedly improve your code using `#pragma` declarations. - -Like the thrift store 8-track player you turned into that lamp in the foyer, `#pragma` remains a curious vestige of the past: Once the secret language of compilers, now re-purposed to better-communicate intent to other programmers. How delightfully vintage! +Though it skirts the line between comment and code, +`#pragma` remains a vital tool in the modern app developer's toolbox. +Whether it's for grouping methods or suppressing spurious warnings, +judicious use of `#pragma` can go a long way +to make projects easier to understand and maintain. + +Like the thrift store 8-track player you turned into that lamp in the foyer, +`#pragma` remains a curious vestige of the past: +Once the secret language of compilers, +now re-purposed to better-communicate intent to other programmers. +_How delightfully vintage!_ diff --git a/2012-10-08-at-compiler-directives.md b/2012-10-08-at-compiler-directives.md deleted file mode 100644 index 20a23e21..00000000 --- a/2012-10-08-at-compiler-directives.md +++ /dev/null @@ -1,330 +0,0 @@ ---- -title: "@" -author: Mattt Thompson -category: Objective-C -tags: nshipster -excerpt: "If we were to go code-watching for Objective-C, what would we look for? Square brackets, ridiculously-long method names, and `@`'s. \"at\" sign compiler directives are as central to understanding Objective-C's gestalt as its ancestry and underlying mechanisms. It's the sugary glue that allows Objective-C to be such a powerful, expressive language, and yet still compile all the way down to C." -status: - swift: n/a ---- - -Birdwatchers refer to it as (and I swear I'm not making this up) ["Jizz"](http://en.wikipedia.org/wiki/Jizz_%28birding%29): those indefinable characteristics unique to a particular kind of thing. - -This term can be appropriated to describe how seasoned individuals might distinguish [Rust](http://www.rust-lang.org) from [Go](http://golang.org), or [Ruby](http://www.ruby-lang.org) from [Elixir](http://elixir-lang.org) at a glance. - -Some just stick out like sore thumbs: - -Perl, with all of its short variable names with special characters, reads like [Q\*bert swearing](http://imgur.com/WyG2D). - -Lisp, whose profusion of parentheses is best captured by [that old joke](http://discuss.fogcreek.com/joelonsoftware3/default.asp?cmd=show&ixPost=94232&ixReplies=38) about the Russians in the 1980's proving that they had stolen the source code of some SDI missile interceptor code by showing the last page: - -~~~ lisp - ))) - ) ) - ))) ) )) - ))))) - ))) - )) - )))) )) - )))) )) - ))) -) -~~~ - -So if we were to go code-watching for the elusive Objective-C species, what would we look for? That's right: - -- Square brackets -- Ridiculously-long method names -- `@`'s - -`@`, or "at" sign compiler directives, are as central to understanding Objective-C's gestalt as its ancestry and underlying mechanisms. It's the sugary glue that allows Objective-C to be such a powerful, expressive language, and yet still compile all the way down to C. - -Its uses are varied and disparate, to the point that the only accurate way to describe what `@` means by itself is "shorthand for something to do with Objective-C". They cover a broad range in usefulness and obscurity, from staples like `@interface` and `@implementation` to ones you could go your whole career without running into, like `@defs` and `@compatibility_alias`. - -But to anyone aspiring to be an NSHipster, intimate familiarity with `@` directives is tantamount to a music lover's ability to enumerate the entire Beatles catalog in chronological order (and most importantly, having unreasonably strong opinions about each of them). - -## Interface & Implementation - -`@interface` and `@implementation` are the first things you learn about when you start Objective-C: - -- `@interface`...`@end` -- `@implementation`...`@end` - -What you don't learn about until later on, are categories and class extensions. - -Categories allow you to extend the behavior of existing classes by adding new class or instance methods. As a convention, categories are defined in their own `.{h,m}` files, like so: - -#### MyObject+CategoryName.h - -~~~{objective-c} -@interface MyObject (CategoryName) - - (void)foo; - - (BOOL)barWithBaz:(NSInteger)baz; -@end -~~~ - -#### MyObject+CategoryName.m - -~~~{objective-c} -@implementation MyObject (CategoryName) - - (void)foo { - // ... - } - - - (BOOL)barWithBaz:(NSInteger)baz { - return YES; - } -@end -~~~ - -Categories are particularly useful for convenience methods on standard framework classes (just don't go overboard with your utility functions). - -> Pro Tip: Rather than littering your code with random, arbitrary color values, create an `NSColor` / `UIColor` color palette category that defines class methods like `+appNameDarkGrayColor`. You can then add a semantic layer on top of that by creating method aliases like `+appNameTextColor`, which returns `+appNameDarkGrayColor`. - -Extensions look like categories, but omit the category name. These are typically declared before an `@implementation` to specify a private interface, and even override properties declared in the interface: - -~~~{objective-c} -@interface MyObject () -@property (readwrite, nonatomic, strong) NSString *name; -- (void)doSomething; -@end - -@implementation MyObject -@synthesize name = _name; - -// ... - -@end -~~~ - -### Properties - -Property directives are likewise concepts learned early on: - -- `@property` -- `@synthesize` -- `@dynamic` - -One interesting note with properties is that as of Xcode 4.4, it is no longer necessary to explicitly synthesize properties. Properties declared in an `@interface` are automatically synthesized (with leading underscore ivar name, i.e. `@synthesize propertyName = _propertyName`) in the implementation. - -### Forward Class Declarations - -Occasionally, `@interface` declarations will reference an external class in a property or as a parameter type. Rather than adding `#import` statements for each class, it's good practice to use forward class declarations in the header, and import them in the implementation. - -- `@class` - -Shorter compile times, less chance of cyclical references; you should definitely get in the habit of doing this if you aren't already. - -### Instance Variable Visibility - -It's a matter of general convention that classes provide state and mutating interfaces through properties and methods, rather than directly exposing ivars. - -Although ARC makes working with ivars much safer by taking care of memory management, the aforementioned automatic property synthesis removes the one place where ivars would otherwise be declared. - -Nonetheless, in cases where ivars _are_ directly manipulated, there are the following visibility directives: - -- `@public`: instance variable can be read and written to directly, using the notation `person->age = 32"` -- `@package`: instance variable is public, except outside of the framework in which it is specified (64-bit architectures only) -- `@protected`: instance variable is only accessible to its class and derived classes -- `@private`: instance variable is only accessible to its class - -~~~{objective-c} -@interface Person : NSObject { - @public - NSString *name; - int age; - - @private - int salary; -} -~~~ - -## Protocols - -There's a distinct point early in an Objective-C programmer's evolution, when she realizes that she can define her own protocols. - -The beauty of protocols is that they allow programmers to design contracts that can be adopted outside of a class hierarchy. It's the egalitarian mantra at the heart of the American Dream: that it doesn't matter who you are, or where you come from: anyone can achieve anything if they work hard enough. - -...or at least that's idea, right? - -- `@protocol`...`@end`: Defines a set of methods to be implemented by any class conforming to the protocol, as if they were added to the interface of that class. - -Architectural stability and expressiveness without the burden of coupling--protocols are awesome. - -### Requirement Options - -You can further tailor a protocol by specifying methods as required or optional. Optional methods are stubbed in the interface, so as to be auto-completed in Xcode, but do not generate a warning if the method is not implemented. Protocol methods are required by default. - -The syntax for `@required` and `@optional` follows that of the visibility macros: - -~~~{objective-c} -@protocol CustomControlDelegate - - (void)control:(CustomControl *)control didSucceedWithResult:(id)result; -@optional - - (void)control:(CustomControl *)control didFailWithError:(NSError *)error; -@end -~~~ - -## Exception Handling - -Objective-C communicates unexpected state primarily through `NSError`. Whereas other languages would use exception handling for this, Objective-C relegates exceptions to truly exceptional behavior, including programmer error. - -`@` directives are used for the traditional convention of `try/catch/finally` blocks: - -~~~{objective-c} -@try{ - // attempt to execute the following statements - [self getValue:&value error:&error]; - - // if an exception is raised, or explicitly thrown... - if (error) { - @throw exception; - } -} @catch(NSException *e) { - // ...handle the exception here -} @finally { - // always execute this at the end of either the @try or @catch block - [self cleanup]; -} -~~~ - -## Literals - -Literals are shorthand notation for specifying fixed values. Literals are more --or-less directly correlated with programmer happiness. By this measure, Objective-C has long been a language of programmer misery. - -### Object Literals - -Until recently, Objective-C only had literals for `NSString`. But with the release of the [Apple LLVM 4.0 compiler](http://clang.llvm.org/docs/ObjectiveCLiterals.html), literals for `NSNumber`, `NSArray` and `NSDictionary` were added, with much rejoicing. - -- `@""`: Returns an `NSString` object initialized with the Unicode content inside the quotation marks. -- `@42`, `@3.14`, `@YES`, `@'Z'`: Returns an `NSNumber` object initialized with pertinent class constructor, such that `@42` → `[NSNumber numberWithInteger:42]`, or `@YES` → `[NSNumber numberWithBool:YES]`. Supports the use of suffixes to further specify type, like `@42U` → `[NSNumber numberWithUnsignedInt:42U]`. -- `@[]`: Returns an `NSArray` object initialized with the comma-delimited list of objects as its contents. It uses +arrayWithObjects:count: class constructor method, which is a more precise alternative to the more familiar `+arrayWithObjects:`. For example, `@[@"A", @NO, @2.718]` → `id objects[] = {@"A", @NO, @2.718}; [NSArray arrayWithObjects:objects count:3]`. -- `@{}`: Returns an `NSDictionary` object initialized with the specified key-value pairs as its contents, in the format: `@{@"someKey" : @"theValue"}`. -- `@()`: Dynamically evaluates the boxed expression and returns the appropriate object literal based on its value (i.e. `NSString` for `const char*`, `NSNumber` for `int`, etc.). This is also the designated way to use number literals with `enum` values. - -### Objective-C Literals - -Selectors and protocols can be passed as method parameters. `@selector()` and `@protocol()` serve as pseudo-literal directives that return a pointer to a particular selector (`SEL`) or protocol (`Protocol *`). - -- `@selector()`: Returns an `SEL` pointer to a selector with the specified name. Used in methods like `-performSelector:withObject:`. -- `@protocol()`: Returns a `Protocol *` pointer to the protocol with the specified name. Used in methods like `-conformsToProtocol:`. - -### C Literals - -Literals can also work the other way around, transforming Objective-C objects into C values. These directives in particular allow us to peek underneath the Objective-C veil, to begin to understand what's really going on. - -Did you know that all Objective-C classes and objects are just glorified `struct`s? Or that the entire identity of an object hinges on a single `isa` field in that `struct`? - -For most of us, at least most of the time, coming into this knowledge is but an academic exercise. But for anyone venturing into low-level optimizations, this is simply the jumping-off point. - -- `@encode()`: Returns the [type encoding](http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html) of a type. This type value can be used as the first argument encode in `NSCoder -encodeValueOfObjCType:at:`. -- `@defs()`: Returns the layout of an Objective-C class. For example, to declare a struct with the same fields as an `NSObject`, you would simply do: - -~~~{objective-c} -struct { - @defs(NSObject) -} -~~~ - -> Ed. As pointed out by readers [@secboffin](http://twitter.com/secboffin) & [@ameaijou](http://twitter.com/ameaijou), `@defs` is unavailable in the modern Objective-C runtime. - -## Optimizations - -There are some `@` compiler directives specifically purposed for providing shortcuts for common optimizations. - -- `@autoreleasepool{}`: If your code contains a tight loop that creates lots of temporary objects, you can use the `@autoreleasepool` directive to optimize for these short-lived, locally-scoped objects by being more aggressive about how they're deallocated. `@autoreleasepool` replaces and improves upon the old `NSAutoreleasePool`, which is significantly slower, and unavailable with ARC. -- `@synchronized(){}`: This directive offers a convenient way to guarantee the safe execution of a particular block within a specified context (usually `self`). Locking in this way is expensive, however, so for classes aiming for a particular level of thread safety, a dedicated `NSLock` property or the use of low-level locking functions like `OSAtomicCompareAndSwap32(3)` are recommended. - -## Compatibility - -In case all of the previous directives were old hat for you, there's a strong likelihood that you didn't know about this one: - -- `@compatibility_alias`: Allows existing classes to be aliased by a different name. - -For example [PSTCollectionView](https://github.com/steipete/PSTCollectionView) uses `@compatibility_alias` to significantly improve the experience of using the backwards-compatible, drop-in replacement for [UICollectionView](http://nshipster.com/uicollectionview/): - -~~~{objective-c} -// Allows code to just use UICollectionView as if it would be available on iOS SDK 5. -// http://developer.apple. com/legacy/mac/library/#documentation/DeveloperTools/gcc-3. 3/gcc/compatibility_005falias.html -#if __IPHONE_OS_VERSION_MAX_ALLOWED < 60000 -@compatibility_alias UICollectionViewController PSTCollectionViewController; -@compatibility_alias UICollectionView PSTCollectionView; -@compatibility_alias UICollectionReusableView PSTCollectionReusableView; -@compatibility_alias UICollectionViewCell PSTCollectionViewCell; -@compatibility_alias UICollectionViewLayout PSTCollectionViewLayout; -@compatibility_alias UICollectionViewFlowLayout PSTCollectionViewFlowLayout; -@compatibility_alias UICollectionViewLayoutAttributes PSTCollectionViewLayoutAttributes; -@protocol UICollectionViewDataSource @end -@protocol UICollectionViewDelegate @end -#endif -~~~ - -Using this clever combination of macros, a developer can develop with `UICollectionView` by including `PSTCollectionView`--without worrying about the deployment target of the final project. As a drop-in replacement, the same code works more-or-less identically on iOS 6 as it does on iOS 4.3. - ---- - -So to review: - -**Interfaces & Implementation** - -- `@interface`...`@end` -- `@implementation`...`@end` -- `@class` - -**Instance Variable Visibility** - -- `@public` -- `@package` -- `@protected` -- `@private` - -**Properties** - -- `@property` -- `@synthesize` -- `@dynamic` - -**Protocols** - -- `@protocol` -- `@required` -- `@optional` - -**Exception Handling** - -- `@try` -- `@catch` -- `@finally` -- `@throw` - -**Object Literals** - -- `@""` -- `@42`, `@3.14`, `@YES`, `@'Z'` -- `@[]` -- `@{}` -- `@()` - -**Objective-C Literals** - -- `@selector()` -- `@protocol()` - -**C Literals** - -- `@encode()` -- `@defs()` - -**Optimizations** - -- `@autoreleasepool{}` -- `@synchronized{}` - -**Compatibility** - -- `@compatibility_alias` - -Thus concludes this exhaustive rundown of the many faces of `@`. It's a versatile, power-packed character, that embodies the underlying design and mechanisms of the language. - -> This should be a complete list, but there's always a chance that some new or long-forgotten ones slipped between the cracks. If you know of any `@` directives that were left out, be sure to let [@NSHipster](https://twitter.com/nshipster) know. diff --git a/2012-10-15-addressbookui.md b/2012-10-15-addressbookui.md index a8cd9c88..8b1528b1 100644 --- a/2012-10-15-addressbookui.md +++ b/2012-10-15-addressbookui.md @@ -1,6 +1,6 @@ --- title: AddressBookUI -author: Mattt Thompson +author: Mattt category: Cocoa excerpt: "Address Book UI is an iOS framework for displaying, selecting, editing, and creating contacts in a user's Address Book. Similar to the Message UI framework, Address Book UI contains a number of controllers that can be presented modally, to provide common system functionality in a uniform interface." status: @@ -28,17 +28,17 @@ 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: -~~~{swift} +```swift let countryCode: String = NSLocale(localeIdentifier: "en_US").objectForKey(NSLocaleCountryCode) as String -~~~ +``` -~~~{objective-c} +```objc [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} +```swift let addressComponents = [ kABPersonAddressStreetKey: "70 NW Couch Street", kABPersonAddressCityKey: "Portland", @@ -48,33 +48,33 @@ let addressComponents = [ ] ABCreateStringWithAddressDictionary(addressComponents, true) -~~~ +``` -~~~ +``` 70 NW Couch Street Portland‎ OR‎ 97209 United States -~~~ +``` -Nowhere else in all of the other frameworks is this functionality provided. It's not part of [`NSLocale`](http://nshipster.com/nslocale/), or even Map Kit or Core Location. For all of the care and attention to detail that Apple puts into localization, it's surprising that such an important task is relegated to the corners of an obscure, somewhat-unrelated framework. +Nowhere else in all of the other frameworks is this functionality provided. It's not part of [`NSLocale`](https://nshipster.com/nslocale/), or even Map Kit or Core Location. For all of the care and attention to detail that Apple puts into localization, it's surprising that such an important task is relegated to the corners of an obscure, somewhat-unrelated framework. > Unfortunately, Address Book UI is not available in OS X, and it would appear that there's no equivalent function provided on this platform. For you see, address formats vary greatly across different regions. For example, addresses in the United States take the form: -~~~ +``` Street Address City State ZIP Country -~~~ +``` Whereas addresses in Japan follow a different convention: -~~~ +``` Postal Code Prefecture Municipality Street Address Country -~~~ +``` -This is at least as jarring a difference in localization as [swapping periods for commas the radix point](http://en.wikipedia.org/wiki/Decimal_mark#Hindu.E2.80.93Arabic_numeral_system), so make sure to use this function anytime you're displaying an address from its components. +This is at least as jarring a difference in localization as [swapping periods for commas the radix point](https://en.wikipedia.org/wiki/Decimal_mark#Hindu.E2.80.93Arabic_numeral_system), so make sure to use this function anytime you're displaying an address from its components. diff --git a/2012-10-22-nslinguistictagger.md b/2012-10-22-nslinguistictagger.md index 6d2f69fe..21365421 100644 --- a/2012-10-22-nslinguistictagger.md +++ b/2012-10-22-nslinguistictagger.md @@ -1,6 +1,6 @@ --- title: NSLinguisticTagger -author: Mattt Thompson +author: Mattt category: Cocoa tags: nshipster excerpt: "NSLinguisticTagger is a veritable Swiss Army Knife of linguistic functionality, with the ability to tokenize natural language strings into words, determine their part-of-speech & stem, extract names of people, places, & organizations, and tell you the languages & respective writing system used in the string." @@ -9,7 +9,7 @@ status: reviewed: September 8, 2015 --- -`NSLinguisticTagger` is a veritable Swiss Army Knife of linguistic functionality, with the ability to [tokenize](http://en.wikipedia.org/wiki/Tokenization) natural language strings into words, determine their part-of-speech & [stem](http://en.wikipedia.org/wiki/Word_stem), extract names of people, places, & organizations, and tell you the languages & respective [writing system](http://en.wikipedia.org/wiki/Writing_system) used in the string. +`NSLinguisticTagger` is a veritable Swiss Army Knife of linguistic functionality, with the ability to [tokenize](https://en.wikipedia.org/wiki/Tokenization) natural language strings into words, determine their part-of-speech & [stem](https://en.wikipedia.org/wiki/Word_stem), extract names of people, places, & organizations, and tell you the languages & respective [writing system](https://en.wikipedia.org/wiki/Writing_system) used in the string. For most of us, this is far more power than we know what to do with. But perhaps this is just for lack sufficient opportunity to try. After all, almost every application deals with natural language in one way or another--perhaps `NSLinguisticTagger` could add a new level of polish, or enable brand new features entirely. @@ -23,7 +23,7 @@ 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: -~~~{swift} +```swift let question = "What is the weather in San Francisco?" let options: NSLinguisticTaggerOptions = [.OmitWhitespace, .OmitPunctuation, .JoinNames] let schemes = NSLinguisticTagger.availableTagSchemesForLanguage("en") @@ -33,8 +33,8 @@ tagger.enumerateTagsInRange(NSMakeRange(0, (question as NSString).length), schem let token = (question as NSString).substringWithRange(tokenRange) println("\(token): \(tag)") } -~~~ -~~~{objective-c} +``` +```objc NSString *question = @"What is the weather in San Francisco?"; NSLinguisticTaggerOptions options = NSLinguisticTaggerOmitWhitespace | NSLinguisticTaggerOmitPunctuation | NSLinguisticTaggerJoinNames; NSLinguisticTagger *tagger = [[NSLinguisticTagger alloc] initWithTagSchemes: [NSLinguisticTagger availableTagSchemesForLanguage:@"en"] options:options]; @@ -43,7 +43,7 @@ tagger.string = question; NSString *token = [question substringWithRange:tokenRange]; NSLog(@"%@: %@", token, tag); }]; -~~~ +``` This code would print the following: @@ -56,7 +56,7 @@ This code would print the following: If we filter on nouns, verbs, and place name, we get `[is, weather, San Francisco]`. -Just based on this alone, or perhaps in conjunction with something like the [Latent Semantic Mapping](http://developer.apple.com/library/mac/#documentation/LatentSemanticMapping/Reference/LatentSemanticMapping_header_reference/Reference/reference.html) framework, we can conclude that a reasonable course of action would be to make an API request to determine the current weather conditions in San Francisco. +Just based on this alone, or perhaps in conjunction with something like the [Latent Semantic Mapping](https://developer.apple.com/library/mac/#documentation/LatentSemanticMapping/Reference/LatentSemanticMapping_header_reference/Reference/reference.html) framework, we can conclude that a reasonable course of action would be to make an API request to determine the current weather conditions in San Francisco. ## Tagging Schemes @@ -72,54 +72,54 @@ Here's a list of the various token types associated with each scheme (`NSLinguis - - - + + + diff --git a/2012-10-29-uilocalizedindexedcollation.md b/2012-10-29-uilocalizedindexedcollation.md index e0274042..542f4307 100644 --- a/2012-10-29-uilocalizedindexedcollation.md +++ b/2012-10-29-uilocalizedindexedcollation.md @@ -1,6 +1,6 @@ --- title: UILocalizedIndexedCollation -author: Mattt Thompson +author: Mattt category: Cocoa tags: nshipster excerpt: "UITableView starts to become unwieldy once it gets to a few hundred rows. If users are reduced to frantically scratching at the screen like a cat playing Fruit Ninja in order to get at what they want... you may want to rethink your UI approach." @@ -9,17 +9,17 @@ status: reviewed: September 8, 2015 --- -UITableView starts to become unwieldy once it gets to a few hundred rows. If users are reduced to frantically scratching at the screen like a [cat playing Fruit Ninja](http://www.youtube.com/watch?v=CdEBgZ5Y46U) in order to get at what they want... you may want to rethink your UI approach. +UITableView starts to become unwieldy once it gets to a few hundred rows. If users are reduced to frantically scratching at the screen like a [cat playing Fruit Ninja](https://www.youtube.com/watch?v=CdEBgZ5Y46U) in order to get at what they want... you may want to rethink your UI approach. So, what are your options? -Well, you could organize your data into a hierarchy, which could dramatically reduce the number of rows displayed on each screen in fashion, based on its [branching factor](http://en.wikipedia.org/wiki/Branching_factor). +Well, you could organize your data into a hierarchy, which could dramatically reduce the number of rows displayed on each screen in fashion, based on its [branching factor](https://en.wikipedia.org/wiki/Branching_factor). You could also add a `UISearchBar` to the top of your table view, allowing the user to filter on keywords to get exactly what they're looking for (or--perhaps more importantly--determine that what they seek doesn't exist in the first place). There is also a third approach, which is generally under-utilized in iOS applications: **section index titles**. These are the vertically flowing letters found along the right side of table views in your Address Book contacts list or Music library: -![Section Index Titles Example]({{ site.asseturl }}/uilocalizedindexedcollation-example.png) +![Section Index Titles Example]({% asset uilocalizedindexedcollation-example.png @path %}) As the user scrolls their finger down the list, the table view jumps to the corresponding section. Even the most tiresome table view is rendered significantly more usable as a result. @@ -43,13 +43,13 @@ To give you a better idea of how section index titles vary between locales: > In order to see these for yourself, you'll need to explicitly add the desired locales to your Project Localizations list. -| Locale | Section Index Titles | -|------------|----------------------| -| en_US | `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, #` | -| ja_JP | `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, あ, か, さ, た, な, は, ま, や, ら, わ, #` | -| sv_SE | `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, Å, Ä, Ö, #` | -| ko_KO | `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, ㄱ, ㄴ, ㄷ, ㄹ, ㅁ, ㅂ, ㅅ, ㅇ, ㅈ, ㅊ, ㅋ, ㅌ, ㅍ, ㅎ, #` | -| AR_sa | 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, آ, ب, ت, ث, ج, ح, خ, د, ذ, ر, ز, س, ش, ص, ض, ط, ظ, ع, غ, ف, ق, ك, ل, م, ن, ه, و, ي, # | +| Locale | Section Index Titles | +| ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| en_US | `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, #` | +| ja_JP | `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, あ, か, さ, た, な, は, ま, や, ら, わ, #` | +| sv_SE | `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, Å, Ä, Ö, #` | +| ko_KO | `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, ㄱ, ㄴ, ㄷ, ㄹ, ㅁ, ㅂ, ㅅ, ㅇ, ㅈ, ㅊ, ㅋ, ㅌ, ㅍ, ㅎ, #` | +| AR_sa | 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, آ, ب, ت, ث, ج, ح, خ, د, ذ, ر, ز, س, ش, ص, ض, ط, ظ, ع, غ, ف, ق, ك, ل, م, ن, ه, و, ي, # | Aren't you glad you don't have to do this yourself? @@ -61,19 +61,19 @@ Finally, the table view should implement `-tableView:sectionForSectionIndexTitle All told, here's what a typical table view data source implementation looks like: -~~~{swift} +```swift class ObjectTableViewController: UITableViewController { - let collation = UILocalizedIndexedCollation.currentCollation() + let collation = UILocalizedIndexedCollation.current() var sections: [[AnyObject]] = [] var objects: [AnyObject] = [] { didSet { - let selector: Selector = "localizedTitle" - sections = Array(count: collation.sectionTitles.count, repeatedValue: []) + let selector = #selector(getter: UIApplicationShortcutItem.localizedTitle) + sections = Array(repeating: [], count: collation.sectionTitles.count) - let sortedObjects = collation.sortedArrayFromArray(objects, collationStringSelector: selector) + let sortedObjects = collation.sortedArray(from: objects, collationStringSelector: selector) for object in sortedObjects { - let sectionNumber = collation.sectionForObject(object, collationStringSelector: selector) - sections[sectionNumber].append(object) + let sectionNumber = collation.section(for: object, collationStringSelector: selector) + sections[sectionNumber].append(object as AnyObject) } self.tableView.reloadData() @@ -82,21 +82,21 @@ class ObjectTableViewController: UITableViewController { // MARK: UITableViewDelegate - override func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String { + override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { return collation.sectionTitles[section] } - override func sectionIndexTitlesForTableView(tableView: UITableView) -> [String] { + override func sectionIndexTitles(for tableView: UITableView) -> [String]? { return collation.sectionIndexTitles } - override func tableView(tableView: UITableView, sectionForSectionIndexTitle title: String, atIndex index: Int) -> Int { - return collation.sectionForSectionIndexTitleAtIndex(index) + override func tableView(_ tableView: UITableView, sectionForSectionIndexTitle title: String, at index: Int) -> Int { + return collation.section(forSectionIndexTitle: index) } } -~~~ +``` -~~~{objective-c} +```objc - (void)setObjects:(NSArray *)objects { SEL selector = @selector(localizedTitle); NSInteger index, sectionTitlesCount = [[[UILocalizedIndexedCollation currentCollation] sectionTitles] count]; @@ -137,7 +137,7 @@ sectionForSectionIndexTitle:(NSString *)title { return [[UILocalizedIndexedCollation currentCollation] sectionForSectionIndexTitleAtIndex:index]; } -~~~ +``` ## UITableViewIndexSearch @@ -152,4 +152,3 @@ So remember, NSHipsters one and all: if you see an excessively long table view, ...which is to say, refactor your content with some combination of hierarchies, a search bar, and section indexes. And when implementing section index titles, take advantage of `UILocalizedIndexedCollation`. Together, we can put an end to scroll view-induced repetitive stress injuries, and spend more time enjoying the finer things in life, like watching videos of pets playing with iPads. - diff --git a/2012-11-05-nsurlprotocol.md b/2012-11-05-nsurlprotocol.md index f823c90d..b40513c4 100644 --- a/2012-11-05-nsurlprotocol.md +++ b/2012-11-05-nsurlprotocol.md @@ -1,101 +1,19 @@ --- title: NSURLProtocol -author: Mattt Thompson +author: Mattt category: Cocoa excerpt: "Foundation’s URL Loading System is something that every iOS developer would do well to buddy up with. And of all of networking classes and protocols of Foundation, NSURLProtocol is perhaps the most obscure and powerful." status: - swift: n/a + swift: n/a --- iOS is all about networking--whether it's reading or writing state to and from the server, offloading computation to a distributed system, or loading remote images, audio, and video from the cloud. -Because of this, Foundation's [URL Loading System](https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/URLLoadingSystem/URLLoadingSystem.html#//apple_ref/doc/uid/10000165i) is something that every iOS developer would do well to buddy up with. - -When given the choice, applications should adopt the highest-level framework possible for what needs to be done. So, if that task is communicating over `http://`, `https://` or `ftp://`, then `NSURLConnection` and friends are a clear choice. Apple's networking classes cover the essentials for modern Objective-C application development, from URL and cache management to authentication & cookie storage: - -
-
The URL Loading System
-
NSLinguisticTagSchemeTokenTypeNSLinguisticTagSchemeLexicalClassNSLinguisticTagSchemeNameTypeNSLinguisticTagSchemeTokenTypeNSLinguisticTagSchemeLexicalClassNSLinguisticTagSchemeNameType
    -
  • NSLinguisticTagWord
  • -
  • NSLinguisticTagPunctuation
  • -
  • NSLinguisticTagWhitespace
  • -
  • NSLinguisticTagOther
  • +
  • NSLinguisticTagWord
  • +
  • NSLinguisticTagPunctuation
  • +
  • NSLinguisticTagWhitespace
  • +
  • NSLinguisticTagOther
    -
  • NSLinguisticTagNoun
  • -
  • NSLinguisticTagVerb
  • -
  • NSLinguisticTagAdjective
  • -
  • NSLinguisticTagAdverb
  • -
  • NSLinguisticTagPronoun
  • -
  • NSLinguisticTagDeterminer
  • -
  • NSLinguisticTagParticle
  • -
  • NSLinguisticTagPreposition
  • -
  • NSLinguisticTagNumber
  • -
  • NSLinguisticTagConjunction
  • -
  • NSLinguisticTagInterjection
  • -
  • NSLinguisticTagClassifier
  • -
  • NSLinguisticTagIdiom
  • -
  • NSLinguisticTagOtherWord
  • -
  • NSLinguisticTagSentenceTerminator
  • -
  • NSLinguisticTagOpenQuote
  • -
  • NSLinguisticTagCloseQuote
  • -
  • NSLinguisticTagOpenParenthesis
  • -
  • NSLinguisticTagCloseParenthesis
  • -
  • NSLinguisticTagWordJoiner
  • -
  • NSLinguisticTagDash
  • -
  • NSLinguisticTagOtherPunctuation
  • -
  • NSLinguisticTagParagraphBreak
  • -
  • NSLinguisticTagOtherWhitespace
  • +
  • NSLinguisticTagNoun
  • +
  • NSLinguisticTagVerb
  • +
  • NSLinguisticTagAdjective
  • +
  • NSLinguisticTagAdverb
  • +
  • NSLinguisticTagPronoun
  • +
  • NSLinguisticTagDeterminer
  • +
  • NSLinguisticTagParticle
  • +
  • NSLinguisticTagPreposition
  • +
  • NSLinguisticTagNumber
  • +
  • NSLinguisticTagConjunction
  • +
  • NSLinguisticTagInterjection
  • +
  • NSLinguisticTagClassifier
  • +
  • NSLinguisticTagIdiom
  • +
  • NSLinguisticTagOtherWord
  • +
  • NSLinguisticTagSentenceTerminator
  • +
  • NSLinguisticTagOpenQuote
  • +
  • NSLinguisticTagCloseQuote
  • +
  • NSLinguisticTagOpenParenthesis
  • +
  • NSLinguisticTagCloseParenthesis
  • +
  • NSLinguisticTagWordJoiner
  • +
  • NSLinguisticTagDash
  • +
  • NSLinguisticTagOtherPunctuation
  • +
  • NSLinguisticTagParagraphBreak
  • +
  • NSLinguisticTagOtherWhitespace
    -
  • NSLinguisticTagPersonalName
  • -
  • NSLinguisticTagPlaceName
  • -
  • NSLinguisticTagOrganizationName
  • +
  • NSLinguisticTagPersonalName
  • +
  • NSLinguisticTagPlaceName
  • +
  • NSLinguisticTagOrganizationName
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
URL Loading
NSURLConnection
NSURLRequestNSMutableURLRequest
NSURLResponseNSHTTPURLResponse
Cache Management
NSURLCache
NSCacheURLRequest
NSCachedURLResponse
Authentication & Credentials
NSURLCredential
NSURLCredentialStorage
NSURLAuthenticationChallenge
NSURLProtectionSpace
Cookie Storage
NSHTTPCookie
NSHTTPCookieStorage
Protocol Support
NSURLProtocol
- - -Although there's a lot to the URL Loading System, it's designed in a way that hides the underlying complexity, with hooks to provide configuration when needed. Any request going through `NSURLConnection` is intercepted by other parts of the system along the way, allowing for things like cached responses being transparently loaded from disk when available. +Because of this, Foundation's [URL Loading System](https://developer.apple.com/documentation/foundation/url_loading_system) is something that every iOS developer would do well to buddy up with. + +When given the choice, applications should adopt the highest-level framework possible for what needs to be done. So, if that task is communicating over `http://`, `https://` or `ftp://`, then `NSURLSession` and friends are a clear choice. Apple's networking classes cover the essentials for modern app development, from URL and cache management to authentication & cookie storage. + +Although there's a lot to the URL Loading System, it's designed in a way that hides the underlying complexity, with hooks to provide configuration when needed. Any request going through `NSURLSession` is intercepted by other parts of the system along the way, allowing for things like cached responses being transparently loaded from disk when available. Which brings us to this week's topic: `NSURLProtocol`. @@ -112,7 +30,7 @@ If you aren't already `mindblown.gif`, here are some examples of what this can b - Creating a proxy server for a local data transformation service with a URL request interface - Deliberately sending malformed & illegal response data to test the robustness of the application - Filtering sensitive information from requests or responses -- Implementing an `NSURLConnection`-compatible interface to an existing protocol. +- Implementing an `NSURLSession`-compatible interface to an existing protocol. Again, it's important to reiterate that the whole point of `NSURLProtocol` is that you can change everything about the loading behavior of your application without doing anything differently with how your application communicates to the network. @@ -146,18 +64,18 @@ This is especially important for subclasses created to interact with protocols t The most important methods in your subclass are `-startLoading` and `-stopLoading`. What goes into either of these methods is entirely dependent on what your subclass is trying to accomplish, but there is one commonality: communicating with the protocol client. -Each instance of a `NSURLProtocol` subclass has a `client` property, which is the object that is communicating with the URL Loading system. It's not `NSURLConnection`, but the object does conform to a protocol that should look familiar to anyone who has implemented `NSURLConnectionDelegate` +Each instance of a `NSURLProtocol` subclass has a `client` property, which is the object that is communicating with the URL Loading system. It's not `NSURLSession`, but the object does conform to a protocol that should look familiar to anyone who has implemented a session delegate. #### `` -* `-URLProtocol:cachedResponseIsValid:` -* `-URLProtocol:didCancelAuthenticationChallenge:` -* `-URLProtocol:didFailWithError:` -* `-URLProtocol:didLoadData:` -* `-URLProtocol:didReceiveAuthenticationChallenge:` -* `-URLProtocol:didReceiveResponse:cacheStoragePolicy:` -* `-URLProtocol:wasRedirectedToRequest:redirectResponse:` -* `-URLProtocolDidFinishLoading:` +- `-URLProtocol:cachedResponseIsValid:` +- `-URLProtocol:didCancelAuthenticationChallenge:` +- `-URLProtocol:didFailWithError:` +- `-URLProtocol:didLoadData:` +- `-URLProtocol:didReceiveAuthenticationChallenge:` +- `-URLProtocol:didReceiveResponse:cacheStoragePolicy:` +- `-URLProtocol:wasRedirectedToRequest:redirectResponse:` +- `-URLProtocolDidFinishLoading:` In your implementation of `-startLoading` and `-stopLoading`, you will need to send each delegate method to your `client` when appropriate. For something simple, this may mean sending several in rapid succession, but it's important nonetheless. @@ -170,7 +88,3 @@ When a request is loaded, each registered protocol is asked "hey, can you handle --- Like the URL Loading System that contains it, `NSURLProtocol` is incredibly powerful, and can be used in exceedingly clever ways. As a relatively obscure class, we've only just started to mine its potential for how we can use it to make our code cleaner, faster, and more robust. - -So go forth and hack! I can't wait to see what y'all come up with! - - diff --git a/2012-11-12-nsvaluetransformer.md b/2012-11-12-nsvaluetransformer.md deleted file mode 100644 index 4975906b..00000000 --- a/2012-11-12-nsvaluetransformer.md +++ /dev/null @@ -1,133 +0,0 @@ ---- -title: NSValueTransformer -author: Mattt Thompson -category: Cocoa -tags: nshipster -excerpt: "Of all the Foundation classes, NSValueTransformer is perhaps the one that fared the worst in the shift from OS X to iOS. But you know what? It's ripe for a comeback. With a little bit of re-tooling and some recontextualization, this blast from the past could be the next big thing in your application." -status: - swift: 2.0 - reviewed: September 8, 2015 ---- - -Of all the Foundation classes, `NSValueTransformer` is perhaps the one that fared the worst in the shift from OS X to iOS. - -Why? Well, there are two reasons: - -The first and most obvious reason is that `NSValueTransformer` was mainly used in AppKit with Cocoa bindings. Here, they could automatically transform values from one property to another without the need of intermediary glue code, like for negating a boolean, or checking whether a value was `nil`. iOS, of course, doesn't have bindings. - -The second reason has less to do with iOS than the Objective-C runtime itself. With the introduction of blocks, it got a whole lot easier to pass behavior between objects--significantly easier than, say `NSValueTransformer` or `NSInvocation`. So even if iOS were to get bindings tomorrow, it's uncertain as to whether `NSValueTransformer` would play as significant a role this time around. - -But you know what? `NSValueTransformer` is ripe for a comeback. With a little bit of re-tooling and some recontextualization, this blast from the past could be the next big thing in your application. - ---- - -`NSValueTransformer` is an abstract class that transforms one value into another. A transformation specifies what kinds of input values can be handled, and can even supports reversible transformations, where applicable. - -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? { - guard let type = value as? AnyClass else { return nil } - return NSStringFromClass(type) - } -} -~~~ - -~~~{objective-c} -@interface ClassNameTransformer: NSValueTransformer {} -@end - -#pragma mark - - -@implementation ClassNameTransformer -+ (Class)transformedValueClass { - return [NSString class]; -} - -+ (BOOL)allowsReverseTransformation { - return NO; -} - -- (id)transformedValue:(id)value { - return (value == nil) ? nil : NSStringFromClass([value class]); -} -@end -~~~ - -`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): - -~~~{swift} -let ClassNameTransformerName = "ClassNameTransformer" - -// Set the value transformer -NSValueTransformer.setValueTransformer(ClassNameTransformer(), forName: ClassNameTransformerName) - -// Get the value transformer -let valueTransformer = NSValueTransformer(forName: ClassNameTransformerName) -~~~ - -~~~{objective-c} -NSString * const ClassNameTransformerName = @"ClassNameTransformer"; - -// Set the value transformer -[NSValueTransformer setValueTransformer:[[ClassNameTransformer alloc] init] forName:ClassNameTransformerName]; - -// Get the value transformer -NSValueTransformer *valueTransformer = [NSValueTransformer valueTransformerForName: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. - -In this age of blocks, we want--nay, _demand_--a way to declare functionality in one (albeit gigantic) line of code. - -Nothing [a little metaprogramming](https://github.com/mattt/TransformerKit/blob/master/TransformerKit/NSValueTransformer%2BTransformerKit.m#L36) can't fix. Behold: - -~~~{swift} -let TKCapitalizedStringTransformerName = "TKCapitalizedStringTransformerName" - -NSValueTransformer.registerValueTransformerWithName(TKCapitalizedStringTransformerName, - transformedValueClass:NSString.self) { obj in - guard let str = obj as? String else { return nil } - return str.capitalizedString -} -~~~ -~~~{objective-c} -NSString * const TKCapitalizedStringTransformerName = @"TKCapitalizedStringTransformerName"; - -[NSValueTransformer registerValueTransformerWithName:TKCapitalizedStringTransformerName - transformedValueClass:[NSString class] -returningTransformedValueWithBlock:^id(id value) { - return [value capitalizedString]; -}]; -~~~ - -Not to break the 4th wall or anything, but in the middle of writing this article, I was compelled to see what could be done to improve the experience of `NSValueTransformer`. What I came up with was [TransformerKit](https://github.com/mattt/TransformerKit). - -The entire library is based on some objc runtime hackery in an `NSValueTransformer` category. Also included with this category are a number of convenient examples, like string case transformers (i.e. `CamelCase`, `llamaCase`, `snake_case`, and `train-case`). - -Now with its sexy new getup, we start to form a better understanding of where this could be useful: - -- `NSValueTransformers` are the ideal way to represent an ordered chain of fixed transformations. For instance, an app interfacing with a legacy system might transform user input through a succession of string transformations (trim whitespace, remove diacritics, and then capitalize letters) before sending it off to the mainframe. -- Unlike blocks, `NSValueTransformer` encapsulates reversible transformations. Let's say you were wanted to map keys from a REST API representation into a Model object; you could create a reversible transformation that converted `snake_case` to `llamaCase` when initializing, and `llamaCase` to `snake_case` when serializing back to the server. -- Another advantage over blocks is that `NSValueTransformer` subclasses can expose new properties that could be used to configure behavior in a particular way. Access to `ivars` also make it easier to cleanly memoize results, or do any necessary book-keeping along the way. -- Lest we forget, `NSValueTransformer` can be used with Core Data, as a way to encode and decode compound data types from blob fields. It seems to have fallen out of fashion over the years, but serializing simple collections in this way, for example, is an excellent strategy for information that isn't well-modeled as its own entity. Just don't serialize images to a database this way--that's generally a Bad Idea™. - -And the list goes on. - ---- - -`NSValueTransformer`, far from a vestige of AppKit, remains Foundation's purest connection to that fundamental concept of computation: input goes in, output comes out. - -Although it hasn't aged very well on its own, a little modernization restores `NSValueTransformer` to that highest esteem of NSHipsterdom: the solution that we didn't know we needed, but was there all along. diff --git a/2012-11-12-valuetransformer.md b/2012-11-12-valuetransformer.md new file mode 100644 index 00000000..af0cd83c --- /dev/null +++ b/2012-11-12-valuetransformer.md @@ -0,0 +1,235 @@ +--- +title: ValueTransformer +author: Mattt +category: Cocoa +tags: nshipster +excerpt: >- + `ValueTransformer` is perhaps the one that fared the worst + in the shift from macOS to iOS. + But you know what? It's ripe for a comeback. + With a little bit of re-tooling and some recontextualization, this blast from the past could be the next big thing in your application. +revisions: + "2012-11-12": Original publication + "2018-10-17": Updated for Swift 4.2 +status: + swift: 4.2 + reviewed: October 17, 2018 +--- + +Of all the Foundation classes, +`ValueTransformer` is perhaps the one that fared the worst +in the shift from macOS to iOS. + +Why? Here are two reasons: + +First, +`ValueTransformer` was used primarily in AppKit with Cocoa bindings. +There, they could automatically transform values from one property to another, +like for negating a boolean or checking whether a value was `nil`, +without the need of intermediary glue code. +iOS doesn't have bindings. + +The second reason has less to do with iOS than the Objective-C runtime itself. +With the introduction of blocks, +it got a whole lot easier to pass behavior between objects --- +significantly easier than, say `ValueTransformer` or `NSInvocation`. +So even if iOS were to get bindings tomorrow, +it's unclear whether `ValueTransformer` +would play a significant role this time around. + +But you know what? +`ValueTransformer` might just be ripe for a comeback. +With a little bit of re-tooling and some recontextualization, +this blast from the past could be the next big thing in your application. + +--- + +`ValueTransformer` is an abstract class that transforms one value into another. +A transformation specifies what kinds of input values can be handled +and whether it supports reversible transformations. + +A typical implementation looks something like this: + +```swift +class ClassNameTransformer: ValueTransformer { + override class func transformedValueClass() -> AnyClass { + return NSString.self + } + + override class func allowsReverseTransformation() -> Bool { + return false + } + + override func transformedValue(_ value: Any?) -> Any? { + guard let type = value as? AnyClass else { return nil } + return NSStringFromClass(type) + } +} +``` + +```objc +@interface ClassNameTransformer: NSValueTransformer {} +@end + +#pragma mark - + +@implementation ClassNameTransformer ++ (Class)transformedValueClass { + return [NSString class]; +} + ++ (BOOL)allowsReverseTransformation { + return NO; +} + +- (id)transformedValue:(id)value { + return (value == nil) ? nil : NSStringFromClass([value class]); +} +@end +``` + +`ValueTransformer` 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 a named _instance_ to act as a singleton: + +```swift +extension ClassNameTransformer { + static let name = NSValueTransformerName(rawValue: "ClassNameTransformer") +} + +// Set the value transformer +ValueTransformer.setValueTransformer(ClassNameTransformer(), + forName: ClassNameTransformer.name) + +// Get the value transformer +let valueTransformer = ValueTransformer(forName: ClassNameTransformer.name) +``` + +```objc +NSValueTransformerName const ClassNameTransformerName = @"ClassNameTransformer"; + +// Set the value transformer +[NSValueTransformer setValueTransformer:[[ClassNameTransformer alloc] init] forName:ClassNameTransformerName]; + +// Get the value transformer +NSValueTransformer *valueTransformer = [NSValueTransformer valueTransformerForName:ClassNameTransformerName]; +``` + +A common pattern is to register the singleton instance +in the `+initialize` method of the value transformer subclass +so it can be used without additional setup. + +Now at this point you probably realize `ValueTransformer`'s fatal flaw: +it's super annoying to set up! +Create a class, +implement a handful of simple methods, +define a constant, +_and_ register it in an `+initialize` method? No thanks. + +In this age of blocks, +we want --- nay, _demand_ --- +a way to declare functionality in one (albeit gigantic) line of code. + +Nothing [a little metaprogramming](https://github.com/mattt/TransformerKit/blob/master/TransformerKit/NSValueTransformer%2BTransformerKit.m#L36) can't fix. +Behold: + +```swift +let TKCapitalizedStringTransformerName = + NSValueTransformerName(rawValue: "TKCapitalizedStringTransformerName") + +ValueTransformer.registerValueTransformerWithName(TKCapitalizedStringTransformerName, + transformedValueClass:NSString.self) { object in + guard let string = object as? String else { return nil } + return string.capitalized +} +``` + +```objc +NSValueTransformerName const TKCapitalizedStringTransformerName = @"TKCapitalizedStringTransformerName"; + +[NSValueTransformer registerValueTransformerWithName:TKCapitalizedStringTransformerName + transformedValueClass:[NSString class] +returningTransformedValueWithBlock:^id(id value) { + return [value capitalizedString]; +}]; +``` + +{% info %} +Not to break the 4th wall or anything, +but in the process of writing this article, +I was compelled to see how much I could improve +the experience of using `ValueTransformer`. +What I came up with was +[TransformerKit](https://github.com/mattt/TransformerKit). + +At the core of the library is some Obj-C runtime hackery +in an `ValueTransformer` category. +Also included are some helpful examples, +like string case transformers +(i.e. `CamelCase`, `llamaCase`, `snake_case`, and `train-case`). +{% endinfo %} + +--- + +Now with a fresh new look, +we can start to get a better understanding of +how we might take advantage of `ValueTransformer`: + +## Making Business Logic More Functional + +`ValueTransformer` objects are a great way to represent +an ordered chain of fixed transformations. +For instance, an app interfacing with a legacy system +might transform user input through a succession of string transformations +(trim whitespace, remove diacritics, and then capitalize letters) +before sending it off to the mainframe. + +## Thinking Forwards and Backwards + +Unlike blocks, value transformers have the concept of reversibility, +which enables some interesting use cases. + +Say you were wanted to map keys from a REST API representation into a model. +You could create a reversible transformation that converted `snake_case` to `llamaCase` when initializing, +and `llamaCase` to `snake_case` when serializing back to the server. + +## Configuring Functionality + +Another advantage over blocks is that +`ValueTransformer` subclasses can expose new properties +that can be used to configure behavior in a particular way. +Access to properties also provides a clean way to cache or memoize results +and do any necessary book-keeping along the way. + +## Transforming Your Core Data Stack + +Lest we forget, +`ValueTransformer` can be used alongside Core Data +to encode and decode compound data types from blob fields. +It seems to have fallen out of fashion over the years, +but serializing simple collections in this way +can be a winning strategy for difficult-to-model data. +(Just don't use this approach to serialize images or other binary data; +use external storage instead) + +--- + +`ValueTransformer`, +far from a vestige of AppKit, +remains Foundation's purest connection to functional programming: +input goes in, output comes out. + +While it's true that Objective-C blocks +and all of the advanced language features in Swift +are superior examples of the functional programming paradigm. +`ValueTransformer` has a special place in Cocoa's history and Xcode's tooling. +For that reason, object orientation is transformed +from an embarrassing liability to its greatest asset. + +And though it hasn't aged very well on its own, +a little modernization restores `ValueTransformer` +to that highest esteem of NSHipsterdom: +a solution that we didn't know we needed +but was there all along. diff --git a/2012-11-19-ns_enum-ns_options.md b/2012-11-19-ns_enum-ns_options.md index 0bfda2d7..8577f88f 100644 --- a/2012-11-19-ns_enum-ns_options.md +++ b/2012-11-19-ns_enum-ns_options.md @@ -1,6 +1,6 @@ --- title: "NS_ENUM & NS_OPTIONS" -author: Mattt Thompson +author: Mattt category: Cocoa tags: nshipster, popular excerpt: "A skilled Objective-C developer is able to gracefully switch between Objective and Procedural paradigms, and use each to their own advantage." @@ -27,47 +27,39 @@ And on that note, this week's topic has to do with two simple-but-handy macros: Introduced in Foundation with iOS 6 / OS X Mountain Lion, the `NS_ENUM` and `NS_OPTIONS` macros are the new, preferred way to declare `enum` types. -> If you'd like to use either macro when targeting a previous version of iOS or OS X, you can simply inline like so: - -~~~{objective-c} -#ifndef NS_ENUM -#define NS_ENUM(_type, _name) enum _name : _type _name; enum _name : _type -#endif -~~~ - `enum`, or enumerated value types, are the C way to define constants for fixed values, like days of the week, or available styles of table view cells. In an `enum` declaration, constants without explicit values will automatically be assigned values sequentially, starting from `0`. There are several legal ways that `enum`s can be defined. What's confusing is that there are subtle functional differences between each approach, and without knowing any better, someone is just as likely to use them interchangeably. For instance: -~~~{objective-c} +```objc enum { UITableViewCellStyleDefault, UITableViewCellStyleValue1, UITableViewCellStyleValue2, UITableViewCellStyleSubtitle }; -~~~ +``` ...declares integer values, but no type. Whereas: -~~~{objective-c} +```objc typedef enum { UITableViewCellStyleDefault, UITableViewCellStyleValue1, UITableViewCellStyleValue2, UITableViewCellStyleSubtitle } UITableViewCellStyle; -~~~ +``` ...defines the `UITableViewCellStyle` type, suitable for specifying the type of method parameters. However, Apple had previously defined all of their `enum` types as: -~~~{objective-c} +```objc typedef enum { UITableViewCellStyleDefault, UITableViewCellStyleValue1, @@ -76,7 +68,7 @@ typedef enum { }; typedef NSInteger UITableViewCellStyle; -~~~ +``` ...which ensures a fixed size for `UITableViewCellStyle`, but does nothing to hint the relation between the aforementioned `enum` and the new type to the compiler. @@ -86,14 +78,14 @@ Thankfully, Apple has decided on "One Macro To Rule Them All" with `NS_ENUM`. Now, `UITableViewCellStyle` is declared with: -~~~{objective-c} +```objc typedef NS_ENUM(NSInteger, UITableViewCellStyle) { UITableViewCellStyleDefault, UITableViewCellStyleValue1, UITableViewCellStyleValue2, UITableViewCellStyleSubtitle }; -~~~ +``` The first argument for `NS_ENUM` is the type used to store the new type. In a 64-bit environment, `UITableViewCellStyle` will be 8 bytes long--same as `NSInteger`. Make sure that the specified size can fit all of the defined values, or else an error will be generated. The second argument is the name of the new type (as you probably guessed). Inside the block, the values are defined as usual. @@ -101,7 +93,7 @@ This approach combines the best of all of the aforementioned approaches, and eve ## `NS_OPTIONS` -`enum` can also be used to define a [bitmask][1]. Using a convenient property of binary math, a single integer value can encode a combination of values all at once using the bitwise `OR` (`|`), and decoded with bitwise `AND` (`&`). Each subsequent value, rather than automatically being incremented by 1 from 0, are manually given a value with a bit offset: `1 << 0`, `1 << 1`, `1 << 2`, and so on. If you imagine the binary representation of a number, like `10110` for 22, each bit can be thought to represent a single boolean. In UIKit, for example, `UIViewAutoresizing` is a bitmask that can represent any combination of flexible top, bottom, left, and right margins, or width and height. +`enum` can also be used to define a [bitmask][1]. Using a convenient property of binary math, a single integer value can encode a combination of values all at once using the bitwise `OR` (`|`), and decoded with bitwise `AND` (`&`). Each subsequent value, rather than automatically being incremented by 1 from 0, are manually given a value with a bit offset: `1 << 0`, `1 << 1`, `1 << 2`, and so on. If you imagine the binary representation of a number, like `10110` for 22, each bit can be though to represent a single boolean. In UIKit, for example, `UIViewAutoresizing` is a bitmask that can represent any combination of flexible top, bottom, left, and right margins, or width and height. Rather than `NS_ENUM`, bitmasks should now use the `NS_OPTIONS` macro. @@ -111,4 +103,4 @@ The syntax is exactly the same as `NS_ENUM`, but this macro alerts the compiler `NS_ENUM` and `NS_OPTIONS` are handy additions to the Objective-C development experience, and reaffirm the healthy dialectic between its objective and procedural nature. Keep this in mind as you move forward in your own journey to understand the logical tensions that underpin everything around us. -[1]: http://en.wikipedia.org/wiki/Mask_(computing) +[1]: https://en.wikipedia.org/wiki/Mask_(computing) diff --git a/2012-11-26-nsorderedset.md b/2012-11-26-nsorderedset.md index f44f4f21..2d9556d0 100644 --- a/2012-11-26-nsorderedset.md +++ b/2012-11-26-nsorderedset.md @@ -1,17 +1,17 @@ --- title: NSOrderedSet -author: Mattt Thompson +author: Mattt category: Cocoa tags: nshipster excerpt: "Why isn't NSOrderedSet a subclass of NSSet? The answer may surprise you." status: - swift: 2.0 - reviewed: September 15, 2015 + swift: 2.0 + reviewed: September 15, 2015 --- Here's a question: why isn't `NSOrderedSet` a subclass of `NSSet`? -It seems perfectly logical, after all, for `NSOrderedSet`--a class that enforces the same uniqueness constraint of `NSSet`--to be a _subclass_ of `NSSet`. It has the same methods as `NSSet`, with the addition of some `NSArray`-style methods like `objectAtIndex:`. By all accounts, it would seem to perfectly satisfy the requirements of the [Liskov substitution principle](http://en.wikipedia.org/wiki/Liskov_substitution_principle), that: +It seems perfectly logical, after all, for `NSOrderedSet`--a class that enforces the same uniqueness constraint of `NSSet`--to be a _subclass_ of `NSSet`. It has the same methods as `NSSet`, with the addition of some `NSArray`-style methods like `objectAtIndex:`. By all accounts, it would seem to perfectly satisfy the requirements of the [Liskov substitution principle](https://en.wikipedia.org/wiki/Liskov_substitution_principle), that: > If `S` is a subtype of `T`, then objects of type `T` in a program may be replaced with objects of type `S` without altering any of the desirable properties of that program. @@ -19,7 +19,7 @@ So why is `NSOrderedSet` a subclass of `NSObject` and not `NSSet` or even `NSArr _Mutable / Immutable Class Clusters_ -[Class Clusters](http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/CocoaFundamentals/CocoaObjects/CocoaObjects.html%23//apple_ref/doc/uid/TP40002974-CH4-SW34) are a design pattern at the heart of the Foundation framework; the essence of Objective-C's simplicity in everyday use. +[Class Clusters](https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/CocoaFundamentals/CocoaObjects/CocoaObjects.html%23//apple_ref/doc/uid/TP40002974-CH4-SW34) are a design pattern at the heart of the Foundation framework; the essence of Objective-C's simplicity in everyday use. But class clusters offer simplicity at the expense of extensibility, which becomes especially tricky when it comes to mutable / immutable class pairs like `NSSet` / `NSMutableSet`. @@ -27,25 +27,25 @@ 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: -~~~{swift} +```swift let immutable = NSSet() let mutable = immutable.mutableCopy() as! NSMutableSet mutable.isKindOfClass(NSSet.self) // true mutable.isKindOfClass(NSMutableSet.self) // true -~~~ +``` -~~~{objective-c} +```objc NSSet* immutable = [NSSet set]; NSMutableSet* mutable = [immutable mutableCopy]; [mutable isKindOfClass:[NSSet class]]; // YES [mutable isKindOfClass:[NSMutableSet class]]; // YES -~~~ +``` Now let's suppose that `NSOrderedSet` was indeed a subclass of `NSSet`: -~~~{swift} +```swift // class NSOrderedSet: NSSet {...} let immutable = NSOrderedSet() @@ -53,9 +53,9 @@ let mutable = immutable.mutableCopy() as! NSMutableOrderedSet mutable.isKindOfClass(NSSet.self) // true mutable.isKindOfClass(NSMutableSet.self) // false (!) -~~~ +``` -~~~{objective-c} +```objc // @interface NSOrderedSet : NSSet NSOrderedSet* immutable = [NSOrderedSet orderedSet]; @@ -63,13 +63,13 @@ NSMutableOrderedSet* mutable = [immutable mutableCopy]; [mutable isKindOfClass:[NSSet class]]; // YES [mutable isKindOfClass:[NSMutableSet class]]; // NO (!) -~~~ +``` - +{% asset nsorderedset-case-1.svg %}" 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} +```swift // class NSOrderedSet: NSSet {...} // class NSMutableOrderedSet: NSMutableSet {...} @@ -79,9 +79,9 @@ let mutable = immutable.mutableCopy() as! NSMutableOrderedSet mutable.isKindOfClass(NSSet.self) // true mutable.isKindOfClass(NSMutableSet.self) // true mutable.isKindOfClass(NSOrderedSet.self) // false (!) -~~~ +``` -~~~{objective-c} +```objc // @interface NSOrderedSet : NSSet // @interface NSMutableOrderedSet : NSMutableSet @@ -91,19 +91,19 @@ NSMutableOrderedSet* mutable = [immutable mutableCopy]; [mutable isKindOfClass:[NSSet class]]; // YES [mutable isKindOfClass:[NSMutableSet class]]; // YES [mutable isKindOfClass:[NSOrderedSet class]]; // NO (!) -~~~ +``` - +{% asset nsorderedset-case-2.svg %} This is perhaps even worse, as now `NSMutableOrderedSet` couldn't be used as a method parameter expecting an `NSOrderedSet`. No matter how we approach it, we can't stack a mutable / immutable class pair on top of another existing mutable / immutable class pair. It just won't work in Objective-C. -Rather than subject ourselves to the perils of [multiple inheritance](http://en.wikipedia.org/wiki/Multiple_inheritance), we could use Protocols to get us out of this pickle (as it does every other time the spectre of multiple inheritance is raised). Indeed, Foundation's collection classes _could_ become more aspect-oriented by adding protocols: +Rather than subject ourselves to the perils of [multiple inheritance](https://en.wikipedia.org/wiki/Multiple_inheritance), we could use Protocols to get us out of this pickle (as it does every other time the spectre of multiple inheritance is raised). Indeed, Foundation's collection classes _could_ become more aspect-oriented by adding protocols: -* `NSArray : NSObject ` -* `NSSet : NSObject ` -* `NSOrderedSet : NSObject ` +- `NSArray : NSObject ` +- `NSSet : NSObject ` +- `NSOrderedSet : NSObject ` However, to reap any benefit from this arrangement, all of the existing APIs would have to be restructured to have parameters accept `id ` instead of `NSArray`. But the transition would be painful, and would likely open up a whole can of edge cases... which would mean that it would never be fully adopted... which would mean that there's less incentive to adopt this approach when defining your own APIs... which are less fun to write because there's now two incompatible ways to do something instead of one... which... @@ -111,7 +111,7 @@ However, to reap any benefit from this arrangement, all of the existing APIs wou --- -`NSOrderedSet` was introduced in iOS 5 & OS X Lion. The only APIs changed to add support for `NSOrderedSet`, though, were part of [Core Data](http://developer.apple.com/library/mac/#releasenotes/DataManagement/RN-CoreData/_index.html). +`NSOrderedSet` was introduced in iOS 5 & OS X Lion. The only APIs changed to add support for `NSOrderedSet`, though, were part of [Core Data](https://developer.apple.com/library/mac/#releasenotes/DataManagement/RN-CoreData/_index.html). This was fantastic news for anyone using Core Data at the time, as it solved one of the long-standing annoyances of not having a way to arbitrarily order relationship collections. Previously, you'd have to add a `position` attribute, which would be re-calculated every time a collection was modified. There wasn't a built-in way to validate that your collection positions were unique or that the sequence didn't have any gaps. @@ -123,6 +123,6 @@ Although it is perfectly suited to that one particular use case in Core Data, `N --- -So, as a general rule: **`NSOrderedSet` is useful for intermediary and internal representations, but you probably shouldn't introduce it as a method parameter unless it's particularly well-suited to the semantics of the data model.** +So, as a general rule: **`NSOrderedSet` is useful for intermediary and internal representations, but you probably shouldn't introduce it as a method parameters unless it's particularly well-suited to the semantics of the data model.** If nothing else, `NSOrderedSet` illuminates some of the fascinating implications of Foundation's use of the class cluster design pattern. In doing so, it allows us better understand the trade-off between simplicity and extensibility as we make these choices in our own application designs. diff --git a/2012-12-03-kvc-collection-operators.md b/2012-12-03-kvc-collection-operators.md index a0418dc8..30e01486 100644 --- a/2012-12-03-kvc-collection-operators.md +++ b/2012-12-03-kvc-collection-operators.md @@ -1,6 +1,6 @@ --- title: KVC Collection Operators -author: Mattt Thompson +author: Mattt category: Cocoa tags: nshipster excerpt: "Rubyists laugh at Objective-C’s bloated syntax. Although we lost a few pounds over the summer with our sleek new object literals, those Red-headed bullies still taunt us with their map one-liners and their fancy Symbol#to_proc. Fortunately, Key-Value Coding has an ace up its sleeves." @@ -11,27 +11,27 @@ status: Rubyists laugh at Objective-C's bloated syntax. -Although we lost a few pounds over the summer with our [sleek new object literals](http://nshipster.com/at-compiler-directives/), those Red-headed bullies still taunt us with their `map` one-liners and their fancy [`Symbol#to_proc`](http://pragdave.me/blog/2005/11/04/symboltoproc/). +Although we lost a few pounds over the summer with our [sleek new object literals](https://nshipster.com/at-compiler-directives/), those Red-headed bullies still taunt us with their `map` one-liners and their fancy [`Symbol#to_proc`](http://pragdave.me/blog/2005/11/04/symboltoproc/). -Really, a lot of how elegant (or clever) a language is comes down to how well it avoids loops. `for`, `while`; even [fast enumeration expressions](http://nshipster.com/enumerators/) are a drag. No matter how you sugar-coat them, loops will be a block of code that does something that is much simpler to describe in natural language. +Really, a lot of how elegant (or clever) a language is comes down to how well it avoids loops. `for`, `while`; even [fast enumeration expressions](https://nshipster.com/enumerators/) are a drag. No matter how you sugar-coat them, loops will be a block of code that does something that is much simpler to describe in natural language. "get me the average salary of all of the employees in this array", versus... -~~~{objective-c} +```objc double totalSalary = 0.0; for (Employee *employee in employees) { totalSalary += [employee.salary doubleValue]; } double averageSalary = totalSalary / [employees count]; -~~~ +``` Meh. Fortunately, [Key-Value Coding](https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/KeyValueCoding/Articles/KeyValueCoding.html) gives us a much more concise--almost Ruby-like--way to do this: -~~~{objective-c} +```objc [employees valueForKeyPath:@"@avg.salary"]; -~~~ +``` [KVC Collection Operators](https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/KeyValueCoding/Articles/CollectionOperators.html) allows actions to be performed on a collection using key path notation in `valueForKeyPath:`. Any time you see `@` in a key path, it denotes a particular aggregate function whose result can be returned or chained, just like any other key path. @@ -43,13 +43,13 @@ Collection Operators fall into one of three different categories, according to t The best way to understand how these work is to see them in action. Consider a `Product` class, and a `products` array with the following data: -~~~{objective-c} +```objc @interface Product : NSObject @property NSString *name; @property double price; @property NSDate *launchedOn; @end -~~~ +``` > Key-Value Coding automatically boxes and un-boxes scalars into `NSNumber` or `NSValue` as necessary to make everything work. @@ -95,32 +95,38 @@ The best way to understand how these work is to see them in action. Consider a ` _Example_: -~~~{objective-c} +```objc [products valueForKeyPath:@"@count"]; // 4 [products valueForKeyPath:@"@sum.price"]; // 3526.00 [products valueForKeyPath:@"@avg.price"]; // 881.50 [products valueForKeyPath:@"@max.price"]; // 1699.00 [products valueForKeyPath:@"@min.launchedOn"]; // June 11, 2012 -~~~ +``` -> Pro Tip: To get the aggregate value of an array or set of `NSNumber`s, you can simply pass `self` as the key path after the operator, e.g. `[@[@(1), @(2), @(3)] valueForKeyPath:@"@max.self"]` (/via [@davandermobile](http://twitter.com/davandermobile), citing [Objective Sea](http://objectivesea.tumblr.com/post/34552840247/max-value-nsset-kvc)) +{% info %} +To get the aggregate value of an array or set of `NSNumber` values, +you can simply pass `self` as the key path after the operator. +For example, +`[@[@(1), @(2), @(3)] valueForKeyPath:@"@max.self"]` +produces the value `3`. +{% endinfo %} ### Object Operators Let's say we have an `inventory` array, representing the current stock of our local Apple store (which is running low on iPad Mini, and doesn't have the new iMac, which hasn't shipped yet): -~~~{objective-c} +```objc NSArray *inventory = @[iPhone5, iPhone5, iPhone5, iPadMini, macBookPro, macBookPro]; -~~~ +``` - `@unionOfObjects` / `@distinctUnionOfObjects`: Returns an array of the objects in the property specified in the key path to the right of the operator. `@distinctUnionOfObjects` removes duplicates, whereas `@unionOfObjects` does not. _Example_: -~~~{objective-c} +```objc [inventory valueForKeyPath:@"@unionOfObjects.name"]; // "iPhone 5", "iPhone 5", "iPhone 5", "iPad Mini", "MacBook Pro", "MacBook Pro" [inventory valueForKeyPath:@"@distinctUnionOfObjects.name"]; // "iPhone 5", "iPad Mini", "MacBook Pro" -~~~ +``` ### Array and Set Operators @@ -133,9 +139,9 @@ This would be useful if we were to, for example, compare the inventory of severa _Example_: -~~~{objective-c} +```objc [@[appleStoreInventory, verizonStoreInventory] valueForKeyPath:@"@distinctUnionOfArrays.name"]; // "iPhone 5", "iPad Mini", "MacBook Pro" -~~~ +``` --- @@ -149,19 +155,19 @@ This makes sense to spell out, since that's what most people are thinking about However, as it turns out, it _is_ actually possible, with a little help from our friend, `objc/runtime`. -[Guy English](https://twitter.com/gte) has a [pretty amazing post](http://kickingbear.com/blog/archives/9) wherein he [swizzles `valueForKeyPath:`](https://gist.github.com/4196641#file_kb_collection_extensions.m) to parse a custom-defined [DSL](http://en.wikipedia.org/wiki/Domain-specific_language), which extends the existing offerings to interesting effect: +[Guy English](https://twitter.com/gte) has a [pretty amazing post](http://kickingbear.com/blog/archives/9) wherein he [swizzles `valueForKeyPath:`](https://gist.github.com/4196641#file_kb_collection_extensions.m) to parse a custom-defined [DSL](https://en.wikipedia.org/wiki/Domain-specific_language), which extends the existing offerings to interesting effect: -~~~{objective-c} +```objc NSArray *names = [allEmployees valueForKeyPath: @"[collect].{daysOff<10}.name"]; -~~~ +``` This code would get the names of anyone who has taken fewer than 10 days off (to remind them to take a vacation, no doubt!). Or, taken to a ridiculous extreme: -~~~{objective-c} +```objc NSArray *albumCovers = [records valueForKeyPath:@"[collect].{artist like 'Bon Iver'}..albumCoverImageData"]; -~~~ +``` Eat your heart out, Ruby. This one-liner filters a record collection for artists whose name matches "Bon Iver", and initializes an `NSImage` from the album cover image data of the matching albums. diff --git a/2012-12-10-instancetype.md b/2012-12-10-instancetype.md index cdab91f8..dcc53d49 100644 --- a/2012-12-10-instancetype.md +++ b/2012-12-10-instancetype.md @@ -1,6 +1,6 @@ --- title: instancetype -author: Mattt Thompson +author: Mattt category: Objective-C excerpt: "Objective-C is a rapidly evolving language, in a way that you just don't see in established programming languages. Developments range from the mundane to paradigm-changing, but telling the difference takes practice. Because we're talking about low-level language features, it's difficult to understand what implications they may have higher up with API design." status: @@ -11,7 +11,7 @@ Want to know what's coming next in Objective-C? [Keep your ear to the ground](ht Objective-C is a rapidly evolving language, in a way that you just don't see in established programming languages. ARC, object literals, subscripting, blocks: in the span of just three years, so much of how we program in Objective-C has been changed (for the better). -All of this innovation is a result of Apple's philosophy of vertical integration. Just as Apple's investment in designing [its own chipsets](http://en.wikipedia.org/wiki/Apple_A4) gave them leverage to compete aggressively with their mobile hardware, so too has their investment in [LLVM](http://llvm.org) allowed their software to keep pace. +All of this innovation is a result of Apple's philosophy of vertical integration. Just as Apple's investment in designing [its own chipsets](https://en.wikipedia.org/wiki/Apple_A4) gave them leverage to compete aggressively with their mobile hardware, so too has their investment in [LLVM](http://llvm.org) allowed their software to keep pace. Clang developments range from the mundane to paradigm-changing, but telling the difference takes practice. Because we're talking about low-level language features, it's difficult to understand what implications they may have higher up with API design. @@ -29,11 +29,11 @@ Class constructor methods, although they similarly return `id`, don't get the sa You can try this out for yourself: -~~~{objective-c} +```objc [[[NSArray alloc] init] mediaPlaybackAllowsAirPlay]; // ❗ "No visible @interface for `NSArray` declares the selector `mediaPlaybackAllowsAirPlay`" [[NSArray array] mediaPlaybackAllowsAirPlay]; // (No error) -~~~ +``` Because `alloc` and `init` follow the naming convention for being a related result type, the correct type check against `NSArray` is performed. However, the equivalent class constructor `array` does not follow that convention, and is interpreted as `id`. @@ -45,17 +45,17 @@ This is where the compiler steps in to resolve this timeless edge case to the Ob `instancetype` is a contextual keyword that can be used as a result type to signal that a method returns a related result type. For example: -~~~{objective-c} +```objc @interface Person + (instancetype)personWithName:(NSString *)name; @end -~~~ +``` > `instancetype`, unlike `id`, can only be used as the result type in a method declaration. With `instancetype`, the compiler will correctly infer that the result of `+personWithName:` is an instance of a `Person`. -Look for class constructors in Foundation to start using `instancetype` in the near future. New APIs, such as [UICollectionViewLayoutAttributes](http://developer.apple.com/library/ios/#documentation/uikit/reference/UICollectionViewLayoutAttributes_class/Reference/Reference.html) are using `instancetype` already. +Look for class constructors in Foundation to start using `instancetype` in the near future. New APIs, such as [UICollectionViewLayoutAttributes](https://developer.apple.com/library/ios/#documentation/uikit/reference/UICollectionViewLayoutAttributes_class/Reference/Reference.html) are using `instancetype` already. ## Further Implications @@ -63,9 +63,9 @@ Language features are particularly interesting because, again, it's often unclea While `instancetype` may seem to be a rather mundane, albeit welcome addition to the compiler, it can be used to some rather clever ends. -[Jonathan Sterling](https://twitter.com/jonsterling) wrote [this quite interesting article](http://www.jonmsterling.com/posts/2012-02-05-typed-collections-with-self-types-in-objective-c.html), detailing how `instancetype` could be used to encode statically-typed collections, without [generics](http://en.wikipedia.org/wiki/Generic_programming): +[Jonathan Sterling](https://twitter.com/jonsterling) wrote [this quite interesting article](http://www.jonmsterling.com/posts/2012-02-05-typed-collections-with-self-types-in-objective-c.html), detailing how `instancetype` could be used to encode statically-typed collections, without [generics](https://en.wikipedia.org/wiki/Generic_programming): -~~~{objective-c} +```objc NSURL *sites = (id)[NSURL mapCollection]; [sites put:[NSURL URLWithString:@"http://www.jonmsterling.com/"] at:@"jon"]; @@ -73,7 +73,7 @@ NSURL *sites = (id)[NSURL mapCollection]; at:@"nshipster"]; NSURL *jonsSite = [sites at:@"jon"]; // => http://www.jonmsterling.com/ -~~~ +``` Statically-typed collections would make APIs more expressive--no longer would a developer be unsure about what kinds of objects are allowed in a collection parameter. @@ -87,4 +87,4 @@ Know it, love it. And take it as an example of how paying attention to the low-level details can give you insights into powerful new ways to transform Objective-C. -[1]: http://en.wikipedia.org/wiki/C_Sharp_(programming_language) +[1]: https://en.wikipedia.org/wiki/C_Sharp_(programming_language) diff --git a/2012-12-17-cggeometry.md b/2012-12-17-cggeometry.md deleted file mode 100644 index 72604090..00000000 --- a/2012-12-17-cggeometry.md +++ /dev/null @@ -1,334 +0,0 @@ ---- -title: CGGeometry -author: Mattt Thompson -category: Cocoa -excerpt: "Unless you were a Math Geek or an Ancient Greek, Geometry was probably not your favorite subject in high school. No, chances are that you were that kid in class who dutifully programmed all of the necessary formulae into your TI-8X calculator. Keeping in the tradition of doing the least amount of math possible, here are some semi-obscure CoreGraphics functions to make your job easier." -revisions: - "2015-02-17": Added Swift examples and information about how `Swift + CGRect == awesome`; added section for `CGRectIntersect` and `CGRectUnion`. -status: - swift: 2.0 - reviewed: September 8, 2015 ---- - -Unless you were a Math Geek or an Ancient Greek, Geometry was probably not your favorite subject in high school. No, chances are that you were that kid in class who dutifully programmed all of the necessary formulæ into your TI-8X calculator. - -So for those of you who spent more time learning TI-BASIC than Euclidean geometry, here's the cheat-sheet for how geometry works in [Quartz 2D][1], the drawing system used in iOS and OS X: - -- A `CGPoint` is a struct that represents a point in a two-dimensional coordinate system. For iOS, the origin is at the top-left, so points move right and down as their `x` and `y` values, respectively, increase. OS X, by contrast, is oriented with `(0, 0)` in the bottom left, with `y` moving up as it increases. - -- A `CGSize` is a struct that represents the dimensions of `width` and `height`. - -- A `CGRect` is a struct with both a `CGPoint` (`origin`) and a `CGSize` (`size`), representing a rectangle drawn from its `origin` point with the `width` and `height` of its `size`. - -Because `CGRect` is used to represent the `frame` of every view drawn on screen, a programmer's success in graphical programming is contingent on their ability to effectively manipulate rectangle geometry. - -Fortunately for us, Quartz comes with a slew of useful functions to reduce the amount of floating point math we have to do ourselves. As central as view programming is to Cocoa, and as useful as these functions are, however, they remain relatively unknown to most iOS developers. - -This will not stand! Let's shine some light on the most useful functions and save y'all some typing! - -> In Swift, the CoreGraphics framework augments `CGRect` by adding all this functionality as static properties, instance properties, and both mutating and non-mutating functions where appropriate. For idiomatic Swift code, prefer properties and nonmutating instance methods whenever possible. - ---- - -Transformations ---------------- - -First on our list are the geometric transformations. These functions return a `CGRect`, which is the result of performing a particular set of operations on the passed rectangle. - -### `CGRectOffset` - -> `offsetBy` / `CGRectOffset`: Returns a rectangle with an origin that is offset from that of the source rectangle. - -~~~{swift} -// methods: -extension CGRect { - func offsetBy(dx: CGFloat, dy: CGFloat) -> CGRect - mutating func offsetInPlace(dx: CGFloat, dy: CGFloat) -} -// function: -func CGRectOffset(rect: CGRect, dx: CGFloat, dy: CGFloat) -> CGRect -~~~ -~~~{objective-c} -CGRect CGRectOffset( - CGRect rect, - CGFloat dx, - CGFloat dy -) -~~~ - -Consider using this anytime you're changing the origin of a rectangle. Not only can it save a line of code when changing both the horizontal and vertical position, but more importantly, it represents the translation more semantically than manipulating the origin values individually. - -### `CGRectInset` - -> `rectByInsetting` / `CGRectInset`: Returns a rectangle that is smaller or larger than the source rectangle, with the same center point. - -~~~{swift} -// methods: -extension CGRect { - func insetBy(dx: CGFloat, dy: CGFloat) -> CGRect - mutating func insetInPlace(dx: CGFloat, dy: CGFloat) -} -// function: -func CGRectInset(rect: CGRect, dx: CGFloat, dy: CGFloat) -> CGRect -~~~ -~~~{objective-c} -CGRect CGRectInset( - CGRect rect, - CGFloat dx, - CGFloat dy -) -~~~ - -Want to make a view-within-a-view look good? Give it a nice 10pt padding with `CGRectInset`. Keep in mind that the rectangle will be resized around its center by ± `dx` on its left and right edge (for a total of `2 × dx`), and ± `dy` on its top and bottom edge (for a total of `2 × dy`). - -If you're using `CGRectInset` as a convenience function for resizing a rectangle, it is common to chain this with `CGRectOffset` by passing the result of `CGRectInset` as the `rect` argument in `CGRectOffset`. - -### `CGRectIntegral` - -> `integral` / `CGRectIntegral`: Returns the smallest rectangle that results from converting the source rectangle values to integers. - -~~~{swift} -// methods: -extension CGRect { - var integral: CGRect { get } - mutating func makeIntegralInPlace() -} -// function: -func CGRectIntegral(rect: CGRect) -> CGRect -~~~ -~~~{objective-c} -CGRect CGRectIntegral ( - CGRect rect -) -~~~ - -It's important that `CGRect` values all are rounded to the nearest whole point. Fractional values cause the frame to be drawn on a _pixel boundary_. Because pixels are atomic units (cannot be subdivided†) a fractional value will cause the drawing to be averaged over the neighboring pixels, which looks blurry. - -`CGRectIntegral` will `floor` each origin value, and `ceil` each size value, which will ensure that your drawing code will crisply align on pixel boundaries. - -As a rule of thumb, if you are performing any operations that could result in fractional point values (e.g. division, `CGRectGetMid[X|Y]`, or `CGRectDivide`), use `CGRectIntegral` to normalize rectangles to be set as a view frame. - -> † Technically, since the coordinate system operates in terms of points, Retina screens, which have 4 pixels for every point, can draw `± 0.5f` point values on odd pixels without blurriness. - - -Value Helper Functions ----------------------- - -These functions provide a shorthand way to calculate interesting dimensional values about a particular `CGRect`. - -### `CGRectGet[Min|Mid|Max][X|Y]` - -- `CGRectGetMinX` -- `CGRectGetMinY` -- `CGRectGetMidX` -- `CGRectGetMidY` -- `CGRectGetMaxX` -- `CGRectGetMaxY` - -These six functions return the minimum, middle, or maximum `x` or `y` value for a rectangle, taking the form: - -~~~{swift} -func CGRectGet[Min|Mid|Max][X|Y](rect: CGRect) -> CGPoint -~~~ -~~~{objective-c} -CGFloat CGRectGet[Min|Mid|Max][X|Y] ( - CGRect rect -) -~~~ - -These functions will replace code like `frame.origin.x + frame.size.width` with cleaner, more semantically expressive equivalents (especially with the mid and max functions). - -### `CGRectGet[Width|Height]` - -~~~{swift} -// Returns the height of a rectangle. -func CGRectGetHeight(rect: CGRect) -> CGFloat - -// Returns the width of a rectangle. -func CGRectGetWidth(rect: CGRect) -> CGFloat -~~~ -~~~{objective-c} -// Returns the height of a rectangle. -CGFloat CGRectGetHeight ( - CGRect rect -) - -// Returns the width of a rectangle. -CGFloat CGRectGetWidth ( - CGRect rect -) -~~~ - -Much like the previous functions, `CGRectGetWidth` & `CGRectGetHeight` are often preferable to returning the corresponding member of a `CGRect`'s `size`. While it's not extremely competitive in terms of character savings, remember that semantic clarity trumps brevity every time. - -### Swift Additions - -~~~{swift} -extension CGRect { - var minX: CGFloat { get } - var minY: CGFloat { get } - var midX: CGFloat { get } - var midY: CGFloat { get } - var maxX: CGFloat { get } - var maxY: CGFloat { get } - var width: CGFloat { get } - var height: CGFloat { get } -} -~~~ - -The `CGRect` Swift extensions for dimensional values make a huge difference in code readability, as each of the value helper functions is mapped to individual computed properties. - -~~~{swift} -// instead of calling the function -let rightEdge = CGRectMaxX(view.frame) - -// simply access the property -let leftEdge = view.frame.minX -~~~ - - -Identities ----------- - -There are three special rectangle values, each of which have unique properties that are important to know about: - -### `CGRectZero`, `CGRectNull`, & `CGRectInfinite` - -> - `CGRect.zero` / `const CGRect CGRectZero`: A rectangle constant with location (0,0), and width and height of 0. The zero rectangle is equivalent to CGRectMake(0.0f, 0.0f, 0.0f, 0.0f). -> - `CGRect.null` / `const CGRect CGRectNull`: The null rectangle. This is the rectangle returned when, for example, you intersect two disjoint rectangles. **Note that the null rectangle is not the same as the zero rectangle**. -> - `CGRect.infinite` / `const CGRect CGRectInfinite`: A rectangle that has infinite extent. - -`CGRectZero` is perhaps the most useful of all of the special rectangle values. When initializing subviews, their frames are often initialized to `CGRectZero`, deferring their layout to `-layoutSubviews`. - -`CGRectNull` is distinct from `CGRectZero`, despite any implied correspondence to `NULL` == `0`. This value is conceptually similar to `NSNotFound`, in that it represents the absence of an expected value. Be aware of what functions can return `CGRectNull`, and be prepared to handle it accordingly, by testing with `CGRectIsNull`. - -`CGRectInfinite` is the most exotic of all, and has some of the most interesting properties. It intersects with all points and rectangles, contains all rectangles, and its union with any rectangle is itself. Use `CGRectIsInfinite` to check to see if a rectangle is infinite. - - -Relationships -------------- - -Moving from one rectangle to two, a pair of rectangles can be either intersected or combined to create a new `CGRect`: - -### `CGRectIntersection` - -> `intersect` / `CGRectIntersection`: Returns the intersection of two rectangles. - -~~~{swift} -// methods: -extension CGRect { - func intersect(withRect: CGRect) -> CGRect - mutating func intersectInPlace(withRect: CGRect) -} -// function: -func CGRectIntersection(rect1: CGRect, rect2: CGRect) -> CGRect -~~~ -~~~{objective-c} -CGRect CGRectIntersection ( - CGRect rect1, - CGRect rect2 -) -~~~ - -`CGRectIntersection` is a fast way to find the overlapping region between two views. The intersection of two non-overlapping rectangles is a null rect, but if you need to simply check for intersection or containment, use `intersects` / `CGRectIntersectsRect` or `contains` / `CGRectContainsRect` instead. - - -### `CGRectUnion` - -> `union` / `CGRectUnion`: Returns the smallest rectangle that contains the two source rectangles. - -~~~{swift} -// methods: -extension CGRect { - func union(rect: CGRect) -> CGRect - mutating func unionInPlace(rect: CGRect) -} -// function: -func CGRectUnion(rect1: CGRect, rect2: CGRect) -> CGRect -~~~ -~~~{objective-c} -CGRect CGRectUnion ( - CGRect rect1, - CGRect rect2 -) -~~~ - -Need a rectangle that can wrap two separate regions in your view? Remember that you can chain together different methods to produce the rectangle you need. Use `CGRectUnion` and negative values with `CGRectInset` to find a padded rectangle around two items: - -~~~{swift} -let combinedRect = imageRect.union(textRect).insetBy(dx: -10, dy: -10) -~~~ -~~~{objective-c} -CGRect combinedRect = CGRectInset(CGRectUnion(imageRect, textRect), -10, -10); -~~~ - -And Finally... --------------- - -Behold, the most obscure, misunderstood, and useful of the `CGGeometry` functions: `CGRectDivide`. - -### `CGRectDivide` - -> `CGRectDivide`: Divides a source rectangle into two component rectangles. - -~~~{swift} -// method: -extension CGRect { - func divide(atDistance: CGFloat, fromEdge: CGRectEdge) -> (slice: CGRect, remainder: CGRect) -} -// function: -CGRectDivide(rect: CGRect, - slice: UnsafeMutablePointer, - remainder: UnsafeMutablePointer, - amount: CGFloat, - edge: CGRectEdge) -~~~ -~~~{objective-c} -void CGRectDivide( - CGRect rect, - CGRect *slice, - CGRect *remainder, - CGFloat amount, - CGRectEdge edge -) -~~~ - -`CGRectDivide` divides a rectangle into two components in the following way: - -- Take a rectangle and choose an `edge` (left, right, top, or bottom). -- Measure out an `amount` from that edge. -- Everything from the `edge` to the measured `amount` is stored in the rectangle referenced in the `slice` argument. -- The rest of the original rectangle is stored in the `remainder` out argument. - -> Don't fret about the `UnsafeMutablePointer` in the Swift version; those pointers act just like `inout` properties in this case. Create your slice and remainder instances up-front, and prefix with an `&` in the call. Or better yet, use the instance method on an existing `CGRect`: -> -> `let (slice, remainder) = frame.divide(120, fromEdge: .MinXEdge)` - -That `edge` argument takes a value from the `CGRectEdge` enum: - -~~~{swift} -enum CGRectEdge { - case MinXEdge - case MinYEdge - case MaxXEdge - case MaxYEdge -} -~~~ -~~~{objective-c} -enum CGRectEdge { - CGRectMinXEdge, - CGRectMinYEdge, - CGRectMaxXEdge, - CGRectMaxYEdge -} -~~~ - -`CGRectDivide` is perfect for dividing up available space among several views (call it on subsequent `remainder` amounts to accommodate more than two views). Give it a try next time you're manually laying-out a `UITableViewCell`. - ---- - -So what if you didn't pay attention in Geometry class—this is the real world, and in the real world, you have `CGGeometry.h`. - -Know it well, and you'll be on your way to discovering great new user interfaces in your apps. Do good enough of a job with that, and you may run into the greatest arithmetic problem of all: adding up all of the money you'll make with your awesome new app. Mathematical! - -[1]: https://developer.apple.com/library/mac/#documentation/graphicsimaging/Conceptual/drawingwithquartz2d/Introduction/Introduction.html#//apple_ref/doc/uid/TP30001066 diff --git a/2013-01-01-new-years-2013.md b/2013-01-01-new-years-2013.md index 2283e156..b3af4cca 100644 --- a/2013-01-01-new-years-2013.md +++ b/2013-01-01-new-years-2013.md @@ -1,10 +1,8 @@ --- title: "Reader Submissions -
New Year's 2013" -author: Mattt Thompson +author: Mattt category: Reader Submissions excerpt: "In celebration of the forthcoming `year++`, I thought it'd be fun to compile a list of some of your favorite tips and tricks of the trade. Readers were asked to submit their favorite piece of Objective-C trivia, framework arcana, hidden Xcode feature, or anything else they thought is cool." -redirect_from: - - /reader-submissions-new-years-2013/ status: swift: n/a --- @@ -13,9 +11,7 @@ In celebration of the forthcoming `year++`, I thought it'd be fun to compile a l Thanks to [Cédric Luthi](https://github.com/0xced), [Jason Kozemczak](https://github.com/jaykz52), [Jeff Kelley](https://github.com/SlaunchaMan), [Joel Parsons](https://github.com/joelparsons), [Maximilian Tagher](https://github.com/MaxGabriel), [Rob Mayoff](https://github.com/mayoff), [Vadim Shpakovski](https://github.com/shpakovski), & [@alextud](https://github.com/alextud) for [answering the call](https://gist.github.com/4148342) with _excellent_ submissions. - -Associated Objects in Categories --------------------------------- +## Associated Objects in Categories This first tip is so nice it was mentioned twice, both by [Jason Kozemczak](https://github.com/jaykz52) & [Jeff Kelley](https://github.com/SlaunchaMan). @@ -23,15 +19,15 @@ Categories are a well-known feature of Objective-C, allowing new methods to be a ### NSObject+IndieBandName.h -~~~{objective-c} +```objc @interface NSObject (IndieBandName) @property (nonatomic, strong) NSString *indieBandName; @end -~~~ +``` ### NSObject+IndieBandName.m -~~~{objective-c} +```objc #import "NSObject+Extension.h" #import @@ -49,7 +45,7 @@ static const void *IndieBandNameKey = &IndieBandNameKey; } @end -~~~ +``` This way, all of your objects can store and retrieve the name of their band, which--by the way--is performing this Wednesday night, and you should totally come. @@ -57,8 +53,7 @@ While this is a cool trick and all, it should only be used as a method of last r A good example of an associated object is how [AFNetworking](https://github.com/AFNetworking/AFNetworking) adds a property for an image request operation in its [`UIImageView` category](https://github.com/AFNetworking/AFNetworking/blob/master/AFNetworking/UIImageView%2BAFNetworking.m#L39). -LLDB View Hierarchy Dump ------------------------- +## LLDB View Hierarchy Dump [Rob Mayoff](https://github.com/mayoff) responded with an obscure and powerful incantation to make debugging views a delight. Create `.lldbinit` in your home directory, if it doesn't already exist, and add the following: @@ -68,9 +63,7 @@ LLDB View Hierarchy Dump Now you can get a recursive hierarchy of any view in your iOS application with the LLDB debugger. You can try this for yourself by setting a breakpoint in a view controller, and type `rd self.view`. You may be surprised by what's under the hood with some of the built-in UI controls! - -LLDB Print Contents of a `CGPathRef` ------------------------------------- +## LLDB Print Contents of a `CGPathRef` While we're on the subject of LLDB, [Rob Mayoff](https://github.com/mayoff) sent in a useful incantation for printing out the contents of a `CGPathRef` from the debugger: @@ -78,8 +71,7 @@ While we're on the subject of LLDB, [Rob Mayoff](https://github.com/mayoff) sent If you're doing any kind of complex Core Graphics drawing, be sure to keep this one handy. -Use `+initialize`, Not `+load` ------------------------------------- +## Use `+initialize`, Not `+load` [Vadim Shpakovski](https://github.com/shpakovski) wrote in with some advice about class loading and initialization. There are two magical class methods in Objective-C: `+load` and `+initialize`, which are automatically called by virtue of the class being used. The difference between the two methods, however, has significant performance implications for your application. @@ -91,21 +83,19 @@ Use `+initialize`, Not `+load` **tl;dr: Implement `+initialize`, not `+load`, if you need this automatic behavior.** -Xcode Snippets --------------- +## Xcode Snippets [Maximilian Tagher](https://github.com/MaxGabriel) gave a shout-out to the benefits of Xcode Snippets. -Great developers take pride in knowing their tools, and being able to use them to maximum effect. [For better](https://twitter.com/javisoto/status/285531250373046272) or [for worse](http://www.textfromxcode.com), this means knowing Xcode like the back of our hand. Verbose as Objective-C is, "do more by typing less" rings especially true as a productivity mantra, and [Xcode Snippets](http://developer.apple.com/library/mac/#recipes/xcode_help-source_editor/CreatingaCustomCodeSnippet/CreatingaCustomCodeSnippet.html#//apple_ref/doc/uid/TP40009975-CH14-SW1) are one of the best ways to do this. +Great developers take pride in knowing their tools, and being able to use them to maximum effect. [For better](https://twitter.com/javisoto/status/285531250373046272) or [for worse](http://www.textfromxcode.com), this means knowing Xcode like the back of our hand. Verbose as Objective-C is, "do more by typing less" rings especially true as a productivity mantra, and [Xcode Snippets](https://developer.apple.com/library/mac/#recipes/xcode_help-source_editor/CreatingaCustomCodeSnippet/CreatingaCustomCodeSnippet.html#//apple_ref/doc/uid/TP40009975-CH14-SW1) are one of the best ways to do this. If you're looking for a place to start, try downloading and forking [these Xcode Snippets](https://github.com/mattt/Xcode-Snippets). -Macro for Measuring Execution Time ----------------------------------- +## Macro for Measuring Execution Time Here's a helpful macro for easily measuring the elapsed time for executing a particular block of code, sent in from [@alextud](https://github.com/alextud): -~~~{objective-c} +```objc NS_INLINE void MVComputeTimeWithNameAndBlock(const char *caller, void (^block)()) { CFTimeInterval startTimeInterval = CACurrentMediaTime(); block(); @@ -114,26 +104,23 @@ NS_INLINE void MVComputeTimeWithNameAndBlock(const char *caller, void (^block)() } #define MVComputeTime(...) MVComputeTimeWithNameAndBlock(__PRETTY_FUNCTION__, (__VA_ARGS__)) -~~~ +``` -Block Enumeration Methods -------------------------- +## Block Enumeration Methods [Joel Parsons](https://github.com/joelparsons) submitted a great tip about using `-enumerateObjectsWithOptions:usingBlock:` in `NSArray` and other collection classes. By passing the `NSEnumerationConcurrent` option, you can get significant performance benefits over `NSFastEnumeration`'s `for...in`-style enumeration by executing the block concurrently. However, be warned! Not all enumerations lend themselves to concurrent execution, so don't go around replacing all of your `for...in` blocks with `NSEnumerationConcurrent` willy-nilly, unless random crashing is something you like in an app. -Reverse-Engineered Implementation of `NSString` Equality Methods ----------------------------------------------------------------- +## Reverse-Engineered Implementation of `NSString` Equality Methods Displaying his characteristic brilliance and familiarity of Cocoa internals [Cédric Luthi](https://github.com/0xced) submitted [a reverse-engineered implementation of the `NString` equality methods](https://gist.github.com/2275014). Fascinating! -Animate `NSLayoutConstraint.constant` -------------------------------------- +## Animate `NSLayoutConstraint.constant` This one goes out to all you fans of [Cocoa Auto Layout](https://developer.apple.com/library/mac/#documentation/UserExperience/Conceptual/AutolayoutPG/Articles/Introduction.html#//apple_ref/doc/uid/TP40010853), from [Vadim Shpakovski](https://github.com/shpakovski): -~~~{objective-c} +```objc viewConstraint.constant = <#Constant Value From#>; [view layoutIfNeeded]; @@ -143,22 +130,21 @@ viewConstraint.constant = <#Constant Value To#>; [UIView animateWithDuration:ConstantAnimationDuration animations:^{ [view layoutIfNeeded]; }]; -~~~ +``` Attentive readers may have already noted this, but the code above would make an _excellent_ Xcode Snippet, by the way. -Printing `NSCache` Usage ------------------------- +## Printing `NSCache` Usage -Finishing up this batch of tips and tricks is [Cédric Luthi](https://github.com/0xced) again, this time unearthing the private method `cache_print` as a way to get some visibility into [`NSCache`](http://nshipster.com/nscache/): +Finishing up this batch of tips and tricks is [Cédric Luthi](https://github.com/0xced) again, this time unearthing the private method `cache_print` as a way to get some visibility into [`NSCache`](https://nshipster.com/nscache/): -~~~{objective-c} +```objc extern void cache_print(void *cache); - (void) printCache:(NSCache *)cache { cache_print(*((void **)(__bridge void *)cache + 3)); } -~~~ +``` This code sample has only been tested on iOS, and should only be used for debugging (i.e. take this out before submitting to Apple!). diff --git a/2013-01-07-nil.md b/2013-01-07-nil.md index 017828f5..31c20eac 100644 --- a/2013-01-07-nil.md +++ b/2013-01-07-nil.md @@ -1,56 +1,26 @@ --- -title: "nil / Nil / NULL / NSNull" -author: Mattt Thompson +title: nil / Nil / NULL / NSNull +author: Mattt category: Objective-C tags: nshipster, popular -excerpt: "Understanding the concept of nothingness is as much a philosophical issue as it is a pragmatic one. We are inhabitants of a universe of somethings, yet reason in a logical universe of existential uncertainties. As a physical manifestation of a logical system, computers are faced with the intractable problem of how to represent nothing with something." +excerpt: >- + Understanding the concept of nothingness is as much a philosophical issue + as it is a pragmatic one. status: - swift: n/a + swift: n/a --- -Understanding the concept of nothingness is as much a philosophical issue as it is a pragmatic one. We are inhabitants of a universe of _somethings_, yet reason in a logical universe of existential uncertainties. As a physical manifestation of a logical system, computers are faced with the intractable problem of how to represent _nothing_ with _something_. +Understanding the concept of nothingness is as much a philosophical issue +as it is a pragmatic one. +We are inhabitants of a universe of _somethings_, +yet reason in a logical universe of ontological uncertainties. +As a physical manifestation of a logical system, +computers are faced with the intractable problem +of how to represent _nothing_ with _something_. -In Objective-C, there are several different varieties of _nothing_. The reason for this goes back to [a common NSHipster refrain](http://nshipster.com/ns_enum-ns_options/), of how Objective-C bridges the procedural paradigm of C with Smalltalk-inspired object-oriented paradigm. - -C represents _nothing_ as `0` for primitive values, and `NULL` for pointers ([which is equivalent to `0` in a pointer context](http://c-faq.com/null/nullor0.html)). - -Objective-C builds on C's representation of _nothing_ by adding `nil`. `nil` is an _object_ pointer to nothing. Although semantically distinct from `NULL`, they are technically equivalent to one another. - -On the framework level, Foundation defines `NSNull`, which defines a class method, `+null`, which returns the singleton `NSNull` object. `NSNull` is different from `nil` or `NULL`, in that it is an actual object, rather than a zero value. - -Additionally, in [Foundation/NSObjCRuntime.h](https://gist.github.com/4469665), `Nil` is defined as a _class_ pointer to nothing. This lesser-known title-case cousin of `nil` doesn't show up much very often, but it's at least worth noting. - -## There's Something About `nil` - -Newly-`alloc`'d `NSObject`s start life with [their contents set to `0`](https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/NSObject_Class/Reference/Reference.html). This means that all pointers that object has to other objects begin as `nil`, so it's unnecessary to, for instance, set `self.(association) = nil` in `init` methods. - -Perhaps the most notable behavior of `nil`, though, is that it can have messages sent to it. - -In other languages, like C++, this would crash your program, but in Objective-C, invoking a method on `nil` returns a zero value. This greatly simplifies expressions, as it obviates the need to check for `nil` before doing anything: - -~~~{objective-c} -// For example, this expression... -if (name != nil && [name isEqualToString:@"Steve"]) { ... } - -// ...can be simplified to: -if ([name isEqualToString:@"Steve"]) { ... } -~~~ - -Being aware of how `nil` works in Objective-C allows this convenience to be a feature, and not a lurking bug in your application. Make sure to guard against cases where `nil` values are unwanted, either by checking and returning early to fail silently, or adding a `NSParameterAssert` to throw an exception. - -## `NSNull`: Something for Nothing - -`NSNull` is used throughout Foundation and other frameworks to skirt around the limitations of collections like `NSArray` and `NSDictionary` not being able to contain `nil` values. You can think of `NSNull` as effectively [boxing][1] the `NULL` or `nil` value so that it can be used in collections: - -~~~{objective-c} -NSMutableDictionary *mutableDictionary = [NSMutableDictionary dictionary]; -mutableDictionary[@"someKey"] = [NSNull null]; // Sets value of NSNull singleton for `someKey` -NSLog(@"Keys: %@", [mutableDictionary allKeys]); // @[@"someKey"] -~~~ - ---- - -So to recap, here are the four values representing _nothing_ that every Objective-C programmer should know about: +In Objective-C, +there are a few different varieties of nothing: +`NULL`, `nil`, `Nil`, and `NSNull`. @@ -62,26 +32,129 @@ So to recap, here are the four values representing _nothing_ that every Objectiv - - + + - - + + - - + + - - + +
NULL(void *)0NULL(void *)0 literal null value for C pointers
nil(id)0nil(id)0 literal null value for Objective-C objects
Nil(Class)0Nil(Class)0 literal null value for Objective-C classes
NSNull[NSNull null]NSNull[NSNull null] singleton object used to represent null
-[1]: http://en.wikipedia.org/wiki/Object_type_(object-oriented_programming)#Boxing +## NULL + +C represents _nothing_ as `0` for primitive values +and `NULL` for pointers +_([which is equivalent to `0` in a pointer context](http://c-faq.com/null/nullor0.html))_. + +The only time you see `NULL` in Objective-C code +is when interacting with low-level C APIs +like Core Foundation or Core Graphics. + +## nil + +Objective-C builds on C's representation of _nothing_ by adding `nil`. +`nil` is an _object_ pointer to nothing. +Although semantically distinct from `NULL`, +they are technically equivalent. + +{% info %} +Newly-`alloc`'d `NSObject`s +[start life with their contents set to `0`](https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/NSObject_Class/Reference/Reference.html). +For this reason, +it's unnecessary to set an object's non-primitive properties to `nil` +in its initializer. +{% endinfo %} + +Perhaps the most notable behavior of `nil`, +however, is that it can have messages sent to it without a problem. +In other languages, like Java, this kind of behavior would crash your program. +But in Objective-C, +invoking a method on `nil` returns a zero value --- +which is to say, _"`nil` begets `nil`"_. +This fact alone significantly simplifies things for Objective-C developers, +as it obviates the need to check for `nil` before doing anything: + +```objc +// For example, this expression... +if (name != nil && [name isEqualToString:@"Steve"]) { ... } + +// ...can be simplified to: +if ([name isEqualToString:@"Steve"]) { ... } +``` + +With vigilance, this quirk of Objective-C can be a convenient feature +rather than a lurking source of bugs in your application. + +{% warning %} +Make sure to guard against cases where `nil` values are unwanted, +either with a precondition assertion (`NSParameterAssert`) +or by checking explicitly and returning early. +{% endwarning %} + +## Nil + +In addition to `nil`, +[the Objective-C runtime defines `Nil`](https://gist.github.com/4469665) +as a _class_ pointer to nothing. +This lesser-known title-case cousin of `nil` doesn't show up much very often, +but it's at least worth noting. + +## NSNull + +On the framework level, +Foundation defines `NSNull`. +`NSNull` defines a class method (`+null`) +that returns the singleton `NSNull` object. +`NSNull` represents nothing with an actual object +rather than a zero value like `nil` or `NULL`. + +`NSNull` is used throughout Foundation and other frameworks +to skirt around the limitations of collections +like `NSArray` and `NSDictionary` not being able to contain `nil` values. +You can think of `NSNull` as effectively [boxing][boxing] +the `NULL` or `nil` value so that it can be used in collections: + +```objc +NSMutableDictionary *mutableDictionary = [NSMutableDictionary dictionary]; +mutableDictionary[@"someKey"] = [NSNull null]; +NSLog(@"%@", [mutableDictionary allKeys]); +// @[@"someKey"] +``` + +* * * + +Why does Objective-C have four values for nothing when one might suffice? +It all goes back to +[a common refrain](/ns_enum-ns_options/), +about how Objective-C bridges the procedural paradigm of C +with Smalltalk-inspired object-oriented paradigm. + +In the procedural world of C, +values are indistinguishable from their numeric value. +The same zero value might represent +the first index of an array, +the terminating byte of a string, or +the simply result of "2 - 2". +Objective-C constructs an object-oriented layer on top of these primitives, +and in so doing establishes a distinct logical universe. +This abstraction allows for a sentinel value, +the `NSNull` singleton, +to be defined independently of numbers. +`nil` (and `Nil`) therefore serve as intermediaries between these worlds +at their convergence point: zero. + +[boxing]: https://en.wikipedia.org/wiki/Object_type_(object-oriented_programming)#Boxing diff --git a/2013-01-14-__attribute__.md b/2013-01-14-__attribute__.md index 38493060..555a0faf 100644 --- a/2013-01-14-__attribute__.md +++ b/2013-01-14-__attribute__.md @@ -1,6 +1,6 @@ --- title: "__attribute__" -author: Mattt Thompson +author: Mattt category: Objective-C tags: nshipster excerpt: "A recurring theme of this publication has been the importance of a healthy relationship with the compiler. Like any craft, one's effectiveness as a practitioner is contingent on how they treat their tools. Take good care of them, and they'll take good care of you." @@ -14,7 +14,7 @@ A recurring theme of this publication has been the importance of a healthy relat The syntax for this keyword is `__attribute__` followed by two sets of parentheses (the double parentheses makes it easy to "macro out", especially with multiple attributes). Inside the parentheses is a comma-delimited list of attributes. `__attribute__` directives are placed after function, variable, and type declarations. -~~~{objective-c} +```objc // Return the square of a number int square(int n) __attribute__((const)); @@ -25,9 +25,9 @@ void f(void) // Send printf-like message to stderr and exit extern void die(const char *format, ...) __attribute__((noreturn, format(printf, 1, 2))); -~~~ +``` -If this is starting to remind you of ISO C's [`#pragma`](http://nshipster.com/pragma), you're not alone. +If this is starting to remind you of ISO C's [`#pragma`](https://nshipster.com/pragma), you're not alone. In fact, when `__attribute__` was first introduced to GCC, it was faced with some resistance by some who suggested that `#pragma` be used exclusively for the same purposes. @@ -53,11 +53,11 @@ GCC > The `format` attribute specifies that a function takes `printf`, `scanf`, `strftime` or `strfmon` style arguments which should be type-checked against a format string. -~~~{objective-c} +```objc extern int my_printf (void *my_object, const char *my_format, ...) __attribute__((format(printf, 2, 3))); -~~~ +``` Objective-C programmers can also use the `__NSString__` format to enforce the same rules as format strings in `NSString +stringWithFormat:` and `NSLog()`. @@ -65,11 +65,11 @@ Objective-C programmers can also use the `__NSString__` format to enforce the sa > The `nonnull` attribute specifies that some function parameters should be non-null pointers. -~~~{objective-c} +```objc extern void * my_memcpy (void *dest, const void *src, size_t len) __attribute__((nonnull (1, 2))); -~~~ +``` Using `nonnull` encodes expectations about values into an explicit contract, which can help catch any `NULL` pointer bugs lurking in any calling code. Remember: compile-time errors ≫ run-time errors. @@ -85,9 +85,9 @@ For example, AFNetworking [uses the `noreturn` attribute for its network request > The `const` attribute specifies that a function does not examine any values except their arguments, and have no effects except the return value. Note that a function that has pointer arguments and examines the data pointed to must not be declared const. Likewise, a function that calls a non-`const` function usually must not be `const`. It does not make sense for a `const` function to return `void`. -~~~{objective-c} +```objc int square(int n) __attribute__((const)); -~~~ +``` `pure` and `const` are both attributes that invoke a functional programming paradigm in order to allow for significant performance optimizations. `const` can be thought as a stricter form of `pure` since it doesn't depend on global values or pointers. @@ -110,9 +110,9 @@ To check the availability of a particular attribute, you can use the `__has_attr > Clang introduces the availability attribute, which can be placed on declarations to describe the lifecycle of that declaration relative to operating system versions. Consider the function declaration for a hypothetical function f: -~~~{objective-c} +```objc void f(void) __attribute__((availability(macosx,introduced=10.4,deprecated=10.6,obsoleted=10.7))); -~~~ +``` > The `availability` attribute states that `f` was introduced in OS X Tiger, deprecated in OS X Snow Leopard, and obsoleted in OS X Lion. @@ -137,12 +137,12 @@ Supported Platforms: > Clang provides support for C++ function overloading in C. Function overloading in C is introduced using the `overloadable` attribute. For example, one might provide several overloaded versions of a `tgsin` function that invokes the appropriate standard function computing the `sine` of a value with `float`, `double`, or `long double` precision: -~~~{objective-c} +```objc #include float __attribute__((overloadable)) tgsin(float x) { return sinf(x); } double __attribute__((overloadable)) tgsin(double x) { return sin(x); } long double __attribute__((overloadable)) tgsin(long double x) { return sinl(x); } -~~~ +``` Note that `overloadable` only works for functions. You can overload method declarations to some extent by using generic return and parameter types, like `id` and `void *`. diff --git a/2013-01-21-nslocalizedstring.md b/2013-01-21-nslocalizedstring.md index 598aba21..5ef9fba6 100644 --- a/2013-01-21-nslocalizedstring.md +++ b/2013-01-21-nslocalizedstring.md @@ -1,6 +1,6 @@ --- title: NSLocalizedString -author: Mattt Thompson +author: Mattt category: Cocoa tags: nshipster excerpt: "Strings are perhaps the most versatile data type in computing. They're passed around as symbols, used to encode numeric values, associate values to keys, represent resource paths, store linguistic content, and format information. Having a strong handle on user-facing strings is essential to making a great user experience." @@ -14,7 +14,7 @@ In Foundation, there is a convenient macro for denoting strings as user-facing: `NSLocalizedString` provides string localization in "compile-once / run everywhere" fashion, replacing all localized strings with their respective translation according to the string tables of the user settings. But even if you're not going to localize your app to any other markets, `NSLocalizedString` does wonders with respect to copy writing & editing. -> For more information about Localization (l10n) and Internationalization (i18n) [see the NSHipster article about NSLocale](http://nshipster.com/nslocale/). +> For more information about Localization (l10n) and Internationalization (i18n) [see the NSHipster article about NSLocale](https://nshipster.com/nslocale/). --- @@ -22,23 +22,23 @@ In Foundation, there is a convenient macro for denoting strings as user-facing: In practice, the `key` is often just the base translation string to be used, while `comment` is usually `nil`, unless there is an ambiguous context: -~~~{objective-c} +```objc textField.placeholder = NSLocalizedString(@"Username", nil); -~~~ +``` `NSLocalizedString` can also be used as a format string in `NSString +stringWithFormat:`. In these cases, it's important to use the `comment` argument to provide enough context to be properly translated. -~~~{objective-c} +```objc self.title = [NSString stringWithFormat:NSLocalizedString(@"%@'s Profile", @"{User First Name}'s Profile"), user.name]; label.text = [NSString stringWithFormat:NSLocalizedString(@"Showing %lu of %lu items", @"Showing {number} of {total number} items"), [page count], [items count]]; -~~~ +``` ## `NSLocalizedString` & Co. There are four varieties of `NSLocalizedString`, with increasing levels of control (and obscurity): -~~~{objective-c} +```objc NSString * NSLocalizedString( NSString *key, NSString *comment @@ -64,7 +64,7 @@ NSString * NSLocalizedStringWithDefaultValue( NSString *value, NSString *comment ) -~~~ +``` 99% of the time, `NSLocalizedString` will suffice. If you're working in a library or shared component, `NSLocalizedStringFromTable` should be used instead. @@ -74,12 +74,12 @@ At runtime, `NSLocalizedString` determines the preferred language, and finds a c Here's what that looks like: -~~~ +``` /* No comment provided by engineer. */ "Username"="nom d'utilisateur"; /* {User First Name}'s Profile */ "%@'s Profile"="profil de %1$@"; -~~~ +``` `Localizable.strings` files are initially generated with `genstrings`. diff --git a/2013-01-28-nsvalue.md b/2013-01-28-nsvalue.md index 95811ead..ceb8fbaa 100644 --- a/2013-01-28-nsvalue.md +++ b/2013-01-28-nsvalue.md @@ -1,6 +1,6 @@ --- title: NSValue -author: Mattt Thompson +author: Mattt category: Cocoa tags: nshipster excerpt: "Boxing is the process of encapsulating scalars and value types with an object container, and is primarily used to store those values in collection objects—namely arrays and dictionaries. In Foundation, the reigning featherweight champion of boxing is NSValue." @@ -16,7 +16,7 @@ Yet, this is _exactly_ what we do as Objective-C programmers. ...well not _exactly_, but there is a box involved. -As mentioned [time](http://nshipster.com/ns_enum-ns_options/) and [again](http://nshipster.com/nil/) in NSHipster, what makes Objective-C such a curiosity is the way it merges the old, procedural world of C with the modern Object-Oriented influences of Smalltalk. When done correctly, this tension can be exploited to craft semantically rich software without sacrificing performance. But bridging that gap between old and new is a miasma of casts, bridges, and of course, boxes. +As mentioned [time](https://nshipster.com/ns_enum-ns_options/) and [again](https://nshipster.com/nil/) in NSHipster, what makes Objective-C such a curiosity is the way it merges the old, procedural world of C with the modern Object-Oriented influences of Smalltalk. When done correctly, this tension can be exploited to craft semantically rich software without sacrificing performance. But bridging that gap between old and new is a miasma of casts, bridges, and of course, boxes. Boxing is the process of encapsulating scalars (`int`, `double`, `BOOL`, etc.) and value types (`struct`, `enum`) with an object container, and is primarily used to store those values in collection objects—namely arrays and dictionaries. @@ -37,25 +37,25 @@ While boxing is an admittedly dry subject matter, there are two methods in parti > - `value`: The value for the new `NSValue` object. > - `type`: The Objective-C type of value. `type` should be created with the Objective-C `@encode()` compiler directive; it should not be hard-coded as a C string. -`@encode` was discussed in [our rundown of the myriad `@` Compiler Directives](http://nshipster.com/at-compiler-directives/): +`@encode` was discussed in [our rundown of the myriad `@` Compiler Directives](https://nshipster.com/at-compiler-directives/): -> `@encode()`: Returns the [type encoding](http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html) of a type. This type value can be used as the first argument encode in `NSCoder -encodeValueOfObjCType:at`. +> `@encode()`: Returns the [type encoding](https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html) of a type. This type value can be used as the first argument encode in `NSCoder -encodeValueOfObjCType:at`. -The subject of type encodings would make for [a great article of its own](http://nshipster.com/type-encodings/), but the main takeaway is that it serves as a terse, human-readable representation of the structure of a type. For example, +The subject of type encodings would make for [a great article of its own](https://nshipster.com/type-encodings/), but the main takeaway is that it serves as a terse, human-readable representation of the structure of a type. For example, -~~~{objective-c} +```objc typedef struct example { id anObject; char *aString; int anInt; } Example; -~~~ +``` ...has the encoding: -~~~{objective-c} +```objc {example=@*i} -~~~ +``` `NSValue` uses this type encoding to create the necessary data structures to represent these values internally. Neat! diff --git a/2013-02-04-type-encodings.md b/2013-02-04-type-encodings.md index 7041de3f..ac0b45b6 100644 --- a/2013-02-04-type-encodings.md +++ b/2013-02-04-type-encodings.md @@ -1,6 +1,6 @@ --- title: Type Encodings -author: Mattt Thompson +author: Mattt category: Objective-C tags: nshipster excerpt: "From number stations and numerology to hieroglyphics and hobo codes, there is something truly fascinating about finding meaning that hides in plain sight. Though hidden messages in and of themselves are rarely useful or particularly interesting, it's the thrill of the hunt that piques our deepest curiosities." @@ -8,15 +8,15 @@ status: swift: n/a --- -From [number stations](http://en.wikipedia.org/wiki/Numbers_station) and [numerology](http://en.wikipedia.org/wiki/Numerology) to [hieroglyphs](http://en.wikipedia.org/wiki/Egyptian_hieroglyphs) and [hobo codes](http://en.wikipedia.org/wiki/Hobo#Hobo_.28sign.29_code), there is something truly fascinating about finding meaning that hides in plain sight. Though hidden messages in and of themselves are rarely useful or particularly interesting, it's the thrill of the hunt that piques our deepest curiosities. +From [number stations](https://en.wikipedia.org/wiki/Numbers_station) and [numerology](https://en.wikipedia.org/wiki/Numerology) to [hieroglyphs](https://en.wikipedia.org/wiki/Egyptian_hieroglyphs) and [hobo codes](https://en.wikipedia.org/wiki/Hobo#Hobo_.28sign.29_code), there is something truly fascinating about finding meaning that hides in plain sight. Though hidden messages in and of themselves are rarely useful or particularly interesting, it's the thrill of the hunt that piques our deepest curiosities. It is in this spirit that we take a look at [Objective-C Type Encodings](https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html) in this week's edition of NSHipster. --- -[Last week](http://nshipster.com/nsvalue/), in a discussion about `NSValue`, there was mention of `+valueWithBytes:objCType:`, whose second parameter should be created with the Objective-C `@encode()` compiler directive. +[Last week](https://nshipster.com/nsvalue/), in a discussion about `NSValue`, there was mention of `+valueWithBytes:objCType:`, whose second parameter should be created with the Objective-C `@encode()` compiler directive. -`@encode`, one of the [`@` Compiler Directives](http://nshipster.com/at-compiler-directives/), returns a C string that encodes the internal representation of a given type (e.g., `@encode(int)` → `i`), similar to the ANSI C `typeof` operator. Apple's Objective-C runtime uses type encodings internally to help facilitate message dispatching. +`@encode`, one of the [`@` Compiler Directives](https://nshipster.com/at-compiler-directives/), returns a C string that encodes the internal representation of a given type (e.g., `@encode(int)` → `i`), similar to the ANSI C `typeof` operator. Apple's Objective-C runtime uses type encodings internally to help facilitate message dispatching. Here's a rundown of all of the different Objective-C Type Encodings: @@ -30,60 +30,60 @@ Here's a rundown of all of the different Objective-C Type Encodings: - c - A char + c + A char - i - An int + i + An int - s - A short + s + A short - l - A longl is treated as a 32-bit quantity on 64-bit programs. + l + A longl is treated as a 32-bit quantity on 64-bit programs. - q - A long long + q + A long long - C - An unsigned char + C + An unsigned char - I - An unsigned int + I + An unsigned int - S - An unsigned short + S + An unsigned short - L - An unsigned long + L + An unsigned long - Q - An unsigned long long + Q + An unsigned long long - f - A float + f + A float - d - A double + d + A double - B - A C++ bool or a C99 _Bool + B + A C++ bool or a C99 _Bool - v - A void + v + A void - * - A character string (char *) + * + A character string (char *) - @ - An object (whether statically typed or typed id) + @ + An object (whether statically typed or typed id) - # - A class object (Class) + # + A class object (Class) - : - A method selector (SEL) + : + A method selector (SEL) [array type] An array @@ -94,21 +94,22 @@ Here's a rundown of all of the different Objective-C Type Encodings: (name=type...) A union - bnum + bnum A bit field of num bits - ^type + ^type A pointer to type - ? + ? An unknown type (among other things, this code is used for function pointers) -Of course, charts are fine, but experimenting in code is even better: +Charts are fine, +but experimenting in code is even better: -~~~{objective-c} +```objc NSLog(@"int : %s", @encode(int)); NSLog(@"float : %s", @encode(float)); NSLog(@"float * : %s", @encode(float*)); @@ -135,7 +136,7 @@ typedef struct _struct { unsigned long long c; } Struct; NSLog(@"struct : %s", @encode(typeof(Struct))); -~~~ +``` Result: @@ -161,7 +162,7 @@ There are some interesting takeaways from this: - Whereas the standard encoding for pointers is a preceding `^`, `char *` gets its own code: `*`. This makes sense conceptually, as C strings are thought to be entities in and of themselves, rather than a pointer to something else. - `BOOL` is `c`, rather than `i`, as one might expect. Reason being, `char` is smaller than an `int`, and when Objective-C was originally designed in the 80's, bits (much like the dollar) were more valuable than they are today. `BOOL` is specifically a `signed char` (even if `-funsigned-char` is set), to ensure a consistent type between compilers, since `char` could be either `signed` or `unsigned`. -- Passing `NSObject` directly yields `#`. However, passing `[NSObject class]` yields a struct named `NSObject` with a single class field. That is, of course, the `isa` field, which all `NSObject` instances have to signify their type. +- Passing `NSObject` directly yields `#`. However, passing `[NSObject class]` yields a struct named `NSObject` with a single class field: `isa`, which `NSObject` instances have to signify their type. ## Method Encodings @@ -179,32 +180,32 @@ These are the type qualifiers for methods declared in a protocol: - r - const + r + const - n - in + n + in - N - inout + N + inout - o - out + o + out - O - bycopy + O + bycopy - R - byref + R + byref - V - oneway + V + oneway diff --git a/2013-02-11-nsurlcache.md b/2013-02-11-nsurlcache.md index b6fc4a79..9b256ca7 100644 --- a/2013-02-11-nsurlcache.md +++ b/2013-02-11-nsurlcache.md @@ -1,6 +1,6 @@ --- title: NSURLCache -author: Mattt Thompson +author: Mattt category: Cocoa excerpt: "NSURLCache provides a composite in-memory and on-disk caching mechanism for URL requests to your application. As part of Foundation's URL Loading System, any request loaded through NSURLConnection will be handled by NSURLCache." status: @@ -19,16 +19,16 @@ 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} +```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} +```objc - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { @@ -37,7 +37,7 @@ func application(application: UIApplication!, didFinishLaunchingWithOptions laun diskPath:nil]; [NSURLCache setSharedURLCache:URLCache]; } -~~~ +``` 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. @@ -54,7 +54,7 @@ Caching policies are specified in both the request (by the client) and in the re It may not surprise you that these values are poorly understood and often confused with one another. -Adding to the confusion is the fact that `NSURLRequestReloadIgnoringLocalAndRemoteCacheData` and `NSURLRequestReloadRevalidatingCacheData` [_aren't even implemented_](https://gist.github.com/mattt/4753073#file-nsurlrequest-h-L95-L108)! ([Link to Radar](http://openradar.appspot.com/radar?id=1755401)). +Adding to the confusion is the fact that `NSURLRequestReloadIgnoringLocalAndRemoteCacheData` and `NSURLRequestReloadRevalidatingCacheData` [_were not even implemented until iOS 13_](https://developer.apple.com/documentation/ios-ipados-release-notes/ios-13-release-notes)! So here's what you _actually_ need to know about `NSURLRequestCachePolicy`: @@ -67,28 +67,28 @@ So here's what you _actually_ need to know about `NSURLRequestCachePolicy`: - UseProtocolCachePolicy + useProtocolCachePolicy Default behavior - ReloadIgnoringLocalCacheData + reloadIgnoringLocalCacheData Don't use the cache - ReloadIgnoringLocalAndRemoteCacheData - Seriously, don't use the cache + reloadIgnoringLocalAndRemoteCacheData + Seriously, don't use any caches along the way - ReturnCacheDataElseLoad + returnCacheDataElseLoad Use the cache (no matter how out of date), or if no cached response exists, load from the network - ReturnCacheDataDontLoad + returnCacheDataDontLoad Offline mode: use the cache (no matter how out of date), but don't load from the network - ReloadRevalidatingCacheData - Validate cache against server before using + reloadRevalidatingCacheData + Validate cache against server before using @@ -116,7 +116,7 @@ In addition to `Cache-Control`, a server may send additional headers that can be * `Last-Modified` - The value of this header corresponds to the date and time when the requested resource was last changed. For example, if a client requests a timeline of recent photos, `/photos/timeline`, the `Last-Modified` value could be set to when the most recent photo was taken. -* `Etag` - An abbreviation for "entity tag", this is an identifier that represents the contents requested resource. In practice, an `Etag` header value could be something like the [`MD5`](http://en.wikipedia.org/wiki/MD5) digest of the resource properties. This is particularly useful for dynamically generated resources that may not have an obvious `Last-Modified` value. +* `Etag` - An abbreviation for "entity tag", this is an identifier that represents the contents requested resource. In practice, an `Etag` header value could be something like the [`MD5`](https://en.wikipedia.org/wiki/MD5) digest of the resource properties. This is particularly useful for dynamically generated resources that may not have an obvious `Last-Modified` value. ## `NSURLConnectionDelegate` @@ -126,7 +126,7 @@ 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} +```swift // MARK: NSURLConnectionDataDelegate func connection(connection: NSURLConnection!, willCacheResponse cachedResponse: NSCachedURLResponse!) -> NSCachedURLResponse! { @@ -134,13 +134,13 @@ func connection(connection: NSURLConnection!, willCacheResponse cachedResponse: var mutableData = NSMutableData(data: cachedResponse.data) var storagePolicy: NSURLCacheStoragePolicy = .AllowedInMemoryOnly - // ... + <#...#> return NSCachedURLResponse(response: cachedResponse.response, data: mutableData, userInfo: mutableUserInfo, storagePolicy: storagePolicy) } -~~~ +``` -~~~{objective-c} +```objc - (NSCachedURLResponse *)connection:(NSURLConnection *)connection willCacheResponse:(NSCachedURLResponse *)cachedResponse { @@ -148,36 +148,36 @@ func connection(connection: NSURLConnection!, willCacheResponse cachedResponse: NSMutableData *mutableData = [[cachedResponse data] mutableCopy]; NSURLCacheStoragePolicy storagePolicy = NSURLCacheStorageAllowedInMemoryOnly; - // ... + <#...#> return [[NSCachedURLResponse alloc] initWithResponse:[cachedResponse response] data:mutableData userInfo:mutableUserInfo storagePolicy:storagePolicy]; } -~~~ +``` If `-connection:willCacheResponse:` returns `nil`, the response will not be cached. -~~~{swift} +```swift func connection(connection: NSURLConnection!, willCacheResponse cachedResponse: NSCachedURLResponse!) -> NSCachedURLResponse! { return nil } -~~~ +``` -~~~{objective-c} +```objc - (NSCachedURLResponse *)connection:(NSURLConnection *)connection willCacheResponse:(NSCachedURLResponse *)cachedResponse { 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 -Just like its unrelated-but-similarly-named cohort, [`NSCache`](http://nshipster.com/nscache/), `NSURLCache` is not without some peculiarities. +Just like its unrelated-but-similarly-named cohort, [`NSCache`](https://nshipster.com/nscache/), `NSURLCache` is not without some peculiarities. As of iOS 5, disk caching is supported, but only for HTTP, not HTTPS, requests (though iOS 6 added support for this). Peter Steinberger [wrote an excellent article on this subject](http://petersteinberger.com/blog/2012/nsurlcache-uses-a-disk-cache-as-of-ios5/), after digging into the internals while implementing [his own NSURLCache subclass](https://github.com/steipete/SDURLCache). diff --git a/2013-02-18-reactivecocoa.md b/2013-02-18-reactivecocoa.md index 4772d848..559c80b9 100644 --- a/2013-02-18-reactivecocoa.md +++ b/2013-02-18-reactivecocoa.md @@ -1,17 +1,18 @@ --- title: ReactiveCocoa -author: Mattt Thompson +author: Mattt category: Open Source excerpt: "Breaking from a tradition of covering Apple APIs exclusively, this edition of NSHipster will look at an open source project that exemplifies a brave new era of open source contribution to Objective-C: ReactiveCocoa." +retired: true status: - swift: n/a + swift: n/a --- Languages are living works. They are nudged and challenged and bastardized and mashed-up in a perpetual cycle of undirected and rapid evolution. Technologies evolve, requirements change, corporate stewards and open source community come and go; obscure dialects are vaulted to prominence on the shoulders of exciting new frameworks, and thrust into a surprising new context after a long period of dormancy. Objective-C has a remarkable history spanning four acts in as many decades: -**In its 1st act**, Objective-C was adopted as the language of NeXT, powering [NeXTSTEP](http://en.wikipedia.org/wiki/NeXTSTEP) and [the world's first web server](http://en.wikipedia.org/wiki/Web_server#History). +**In its 1st act**, Objective-C was adopted as the language of NeXT, powering [NeXTSTEP](https://en.wikipedia.org/wiki/NeXTSTEP) and [the world's first web server](https://en.wikipedia.org/wiki/Web_server#History). **In its 2nd act**, Objective-C positioned itself in the heart Apple's technology stack (after a prolonged turf war with Java) with Apple's acquisition of NeXT. @@ -27,7 +28,7 @@ Breaking from a tradition of covering Apple APIs exclusively, this edition of NS [ReactiveCocoa](https://github.com/ReactiveCocoa/ReactiveCocoa) is an open source library that brings Functional Reactive Programming paradigm to Objective-C. It was created by [Josh Abernathy](https://github.com/joshaber) & [Justin Spahr-Summers](https://github.com/jspahrsummers) in the development of [GitHub for Mac](http://mac.github.com). Last week, ReactiveCocoa reached a major milestone with its [1.0 release](https://github.com/ReactiveCocoa/ReactiveCocoa/tree/v1.0.0). -[Functional Reactive Programming](http://en.wikipedia.org/wiki/Functional_reactive_programming) (FRP) is a way of thinking about software in terms of transforming inputs to produce output continuously over time. [Josh Abernathy frames the paradigm thusly](http://blog.maybeapps.com/post/42894317939/input-and-output): +[Functional Reactive Programming](https://en.wikipedia.org/wiki/Functional_reactive_programming) (FRP) is a way of thinking about software in terms of transforming inputs to produce output continuously over time. [Josh Abernathy frames the paradigm thusly](http://blog.maybeapps.com/post/42894317939/input-and-output): > Programs take input and produce output. The output is the result of doing something with the input. Input, transform, output, done. > @@ -41,7 +42,7 @@ To illustrate the difference between the conventional, imperative paradigm of Ob ### Conventional -~~~{objective-c} +```objc - (BOOL)isFormValid { return [self.usernameField.text length] > 0 && [self.emailField.text length] > 0 && @@ -59,7 +60,7 @@ replacementString:(NSString *)string return YES; } -~~~ +``` In the conventional example, logic is fragmented across different methods in the view controller, with calls to `self.createButton.enabled = [self isFormValid];` interspersed throughout delegate methods and view lifecycle callbacks. @@ -67,7 +68,7 @@ Compare this with equivalent code using ReactiveCocoa: ### ReactiveCocoa -~~~{objective-c} +```objc RACSignal *formValid = [RACSignal combineLatest:@[ self.username.rac_textSignal, @@ -80,7 +81,7 @@ RACSignal *formValid = [RACSignal }]; RAC(self.createButton.enabled) = formValid; -~~~ +``` Here, all of the logic for validating form input is contained in a single chain of logic and responsibility. Each time any of the text fields is updated, their inputs are reduced into a single boolean value, which automatically enables / disables the create button. @@ -98,31 +99,31 @@ Both signals and sequences are kinds of [streams](https://github.com/ReactiveCoc > Signals send three different types of events to their subscribers: > -> * The **next** event provides a new value from the stream. Unlike Cocoa collections, it is - completely valid for a signal to include `nil`. -> * The **error** event indicates that an error occurred before the signal could - finish. The event may include an `NSError` object that indicates what went - wrong. Errors must be handled specially – they are not included in the - stream's values. -> * The **completed** event indicates that the signal finished successfully, and - that no more values will be added to the stream. Completion must be handled - specially – it is not included in the stream of values. +> - The **next** event provides a new value from the stream. Unlike Cocoa collections, it is +> completely valid for a signal to include `nil`. +> - The **error** event indicates that an error occurred before the signal could +> finish. The event may include an `NSError` object that indicates what went +> wrong. Errors must be handled specially – they are not included in the +> stream's values. +> - The **completed** event indicates that the signal finished successfully, and +> that no more values will be added to the stream. Completion must be handled +> specially – it is not included in the stream of values. > > The lifetime of a signal consists of any number of `next` events, followed by -one `error` or `completed` event (but not both). +> one `error` or `completed` event (but not both). ### `RACSequence` > - **Simplifying Collection Transformations**: Higher-order functions like `map`, `filter`, `fold/reduce` are sorely missing from `Foundation`. > Sequences are a kind of collection, similar in purpose to `NSArray`. Unlike -an array, the values in a sequence are evaluated _lazily_ (i.e., only when they -are needed) by default, potentially improving performance if only part of -a sequence is used. Just like Cocoa collections, sequences cannot contain `nil`. +> an array, the values in a sequence are evaluated _lazily_ (i.e., only when they +> are needed) by default, potentially improving performance if only part of +> a sequence is used. Just like Cocoa collections, sequences cannot contain `nil`. > > `RACSequence` allows any Cocoa collection to be manipulated in a uniform and declarative way. -~~~{objective-c} +```objc RACSequence *normalizedLongWords = [[words.rac_sequence filter:^ BOOL (NSString *word) { return [word length] >= 10; @@ -130,7 +131,7 @@ RACSequence *normalizedLongWords = [[words.rac_sequence map:^(NSString *word) { return [word lowercaseString]; }]; -~~~ +``` ## Precedents in Cocoa @@ -138,7 +139,7 @@ Capturing and responding to changes has a long tradition in Cocoa, and ReactiveC ### RAC vs. KVO -[Key-Value Observing](http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/KeyValueObserving/KeyValueObserving.html) is at the heart of all magic in Cocoa—indeed, it is used extensively by ReactiveCocoa to react to property changes. However, KVO is neither pleasant nor easy to use: its API is overwrought with unused parameters and sorely lacking a blocks-based interface. +[Key-Value Observing](https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/KeyValueObserving/KeyValueObserving.html) is at the heart of all magic in Cocoa—indeed, it is used extensively by ReactiveCocoa to react to property changes. However, KVO is neither pleasant nor easy to use: its API is overwrought with unused parameters and sorely lacking a blocks-based interface. ### RAC vs. Bindings @@ -150,11 +151,11 @@ Although essential to managing the complexity of a OS X application, Bindings' c Objective-C was built from Smalltalk's ideas on top of C's metal, but its cultural imports go far beyond its original pedigree. -`@protocol` was a rejection of C++'s multiple inheritance, favoring an abstract data type pattern comparable to a Java `Interface`. Objective-C 2.0 introduced `@property / @synthesize`, a contemporary of C#'s `get; set;` shorthand for getter and setter methods (as well as dot syntax, which is still a point of contention for NeXTSTEP hard-liners). Blocks injected some functional programming flavor to the language, which paired nicely with Grand Central Dispatch--a queue-based concurrency API almost certainly influenced by Fortran / C / C++ standard [OpenMP](http://en.wikipedia.org/wiki/OpenMP). Subscripting and object literals, a standard feature in scripting languages like Ruby and Javascript, now finally brought to Objective-C thanks to a Clang language extension. +`@protocol` was a rejection of C++'s multiple inheritance, favoring an abstract data type pattern comparable to a Java `Interface`. Objective-C 2.0 introduced `@property / @synthesize`, a contemporary of C#'s `get; set;` shorthand for getter and setter methods (as well as dot syntax, which is still a point of contention for NeXTSTEP hard-liners). Blocks injected some functional programming flavor to the language, which paired nicely with Grand Central Dispatch--a queue-based concurrency API almost certainly influenced by Fortran / C / C++ standard [OpenMP](https://en.wikipedia.org/wiki/OpenMP). Subscripting and object literals, a standard feature in scripting languages like Ruby and Javascript, now finally brought to Objective-C thanks to a Clang language extension. -ReactiveCocoa brings a healthy dose of functional and reactive programming influence to Objective-C, and was itself influenced by C#'s [Rx library](http://msdn.microsoft.com/en-us/data/gg577609.aspx), [Clojure](http://en.wikipedia.org/wiki/Clojure), and [Elm][2]. +ReactiveCocoa brings a healthy dose of functional and reactive programming influence to Objective-C, and was itself influenced by C#'s [Rx library](http://msdn.microsoft.com/en-us/data/gg577609.aspx), [Clojure](https://en.wikipedia.org/wiki/Clojure), and [Elm][2]. Good ideas are contagious. ReactiveCocoa is a reminder that good ideas can come from unlikely places, and that a fresh perspective can make all of the difference with familiar problems. -[1]: http://en.wikipedia.org/wiki/State_(computer_science)#Program_state -[2]: http://en.wikipedia.org/wiki/Elm_(programming_language) +[1]: https://en.wikipedia.org/wiki/State_(computer_science)#Program_state +[2]: https://en.wikipedia.org/wiki/Elm_(programming_language) diff --git a/2013-02-25-nsassertionhandler.md b/2013-02-25-nsassertionhandler.md index 3611ec97..2b1d12fc 100644 --- a/2013-02-25-nsassertionhandler.md +++ b/2013-02-25-nsassertionhandler.md @@ -1,6 +1,6 @@ --- title: NSAssertionHandler -author: Mattt Thompson +author: Mattt category: Cocoa excerpt: "Programming incorporates numerous disciplines of human reasoning, from high-level discourse and semantics—the story we tell each other to explain how a system works—to the mathematical and philosophical machinery that underpins everything." status: @@ -13,9 +13,9 @@ Programming incorporates numerous disciplines of human reasoning, from high-leve Assertions are a concept borrowed from classical logic. In logic, assertions are statements about propositions within a proof. In programming, assertions denote assumptions the programmer has made about the application at the place where they are declared. -When used in the capacity of preconditions and postconditions, which describe expectations about the state of the code at the beginning and end of execution of a method or function, assertions form a [contract](http://en.wikipedia.org/wiki/Design_by_contract). Assertions can also be used to enforce conditions at run-time, in order to prevent execution when certain preconditions fail. +When used in the capacity of preconditions and postconditions, which describe expectations about the state of the code at the beginning and end of execution of a method or function, assertions form a [contract](https://en.wikipedia.org/wiki/Design_by_contract). Assertions can also be used to enforce conditions at run-time, in order to prevent execution when certain preconditions fail. -Assertions are similar to [unit testing](http://en.wikipedia.org/wiki/Unit_testing) in that they define expectations about the way code will execute. Unlike unit tests, assertions exist inside the program itself, and are thereby constrained to the context of the program. Because unit tests are fully independent, they have a much greater capacity to isolate and test certain behaviors, using tools like methods stubs and mock objects. Developers should use assertions and unit tests in combination and in reasonable quantity to test and define behavior in an application. +Assertions are similar to [unit testing](https://en.wikipedia.org/wiki/Unit_testing) in that they define expectations about the way code will execute. Unlike unit tests, assertions exist inside the program itself, and are thereby constrained to the context of the program. Because unit tests are fully independent, they have a much greater capacity to isolate and test certain behaviors, using tools like methods stubs and mock objects. Developers should use assertions and unit tests in combination and in reasonable quantity to test and define behavior in an application. ## Foundation Assertion Handling @@ -53,14 +53,14 @@ And while Foundation assertion macros are extremely useful in their own right— ### LoggingAssertionHandler.h -~~~{objective-c} +```objc @interface LoggingAssertionHandler : NSAssertionHandler @end -~~~ +``` ### LoggingAssertionHandler.m -~~~{objective-c} +```objc @implementation LoggingAssertionHandler - (void)handleFailureInMethod:(SEL)selector @@ -81,7 +81,7 @@ And while Foundation assertion macros are extremely useful in their own right— } @end -~~~ +``` Each thread has the option of specifying an assertion handler. To have the `NSAssertionHandler` subclass start handling failed assertions, set it as the value for the `NSAssertionHandlerKey` key in the thread's `threadDictionary`. @@ -90,18 +90,18 @@ didFinishLaunchingWithOptions:`. ### AppDelegate.m -~~~{objective-c} +```objc - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { NSAssertionHandler *assertionHandler = [[LoggingAssertionHandler alloc] init]; [[[NSThread currentThread] threadDictionary] setValue:assertionHandler forKey:NSAssertionHandlerKey]; - // ... + <#...#> return YES; } -~~~ +``` --- diff --git a/2013-03-04-backrow.md b/2013-03-04-backrow.md index 39d7de1d..fd5db807 100644 --- a/2013-03-04-backrow.md +++ b/2013-03-04-backrow.md @@ -1,10 +1,11 @@ --- title: Back Row -author: Mattt Thompson -category: "" +author: Mattt +category: Miscellaneous excerpt: "Back Row is a private framework used in the Apple TV user experience. Rather than waiting for Apple to open up the Apple TV SDK, one can take matters into their own hands." +retired: true status: - swift: n/a + swift: n/a --- For years, many have predicted the announcement of an Apple TV SDK. We all know it's coming. It has to. And it will be amazing when it happens... whenever that is. @@ -40,39 +41,39 @@ Here's a brief list of some Back Row classes & protocols and their UIKit equival - <BRResponder> - <UIResponder> + <BRResponder> + <UIResponder> - <BRAppliance> - <UIApplication> + <BRAppliance> + <UIApplication> - BRController - UIViewController + BRController + UIViewController - BRMenuController - UITableViewController + BRMenuController + UITableViewController - BRControllerStack - UINavigationController + BRControllerStack + UINavigationController - BRGridView - UICollectionView + BRGridView + UICollectionView - BRListView - UITableView + BRListView + UITableView ## Apple TV Appliance Structure -![Apple TV Home Screen]({{ site.asseturl }}/backrow-home-screen.jpg) +![Apple TV Home Screen]({% asset backrow-home-screen.jpg @path %}) In the current Apple TV interface, the home screen contains a grid of rectangular icons, similar to the home screen on iOS. Each icon corresponds to an _appliance_. @@ -84,7 +85,7 @@ An appliance also has an optional `topShelfController`, which is what displays a Pushing and popping controllers is managed by a shared `BRControllerStack`. When a controller is selected with in a list, it is pushed onto the stack. When the user presses the Menu button, the stack is popped. -![Apple TV YouTube]({{ site.asseturl }}/backrow-youtube.jpg) +![Apple TV YouTube]({% asset backrow-youtube.jpg @path %}) A typical controller consists of a menu list on the right, with some kind of complimentary view on the left. @@ -92,7 +93,7 @@ On top-level controllers, this sometimes takes the form of a `BRMarqueeStack`, w For controllers listing media that can be played, the complimentary view usually shows a preview image, along with meta data in a `BRMetadataControl`, such as runtime, date created, and other relevant information. -![Apple TV Movie]({{ site.asseturl }}/backrow-movie.jpg) +![Apple TV Movie]({% asset backrow-movie.jpg @path %}) iTunes store controllers also use a horizontally-stacked layout, with media information at the top, with related titles listed below. @@ -111,7 +112,7 @@ Here are some of the more interesting parts of the Back Row framework headers (a ## Building Your Own Apple TV App -In the immortal words of Jeff Goldblum: ["Life finds a way"](http://www.youtube.com/watch?v=SkWeMvrNiOM). +In the immortal words of Jeff Goldblum: ["Life finds a way"](https://www.youtube.com/watch?v=SkWeMvrNiOM). Undeterred by the litigious shadow of Apple Inc., nor the undocumented wilds of a private API, a cadre of jail-breaking homesteaders have cracked the nut on Apple TV development with promising results. @@ -127,4 +128,4 @@ Only time will tell. In the meantime, you can get a head-start on what will almost certainly be a gold-rush on the scale of the first generation of iPhone apps. -[1]: http://en.wikipedia.org/wiki/Front_Row_(software) +[1]: https://en.wikipedia.org/wiki/Front_Row_(software) diff --git a/2013-03-11-uiappearance.md b/2013-03-11-uiappearance.md index b95c00dc..272e661f 100644 --- a/2013-03-11-uiappearance.md +++ b/2013-03-11-uiappearance.md @@ -1,12 +1,12 @@ --- title: UIAppearance -author: Mattt Thompson +author: Mattt category: Cocoa tags: nshipster excerpt: "UIAppearance allows the appearance of views and controls to be consistently defined across the entire application." status: - swift: 2.0 - reviewed: September 8, 2015 + swift: 2.0 + reviewed: September 8, 2015 --- Style vs. Substance. @@ -42,7 +42,8 @@ Appearance can be customized for all instances, or scoped to particular view hie ```swift UINavigationBar.appearance().tintColor = myColor ``` -```objective-c + +```objc [[UINavigationBar appearance] setTintColor:myColor]; ``` @@ -58,7 +59,8 @@ UIBarButtonItem.appearanceWhenContainedInInstancesOfClasses([UIToolbar.self]) UIBarButtonItem.appearanceWhenContainedInInstancesOfClasses([UIToolbar.self, UIPopoverController.self]) .tintColor = myNavBarColor ``` -```objective-c + +```objc [[UIBarButtonItem appearanceWhenContainedInInstancesOfClasses:@[[UINavigationBar class]]] setTintColor:myNavBarColor]; [[UIBarButtonItem appearanceWhenContainedInInstancesOfClasses:@[[UINavigationBar class], [UIPopoverController class]]] @@ -73,9 +75,9 @@ UIBarButtonItem.appearanceWhenContainedInInstancesOfClasses([UIToolbar.self, UIP One major downside to `UIAppearance`'s proxy approach is that it's difficult to know which selectors are compatible. -Because +appearance returns an id, Xcode can't provide any code-completion information. This is a major source of confusion and frustration with this feature. +Because +appearance returns an id, Xcode can't provide any code-completion information. This is a major source of confusion and frustration with this feature. -As of iOS 7, UIAppearance now returns instancetype, which allows for code completion to work as expected. Huzzah! +As of iOS 7, UIAppearance now returns instancetype, which allows for code completion to work as expected. Huzzah! In order to find out what methods work with `UIAppearance`, you have to [look at the headers](http://stackoverflow.com/questions/9424112/what-properties-can-i-set-via-an-uiappearance-proxy): @@ -91,21 +93,13 @@ For your convenience, [here is the list of properties as of iOS 7.0](https://gis ## Implementing `` in Custom UIView Subclasses -Much like how [`NSLocalizedString`](http://nshipster.com/nslocalizedstring/) and [`#pragma`](http://nshipster.com/pragma/) are marks of quality in Objective-C code, having custom UI classes conform to `UIAppearance` is not only a best-practice, but it demonstrates a certain level of care being put into its implementation. +Much like how [`NSLocalizedString`](https://nshipster.com/nslocalizedstring/) and [`#pragma`](https://nshipster.com/pragma/) are marks of quality in Objective-C code, having custom UI classes conform to `UIAppearance` is not only a best-practice, but it demonstrates a certain level of care being put into its implementation. [Peter Steinberger](https://twitter.com/steipete) has [this great article](http://petersteinberger.com/blog/2013/uiappearance-for-custom-views/), which describes some of the caveats about implementing `UIAppearance` in custom views. It's a must-read for anyone who aspires to greatness in their open source UI components. -## Alternatives - -Another major shortcoming of `UIAppearance` is that style rules are _imperative_, rather than _declarative_. That is, styling is applied at runtime in code, rather than being interpreted from a list of style rules. - -Yes, if there's one idea to steal from web development, it's the separation of content and presentation. Say what you will about CSS, but stylesheets are _amazing_. - -Stylesheet enthusiasts on iOS now have some options. [Pixate](http://www.pixate.com) is a commercial framework that uses CSS to style applications. [NUI](https://github.com/tombenner/nui), an open-source project by [Tom Benner](https://github.com/tombenner), does much the same with a CSS/SCSS-like language. Another open source project along the same lines is [UISS](https://github.com/robertwijas/UISS) by [Robert Wijas](https://github.com/robertwijas), which allows `UIAppearance` rules to be read from JSON. - --- -Cocoa developers have a long history of obsessing about visual aesthetics, and have often gone to extreme ends to achieve their desired effects. Recall the [Delicious Generation](http://en.wikipedia.org/wiki/Delicious_Generation) of Mac developers, and applications like [Disco](http://discoapp.com), which went so far as to [emit virtual smoke when burning a disc](http://www.youtube.com/watch?v=8Dwi47XOqwI). +Cocoa developers have a long history of obsessing about visual aesthetics, and have often gone to extreme ends to achieve their desired effects. Recall the [Delicious Generation](https://en.wikipedia.org/wiki/Delicious_Generation) of Mac developers, and applications like [Disco](http://discoapp.com), which went so far as to [emit virtual smoke when burning a disc](https://www.youtube.com/watch?v=8Dwi47XOqwI). This spirit of dedication to making things look good is alive and well in iOS. As a community and as an ecosystem, we have relentlessly pushed the envelope in terms of what users should expect from their apps. And though this makes our jobs more challenging, it makes the experience of developing for iOS all the more enjoyable. diff --git a/2013-03-18-c-storage-classes.md b/2013-03-18-c-storage-classes.md index 402eaf40..6e76389b 100644 --- a/2013-03-18-c-storage-classes.md +++ b/2013-03-18-c-storage-classes.md @@ -1,6 +1,6 @@ --- title: C Storage Classes -author: Mattt Thompson +author: Mattt category: Objective-C tags: nshipster excerpt: "In C, the scope and lifetime of a variable or function within a program is determined by its storage class. Understanding these storage classes allows us to decipher common incantations found throughout Objective-C" @@ -33,7 +33,7 @@ Automatic variables have memory automatically allocated when a program enters a Most Objective-C programmers probably aren't familiar with `register` either, as it's just not widely used in the `NS` world. -`register` behaves just like `auto`, except that instead of being allocated onto the stack, they are stored in a [register](http://en.wikipedia.org/wiki/Processor_register). +`register` behaves just like `auto`, except that instead of being allocated onto the stack, they are stored in a [register](https://en.wikipedia.org/wiki/Processor_register). Registers offer faster access than RAM, but because of the complexities of memory management, putting variables in registers does not guarantee a faster program—in fact, it may very well end up slowing down execution by taking up space on the register unnecessarily. As it were, using `register` is actually just a _suggestion_ to the compiler to store the variable in the register; implementations may choose whether or not to honor this. @@ -52,7 +52,7 @@ As a keyword, `static` gets used in a lot of different, incompatible ways, so it A common pattern in Objective-C is the `static` singleton, wherein a statically-declared variable is initialized and returned in either a function or class method. `dispatch once` is used to guarantee that the variable is initialized _exactly_ once in a thread-safe manner: -~~~{objective-c} +```objc + (instancetype)sharedInstance { static id _sharedInstance = nil; static dispatch_once_t onceToken; @@ -62,7 +62,7 @@ A common pattern in Objective-C is the `static` singleton, wherein a statically- return _sharedInstance; } -~~~ +``` The singleton pattern is useful for creating objects that are shared across the entire application, such as an HTTP client or a notification manager, or objects that may be expensive to create, such as formatters. @@ -80,15 +80,15 @@ The pattern is to declare an `extern` `NSString * const` in a public header, and #### AppDelegate.h -~~~{objective-c} +```objc extern NSString * const kAppErrorDomain; -~~~ +``` #### AppDelegate.m -~~~{objective-c} +```objc NSString * const kAppErrorDomain = @"com.example.yourapp.error"; -~~~ +``` It doesn't particularly matter what the value of the string is, so long as it's unique. Using a string constant establishes a strict contract, that the constant variable is used instead of the string's literal value itself. @@ -100,7 +100,7 @@ The pattern follows the same as in the previous example: #### TransactionStateMachine.h -~~~{objective-c} +```objc typedef NS_ENUM(NSUInteger, TransactionState) { TransactionOpened, TransactionPending, @@ -108,11 +108,11 @@ typedef NS_ENUM(NSUInteger, TransactionState) { }; extern NSString * NSStringFromTransactionState(TransactionState state); -~~~ +``` #### TransactionStateMachine.m -~~~{objective-c} +```objc NSString * NSStringFromTransactionState(TransactionState state) { switch (state) { case TransactionOpened: @@ -125,7 +125,7 @@ NSString * NSStringFromTransactionState(TransactionState state) { return nil; } } -~~~ +``` --- diff --git a/2013-03-25-search-kit.md b/2013-03-25-search-kit.md index e520f9e1..112a89e2 100644 --- a/2013-03-25-search-kit.md +++ b/2013-03-25-search-kit.md @@ -1,14 +1,14 @@ --- title: Search Kit -author: Mattt Thompson +author: Mattt category: Cocoa excerpt: "Search Kit is a C framework for searching and indexing content in human languages. It supports matching on phrase or partial word, including logical & wildcard operators, and can rank results by relevance. Search Kit also provides document summarization, which is useful for generating representative excerpts. And best of all: it's thread-safe." status: - swift: 2.0 - reviewed: November 24, 2015 + swift: 2.0 + reviewed: November 24, 2015 revisions: - "2013-03-25": Original publication. - "2015-11-24": Revised for Swift 2.0. + "2013-03-25": Original publication + "2015-11-24": Revised for Swift 2.0 --- NSHipsters love irony, right? How about this for irony: @@ -39,9 +39,9 @@ Finding the answer in a reasonable amount of time requires effort from the start ### Extract -First, content must be extracted from a [corpus](http://en.wikipedia.org/wiki/Text_corpus). For a text document, this could involve removing any styling, formatting, or other meta-information. For a data record, such as an `NSManagedObject`, this means taking all of the salient fields and combining it into a representation. +First, content must be extracted from a [corpus](https://en.wikipedia.org/wiki/Text_corpus). For a text document, this could involve removing any styling, formatting, or other meta-information. For a data record, such as an `NSManagedObject`, this means taking all of the salient fields and combining it into a representation. -Once extracted, the content is [tokenized](http://en.wikipedia.org/wiki/Tokenization) for further processing. +Once extracted, the content is [tokenized](https://en.wikipedia.org/wiki/Tokenization) for further processing. ### Filter @@ -49,11 +49,11 @@ In order to get the most relevant matches, it's important to filter out common, ### Reduce -Along the same lines, words that mean basically the same thing should be reduced down into a common form. Morpheme clusters, such as grammatical conjugations like "computer", "computers", "computed", and "computing", for example, can all be simplified to be just "compute", using a [stemmer](http://en.wikipedia.org/wiki/Stemming). Synonyms, likewise, can be lumped into a common entry using a thesaurus lookup. +Along the same lines, words that mean basically the same thing should be reduced down into a common form. Morpheme clusters, such as grammatical conjugations like "computer", "computers", "computed", and "computing", for example, can all be simplified to be just "compute", using a [stemmer](https://en.wikipedia.org/wiki/Stemming). Synonyms, likewise, can be lumped into a common entry using a thesaurus lookup. ### Index -The end result of extracting, filtering, and reducing content into an array of normalized tokens is to form an [inverted index](http://en.wikipedia.org/wiki/Inverted_index), such that each token points to its origin in the index. +The end result of extracting, filtering, and reducing content into an array of normalized tokens is to form an [inverted index](https://en.wikipedia.org/wiki/Inverted_index), such that each token points to its origin in the index. After repeating this process for each document or record in the corpus until, each token can point to many different articles. In the process of searching, a query is mapped onto one or many of these tokens, retrieving the union of the articles associated with each token. @@ -61,19 +61,20 @@ After repeating this process for each document or record in the corpus until, ea ### Creating an Index -`SKIndexRef` is the central data type in Search Kit, containing all of the information needed to process and fulfill searches, and add information from new documents. Indexes can be persistent / file-based or ephemeral / in-memory. Indexes can either be created from scratch, or loaded from an existing file or data object—and once +`SKIndexRef` is the central data type in Search Kit, containing all of the information needed to process and fulfill searches, and add information from new documents. Indexes can be persistent / file-based or ephemeral / in-memory. Indexes can either be created from scratch, or loaded from an existing file or data object—and once an index is finished being used, like many other C APIs, the index is closed. When starting a new in-memory index, use an empty `NSMutableData` instance as the data store: -~~~{swift} +```swift let mutableData = NSMutableData() let index = SKIndexCreateWithMutableData(mutableData, nil, SKIndexType(kSKIndexInverted.rawValue), nil).takeRetainedValue() -~~~ -~~~{objective-c} +``` + +```objc NSMutableData *mutableData = [NSMutableData data]; SKIndexRef index = SKIndexCreateWithMutableData((__bridge CFMutableDataRef)mutableData, NULL, kSKIndexInverted, NULL); -~~~ +``` ### Adding Documents to an Index @@ -83,53 +84,57 @@ Each `SKDocumentRef` is associated with a URI. For documents on the file system, the URI is simply the location of the file on disk: -~~~{swift} +```swift let fileURL = NSURL(fileURLWithPath: "/path/to/document") let document = SKDocumentCreateWithURL(fileURL).takeRetainedValue() -~~~ -~~~{objective-c} +``` + +```objc NSURL *fileURL = [NSURL fileURLWithPath:@"/path/to/document"]; SKDocumentRef document = SKDocumentCreateWithURL((__bridge CFURLRef)fileURL); -~~~ +``` For Core Data managed objects, the `NSManagedObjectID -URIRepresentation` can be used: -~~~{swift} +```swift let objectURL = objectID.URIRepresentation() let document = SKDocumentCreateWithURL(objectURL).takeRetainedValue() -~~~ -~~~{objective-c} +``` + +```objc NSURL *objectURL = [objectID URIRepresentation]; SKDocumentRef document = SKDocumentCreateWithURL((__bridge CFURLRef)objectURL); -~~~ +``` > For any other kinds of data, it would be up to the developer to define a URI representation. When adding the contents of a `SKDocumentRef` to an `SKIndexRef`, the text can either be specified manually: -~~~{swift} +```swift let string = "Lorem ipsum dolar sit amet" SKIndexAddDocumentWithText(index, document, string, true) -~~~ -~~~{objective-c} +``` + +```objc NSString *string = @"Lorem ipsum dolar sit amet"; SKIndexAddDocumentWithText(index, document, (__bridge CFStringRef)string, true); -~~~ +``` ...or collected automatically from a file: -~~~{swift} +```swift let mimeTypeHint = "text/rtf" SKIndexAddDocument(index, document, mimeTypeHint, true) -~~~ -~~~{objective-c} +``` + +```objc NSString *mimeTypeHint = @"text/rtf"; SKIndexAddDocument(index, document, (__bridge CFStringRef)mimeTypeHint, true); -~~~ +``` To change the way a file-based document's contents are processed, properties can be defined when creating the index: -~~~{swift} +```swift let stopwords: Set = ["all", "and", "its", "it's", "the"] let properties: [NSObject: AnyObject] = [ @@ -141,8 +146,9 @@ let properties: [NSObject: AnyObject] = [ ] let index = SKIndexCreateWithURL(url, nil, SKIndexType(kSKIndexInverted.rawValue), properties).takeRetainedValue() -~~~ -~~~{objective-c} +``` + +```objc NSSet *stopwords = [NSSet setWithObjects:@"all", @"and", @"its", @"it's", @"the", nil]; NSDictionary *properties = @{ @@ -154,25 +160,25 @@ NSDictionary *properties = @{ }; SKIndexRef index = SKIndexCreateWithURL((CFURLRef)url, NULL, kSKIndexInverted, (CFDictionaryRef)properties); -~~~ +``` After adding to or modifying an index's documents, you'll need to commit the changes to the backing store via `SKIndexFlush()` to make your changes available to a search. - ### Searching `SKSearchRef` is the data type constructed to perform a search on an `SKIndexRef`. It contains a reference to the index, a query string, and a set of options: -~~~{swift} +```swift let query = "kind of blue" let options = SKSearchOptions(kSKSearchOptionDefault) let search = SKSearchCreate(index, query, options).takeRetainedValue() -~~~ -~~~{objective-c} +``` + +```objc NSString *query = @"kind of blue"; SKSearchOptions options = kSKSearchOptionDefault; SKSearchRef search = SKSearchCreate(index, (CFStringRef)query, options); -~~~ +``` `SKSearchOptions` is a bitmask with the following possible values: @@ -189,29 +195,30 @@ These options can be specified individually as well: Just creating an `SKSearchRef` kicks off the asynchronous search; results can be accessed with one or more calls to `SKSearchFindMatches`, which returns a batch of results at a time until you've seen all the matching documents. Iterating through the range of found matches provides access to the document URL and relevance score (if calculated): -~~~{swift} +```swift let limit = ... // Maximum number of results let time: NSTimeInterval = ... // Maximum time to get results, in seconds var documentIDs: [SKDocumentID] = Array(count: limit, repeatedValue: 0) var urls: [Unmanaged?] = Array(count: limit, repeatedValue: nil) -var scores: [Float] = Array(count: limit, repeatedValue: 0) +var scores: [Float] = Array(count: limit, repeatedValue: 0) var foundCount = 0 let hasMoreResults = SKSearchFindMatches(search, limit, &documentIDs, &scores, time, &count) SKIndexCopyDocumentURLsForDocumentIDs(index, foundCount, &documentIDs, &urls) - + let results: [NSURL] = zip(urls[0 ..< foundCount], scores).flatMap({ (cfurl, score) -> NSURL? in guard let url = cfurl?.takeRetainedValue() as NSURL? else { return nil } - + print("- \(url): \(score)") return url }) -~~~ -~~~{objective-c} +``` + +```objc NSUInteger limit = ...; // Maximum number of results NSTimeInterval time = ...; // Maximum time to get results, in seconds SKDocumentID documentIDs[limit]; @@ -235,7 +242,7 @@ NSMutableArray *mutableResults = [NSMutableArray array]; CFRelease(url); }]; -~~~ +``` > For more examples of Search Kit in action, be sure to check out [Indragie Karunaratne's](https://github.com/indragiek) project, [SNRSearchIndex](https://github.com/indragiek/SNRSearchIndex). diff --git a/2013-04-01-icloud.md b/2013-04-01-icloud.md index 1f249e49..5619700c 100644 --- a/2013-04-01-icloud.md +++ b/2013-04-01-icloud.md @@ -1,13 +1,13 @@ --- title: iCloud -author: Mattt Thompson -category: "" +author: Mattt +category: Miscellaneous excerpt: "Perhaps what Apple is most renowned for is its consistent track record for creating great webservices. From consumer products like MobileMe and Ping to developer tools including the Provisioning Portal and iTunes Connect, Apple has continued to set new standards for convenience, robustness, and extensibility." status: swift: 1.1 --- -The [Lisa](http://en.wikipedia.org/wiki/Apple_Lisa). The [Twentieth Anniversary Macintosh](http://en.wikipedia.org/wiki/Twentieth_Anniversary_Macintosh). The [iPod Hi-Fi](http://en.wikipedia.org/wiki/IPod_Hi-Fi). The [MacBook Wheel](http://www.youtube.com/watch?v=9BnLbv6QYcA). +The [Lisa](https://en.wikipedia.org/wiki/Apple_Lisa). The [Twentieth Anniversary Macintosh](https://en.wikipedia.org/wiki/Twentieth_Anniversary_Macintosh). The [iPod Hi-Fi](https://en.wikipedia.org/wiki/IPod_Hi-Fi). The [MacBook Wheel](https://www.youtube.com/watch?v=9BnLbv6QYcA). Each of these products exemplifies Apple's obsessive pursuit of quality as much as its unrivaled ability to anticipate the direction of things to come and execute flawlessly. @@ -17,7 +17,7 @@ But perhaps what Apple is most renowned for, however, is its consistent track re So when Apple introduced iCloud at WWDC in 2011, everyone in the audience was rapt in anticipation, overcome with a sense of optimistic wonder. "Not again!", we said to ourselves, shaking our head with a knowing smile. "This changes everything!" -And indeed, iCloud _has_ changed everything. Although it seemed almost too good to be true when Steve Jobs got up on that stage in Moscone to announce iCloud to the world that morning in June—if anything, Apple has __under__-promised and __over__-delivered with iCloud. +And indeed, iCloud _has_ changed everything. Although it seemed almost too good to be true when Steve Jobs got up on that stage in Moscone to announce iCloud to the world that morning in June—if anything, Apple has **under**-promised and **over**-delivered with iCloud. Now, NSHipster usually covers obscure topics that you've probably never heard of. But this is one of those days where it just feels _right_ to take this opportunity today to make sure we're all on the same page about iCloud. @@ -39,7 +39,7 @@ 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} +```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 @@ -48,9 +48,9 @@ func application(application: UIApplication!, didFinishLaunchingWithOptions laun return true } -~~~ +``` -~~~{objective-c} +```objc - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { @@ -69,7 +69,7 @@ didFinishLaunchingWithOptions:(NSDictionary *)launchOptions NSUbiquitousKeyValueStore *ubiquitousKeyValueStore = notification.object; [ubiquitousKeyValueStore synchronize]; } -~~~ +``` ## Document Storage... in the Cloud! diff --git a/2013-04-08-bool.md b/2013-04-08-bool.md index 14a9b176..c111aab6 100644 --- a/2013-04-08-bool.md +++ b/2013-04-08-bool.md @@ -1,110 +1,161 @@ --- title: BOOL / bool / Boolean / NSCFBoolean -author: Mattt Thompson +author: Mattt category: Objective-C tags: nshipster, popular -excerpt: "Once again, encoding our logical universe into the cold, calculating bytecode of computers forces us to deal with these questions one way or another. And as you'll see from our discussion of boolean types in Objective-C and its kin, truth is indeed stranger than fiction." +excerpt: >- + Encoding our logical universe into the cold, calculating bytecode of computers + forces us to deal with these questions one way or another. + And as you'll see from our discussion of Boolean types in Objective-C, + truth is indeed stranger than fiction. status: - swift: n/a + swift: n/a --- -We've talked before about the [philosophical and technical concerns of nothingness in programming](http://nshipster.com/nil/). This week, our attention turns to another fundamental matter: Truth. - -Truth. _Vēritās_. The entire charter of Philosophy is founded upon the pursuit of it, and yet its exact meaning and implications still elude us. Does truth exist independently, or is it defined contingently against falsity? Can a proposition be at once both true _and_ false? Is there absolute truth in anything, or is everything relative? - -Once again, encoding our logical universe into the cold, calculating bytecode of computers forces us to deal with these questions one way or another. And as you'll see from our discussion of boolean types in Objective-C and its kin, truth is indeed stranger than fiction. - ---- - -Objective-C defines `BOOL` to encode truth value. It is a `typedef` of a `signed char`, with the macros `YES` and `NO` to represent true and false, respectively. - -Boolean values are used in conditionals, such as `if` or `while` statements, to conditionally perform logic or repeat execution. When evaluating a conditional statement, the value `0` is considered "false", while any other value is considered "true". Because `NULL` and `nil` are defined as `0`, conditional statements on these nonexistent values are also evaluated as "false". - -**In Objective-C, use the `BOOL` type for parameters, properties, and instance variables dealing with truth values. When assigning literal values, use the `YES` and `NO` macros.** +Truth. +Vēritās. +The entire charter of Philosophy is founded upon the pursuit of it, +and yet its exact meaning and implications still elude us. +Does truth exist independently or is it defined contingently against falsity? +Can a proposition be at once both true _and_ false? Is there absolute truth in anything, or is everything relative? + +Encoding our logical universe into the cold, calculating bytecode of computers +forces us to deal with these questions one way or another. +And, as we'll soon discuss, +truth is indeed stranger than fiction. + +We've talked before about the +[philosophical and technical concerns of nothingness in programming](/nil/). +This week, +our attention turns to another fundamental matter: +_Boolean types in Objective-C_. + + +* * * + +In `C`, +statements like `if` and `while` +evaluate a conditional expression to determine +which code to execute next. +A value of `0` is considered "false" +while any other value is considered "true". + +Objective-C defines the `BOOL` type to encode truth values. +Altogether, +`BOOL` comprises a type definition (`typedef signed char BOOL`) +and the macros `YES` and `NO`, +which represent true and false, +respectively. +By convention, +we use the `BOOL` type for Boolean +parameters, +properties, and +instance variables +and use `YES` and `NO` +when representing literal Boolean values. + +{% info %} + +Because `NULL` and `nil` zero values, +they evaluate to "false" in conditional expressions. + +{% endinfo %} ## The Wrong Answer to the Wrong Question -Novice programmers often include an equality operator when evaluating conditionals: +Novice programmers often include an equality operator in conditional expressions: -~~~{objective-c} +```objc if ([a isEqual:b] == YES) { ... } -~~~ +``` -Not only is this unnecessary, but depending on the left-hand value, it may also cause unexpected results, as described in the [Big Nerd Ranch blog post, "BOOL's Sharp Edges"](http://blog.bignerdranch.com/564-bools-sharp-corners/): +Not only is this unnecessary, +but depending on the left-hand value can cause unexpected results, +as demonstrated by Mark Dalrymple in +[this blog post](https://www.bignerdranch.com/blog/bools-sharp-corners/) +with the following example: -~~~{objective-c} +```objc static BOOL different (int a, int b) { return a - b; } -~~~ +``` -An overly clever C programmer might take some satisfaction in the simplicity of this approach: indeed, two integers are equal if and only if their difference is `0`. +A clever programmer might take some satisfaction in this approach. +Indeed, two integers are equal if and only if their difference is `0`. +However, +because `BOOL` is `typedef`'d as a `signed char`, +this implementation doesn't behave as expected: -However, because of the reality of `BOOL` being `typedef`'d as a `signed char`, this will not behave as expected: - -~~~{objective-c} +```objc if (different(11, 10) == YES) { printf ("11 != 10\n"); } else { printf ("11 == 10\n"); } +// Prints "11 != 10" if (different(10, 11) == YES) { printf ("10 != 11\n"); } else { printf ("10 == 11\n"); } +// Prints "10 == 11" if (different(512, 256) == YES) { printf ("512 != 256\n"); } else { printf ("512 == 256\n"); } -~~~ - -This evaluates to: +// Prints "512 == 256" +``` -~~~ -11 != 10 -10 == 11 -512 == 256 -~~~ +[This might be acceptable for JavaScript](https://www.destroyallsoftware.com/talks/wat), +but Objective-C doesn't suffer fools gladly. -Now, [this might be acceptable for JavaScript](https://www.destroyallsoftware.com/talks/wat), but Objective-C don't suffer fools gladly. - -Deriving truth value directly from an arithmetic operation is never a good idea. Like the sentence ["Colorless green ideas sleep furiously"](http://en.wikipedia.org/wiki/Colorless_green_ideas_sleep_furiously), it may be grammatical (after all, `BOOL` is a `signed char` like any other, so it _could_ be treated as a number), but it doesn't make sense semantically. Instead, use the result of the `==` operator, or cast values into booleans with the `!` (or `!!`) operator. +Deriving truth value directly from an arithmetic operation is never a good idea. +Like the sentence +["Colorless green ideas sleep furiously"](https://en.wikipedia.org/wiki/Colorless_green_ideas_sleep_furiously), +it may be grammatical but it doesn't make any sense. +Instead, +use the result of the `==` operator +or cast values into booleans with the `!` (or `!!`) operator. ## The Truth About `NSNumber` and `BOOL` -Pop quiz: what is the output of the following expression? +**Pop Quiz**: What is the output of the following expression? -~~~{objective-c} +```objc NSLog(@"%@", [@(YES) class]); -~~~ - -The answer: - - __NSCFBoolean +``` -Wait, what? +**Answer**: `__NSCFBoolean` -All this time, we've been led to believe that `NSNumber` [boxes](http://nshipster.com/nsvalue/) primitives into an object representation. Any other integer- or float-derived `NSNumber` object shows its class to be `__NSCFNumber`. What gives? +_Wait, what?_ -`NSCFBoolean` is a private class in the `NSNumber` [class cluster](http://nshipster.com/nsorderedset/). It is a bridge to the [`CFBooleanRef` type](https://developer.apple.com/library/mac/#documentation/CoreFoundation/Reference/CFBooleanRef/Reference/reference.html), which is used to wrap boolean values for Core Foundation property lists and collections. `CFBoolean` defines the constants `kCFBooleanTrue` and `kCFBooleanFalse`. Because `CFNumberRef` and `CFBooleanRef` are different types in Core Foundation, it makes sense that they are represented by different bridging classes in `NSNumber`. +All this time, +we've been led to believe that `NSNumber` +[boxes](/nsvalue/) primitives into an object representation. +Any other integer- or floating-point-derived `NSNumber` object +shows its class to be `__NSCFNumber`. +_So what gives?_ -For most people, boolean values and boxed objects "just work", and don't really care what goes into making the sausage. But here at NSHipster, we're all about sausages. - ---- - -So, to recap, here is a table of all of the truth types and values in Objective-C: +`NSCFBoolean` is a private class in the `NSNumber` +[class cluster](/nsorderedset/). +It's a bridge to the +[`CFBooleanRef` type](https://developer.apple.com/library/mac/#documentation/CoreFoundation/Reference/CFBooleanRef/Reference/reference.html), +which is used to wrap boolean values for Core Foundation property lists and collections. +`CFBoolean` defines the constants `kCFBooleanTrue` and `kCFBooleanFalse`. +Because `CFNumberRef` and `CFBooleanRef` are different types in Core Foundation, +it makes sense that they are represented by different bridging classes in `NSNumber`. - @@ -112,39 +163,48 @@ So, to recap, here is a table of all of the truth types and values in Objective- - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + +
NameTypedef + Typedef Header True Value False Value
BOOLsigned charBOOLsigned char objc.hYESNOYESNO
bool_Bool (int)bool_Bool (int) stdbool.htruefalsetruefalse
Booleanunsigned charBooleanunsigned char MacTypes.hTRUEFALSETRUEFALSE
NSNumber__NSCFBooleanNSNumber__NSCFBoolean Foundation.h@(YES)@(NO)@(YES)@(NO)
CFBooleanRefstructCFBooleanRefstruct CoreFoundation.hkCFBooleanTruekCFBooleanFalsekCFBooleanTruekCFBooleanFalse
+ +* * * + +For most people, +Boolean values and boxed objects "just work". +They don't really care what goes into making the sausage. +But here at NSHipster, we're all about sausages. +That's the honest truth. + diff --git a/2013-04-15-nssecurecoding.md b/2013-04-15-nssecurecoding.md index c9b36052..e2f4d8f0 100644 --- a/2013-04-15-nssecurecoding.md +++ b/2013-04-15-nssecurecoding.md @@ -1,6 +1,6 @@ --- title: NSSecureCoding -author: Mattt Thompson +author: Mattt category: Cocoa excerpt: "A short post for this week: everything you need to know about NSSecureCoding." status: @@ -23,35 +23,35 @@ Why is this important? Recall that `NSCoding` is Foundation's way of marshaling It's not an apples-to-apples comparison, but it's somewhat similar to [recent YAML exploit found in Rails](http://tenderlovemaking.com/2013/02/06/yaml-f7u12.html). -For an [XPC service](http://developer.apple.com/library/mac/#documentation/MacOSX/Conceptual/BPSystemStartup/Chapters/CreatingXPCServices.html), which is designed with security in mind, data integrity of this nature is especially important. It's a safe bet that XPC will only wax influence in subsequent iOS and OS X releases, so it's good to keep this all in mind. +For an [XPC service](https://developer.apple.com/library/mac/#documentation/MacOSX/Conceptual/BPSystemStartup/Chapters/CreatingXPCServices.html), which is designed with security in mind, data integrity of this nature is especially important. It's a safe bet that XPC will only wax influence in subsequent iOS and OS X releases, so it's good to keep this all in mind. Anyway, `NSSecureCoding` patches this vulnerability by establishing a contract for best practices. Now, decoding an object requires the class to be known ahead of time. Whereas a standard, secure implementation of `-initWithCoder:` might have a check like: -~~~{swift} +```swift if let object = decoder.decodeObjectForKey("key") as? SomeClass { - // ... + <#...#> } -~~~ +``` -~~~{objective-c} +```objc id obj = [decoder decodeObjectForKey:@"myKey"]; if (![obj isKindOfClass:[MyClass class]]) { // fail } -~~~ +``` ...an `NSSecureCoding`-conforming class would use: -~~~{swift} +```swift let object = decoder.decodeObjectOfClass(SomeClass.self, forKey: "key") as SomeClass -~~~ +``` -~~~{objective-c} +```objc id obj = [decoder decodeObjectOfClass:[MyClass class] forKey:@"myKey"]; -~~~ +``` Sometimes, a little API change makes all of the difference. diff --git a/2013-04-22-nshipster-quiz-1.md b/2013-04-22-nshipster-quiz-1.md index a97825b0..94e49f02 100644 --- a/2013-04-22-nshipster-quiz-1.md +++ b/2013-04-22-nshipster-quiz-1.md @@ -1,6 +1,6 @@ --- title: "NSHipster Quiz #1" -author: Mattt Thompson +author: Mattt category: Trivia excerpt: "Test your knowledge of general programming knowledge, Cocoa APIs, and Apple trivia in this first-ever NSHipster Quiz. How NSHip are you?" status: @@ -60,43 +60,43 @@ Round 3: Picture Round - 1. What is this? -![Question 1]({{ site.asseturl }}/quiz-1/question-1.jpg) +![Question 1]({% asset quiz-1/question-1.jpg @path %}) - 2. What is this? -![Question 2]({{ site.asseturl }}/quiz-1/question-2.jpg) +![Question 2]({% asset quiz-1/question-2.jpg @path %}) - 3. What is this? -![Question 3]({{ site.asseturl }}/quiz-1/question-3.jpg) +![Question 3]({% asset quiz-1/question-3.jpg @path %}) - 4. What is this? -![Question 4]({{ site.asseturl }}/quiz-1/question-4.jpg) +![Question 4]({% asset quiz-1/question-4.jpg @path %}) - 5. WTF is this? -![Question 5]({{ site.asseturl }}/quiz-1/question-5.jpg) +![Question 5]({% asset quiz-1/question-5.jpg @path %}) - 6. Who is this? -![Question 6]({{ site.asseturl }}/quiz-1/question-6.jpg) +![Question 6]({% asset quiz-1/question-6.jpg @path %}) - 7. Who is this? -![Question 7]({{ site.asseturl }}/quiz-1/question-7.jpg) +![Question 7]({% asset quiz-1/question-7.jpg @path %}) - 8. Who is this? -![Question 8]({{ site.asseturl }}/quiz-1/question-8.jpg) +![Question 8]({% asset quiz-1/question-8.jpg @path %}) - 9. Who is this? -![Question 9]({{ site.asseturl }}/quiz-1/question-9.jpg) +![Question 9]({% asset quiz-1/question-9.jpg @path %}) - 10. In this photo, Bill Gates & Steve Jobs are being interviewed at the D5 conference in 2007 by a man and a woman just off-screen to the left. Who are they? (One point for each person) -![Question 10]({{ site.asseturl }}/quiz-1/question-10.jpg) +![Question 10]({% asset quiz-1/question-10.jpg @path %}) Round 4: Name That Framework! @@ -123,16 +123,16 @@ For each question, a list of three classes from the same framework have been lis Round 1: General Knowledge -------------------------- -1. [NeXTSTEP](http://en.wikipedia.org/wiki/NeXTSTEP) +1. [NeXTSTEP](https://en.wikipedia.org/wiki/NeXTSTEP) 2. [4000](http://www.macrumors.com/2013/03/04/steve-jobs-4000-latte-prank-order-lives-on-at-san-francisco-starbucks/) -3. [`isReady`, `isExecuting`, `isFinished`, `isCancelled`](http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/NSOperation_class/Reference/Reference.html%23//apple_ref/doc/uid/TP40004591-RH2-DontLinkElementID_1) -4. [ textLabel detailTextLabel ](http://developer.apple.com/library/ios/DOCUMENTATION/UserExperience/Conceptual/TableView_iPhone/Art/tvcellstyle_value2.jpg) -5. [`UITableViewDelegate`](http://developer.apple.com/library/ios/documentation/uikit/reference/UITableViewDelegate_Protocol/Reference/Reference.html#//apple_ref/doc/uid/TP40006942-CH3-SW25) -6. [`signed char`](http://nshipster.com/bool/) -7. [Midnight UTC, 1 January 1970](http://en.wikipedia.org/wiki/Unix_epoch) -8. [4.6.2 (4H1003)](http://en.wikipedia.org/wiki/Xcode) -9. [NSIndexSet](http://nshipster.com/nsindexset/) -10. [16](http://en.wikipedia.org/wiki/IPhone_%281st_generation%29) +3. [`isReady`, `isExecuting`, `isFinished`, `isCancelled`](https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/NSOperation_class/Reference/Reference.html%23//apple_ref/doc/uid/TP40004591-RH2-DontLinkElementID_1) +4. [ textLabel detailTextLabel ](https://developer.apple.com/library/ios/DOCUMENTATION/UserExperience/Conceptual/TableView_iPhone/Art/tvcellstyle_value2.jpg) +5. [`UITableViewDelegate`](https://developer.apple.com/library/ios/documentation/uikit/reference/UITableViewDelegate_Protocol/Reference/Reference.html#//apple_ref/doc/uid/TP40006942-CH3-SW25) +6. [`signed char`](https://nshipster.com/bool/) +7. [Midnight UTC, 1 January 1970](https://en.wikipedia.org/wiki/Unix_epoch) +8. [4.6.2 (4H1003)](https://en.wikipedia.org/wiki/Xcode) +9. [NSIndexSet](https://nshipster.com/nsindexset/) +10. [16](https://en.wikipedia.org/wiki/IPhone_%281st_generation%29) Round 2: APIs ------------- @@ -151,29 +151,29 @@ Round 2: APIs Round 3: Picture Round ---------------------- -1. [Apple I](http://en.wikipedia.org/wiki/Apple_I) -2. [Apple eMac](http://en.wikipedia.org/wiki/EMac) -3. [Apple Bandai Pippin](http://en.wikipedia.org/wiki/Apple_Bandai_Pippin) -4. [Apple QuickTake](http://en.wikipedia.org/wiki/Apple_QuickTake) +1. [Apple I](https://en.wikipedia.org/wiki/Apple_I) +2. [Apple eMac](https://en.wikipedia.org/wiki/EMac) +3. [Apple Bandai Pippin](https://en.wikipedia.org/wiki/Apple_Bandai_Pippin) +4. [Apple QuickTake](https://en.wikipedia.org/wiki/Apple_QuickTake) 5. [New Proposed Apple Campus / "Mothership"](http://www.cultofmac.com/108782/apples-magnificent-mothership-campus-gets-new-renders-and-more-details-report/) -6. [Sir Jonathan "Jony" Ive](http://en.wikipedia.org/wiki/Jonathan_Ive) -7. [Scott Forstall](http://en.wikipedia.org/wiki/Scott_Forstall) -8. [Bob Mansfield](http://en.wikipedia.org/wiki/Bob_Mansfield) -9. [Susan Kare](http://en.wikipedia.org/wiki/Susan_kare) +6. [Sir Jonathan "Jony" Ive](https://en.wikipedia.org/wiki/Jonathan_Ive) +7. [Scott Forstall](https://en.wikipedia.org/wiki/Scott_Forstall) +8. [Bob Mansfield](https://en.wikipedia.org/wiki/Bob_Mansfield) +9. [Susan Kare](https://en.wikipedia.org/wiki/Susan_kare) 10. [Kara Swisher & Walt Mossberg ](http://allthingsd.com/20071224/best-of-2007-video-d5-interview-with-bill-gates-and-steve-jobs/) Round 4: Name That Framework! ----------------------------- -1. [App Kit](http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/ApplicationKit/ObjC_classic/_index.html) +1. [App Kit](https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/ApplicationKit/ObjC_classic/_index.html) 2. [AV Foundation](https://developer.apple.com/library/mac/#documentation/AVFoundation/Reference/AVFoundationFramework/_index.html) -3. [Publication Subscription](http://developer.apple.com/library/mac/#documentation/InternetWeb/Reference/PubSubReference/_index.html#//apple_ref/doc/uid/TP40004649) -4. [Core Location](http://developer.apple.com/library/ios/#documentation/CoreLocation/Reference/CoreLocation_Framework/_index.html) -5. [Core Data](http://developer.apple.com/library/ios/#documentation/cocoa/Reference/CoreData_ObjC/_index.html) +3. [Publication Subscription](https://developer.apple.com/library/mac/#documentation/InternetWeb/Reference/PubSubReference/_index.html#//apple_ref/doc/uid/TP40004649) +4. [Core Location](https://developer.apple.com/library/ios/#documentation/CoreLocation/Reference/CoreLocation_Framework/_index.html) +5. [Core Data](https://developer.apple.com/library/ios/#documentation/cocoa/Reference/CoreData_ObjC/_index.html) 6. [Search Kit](https://developer.apple.com/library/mac/#documentation/UserExperience/Reference/SearchKit/Reference/reference.html) -7. [Address Book](http://developer.apple.com/library/ios/#documentation/AddressBook/Reference/AddressBook_iPhoneOS_Framework/_index.html) -8. [GLKit](http://developer.apple.com/library/mac/#documentation/GLkit/Reference/GLKit_Collection/_index.html) -9. [Core Bluetooth](http://developer.apple.com/library/ios/#documentation/CoreBluetooth/Reference/CoreBluetooth_Framework/_index.html) +7. [Address Book](https://developer.apple.com/library/ios/#documentation/AddressBook/Reference/AddressBook_iPhoneOS_Framework/_index.html) +8. [GLKit](https://developer.apple.com/library/mac/#documentation/GLkit/Reference/GLKit_Collection/_index.html) +9. [Core Bluetooth](https://developer.apple.com/library/ios/#documentation/CoreBluetooth/Reference/CoreBluetooth_Framework/_index.html) 10. [Core Image](https://developer.apple.com/library/mac/#documentation/graphicsimaging/Conceptual/CoreImaging/ci_intro/ci_intro.html) * * * diff --git a/2013-04-29-mklocalsearch.md b/2013-04-29-mklocalsearch.md index 75b28061..f3f324a3 100644 --- a/2013-04-29-mklocalsearch.md +++ b/2013-04-29-mklocalsearch.md @@ -1,6 +1,6 @@ --- title: MKLocalSearch -author: Mattt Thompson +author: Mattt category: Cocoa excerpt: "In all of the hubbub of torch burning and pitchfork raising, you may have completely missed a slew of additions to MapKit in iOS 6.1." status: @@ -20,7 +20,7 @@ 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`: -~~~{swift} +```swift let request = MKLocalSearchRequest() request.naturalLanguageQuery = "Restaurants" request.region = mapView.region @@ -33,11 +33,11 @@ search.startWithCompletionHandler { (response, error) in } for item in response.mapItems { - // ... + <#...#> } } -~~~ -~~~{objective-c} +``` +```objc MKLocalSearchRequest *request = [[MKLocalSearchRequest alloc] init]; request.naturalLanguageQuery = @"Restaurants"; request.region = mapView.region; @@ -45,7 +45,7 @@ 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`. diff --git a/2013-05-06-gpuimage.md b/2013-05-06-gpuimage.md index f0f88917..e38b0843 100644 --- a/2013-05-06-gpuimage.md +++ b/2013-05-06-gpuimage.md @@ -1,13 +1,14 @@ --- title: GPUImage -author: Mattt Thompson +author: Mattt category: Open Source excerpt: "GPUImage is a BSD-licensed iOS library that lets you apply GPU-accelerated filters and other effects to images, live camera video, and movies. If you're not careful, you may well end up creating a camera app by the end of the article." +retired: true status: - swift: n/a + swift: n/a --- -Here at NSHipster, we're all about diving into the darker corners of Objective-C to learn something new about the systems we interact with every day. Often, this means sifting through Apple frameworks or language features (it is, after all, a lot of what it means to work in Objective-C). However, [on occasion](http://nshipster.com/reactivecocoa/), it's nice to take a look to the burgeoning landscape of third-party libraries and frameworks (and there are some truly remarkable ones) for a glimpse of what's new and great outside of Cupertino. +Here at NSHipster, we're all about diving into the darker corners of Objective-C to learn something new about the systems we interact with every day. Often, this means sifting through Apple frameworks or language features (it is, after all, a lot of what it means to work in Objective-C). However, [on occasion](https://nshipster.com/reactivecocoa/), it's nice to take a look to the burgeoning landscape of third-party libraries and frameworks (and there are some truly remarkable ones) for a glimpse of what's new and great outside of Cupertino. This week, we'll be taking a look at one of the most impressive open source projects you'll find: [GPUImage](https://github.com/BradLarson/GPUImage). Buckle up, NSHipsters—if you're not careful, you may well end up creating a camera app by the end of the article. @@ -17,11 +18,11 @@ GPUImage is a BSD-licensed iOS library written by [Brad Larson](https://github.c ## GPU vs. CPU -Every iPhone ships with two processors: a [CPU](http://en.wikipedia.org/wiki/Central_processing_unit), or Central Processing Unit and a [GPU](http://en.wikipedia.org/wiki/Graphics_processing_unit), or Graphics Processing Unit. Each processor has its own strengths, and modern chip architecture (like in the iPhone's [A4](http://en.wikipedia.org/wiki/Apple_A4)) integrate the CPU and GPU onto the same physical die. +Every iPhone ships with two processors: a [CPU](https://en.wikipedia.org/wiki/Central_processing_unit), or Central Processing Unit and a [GPU](https://en.wikipedia.org/wiki/Graphics_processing_unit), or Graphics Processing Unit. Each processor has its own strengths, and modern chip architecture (like in the iPhone's [A4](https://en.wikipedia.org/wiki/Apple_A4)) integrate the CPU and GPU onto the same physical die. -When you write C or Objective-C code in Xcode, you're generating instructions that will be handled almost exclusively by the CPU. The GPU, by contrast, is a specialized chip that is especially well-suited for computation that can be split out into many small, independent operations, such as graphics rendering. The kinds of instructions understood by the GPU are quite different from that of the CPU, and as such, we write this code in a different language: [OpenGL](http://en.wikipedia.org/wiki/Opengl) (or specifically, [OpenGL ES](http://en.wikipedia.org/wiki/OpenGL_ES) on the iPhone & iPad). +When you write C or Objective-C code in Xcode, you're generating instructions that will be handled almost exclusively by the CPU. The GPU, by contrast, is a specialized chip that is especially well-suited for computation that can be split out into many small, independent operations, such as graphics rendering. The kinds of instructions understood by the GPU are quite different from that of the CPU, and as such, we write this code in a different language: [OpenGL](https://en.wikipedia.org/wiki/Opengl) (or specifically, [OpenGL ES](https://en.wikipedia.org/wiki/OpenGL_ES) on the iPhone & iPad). -> Check out [Jeff LaMarche's GLProgram OpenGL ES 2.0 book](http://iphonedevelopment.blogspot.com/2010/11/opengl-es-20-for-ios-chapter-4.html) for a great introduction to OpenGL ES and the rendering pipeline. +> Check out [Jeff LaMarche's GLProgram OpenGL ES 2.0 book](https://web.archive.org/web/20150909104553/http://iphonedevelopment.blogspot.in:80/2010/11/opengl-es-20-for-ios-chapter-4.html) for a great introduction to OpenGL ES and the rendering pipeline. Comparing the performance of GPU-based rendering to CPU rendering for something like video, the differences are staggering: @@ -223,18 +224,17 @@ Here's a table of the 125 (!) filters that come with GPUImage: -Seriously, the [Filter Showcase Example App](https://github.com/BradLarson/GPUImage/tree/master/examples/iOS/FilterShowcase) that comes bundled in the repository could easily retail on the AppStore for $3.99, as-is. Add Twitter integration and a few sound effects, and you could bump that up to a respectable $6.99. +Seriously, the [Filter Showcase Example App](https://github.com/BradLarson/GPUImage/tree/master/examples/iOS/FilterShowcase) that comes bundled in the repository could easily retail on the AppStore for $3.99, as-is. Add Twitter integration and a few sound effects, and you could bump that up to a respectable$6.99. ## Rendering Pipeline - - +{% asset gpuimage-pipeline.svg %} GPUImage is, at its core, an Objective-C abstraction around a rendering pipeline. Source images from the camera, network, or disk are loaded and manipulated according to a chain of filters, and finally outputted either a view, graphics context, or data stream. -For example, images from the video camera could have a Color Levels filter applied to simulate different types of [color blindness](http://en.wikipedia.org/wiki/Color_blindness) and displayed in a live view. +For example, images from the video camera could have a Color Levels filter applied to simulate different types of [color blindness](https://en.wikipedia.org/wiki/Color_blindness) and displayed in a live view. -~~~{objective-c} +```objc GPUImageVideoCamera *videoCamera = [[GPUImageVideoCamera alloc] initWithSessionPreset:AVCaptureSessionPreset640x480 cameraPosition:AVCaptureDevicePositionBack]; @@ -251,11 +251,11 @@ GPUImageView *filteredVideoView = [[GPUImageView alloc] initWithFrame:self.view. [self.view addSubview:filteredVideoView]; [videoCamera startCameraCapture]; -~~~ +``` Or, combining various color blending modes, image effects, and adjustments, you could transform still images into something worthy of sharing with your hipster friends (example taken from [FilterKit](https://github.com/eklipse2k8/FilterKit/blob/master/FilterKit/FilterKit/Filters/FKBlueValentine.m), which is built on GPUImage): -~~~{objective-c} +```objc GPUImageFilterGroup *filter = [[GPUImageFilterGroup alloc] init]; GPUImageSaturationFilter *saturationFilter = [[GPUImageSaturationFilter alloc] init]; @@ -275,11 +275,10 @@ GPUImageExposureFilter *exposureFilter = [[GPUImageExposureFilter alloc] init]; [filter addGPUFilter:monochromeFilter]; [filter addGPUFilter:saturationFilter]; [filter addGPUFilter:vignetteFilter]; -~~~ +``` --- Looking through all of what GPUImage can do, one can't help but get _excited_. Easy enough to get started immediately (without needing to know anything about OpenGL) yet performant enough to power whatever you dream up. And not just that, but it also comes with a dizzying number of building blocks—all of the color adjustments, blending modes, and visual effects you could ever want (or never knew you needed). GPUImage is a rare treat for the open source community, and we as Mac & iOS developers are lucky to have it at our disposal. Use it to make something great, and show others the world in a whole new way. - diff --git a/2013-05-13-nscoding.md b/2013-05-13-nscoding.md index 2fcf65e3..08a5e2fe 100644 --- a/2013-05-13-nscoding.md +++ b/2013-05-13-nscoding.md @@ -1,6 +1,6 @@ --- title: NSCoding / NSKeyedArchiver -author: Mattt Thompson +author: Mattt category: Cocoa excerpt: "Among the most important architectural decisions made when building an app is how to persist data between launches. The question of how, exactly, to re-create the state of the app from the time it was last opened; of how to describe the object graph in such a way that it can be flawlessly reconstructed next time." status: @@ -96,7 +96,7 @@ This article will look at the how's, when's, and why's of `NSKeyedArchiver` and For example: -~~~{swift} +```swift class Book: NSObject, NSCoding { var title: String var author: String @@ -138,9 +138,9 @@ class Book: NSObject, NSCoding { coder.encodeBool(self.available, forKey: "available") } } -~~~ +``` -~~~{objective-c} +```objc @interface Book : NSObject @property NSString *title; @property NSString *author; @@ -177,7 +177,7 @@ class Book: NSObject, NSCoding { } @end -~~~ +``` 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). @@ -200,23 +200,23 @@ An `NSCoding`-backed table view controller might, for instance, set its collecti #### Archiving -~~~{swift} +```swift NSKeyedArchiver.archiveRootObject(books, toFile: "/path/to/archive") -~~~ +``` -~~~{objective-c} +```objc [NSKeyedArchiver archiveRootObject:books toFile:@"/path/to/archive"]; -~~~ +``` #### Unarchiving -~~~{swift} +```swift guard let books = NSKeyedUnarchiver.unarchiveObjectWithFile("/path/to/archive") as? [Book] else { return nil } -~~~ +``` -~~~{objective-c} +```objc [NSKeyedUnarchiver unarchiveObjectWithFile:@"/path/to/archive"]; -~~~ +``` ## `NSUserDefaults` @@ -226,28 +226,28 @@ While it is not advisable to store an entire object graph into `NSUserDefaults`, #### Archiving -~~~{swift} +```swift let data = NSKeyedArchiver.archivedDataWithRootObject(books) NSUserDefaults.standardUserDefaults().setObject(data, forKey: "books") -~~~ +``` -~~~{objective-c} +```objc NSData *data = [NSKeyedArchiver archivedDataWithRootObject:books]; [[NSUserDefaults standardUserDefaults] setObject:data forKey:@"books"]; -~~~ +``` #### Unarchiving -~~~{swift} +```swift if let data = NSUserDefaults.standardUserDefaults().objectForKey("books") as? NSData { let books = NSKeyedUnarchiver.unarchiveObjectWithData(data) } -~~~ +``` -~~~{objective-c} +```objc NSData *data = [[NSUserDefaults standardUserDefaults] objectForKey:@"books"]; NSArray *books = [NSKeyedUnarchiver unarchiveObjectWithData:data]; -~~~ +``` --- @@ -258,6 +258,6 @@ The decision to use Core Data in an application may appear to be a no-brainer, i And even if most applications _would_ benefit from Core Data at some point, there is wisdom to letting complexity evolve from a simple as necessary. And as far as persistence goes, it doesn't get much simpler than `NSCoding`. -[1]: http://developer.apple.com/library/mac/#documentation/cocoa/Conceptual/CoreData/cdProgrammingGuide.html -[2]: http://developer.apple.com/library/ios/#Documentation/Cocoa/Reference/Foundation/Classes/NSKeyedArchiver_Class/Reference/Reference.html -[3]: http://developer.apple.com/library/ios/#documentation/Cocoa/Reference/Foundation/Classes/NSKeyedUnarchiver_Class/Reference/Reference.html +[1]: https://developer.apple.com/library/mac/#documentation/cocoa/Conceptual/CoreData/cdProgrammingGuide.html +[2]: https://developer.apple.com/library/ios/#Documentation/Cocoa/Reference/Foundation/Classes/NSKeyedArchiver_Class/Reference/Reference.html +[3]: https://developer.apple.com/library/ios/#documentation/Cocoa/Reference/Foundation/Classes/NSKeyedUnarchiver_Class/Reference/Reference.html diff --git a/2013-05-20-core-data-libraries-and-utilities.md b/2013-05-20-core-data-libraries-and-utilities.md index 79b3d61c..07d2124c 100644 --- a/2013-05-20-core-data-libraries-and-utilities.md +++ b/2013-05-20-core-data-libraries-and-utilities.md @@ -1,18 +1,19 @@ --- title: "Core Data Libraries & Utilities" -author: Mattt Thompson +author: Mattt category: Open Source excerpt: "We were a bit hard on Core Data last week, so for this issue of NSHipster, we bring you a guided tour of the best open source libraries for working with Core Data. Read on to see how you might make the most from your Core Data experience." +retired: true status: - swift: n/a - reviewed: August 12, 2015 + swift: n/a + reviewed: August 12, 2015 --- -So let's say that, having determined your particular needs and compared all of the alternatives, you've chosen [Core Data](http://developer.apple.com/library/mac/#documentation/cocoa/Conceptual/CoreData/cdProgrammingGuide.html) for your next app. +So let's say that, having determined your particular needs and compared all of the alternatives, you've chosen [Core Data](https://developer.apple.com/library/mac/#documentation/cocoa/Conceptual/CoreData/cdProgrammingGuide.html) for your next app. Nothing wrong with that! Core Data is a great choice for apps that model, persist, and query large object graphs. -Sure it's complicated, cumbersome, and yes, at times, a real [pain in the ass](http://nshipster.com/nscoding#figure-2)—but gosh darn it, some of the best and most popular apps ever built use Core Data. And if it's good enough for them, it's probably good enough for you, too. +Sure it's complicated, cumbersome, and yes, at times, a real [pain in the ass](https://nshipster.com/nscoding#figure-2)—but gosh darn it, some of the best and most popular apps ever built use Core Data. And if it's good enough for them, it's probably good enough for you, too. ...but that's not to say that Core Data can't be improved. @@ -34,27 +35,22 @@ This week on NSHipster: a guided tour of the best open source libraries for work Magical Record Saul Mora - Objective-Record Marin Usalj - SSDataKit Sam Soffes - ios-queryable Marty Dill - ReactiveCoreData Jacob Gorban - @@ -67,32 +63,22 @@ This week on NSHipster: a guided tour of the best open source libraries for work RestKit Blake Watters - - - - AFIncrementalStore - Mattt Thompson - MMRecord Conrad Stoll - SLRESTfulCoreData Oliver Letterer - Overcoat Guillermo Gonzalez - Mantle Mantle - @@ -105,16 +91,13 @@ This week on NSHipster: a guided tour of the best open source libraries for work TICoreDataSync Tim Isted, Michael Fey, Kevin Hoctor, Christian Beer, Tony Arnold, and Danny Greg - UbiquityStoreManager Maarten Billemont - - Utilities @@ -124,7 +107,6 @@ This week on NSHipster: a guided tour of the best open source libraries for work mogenerator Jonathan 'Wolf' Rentzsch - @@ -145,7 +127,7 @@ There are a number of open source libraries that collectively identify and corre It should be no surprise that programmers, having learned how to do things a certain way, will bring those ideas and conventions to other technologies. For the large influx of Ruby developers coming over to iOS, that familiar paradigm was [Active Record](http://api.rubyonrails.org/classes/ActiveRecord/Base.html). -Contrary to popular belief, Core Data is _not_ an [Object-Relational Mapper](http://en.wikipedia.org/wiki/Object-relational_mapping), but rather an object graph and persistence framework, capable of much more than the [Active Record pattern](http://en.wikipedia.org/wiki/Active_record_pattern) alone is capable of. Using Core Data as an ORM necessarily limits the capabilities of Core Data and muddies its conceptual purity. But for many developers longing for the familiarity of an ORM, this trade-off is a deal at twice the price! +Contrary to popular belief, Core Data is _not_ an [Object-Relational Mapper](https://en.wikipedia.org/wiki/Object-relational_mapping), but rather an object graph and persistence framework, capable of much more than the [Active Record pattern](https://en.wikipedia.org/wiki/Active_record_pattern) alone is capable of. Using Core Data as an ORM necessarily limits the capabilities of Core Data and muddies its conceptual purity. But for many developers longing for the familiarity of an ORM, this trade-off is a deal at twice the price! #### [Magical Record](https://github.com/magicalpanda/MagicalRecord) @@ -158,17 +140,17 @@ Contrary to popular belief, Core Data is _not_ an [Object-Relational Mapper](htt > And yeah, no AppDelegate code. > It's fully tested with [Kiwi](https://github.com/kiwi-bdd/Kiwi). -### Inspired by [LINQ](http://en.wikipedia.org/wiki/Language_Integrated_Query) +### Inspired by [LINQ](https://en.wikipedia.org/wiki/Language_Integrated_Query) -Here's a fun game: the next time you meet a developer coming over from the .NET world, set a timer to see how long it takes them to start raving about [LINQ](http://en.wikipedia.org/wiki/Language_Integrated_Query). Seriously, people _love_ LINQ. +Here's a fun game: the next time you meet a developer coming over from the .NET world, set a timer to see how long it takes them to start raving about [LINQ](https://en.wikipedia.org/wiki/Language_Integrated_Query). Seriously, people _love_ LINQ. -For the uninitiated, LINQ is like [SQL](http://en.wikipedia.org/wiki/SQL), but integrated as a language feature. Think `NSPredicate`, [`NSSortDescriptor`](http://nshipster.com/nssortdescriptor/), and [`Key-Value Coding`](http://nshipster.com/kvc-collection-operators/) with a much nicer syntax: +For the uninitiated, LINQ is like [SQL](https://en.wikipedia.org/wiki/SQL), but integrated as a language feature. Think `NSPredicate`, [`NSSortDescriptor`](https://nshipster.com/nssortdescriptor/), and [`Key-Value Coding`](https://nshipster.com/kvc-collection-operators/) with a much nicer syntax: -~~~ +``` from c in SomeCollection where c.SomeProperty < 10 select new {c.SomeProperty, c.OtherProperty}; -~~~ +``` #### [ios-queryable](https://github.com/martydill/ios-queryable) @@ -176,7 +158,7 @@ from c in SomeCollection ### Inspired by [ReactiveCocoa](https://github.com/ReactiveCocoa) -ReactiveCocoa, which itself [brings the functional reactive paradigm to Objective-C](http://nshipster.com/reactivecocoa/), is now being used to bring some functional sanity and order to Core Data. This is still uncharted territory, but the initial results are indeed promising. +ReactiveCocoa, which itself [brings the functional reactive paradigm to Objective-C](https://nshipster.com/reactivecocoa/), is now being used to bring some functional sanity and order to Core Data. This is still uncharted territory, but the initial results are indeed promising. #### [ReactiveCoreData](https://github.com/apparentsoft/ReactiveCoreData) @@ -192,7 +174,7 @@ Fortunately, there are a wealth of open-source libraries that can help alleviate #### [RestKit](https://github.com/RestKit/RestKit) -> RestKit is a modern Objective-C framework for implementing RESTful web services clients on iOS and OS X. It provides a powerful [object mapping](https://github.com/RestKit/RestKit/wiki/Object-mapping) engine that seamlessly integrates with [Core Data](http://developer.apple.com/library/mac/#documentation/cocoa/Conceptual/CoreData/cdProgrammingGuide.html) and a simple set of networking primitives for mapping HTTP requests and responses built on top of [AFNetworking](https://github.com/AFNetworking/AFNetworking). It has an elegant, carefully designed set of APIs that make accessing and modeling RESTful resources feel almost magical. +> RestKit is a modern Objective-C framework for implementing RESTful web services clients on iOS and OS X. It provides a powerful [object mapping](https://github.com/RestKit/RestKit/wiki/Object-mapping) engine that seamlessly integrates with [Core Data](https://developer.apple.com/library/mac/#documentation/cocoa/Conceptual/CoreData/cdProgrammingGuide.html) and a simple set of networking primitives for mapping HTTP requests and responses built on top of [AFNetworking](https://github.com/AFNetworking/AFNetworking). It has an elegant, carefully designed set of APIs that make accessing and modeling RESTful resources feel almost magical. #### [AFIncrementalStore](https://github.com/AFNetworking/AFIncrementalStore) @@ -232,11 +214,10 @@ We would be remiss to survey the open source Core Data ecosystem without mention #### [Mogenerator](https://github.com/rentzsch/mogenerator) -> `mogenerator` is a command-line tool that, given an `.xcdatamodel` file, will generate *two classes per entity*. The first class, `_MyEntity`, is intended solely for machine consumption and will be continuously overwritten to stay in sync with your data model. The second class, `MyEntity`, subclasses `_MyEntity`, won't ever be overwritten and is a great place to put your custom logic. +> `mogenerator` is a command-line tool that, given an `.xcdatamodel` file, will generate _two classes per entity_. The first class, `_MyEntity`, is intended solely for machine consumption and will be continuously overwritten to stay in sync with your data model. The second class, `MyEntity`, subclasses `_MyEntity`, won't ever be overwritten and is a great place to put your custom logic. --- Remember: there is no silver bullet. There is no one-size-fits-all solution. Just as Core Data may only be advisable in particular circumstances, so too are the aforementioned Core Data libraries. Dividing the ecosystem up into broad categories is informative if only to help identify the relative strengths and trade-offs of each library. Only you can determine (yes, sometimes through trial and error) which solution is the best for you. - diff --git a/2013-05-27-unit-testing.md b/2013-05-27-unit-testing.md index 606d6dfe..ab9fc26a 100644 --- a/2013-05-27-unit-testing.md +++ b/2013-05-27-unit-testing.md @@ -1,6 +1,6 @@ --- title: Unit Testing -author: Mattt Thompson +author: Mattt category: Objective-C excerpt: "Unit Testing is an emotional topic for developers. It inspires a sense of superiority to its most zealous adherents, and evokes a feeling of inadequacy to non-practitioners. Cargo Cults like TDD stake their reputation on unit testing to the point of co-opting and conflating utility with morality." status: @@ -29,7 +29,7 @@ It's a simple enough premise: write code to construct environments that exercise Unit Tests were added into a separate testing target in the Xcode Project. Each test file defines an `SenTestCase` subclass, which implements a series of methods beginning with the word `test`. C `assert`-style macros are used to fail tests if the specified condition is not met. Each test is run in sequence, independently of one another, with the results logged afterwards: -~~~{objective-c} +```objc #import #import "Person.h" @@ -43,7 +43,7 @@ Unit Tests were added into a separate testing target in the Xcode Project. Each person.lastName = @"Picasso"; STAssertEqualObjects([person fullName], @"Pablo Picasso", nil); } -~~~ +``` The SenTestingKit assertions are about what you'd expect, offering bread-and-butter equality, existence, and truth checks: @@ -70,7 +70,7 @@ The only chance testing has to remain relevant in high-pressure situations is to ## Open Source Libraries -There are a myriad of open source libraries that attempt to make testing more palatable by way of syntactic sugar and features like [method stubs](https://en.wikipedia.org/wiki/Method_stub), [mock objects](https://en.wikipedia.org/wiki/Mock_object), and [promises](http://en.wikipedia.org/wiki/Futures_and_promises). +There are a myriad of open source libraries that attempt to make testing more palatable by way of syntactic sugar and features like [method stubs](https://en.wikipedia.org/wiki/Method_stub), [mock objects](https://en.wikipedia.org/wiki/Mock_object), and [promises](https://en.wikipedia.org/wiki/Futures_and_promises). Here's a list of some of the most useful open source libraries for unit testing: @@ -82,12 +82,10 @@ Here's a list of some of the most useful open source libraries for unit testing: OCMock Erik Doernenburg - OCMockito Jon Reid - @@ -98,12 +96,10 @@ Here's a list of some of the most useful open source libraries for unit testing: Expecta Peter Jihoon Kim - OCHamcrest Jon Reid - @@ -114,17 +110,14 @@ Here's a list of some of the most useful open source libraries for unit testing: Specta Peter Jihoon Kim - Kiwi Allen Ding - Cedar Pivotal Labs - @@ -135,7 +128,6 @@ Here's a list of some of the most useful open source libraries for unit testing: GHUnit Gabriel Handford - @@ -144,7 +136,6 @@ Here's a list of some of the most useful open source libraries for unit testing: Making tests easier to write is one thing, but getting them to run without affecting productivity is quite another. - ### Jenkins For a long time, installing [Jenkins](http://jenkins-ci.org) on a dedicated Mac Mini was the state-of-the-art for automated build servers. @@ -165,7 +156,7 @@ All of the configuration for setup is defined in `.travis.yml`: #### .travis.yml -~~~ +``` language: objective-c before_install: - brew update @@ -173,7 +164,7 @@ before_install: - cd Tests && pod install && cd $TRAVIS_BUILD_DIR - mkdir -p "Tests/AFNetworking Tests.xcodeproj/xcshareddata/xcschemes" && cp Tests/Schemes/*.xcscheme "Tests/AFNetworking Tests.xcodeproj/xcshareddata/xcschemes/" script: rake test -~~~ +``` Full documentation for the Travis configuration file [can be found on Travis-CI.org](http://about.travis-ci.org/docs/user/build-configuration/). diff --git a/2013-06-02-nsdatadetector.md b/2013-06-02-nsdatadetector.md index b9bb87ab..e5e87e8a 100644 --- a/2013-06-02-nsdatadetector.md +++ b/2013-06-02-nsdatadetector.md @@ -1,61 +1,140 @@ --- title: NSDataDetector -author: Mattt Thompson +author: Mattt category: Cocoa tags: nshipster -excerpt: "Until humanity embraces RDF for all of their daily interactions, a large chunk of artificial intelligence is going to go into figuring out what the heck we're all talking about. Fortunately for Cocoa developers, there's NSDataDetector." +excerpt: >- + Until humanity embraces RDF for our daily interactions, + computers will have to work overtime + to figure out what the heck we're all talking about. +revisions: + "2013-06-02": Original publication + "2018-08-29": Updated for Swift 4.2 status: - swift: 2.0 - reviewed: September 8, 2015 + swift: 4.2 + reviewed: August 29, 2018 --- -Machines speak in binary, while humans speak in riddles, half-truths, and omissions. +Text means nothing without context. -And until humanity embraces [RDF](http://en.wikipedia.org/wiki/Resource_Description_Framework) for all of their daily interactions, a large chunk of artificial intelligence is going to go into figuring out what the heck we're all talking about. +What gives weight to our words +is their relation to one another, +to ourselves, +and to our location space-time. -Because in the basic interactions of our daily lives—meeting people, making plans, finding information online—there is immense value in automatically converting from implicit human language to explicit structured data, so that it can be easily added to our calendars, address books, maps, and reminders. +Consider +endophoric expressions +whose meaning depends on the surrounding text, +or deictic expressions, +whose meaning is dependent on who the speaker is, +where they are, and when they said it. +Now consider how difficult it would be +for a computer to make sense of an utterance like +_"I'll be home in 5 minutes"_? +(And that's to say nothing of the challenges of +ambiguity and variation +in representations of dates, addresses, and other information.) -Fortunately for Cocoa developers, there's an easy solution: `NSDataDetector`. +For better or worse, +that's how we communicate. +And until humanity embraces +[RDF](https://www.w3.org/RDF/) +for our daily interactions, +computers will have to work overtime +to figure out what the heck we're all talking about. --- -`NSDataDetector` is a subclass of [`NSRegularExpression`](https://developer.apple.com/library/mac/#documentation/Foundation/Reference/NSRegularExpression_Class/Reference/Reference.html), but instead of matching on an ICU pattern, it detects semi-structured information: dates, addresses, links, phone numbers and transit information. +There's immense value in transforming natural language +into structured data that's compatible with our +calendars, address books, maps, and reminders. +Manual data entry, however, amounts to drudgery, +and is the last thing you want to force on users. -It does all of this with frightening accuracy. `NSDataDetector` will match flight numbers, address snippets, oddly formatted digits, and even relative deictic expressions like "next Saturday at 5". +On other platforms, +you might delegate this task to a web service +or hack something together that works well enough. +Fortunately for us Cocoa developers, +Foundation us covered with `NSDataDetector`. -You can think of it as a regexp matcher with incredibly complicated expressions that can extract information from natural language (though its actual implementation details may be somewhat more complicated than that). +You can use `NSDataDetector` to extract +dates, links, phone numbers, addresses, and transit information +from natural language text. -`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. +First, create a detector, +by specifying the result types that you're interested in. +Then call the `enumerateMatches(in:options:range:using:)` method, +passing the text to be processed. +The provided closure is executed once for each result. -~~~{swift} -let string = "123 Main St. / (555) 555-5555" -let types: NSTextCheckingType = [.Address, .PhoneNumber] -let detector = try? NSDataDetector(types: types.rawValue) -detector?.enumerateMatchesInString(string, options: [], range: NSMakeRange(0, (string as NSString).length)) { (result, flags, _) in +```swift +let string = "123 Main St. / (555) 555-1234" + +let types: NSTextCheckingResult.CheckingType = [.phoneNumber, .address] +let detector = try NSDataDetector(types: types.rawValue) +detector.enumerateMatches(in: string, + options: [], + range: range) { (result, _, _) in print(result) } -~~~ -~~~{objective-c} +``` + +```objc +NSString *string = @"123 Main St. / (555) 555-1234"; + NSError *error = nil; -NSDataDetector *detector = [NSDataDetector dataDetectorWithTypes:NSTextCheckingTypeAddress - | NSTextCheckingTypePhoneNumber - error:&error]; +NSDataDetector *detector = + [NSDataDetector dataDetectorWithTypes:NSTextCheckingTypeAddress | + NSTextCheckingTypePhoneNumber + error:&error]; -NSString *string = @"123 Main St. / (555) 555-5555"; [detector enumerateMatchesInString:string options:kNilOptions range:NSMakeRange(0, [string length]) usingBlock: ^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) { - NSLog(@"Match: %@", result); + NSLog(@"%@", result); }]; -~~~ +``` + +As you might expect, +running this code produces two results: +the address "123 Main St." +and the phone number "(555) 555-1234". + +> When initializing `NSDataDetector`, +> specify only the types you're interested in +> because any unused types will only slow you down. + +## Discerning Information from Results + +`NSDataDetector` produces `NSTextCheckingResult` objects. + +On the one hand, +this makes sense +because `NSDataDetector` is actually a subclass of `NSRegularExpression`. +On the other hand, +there's not much overlap between a pattern match and detected data +other than the range and type. +So what you get is an API that's polluted +and offers no strong guarantees about what information is present +under which circumstances. -> 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. +> To make matters worse, +> `NSTextCheckingResult` is also used by `NSSpellServer`. +> _Gross._ -## Data Detector Match Types +To get information about data detector results, +you need to first check its `resultType`; +depending on that, +you might access information directly through properties, +(in the case of links, phone numbers, and dates), +or indirectly by keyed values on the `components` property +(for addresses and transit information). -Because of how much `NSTextCheckingResult` is used for, it's not immediately clear which properties are specific to `NSDataDetector`. For your reference, here is a table of the different `NSTextCheckingTypes` for `NSDataDetector` matches, and their associated properties: +Here's a rundown of the various +`NSDataDetector` result types +and their associated properties: @@ -65,125 +144,202 @@ Because of how much `NSTextCheckingResult` is used for, it's not immediately cle - - + - + - + - + - + - - -
NSTextCheckingTypeDate.link
    -
  • date
  • -
  • duration
  • -
  • timeZone
  • +
  • .url
NSTextCheckingTypeAddress.phoneNumber
    -
  • addressComponents*
  • -
      -
    • NSTextCheckingNameKey
    • -
    • NSTextCheckingJobTitleKey
    • -
    • NSTextCheckingOrganizationKey
    • -
    • NSTextCheckingStreetKey
    • -
    • NSTextCheckingCityKey
    • -
    • NSTextCheckingStateKey
    • -
    • NSTextCheckingZIPKey
    • -
    • NSTextCheckingCountryKey
    • -
    • NSTextCheckingPhoneKey
    • -
    +
  • .phoneNumber
NSTextCheckingTypeLink.date
    -
  • url
  • +
  • .date
  • +
  • .duration
  • +
  • .timeZone
NSTextCheckingTypePhoneNumber.address
    -
  • phoneNumber
  • +
  • .components
  • +
      +
    • .name
    • +
    • .jobTitle
    • +
    • .organization
    • +
    • .street
    • +
    • .city
    • +
    • .state
    • +
    • .zip
    • +
    • .country
    • +
    • .phone
    • +
NSTextCheckingTypeTransitInformation.transitInformation
    -
  • components*
  • +
  • .components
    • -
    • NSTextCheckingAirlineKey
    • -
    • NSTextCheckingFlightKey
    • +
    • .airline
    • +
    • .flight
* NSDictionary properties have values at defined keys. -
-## Data Detection on iOS +## Data Detector Data Points -Somewhat confusingly, iOS also defines `UIDataDetectorTypes`. A bitmask of these values can be set as the `dataDetectorTypes` of a `UITextView` to have detected data automatically linked in the displayed text. +Let's put `NSDataDetector` through its paces. +That way, we'll not only have a complete example of how to use it +to its full capacity +but see what it's actually capable of. -`UIDataDetectorTypes` is distinct from `NSTextCheckingTypes` in that equivalent enum constants (e.g. `UIDataDetectorTypePhoneNumber` and `NSTextCheckingTypePhoneNumber`) do not have the same integer value, and not all values in one are found in the other. Converting from `UIDataDetectorTypes` to `NSTextCheckingTypes` can be accomplished with a function: +The following text contains one of each of the type of data +that `NSDataDetector` should be able to detect: -~~~{swift} -func NSTextCheckingTypesFromUIDataDetectorTypes(dataDetectorType: UIDataDetectorTypes) -> NSTextCheckingType { - var textCheckingType: NSTextCheckingType = [] - - if dataDetectorType.contains(.Address) { - textCheckingType.insert(.Address) - } - - if dataDetectorType.contains(.CalendarEvent) { - textCheckingType.insert(.Date) - } - - if dataDetectorType.contains(.Link) { - textCheckingType.insert(.Link) +```swift +let string = """ + My flight (AA10) is scheduled for tomorrow night from 9 PM PST to 5 AM EST. + I'll be staying at The Plaza Hotel, 768 5th Ave, New York, NY 10019. + You can reach me at 555-555-1234 or me@example.com +""" +``` + +We can have `NSDataDetector` check for everything +by passing `NSTextCheckingAllTypes` to its initializer. +The rest is a matter of switching over each `resultType` +and extracting their respective details: + +```swift +let detector = try NSDataDetector(types: NSTextCheckingAllTypes) +let range = NSRange(string.startIndex.. Even after trying a handful of different representations +> ("American Airlines 10", "AA 10", "AA #10", "American Airlines (AA) #10") +> and airlines +> ("Delta 1226", "DL 1226") +> I still wasn't able to find an example that populated the `airline` property. +> If anyone knows what's up, [@ us](https://twitter.com/NSHipster/). + +## Detect (Rough) Edges + +Useful as `NSDataDetector` is, +it's not a particularly _nice_ API to use. + +With all of the charms of its parent class, +[`NSRegularExpression`](https://nshipster.com/nsregularexpression/), +the same, cumbersome initialization pattern of +[NSLinguisticTagger](https://nshipster.com/nslinguistictagger/), +and an +[incomplete Swift interface](https://developer.apple.com/documentation/foundation/nstextcheckingtypes), +`NSDataDetector` has an interface that only a mother could love. + +But that's only the API itself. + +In a broader context, +you might be surprised to learn that a nearly identical API can be found +in the `dataDetectorTypes` properties of `UITextView` and `WKWebView`. +_Nearly_ identical. + +`UIDataDetectorTypes` and `WKDataDetectorTypes` are distinct from +and incompatible with `NSTextCheckingTypes`, +which is inconvenient but not super conspicuous. +But what's utterly inexplicable is that these APIs +can detect [shipment tracking numbers](https://developer.apple.com/documentation/uikit/uidatadetectortypes/1648142-shipmenttrackingnumber) +and [lookup suggestions](https://developer.apple.com/documentation/uikit/uidatadetectortypes/1648141-lookupsuggestion), +neither of which are supported by `NSDataDetector`. +It's hard to imagine why shipment tracking numbers wouldn't be supported, +which leads one to believe that it's an oversight. --- -Do I detect some disbelief of how easy it is to translate between natural language and structured data? This should not be surprising, given how [insanely](http://nshipster.com/cfstringtransform/) [great](http://nshipster.com/nslinguistictagger/) Cocoa's linguistic APIs are. +Humans have an innate ability to derive meaning from language. +We can stitch together linguistic, situational and cultural information +into a coherent interpretation at a subconscious level. +Ironically, it's difficult to put this process into words --- +or code as the case may be. +Computers aren't hard-wired for understanding like we are. -Don't make your users re-enter information by hand just because of a programming oversight. Take advantage of `NSDataDetector` in your app to unlock the structured information already hiding in plain sight. +Despite its shortcomings, +`NSDataDetector` can prove invaluable for certain use cases. +Until something better comes along, +take advantage of it in your app +to unlock the structured information hiding in plain sight. diff --git a/2013-06-12-nshipster-quiz-2.md b/2013-06-12-nshipster-quiz-2.md index ea986289..94c3efc5 100644 --- a/2013-06-12-nshipster-quiz-2.md +++ b/2013-06-12-nshipster-quiz-2.md @@ -1,13 +1,13 @@ --- title: "NSHipster Quiz #2" -author: Mattt Thompson +author: Mattt category: Trivia excerpt: "Go up against some of the brightest minds in all things Apple at this special WWDC edition of NSHipster Quiz. Sure, you conform to the NSHipster protocol, but do you have what it takes to implement all of the @optional methods? Take the quiz and see for yourself!" status: swift: n/a --- -On June 11th, we organized an NSHipster Pub Quiz for WWDC attendees. Like [our first quiz](http://nshipster.com/nshipster-quiz-1/), questions ranged from random Apple trivia to obscure framework questions. The event was hosted by [New Relic](http://newrelic.com), and sponsored by [Heroku](https://heroku.com) & [Mutual Mobile](http://www.mutualmobile.com). About 100 developers attended the event, with the team "UIResponders" taking top prize. +On June 11th, we organized an NSHipster Pub Quiz for WWDC attendees. Like [our first quiz](https://nshipster.com/nshipster-quiz-1/), questions ranged from random Apple trivia to obscure framework questions. The event was hosted by [New Relic](http://newrelic.com), and sponsored by [Heroku](https://heroku.com) & [Mutual Mobile](http://www.mutualmobile.com). About 100 developers attended the event, with the team "UIResponders" taking top prize. For everyone that couldn't make it to the event, here's an opportunity to play along at home. Here are some ground rules: @@ -62,43 +62,43 @@ Round 3: Picture Round - 1. Which WWDC keynote was this from? -![Question 1]({{ site.asseturl }}/quiz-2/question-1.jpg) +![Question 1]({% asset quiz-2/question-1.jpg @path %}) - 2. Which WWDC keynote was this from? -![Question 2]({{ site.asseturl }}/quiz-2/question-2.jpg) +![Question 2]({% asset quiz-2/question-2.jpg @path %}) - 3. Which WWDC keynote was this from? -![Question 3]({{ site.asseturl }}/quiz-2/question-3.jpg) +![Question 3]({% asset quiz-2/question-3.jpg @path %}) - 4. Which WWDC keynote was this from? -![Question 4]({{ site.asseturl }}/quiz-2/question-4.jpg) +![Question 4]({% asset quiz-2/question-4.jpg @path %}) - 5. WTF is this? -![Question 5]({{ site.asseturl }}/quiz-2/question-5.jpg) +![Question 5]({% asset quiz-2/question-5.jpg @path %}) - 6. What is this? -![Question 6]({{ site.asseturl }}/quiz-2/question-6.jpg) +![Question 6]({% asset quiz-2/question-6.jpg @path %}) - 7. What is this? -![Question 7]({{ site.asseturl }}/quiz-2/question-7.jpg) +![Question 7]({% asset quiz-2/question-7.jpg @path %}) - 8. What is this? (and which generation?) -![Question 8]({{ site.asseturl }}/quiz-2/question-8.jpg) +![Question 8]({% asset quiz-2/question-8.jpg @path %}) - 9. Which "Core" framework is represented by this logo? -![Question 9]({{ site.asseturl }}/quiz-2/question-9.jpg) +![Question 9]({% asset quiz-2/question-9.jpg @path %}) - 10. Everybody loves Craig _/fɛdɹ̩igi/_ (Pictured). How do you spell his last name? -![Question 10]({{ site.asseturl }}/quiz-2/question-10.jpg) +![Question 10]({% asset quiz-2/question-10.jpg @path %}) Round 4: Name That Framework! ----------------------------- @@ -123,13 +123,13 @@ For each question, a list of three classes from the same framework have been lis Round 1: General Knowledge -------------------------- -1. [A white-haired German Shepherd named Maverick](http://en.wikipedia.org/wiki/Mavericks_(location)#History) (anything about a dog gets the point) -2. [`^@`](http://nshipster.com/type-encodings/) +1. [A white-haired German Shepherd named Maverick](https://en.wikipedia.org/wiki/Mavericks_(location)#History) (anything about a dog gets the point) +2. [`^@`](https://nshipster.com/type-encodings/) 3. [`genstrings`](https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man1/genstrings.1.html) 4. `NSArray -firstObject` 5. [ICU (International Components for Unicode)](http://site.icu-project.org) -6. [ Tysons Corner, Virginia & Glendale, California](http://en.wikipedia.org/wiki/Apple_Store#History) -7. [BackRow](http://nshipster.com/backrow/) +6. [ Tysons Corner, Virginia & Glendale, California](https://en.wikipedia.org/wiki/Apple_Store#History) +7. [BackRow](https://nshipster.com/backrow/) 8. [Phil Schiller](http://www.theverge.com/2013/6/13/4423844/cant-innovate-anymore-my-ass-apple) 9. Pacific Heights, Mission, Nob Hill, Russian Hill, Marina, Presidio 10. ["So let's raise the bar / And our cups to the stars"](http://rock.rapgenius.com/Daft-punk-get-lucky-lyrics) @@ -138,12 +138,12 @@ Round 1: General Knowledge Round 2: Before & After ----------------------- -1. [Cocoa](http://en.wikipedia.org/wiki/Cocoa_%28API%29) -2. [Sherlock](http://en.wikipedia.org/wiki/Sherlock_%28Software%29) -3. [Mac OS X 10.2](http://en.wikipedia.org/wiki/Mac_OS_X_v10.2) -4. [Bonjour](http://en.wikipedia.org/wiki/Bonjour_%28Software%29) -5. [iTunes](http://en.wikipedia.org/wiki/Itunes#History) -6. [System 6 / Mac OS 6](http://en.wikipedia.org/wiki/System_6) +1. [Cocoa](https://en.wikipedia.org/wiki/Cocoa_%28API%29) +2. [Sherlock](https://en.wikipedia.org/wiki/Sherlock_%28Software%29) +3. [Mac OS X 10.2](https://en.wikipedia.org/wiki/Mac_OS_X_v10.2) +4. [Bonjour](https://en.wikipedia.org/wiki/Bonjour_%28Software%29) +5. [iTunes](https://en.wikipedia.org/wiki/Itunes#History) +6. [System 6 / Mac OS 6](https://en.wikipedia.org/wiki/System_6) 7. [Accessibility](http://www.apple.com/accessibility/) 8. [Pixar](https://en.wikipedia.org/wiki/Pixar) 9. [1 Infinite Loop](https://en.wikipedia.org/wiki/1_infinite_loop) @@ -156,25 +156,25 @@ Round 3: Picture Round 2. 2009 3. 2012 4. 2008 -5. [eMate 300](http://en.wikipedia.org/wiki/EMate_300) -6. [Xserve RAID](http://en.wikipedia.org/wiki/Xserve_RAID) -7. [iSight](http://en.wikipedia.org/wiki/ISight) -8. [3rd gen. iPod Shuffle](http://en.wikipedia.org/wiki/Ipod_shuffle#Third_generation) -9. [Core Audio](http://en.wikipedia.org/wiki/Core_Audio) -10. ["Federighi"](http://en.wikipedia.org/wiki/Craig_Federighi) +5. [eMate 300](https://en.wikipedia.org/wiki/EMate_300) +6. [Xserve RAID](https://en.wikipedia.org/wiki/Xserve_RAID) +7. [iSight](https://en.wikipedia.org/wiki/ISight) +8. [3rd gen. iPod Shuffle](https://en.wikipedia.org/wiki/Ipod_shuffle#Third_generation) +9. [Core Audio](https://en.wikipedia.org/wiki/Core_Audio) +10. ["Federighi"](https://en.wikipedia.org/wiki/Craig_Federighi) Round 4: Name That Framework! ----------------------------- -1. [Core Telephony](http://developer.apple.com/library/ios/#documentation/NetworkingInternet/Reference/CoreTelephonyFrameworkReference/_index.html) -2. [Core Motion](http://developer.apple.com/library/ios/#documentation/CoreMotion/Reference/CoreMotion_Reference/_index.html) -3. [Ad Support](http://developer.apple.com/library/ios/#documentation/DeviceInformation/Reference/AdSupport_Framework/_index.html) -4. [Social](http://developer.apple.com/library/ios/#documentation/Social/Reference/Social_Framework/_index.html%23//apple_ref/doc/uid/TP40012233) +1. [Core Telephony](https://developer.apple.com/library/ios/#documentation/NetworkingInternet/Reference/CoreTelephonyFrameworkReference/_index.html) +2. [Core Motion](https://developer.apple.com/library/ios/#documentation/CoreMotion/Reference/CoreMotion_Reference/_index.html) +3. [Ad Support](https://developer.apple.com/library/ios/#documentation/DeviceInformation/Reference/AdSupport_Framework/_index.html) +4. [Social](https://developer.apple.com/library/ios/#documentation/Social/Reference/Social_Framework/_index.html%23//apple_ref/doc/uid/TP40012233) 5. [Foundation](https://developer.apple.com/library/mac/#documentation/cocoa/reference/foundation/ObjC_classic/_index.html) -6. [EventKit](http://developer.apple.com/library/ios/#documentation/EventKit/Reference/EventKitFrameworkRef/_index.html) -7. [Game Kit](http://developer.apple.com/library/ios/#documentation/GameKit/Reference/GameKit_Collection/_index.html) +6. [EventKit](https://developer.apple.com/library/ios/#documentation/EventKit/Reference/EventKitFrameworkRef/_index.html) +7. [Game Kit](https://developer.apple.com/library/ios/#documentation/GameKit/Reference/GameKit_Collection/_index.html) 8. [Foundation](https://developer.apple.com/library/mac/#documentation/cocoa/reference/foundation/ObjC_classic/_index.html) -9. [Core Data](http://developer.apple.com/library/ios/#documentation/Cocoa/Reference/CoreData_ObjC/_index.html) +9. [Core Data](https://developer.apple.com/library/ios/#documentation/Cocoa/Reference/CoreData_ObjC/_index.html) 10. [Foundation](https://developer.apple.com/library/mac/#documentation/cocoa/reference/foundation/ObjC_classic/_index.html) or [Core Foundation](https://developer.apple.com/library/mac/#documentation/CoreFoundation/Reference/CoreFoundation_Collection/_index.html) (2 points if you got both) * * * diff --git a/2013-06-17-object-subscripting.md b/2013-06-17-object-subscripting.md index 82ca3a2b..0417ddfd 100644 --- a/2013-06-17-object-subscripting.md +++ b/2013-06-17-object-subscripting.md @@ -1,6 +1,6 @@ --- title: Object Subscripting -author: Mattt Thompson +author: Mattt category: Objective-C excerpt: "Xcode 4.4 quietly introduced a syntactic revolution to Objective-C. Like all revolutions, however, its origins and agitators require some effort to trace." status: @@ -19,17 +19,17 @@ Clang 3.1 added three features to Objective-C whose aesthetic & cosmetic impact In a single Xcode release, Objective-C went from this: -~~~{objective-c} +```objc NSDictionary *dictionary = [NSDictionary dictionaryWithObject:[NSNumber numberWithInteger:42] forKey:@"foo"]; id value = [dictionary objectForKey:@"foo"]; -~~~ +``` ...to this: -~~~{objective-c} +```objc NSDictionary *dictionary = @{@"foo": @42}; id value = dictionary[@"foo"]; -~~~ +``` Concision is the essence of clarity. @@ -49,10 +49,10 @@ Over time, scripting languages began to take greater liberties with this familia With Clang 3.1, everything has come full-circle: what began as a C operator and co-opted by scripting languages, has now been rolled back into Objective-C. And like the aforementioned scripting languages of yore, the `[]` subscripting operator in Objective-C has been similarly overloaded to handle both integer-indexed and object-keyed accessors. -~~~{objective-c} +```objc dictionary[@"foo"] = @42; array[0] = @"bar" -~~~ +``` > If Objective-C is a superset of C, how can Object Subscripting overload the `[]` C operator? The modern Objective-C runtime prohibits pointer arithmetic on objects, making this semantic pivot possible. @@ -62,10 +62,10 @@ Where this really becomes interesting is when you extend your own classes with s To add custom-indexed subscripting support to your class, simply declare and implement the following methods: -~~~{objective-c} +```objc - (id)objectAtIndexedSubscript:(*IndexType*)idx; - (void)setObject:(id)obj atIndexedSubscript:(*IndexType*)idx; -~~~ +``` `*IndexType*` can be any integral type, such as `char`, `int`, or `NSUInteger`, as used by `NSArray`. @@ -73,10 +73,10 @@ To add custom-indexed subscripting support to your class, simply declare and imp Similarly, custom-keyed subscripting can be added to your class by declaring and implementing these methods: -~~~{objective-c} +```objc - (id)objectForKeyedSubscript:(*KeyType*)key; - (void)setObject:(id)obj forKeyedSubscript:(*KeyType*)key; -~~~ +``` `*KeyType*` can be any Objective-C object pointer type. @@ -86,25 +86,25 @@ Similarly, custom-keyed subscripting can be added to your class by declaring and The whole point in describing all of this is to encourage unconventional thinking about this whole language feature. At the moment, a majority of custom subscripting in classes is used as a convenience accessor to a private collection class. But there's nothing to stop you from, for instance, doing this: -~~~{objective-c} +```objc routes[@"GET /users/:id"] = ^(NSNumber *userID){ - // ... + <#...#> } -~~~ +``` ...or this: -~~~{objective-c} +```objc id piece = chessBoard[@"E1"]; -~~~ +``` ...or this: -~~~{objective-c} +```objc NSArray *results = managedObjectContext[@"Product WHERE stock > 20"]; -~~~ +``` -Because of how flexible and concise subscripting is, it is extremely well-purposed for creating [DSL](http://en.wikipedia.org/wiki/Domain-specific_language)s. When defining custom subscripting methods on your own class, there are no restrictions on how they are implemented. You can use this syntax to provide a shorthand for defining application routes, search queries, compound property accessors, or plain-old KVO. +Because of how flexible and concise subscripting is, it is extremely well-purposed for creating [DSL](https://en.wikipedia.org/wiki/Domain-specific_language)s. When defining custom subscripting methods on your own class, there are no restrictions on how they are implemented. You can use this syntax to provide a shorthand for defining application routes, search queries, compound property accessors, or plain-old KVO. --- diff --git a/2013-06-24-uuid-udid-unique-identifier.md b/2013-06-24-uuid-udid-unique-identifier.md index 6ad4ae9e..51fcfa47 100644 --- a/2013-06-24-uuid-udid-unique-identifier.md +++ b/2013-06-24-uuid-udid-unique-identifier.md @@ -1,6 +1,6 @@ --- title: "NSUUID /
CFUUIDRef /
UIDevice -uniqueIdentifier /
-identifierForVendor" -author: Mattt Thompson +author: Mattt category: Cocoa excerpt: "Until recently, it was trivial to uniquely identify devices between application launches, and even across applications: a simple call to UIDevice -uniqueIdentifier, and you were all set." status: @@ -21,7 +21,7 @@ Just as privacy and piracy have phonetic and conceptual similarities, device ide - **UUID _(Universally Unique Identifier)_**: A sequence of 128 bits that can guarantee uniqueness across space and time, defined by [RFC 4122](http://www.ietf.org/rfc/rfc4122.txt). - **GUID _(Globally Unique Identifier)_**: Microsoft's implementation of the UUID specification; often used interchangeably with UUID. -- **UDID _(Unique Device Identifier)_**: A sequence of 40 hexadecimal characters that uniquely identify an iOS device (the device's [Social Security Number](https://en.wikipedia.org/wiki/Social_Security_number), if you will). This value can be [retrieved through iTunes](http://whatsmyudid.com), or found using `UIDevice -uniqueIdentifier`. Derived from hardware details like [MAC address](http://en.wikipedia.org/wiki/MAC_address). +- **UDID _(Unique Device Identifier)_**: A sequence of 40 hexadecimal characters that uniquely identify an iOS device (the device's [Social Security Number](https://en.wikipedia.org/wiki/Social_Security_number), if you will). This value can be [retrieved through iTunes](http://whatsmyudid.com), or found using `UIDevice -uniqueIdentifier`. Derived from hardware details like [MAC address](https://en.wikipedia.org/wiki/MAC_address). Incidentally, all of the suggested replacements for `UIDevice -uniqueIdentifier` listed in its deprecation notes return UUID, whether created automatically with `UIDevice -identifierForVendor` & `ASIdentifierManager -advertisingIdentifier` or manually with `NSUUID` (or `CFUUIDCreate`). @@ -41,38 +41,38 @@ However, for advertising networks, which require a consistent identifier across > iOS 6 introduces the Advertising Identifier, a non-permanent, non-personal, device identifier, that advertising networks will use to give you more control over advertisers’ ability to use tracking methods. If you choose to limit ad tracking, advertising networks using the Advertising Identifier may no longer gather information to serve you targeted ads. In the future all advertising networks will be required to use the Advertising Identifier. However, until advertising networks transition to using the Advertising Identifier you may still receive targeted ads from other networks. -As the sole component of the [Ad Support framework](http://developer.apple.com/library/ios/#documentation/DeviceInformation/Reference/AdSupport_Framework/_index.html#//apple_ref/doc/uid/TP40012658), `ASIdentifierManager`'s modus operandi is clear: provide a way for ad networks to track users between different applications in a way that doesn't compromise privacy. +As the sole component of the [Ad Support framework](https://developer.apple.com/library/ios/#documentation/DeviceInformation/Reference/AdSupport_Framework/_index.html#//apple_ref/doc/uid/TP40012658), `ASIdentifierManager`'s modus operandi is clear: provide a way for ad networks to track users between different applications in a way that doesn't compromise privacy. Users can opt out of ad targeting in a Settings screen added in iOS 6.1, found at **Settings > General > About > Advertising**: -![Limit Ad Tracking]({{ site.asseturl }}/ad-support-limit-ad-tracking.png) +![Limit Ad Tracking]({% asset ad-support-limit-ad-tracking.png @path %}) ## NSUUID & CFUUIDRef `NSUUID` was added to Foundation in iOS 6 as a way to easily create UUIDs. How easy? -~~~{swift} -let UUID = NSUUID().UUIDString -~~~ +```swift +let UUID = NSUUID.UUID().UUIDString +``` -~~~{objective-c} +```objc 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} +```swift let UUID = CFUUIDCreateString(nil, CFUUIDCreate(nil)) -~~~ +``` -~~~{objective-c} +```objc 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): +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`](https://developer.apple.com/library/ios/#documentation/cocoa/reference/foundation/Classes/NSUserDefaults_Class/Reference/Reference.html): -~~~{swift} +```swift func application(application: UIApplication!, didFinishLaunchingWithOptions launchOptions: NSDictionary!) -> Bool { let userDefaults = NSUserDefaults.standardUserDefaults() @@ -85,9 +85,9 @@ For apps building against a base SDK without the vendor or advertising identifie return true } -~~~ +``` -~~~{objective-c} +```objc - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { @@ -101,7 +101,7 @@ didFinishLaunchingWithOptions:(NSDictionary *)launchOptions [[NSUserDefaults standardUserDefaults] synchronize]; } } -~~~ +``` 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-01-enumerators.md b/2013-07-01-enumerators.md index 3f7bcd1f..dc499725 100644 --- a/2013-07-01-enumerators.md +++ b/2013-07-01-enumerators.md @@ -1,6 +1,6 @@ --- title: "NSFastEnumeration / NSEnumerator" -author: Mattt Thompson +author: Mattt category: Cocoa excerpt: "Enumeration is where computation gets interesting. It's one thing to encode logic that's executed once, but applying it across a collection—that's what makes programming so powerful." status: @@ -25,26 +25,26 @@ This article will cover all of the different ways collections are enumerated in `for` and `while` loops are the "classic" method of iterating over a collection. Anyone who's taken Computer Science 101 has written code like this before: -~~~{objective-c} +```objc for (NSUInteger i = 0; i < [array count]; i++) { id object = array[i]; NSLog(@"%@", object) } -~~~ +``` -But as anyone who has used C-style loops knows, this method is prone to [off-by-one errors](http://en.wikipedia.org/wiki/Off-by-one_error)—particularly when used in a non-standard way. +But as anyone who has used C-style loops knows, this method is prone to [off-by-one errors](https://en.wikipedia.org/wiki/Off-by-one_error)—particularly when used in a non-standard way. -Fortunately, Smalltalk significantly improved this state of affairs with an idea called [list comprehensions](http://en.wikipedia.org/wiki/List_comprehension), which are commonly known today as `for/in` loops. +Fortunately, Smalltalk significantly improved this state of affairs with an idea called [list comprehensions](https://en.wikipedia.org/wiki/List_comprehension), which are commonly known today as `for/in` loops. ## List Comprehension (`for/in`) By using a higher level of abstraction, declaring the intention of iterating through all elements of a collection, not only are we less prone to error, but there's a lot less to type: -~~~{objective-c} +```objc for (id object in array) { NSLog(@"%@", object); } -~~~ +``` In Cocoa, comprehensions are available to any class that implements the `NSFastEnumeration` protocol, including `NSArray`, `NSSet`, and `NSDictionary`. @@ -52,11 +52,11 @@ In Cocoa, comprehensions are available to any class that implements the `NSFastE `NSFastEnumeration` contains a single method: -~~~{objective-c} +```objc - (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id *)stackbuf count:(NSUInteger)len -~~~ +``` > - `state`: Context information that is used in the enumeration to, in addition to other possibilities, ensure that the collection has not been mutated. > - `stackbuf`: A C array of objects over which the sender is to iterate. @@ -66,14 +66,14 @@ One single, _deceptively complicated_ method. There's that `stackbuf` out pointe ### `NSFastEnumerationState` -~~~{objective-c} +```objc typedef struct { unsigned long state; id *itemsPtr; unsigned long *mutationsPtr; unsigned long extra[5]; } NSFastEnumerationState; -~~~ +``` > - `state`: Arbitrary state information used by the iterator. Typically this is set to 0 at the beginning of the iteration. > - `itemsPtr`: A C array of objects. @@ -94,30 +94,30 @@ But of course, before `NSFastEnumeration` (circa OS X Leopard / iOS 2.0), there For the uninitiated, `NSEnumerator` is an abstract class that implements two methods: -~~~{objective-c} +```objc - (id)nextObject - (NSArray *)allObjects -~~~ +``` `nextObject` returns the next object in the collection, or `nil` if unavailable. `allObjects` returns all of the remaining objects, if any. `NSEnumerator`s can only go forward, and only in single increments. To enumerate through all elements in a collection, one would use `NSEnumerator` thusly: -~~~{objective-c} +```objc id object = nil; NSEnumerator *enumerator = ...; while ((object = [enumerator nextObject])) { NSLog(@"%@", object); } -~~~ +``` ...or because `NSEnumerator` itself conforms to `` in an attempt to stay hip to the way kids do things these days: -~~~{objective-c} +```objc for (id object in enumerator) { NSLog(@"%@", object); } -~~~ +``` If you're looking for a convenient way to add fast enumeration to your own non-collection-class-backed objects, `NSEnumerator` is likely a much more palatable option than getting your hands messy with `NSFastEnumeration`'s implementation details. @@ -131,11 +131,11 @@ Some quick points of interest about `NSEnumeration`: Finally, with the introduction of blocks in OS X Snow Leopard / iOS 4, came a new block-based way to enumerate collections: -~~~{objective-c} +```objc [array enumerateObjectsUsingBlock:^(id object, NSUInteger idx, BOOL *stop) { NSLog(@"%@", object); }]; -~~~ +``` Collection classes like `NSArray`, `NSSet`, `NSDictionary`, and `NSIndexSet` include a similar set of block enumeration methods. @@ -145,20 +145,20 @@ Unless you actually need the numerical index while iterating, it's almost always One last thing to be aware of are the expanded method variants with an `options` parameter: -~~~{objective-c} +```objc - (void)enumerateObjectsWithOptions:(NSEnumerationOptions)opts usingBlock:(void (^)(id obj, NSUInteger idx, BOOL *stop))block -~~~ +``` ### `NSEnumerationOptions` -~~~{objective-c} +```objc enum { NSEnumerationConcurrent = (1UL << 0), NSEnumerationReverse = (1UL << 1), }; typedef NSUInteger NSEnumerationOptions; -~~~ +``` > - `NSEnumerationConcurrent`: Specifies that the Block enumeration should be concurrent. The order of invocation is nondeterministic and undefined; this flag is a hint and may be ignored by the implementation under some circumstances; the code of the Block must be safe against concurrent invocation. > - `NSEnumerationReverse`: Specifies that the enumeration should be performed in reverse. This option is available for `NSArray` and `NSIndexSet` classes; its behavior is undefined for `NSDictionary` and `NSSet` classes, or when combined with the `NSEnumerationConcurrent` flag. diff --git a/2013-07-08-nsexpression.md b/2013-07-08-nsexpression.md index 0fd177b9..397aca90 100644 --- a/2013-07-08-nsexpression.md +++ b/2013-07-08-nsexpression.md @@ -1,6 +1,6 @@ --- title: NSExpression -author: Mattt Thompson +author: Mattt category: Cocoa tags: nshipster excerpt: "Cocoa is the envy of other standard libraries when it comes to querying and arranging information. With NSPredicate, NSSortDescriptor, and an occasional NSFetchRequest, even the most complex data tasks can be reduced into just a few, extremely-understandable lines of code." @@ -9,7 +9,7 @@ status: reviewed: September 8, 2015 --- -Cocoa is the envy of other standard libraries when it comes to querying and arranging information. With `NSPredicate`, [`NSSortDescriptor`](http://nshipster.com/nssortdescriptor/), and an occasional `NSFetchRequest`, even the most complex data tasks can be reduced into just a few, _extremely-understandable_ lines of code. +Cocoa is the envy of other standard libraries when it comes to querying and arranging information. With `NSPredicate`, [`NSSortDescriptor`](https://nshipster.com/nssortdescriptor/), and an occasional `NSFetchRequest`, even the most complex data tasks can be reduced into just a few, _extremely-understandable_ lines of code. Now, NSHipsters are no doubt already familiar with `NSPredicate` (and if you aren't, be sure to tune in next week!), but if we take a closer look at `NSPredicate`, we see that `NSPredicate` is actually made up of smaller, atomic parts: two `NSExpression`s (a left-hand value & a right-hand value), compared with an operator (e.g. `<`, `IN`, `LIKE`, etc.). @@ -28,10 +28,10 @@ let mathExpression = NSExpression(format: "4 + 5 - 2**3") let mathValue = mathExpression.expressionValueWithObject(nil, context: nil) as? Int // 1 ``` -~~~ objective-c +``` objective-c NSExpression *expression = [NSExpression expressionWithFormat:@"4 + 5 - 2**3"]; id value = [expression expressionValueWithObject:nil context:nil]; // => 1 -~~~ +``` It's no [Wolfram Alpha](http://www.wolframalpha.com/input/?i=finn+the+human+like+curve), but if your app does anything where evaluating mathematical expressions would be useful, well... there you go. @@ -45,17 +45,17 @@ let statsExpression = NSExpression(forFunction:"stddev:", arguments:[NSExpressio let statsValue = statsExpression.expressionValueWithObject(nil, context: nil) as? Double // 3.21859... ``` -~~~ objective-c +``` objective-c NSArray *numbers = @[@1, @2, @3, @4, @4, @5, @9, @11]; NSExpression *expression = [NSExpression expressionForFunction:@"stddev:" arguments:@[[NSExpression expressionForConstantValue:numbers]]]; id value = [expression expressionValueWithObject:nil context:nil]; // => 3.21859... -~~~ +``` > `NSExpression` functions take a given number of sub-expression arguments. For instance, in the above example, to get the standard deviation of the collection, the array of numbers had to be wrapped with `+expressionForConstantValue:`. A minor inconvenience (which ultimately allows `NSExpression` to be incredibly flexible), but enough to trip up anyone trying things out for the first time. -If you found the [Key-Value Coding Simple Collection Operators](http://nshipster.com/kvc-collection-operators/) (`@avg`, `@sum`, et al.) lacking, perhaps `NSExpression`'s built-in statistical, arithmetic, and bitwise functions will pique your interest. +If you found the [Key-Value Coding Simple Collection Operators](https://nshipster.com/kvc-collection-operators/) (`@avg`, `@sum`, et al.) lacking, perhaps `NSExpression`'s built-in statistical, arithmetic, and bitwise functions will pique your interest. -> **A word of caution**: [according to this table in Apple's documentation for `NSExpression`](http://developer.apple.com/library/ios/#documentation/cocoa/reference/foundation/Classes/NSExpression_Class/Reference/NSExpression.html), there is apparently no overlap between the availability of functions between OS X & iOS. It would appear that recent versions of iOS do, indeed, support functions like `stddev:`, but this is not reflected in headers or documentation. Any details [in the form of a pull request](https://github.com/NSHipster/articles/pulls) would be greatly appreciated. +> **A word of caution**: [according to this table in Apple's documentation for `NSExpression`](https://developer.apple.com/library/ios/#documentation/cocoa/reference/foundation/Classes/NSExpression_Class/Reference/NSExpression.html), there is apparently no overlap between the availability of functions between OS X & iOS. It would appear that recent versions of iOS do, indeed, support functions like `stddev:`, but this is not reflected in headers or documentation. Any details [in the form of a pull request](https://github.com/NSHipster/articles/pulls) would be greatly appreciated. ### Statistics @@ -100,10 +100,10 @@ So mentioned, because `ceiling` is easily confused with `ceil(3)`. Whereas `ceil ### Random Functions -Two variations—one with and one without an argument. Taking no argument, `random` returns an equivalent of `rand()`, while `randomn:` takes a single number and returns an equivalent of `rand(3)`. +Two variations—one with and one without an argument. Taking no argument, `random` returns an equivalent of `rand(3)`, while `random:` takes a random element from the `NSExpression` of an array of numbers. - `random` -- `randomn:` +- `random:` ### Binary Arithmetic @@ -140,7 +140,7 @@ extension NSNumber { } } ``` -~~~ objective-c +``` objective-c @interface NSNumber (Factorial) - (NSNumber *)factorial; @end @@ -150,7 +150,7 @@ extension NSNumber { return @(tgamma([self doubleValue] + 1)); } @end -~~~ +``` Then, use the function thusly (the `FUNCTION()` macro in `+expressionWithFormat:` is shorthand for the process of building out with `-expressionForFunction:`, et al.): @@ -159,10 +159,10 @@ let functionExpression = NSExpression(format:"FUNCTION(4.2, 'factorial')") let functionValue = functionExpression.expressionValueWithObject(nil, context: nil) as? Double // 32.578... ``` -~~~ objective-c +``` objective-c NSExpression *expression = [NSExpression expressionWithFormat:@"FUNCTION(4.2, 'factorial')"]; id value = [expression expressionValueWithObject:nil context:nil]; // 32.578... -~~~ +``` The advantage here, over calling `-factorial` directly is the ability to invoke the function in an `NSPredicate` query. For example, a `location:withinRadius:` method might be defined to easily query managed objects nearby a user's current location. diff --git a/2013-07-15-nspredicate.md b/2013-07-15-nspredicate.md index 40d34db2..4694191a 100644 --- a/2013-07-15-nspredicate.md +++ b/2013-07-15-nspredicate.md @@ -1,27 +1,27 @@ --- title: NSPredicate -author: Mattt Thompson +author: Mattt category: Cocoa tags: nshipster, popular excerpt: "NSPredicate is a Foundation class that specifies how data should be fetched or filtered. Its query language, which is like a cross between a SQL WHERE clause and a regular expression, provides an expressive, natural language interface to define logical conditions on which a collection is searched." status: - swift: 2.0 - reviewed: September 19, 2015 + swift: 4.2 + reviewed: January 9, 2019 --- `NSPredicate` is a Foundation class that specifies how data should be fetched or filtered. Its query language, which is like a cross between a SQL `WHERE` clause and a regular expression, provides an expressive, natural language interface to define logical conditions on which a collection is searched. -It's easier to show `NSPredicate` in use, rather than talk about it in the abstract, so we're going to revisit the example data set used in the [`NSSortDescriptor` article](http://nshipster.com/nssortdescriptor/): +It's easier to show `NSPredicate` in use, rather than talk about it in the abstract, so we're going to revisit the example data set used in the [`NSSortDescriptor` article](https://nshipster.com/nssortdescriptor/): | `firstName` | `lastName` | `age` | -|-------------|------------|-------| +| ----------- | ---------- | ----- | | Alice | Smith | 24 | | Bob | Jones | 27 | | Charlie | Smith | 33 | | Quentin | Alberts | 31 | -~~~{swift} -class Person: NSObject { +```swift +@objcMembers class Person: NSObject { let firstName: String let lastName: String let age: Int @@ -41,23 +41,23 @@ 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 people = [alice, bob, charlie, quentin] as NSArray let bobPredicate = NSPredicate(format: "firstName = 'Bob'") let smithPredicate = NSPredicate(format: "lastName = %@", "Smith") let thirtiesPredicate = NSPredicate(format: "age >= 30") -(people as NSArray).filteredArrayUsingPredicate(bobPredicate) +people.filtered(using: bobPredicate) // ["Bob Jones"] -(people as NSArray).filteredArrayUsingPredicate(smithPredicate) +people.filtered(using: smithPredicate) // ["Alice Smith", "Charlie Smith"] -(people as NSArray).filteredArrayUsingPredicate(thirtiesPredicate) +people.filtered(using: thirtiesPredicate) // ["Charlie Smith", "Quentin Alberts"] -~~~ +``` -~~~{objective-c} +```objc @interface Person : NSObject @property NSString *firstName; @property NSString *lastName; @@ -99,7 +99,7 @@ NSLog(@"Smiths: %@", [people filteredArrayUsingPredicate:smithPredicate]); // ["Charlie Smith", "Quentin Alberts"] NSLog(@"30's: %@", [people filteredArrayUsingPredicate:thirtiesPredicate]); -~~~ +``` ## Using `NSPredicate` with Collections @@ -122,35 +122,35 @@ 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. -~~~{swift} +```swift let ageIs33Predicate = NSPredicate(format: "%K = %@", "age", "33") -(people as NSArray).filteredArrayUsingPredicate(ageIs33Predicate) +people.filtered(using: ageIs33Predicate) // ["Charlie Smith"] -~~~ +``` -~~~{objective-c} +```objc NSPredicate *ageIs33Predicate = [NSPredicate predicateWithFormat:@"%K = %@", @"age", @33]; // ["Charlie Smith"] NSLog(@"Age 33: %@", [people filteredArrayUsingPredicate:ageIs33Predicate]); -~~~ +``` > - `$VARIABLE_NAME` is a value that can be substituted with `NSPredicate -predicateWithSubstitutionVariables:`. -~~~{swift} +```swift let namesBeginningWithLetterPredicate = NSPredicate(format: "(firstName BEGINSWITH[cd] $letter) OR (lastName BEGINSWITH[cd] $letter)") -(people as NSArray).filteredArrayUsingPredicate(namesBeginningWithLetterPredicate.predicateWithSubstitutionVariables(["letter": "A"])) +people.filtered(using: namesBeginningWithLetterPredicate.withSubstitutionVariables(["letter": "A"])) // ["Alice Smith", "Quentin Alberts"] -~~~ +``` -~~~{objective-c} +```objc 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 @@ -170,13 +170,13 @@ NSLog(@"'A' Names: %@", [people filteredArrayUsingPredicate:[namesBeginningWithL ### String Comparisons -> String comparisons are by default case and diacritic sensitive. You can modify an operator using the key characters c and d within square braces to specify case and diacritic insensitivity respectively, for example firstName BEGINSWITH[cd] $FIRST_NAME. +> String comparisons are by default case and diacritic sensitive. You can modify an operator using the key characters c and d within square braces to specify case and diacritic insensitivity respectively, for example firstName BEGINSWITH[cd] \$FIRST_NAME. > - `BEGINSWITH`: The left-hand expression begins with the right-hand expression. > - `CONTAINS`: The left-hand expression contains the right-hand expression. > - `ENDSWITH`: The left-hand expression ends with the right-hand expression. > - `LIKE`: The left hand expression equals the right-hand expression: `?` and `*` are allowed as wildcard characters, where `?` matches 1 character and `*` matches 0 or more characters. -> - `MATCHES`: The left hand expression equals the right hand expression using a regex-style comparison according to ICU v3 (for more details see the [ICU User Guide for Regular Expressions](http://userguide.icu-project.org/strings/regexp)). +> - `MATCHES`: The left hand expression equals the right hand expression using a regex-style comparison according to ICU v3 (for more details see the [ICU User Guide for Regular Expressions](http://userguide.icu-project.org/strings/regexp)). ### Aggregate Operations @@ -205,34 +205,46 @@ We saw that `AND` & `OR` can be used in predicate format strings to create compo For example, the following predicates are equivalent: -~~~{swift} -NSCompoundPredicate(type: .AndPredicateType, subpredicates: [NSPredicate(format: "age > 25"), NSPredicate(format: "firstName = %@", "Quentin")]) +```swift +NSCompoundPredicate( + type: .and, + subpredicates: [ + NSPredicate(format: "age > 25"), + NSPredicate(format: "firstName = %@", "Quentin") + ] +) NSPredicate(format: "(age > 25) AND (firstName = %@)", "Quentin") -~~~ +``` -~~~{objective-c} +```objc [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` -Similarly, if after reading [last week's article](http://nshipster.com/nsexpression/) you now find yourself with more `NSExpression` objects than you know what to do with, `NSComparisonPredicate` can help you out. +Similarly, if after reading [last week's article](https://nshipster.com/nsexpression/) you now find yourself with more `NSExpression` objects than you know what to do with, `NSComparisonPredicate` can help you out. Like `NSCompoundPredicate`, `NSComparisonPredicate` constructs an `NSPredicate` from subcomponents—in this case, `NSExpression`s on the left and right hand sides. Analyzing its class constructor provides a glimpse into the way `NSPredicate` format strings are parsed: -~~~{objective-c} +```objc + (NSPredicate *)predicateWithLeftExpression:(NSExpression *)lhs rightExpression:(NSExpression *)rhs modifier:(NSComparisonPredicateModifier)modifier type:(NSPredicateOperatorType)type options:(NSUInteger)options -~~~ +``` + +```swift +init(leftExpression lhs: NSExpression, + rightExpression rhs: NSExpression, +customSelector selector: Selector) +``` #### Parameters @@ -244,26 +256,26 @@ Analyzing its class constructor provides a glimpse into the way `NSPredicate` fo ### `NSComparisonPredicate` Types -~~~{swift} -public enum NSPredicateOperatorType : UInt { - case LessThanPredicateOperatorType - case LessThanOrEqualToPredicateOperatorType - case GreaterThanPredicateOperatorType - case GreaterThanOrEqualToPredicateOperatorType - case EqualToPredicateOperatorType - case NotEqualToPredicateOperatorType - case MatchesPredicateOperatorType - case LikePredicateOperatorType - case BeginsWithPredicateOperatorType - case EndsWithPredicateOperatorType - case InPredicateOperatorType - case CustomSelectorPredicateOperatorType - case ContainsPredicateOperatorType - case BetweenPredicateOperatorType +```swift +enum NSComparisonPredicate.Operator: UInt { + case lessThan + case lessThanOrEqualTo + case greaterThan + case greaterThanOrEqualTo + case equalTo + case notEqualTo + case matches + case like + case beginsWith + case endsWith + case `in` + case customSelector + case contains + case between } -~~~ +``` -~~~{objective-c} +```objc enum { NSLessThanPredicateOperatorType = 0, NSLessThanOrEqualToPredicateOperatorType, @@ -282,36 +294,36 @@ enum { }; typedef NSUInteger NSPredicateOperatorType; -~~~ +``` ### `NSComparisonPredicate` Options -> - `NSCaseInsensitivePredicateOption`: A case-insensitive predicate. You represent this option in a predicate format string using a [c] following a string operation (for example, "NeXT" like[c] "next"). -> - `NSDiacriticInsensitivePredicateOption`: A diacritic-insensitive predicate. You represent this option in a predicate format string using a [d] following a string operation (for example, "naïve" like[d] "naive"). -> - `NSNormalizedPredicateOption`: Indicates that the strings to be compared have been preprocessed. This option supersedes NSCaseInsensitivePredicateOption and NSDiacriticInsensitivePredicateOption, and is intended as a performance optimization option. You represent this option in a predicate format string using a [n] following a string operation (for example, "WXYZlan" matches[n] ".lan"). -> - `NSLocaleSensitivePredicateOption`: Indicates that strings to be compared using `<`, `<=`, `=`, `=>`, `>` should be handled in a locale-aware fashion. You represent this option in a predicate format string using a `[l]` following one of the `<`, `<=`, `=`, `=>`, `>` operators (for example, "straße" >[l] "strasse"). +> - `NSCaseInsensitivePredicateOption`: A case-insensitive predicate. You represent this option in a predicate format string using a [c] following a string operation (for example, `"NeXT" like[c] "next"`). +> - `NSDiacriticInsensitivePredicateOption`: A diacritic-insensitive predicate. You represent this option in a predicate format string using a [d] following a string operation (for example, `"naïve" like[d] "naive"`). +> - `NSNormalizedPredicateOption`: Indicates that the strings to be compared have been preprocessed. This option supersedes NSCaseInsensitivePredicateOption and NSDiacriticInsensitivePredicateOption, and is intended as a performance optimization option. You represent this option in a predicate format string using a [n] following a string operation (for example, `"WXYZlan" matches[n] ".lan"`). +> - `NSLocaleSensitivePredicateOption`: Indicates that strings to be compared using `<`, `<=`, `=`, `=>`, `>` should be handled in a locale-aware fashion. You represent this option in a predicate format string using a `[l]` following one of the `<`, `<=`, `=`, `=>`, `>` operators (for example, `"straße" >[l] "strasse"`). ## Block Predicates Finally, if you just can't be bothered to learn the `NSPredicate` format syntax, you can go through the motions with `NSPredicate +predicateWithBlock:`. -~~~{swift} +```swift let shortNamePredicate = NSPredicate { (evaluatedObject, _) in return (evaluatedObject as! Person).firstName.utf16.count <= 5 } -(people as NSArray).filteredArrayUsingPredicate(shortNamePredicate) +people.filtered(using: shortNamePredicate) // ["Alice Smith", "Bob Jones"] -~~~ +``` -~~~{objective-c} +```objc NSPredicate *shortNamePredicate = [NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings) { - return [[evaluatedObject firstName] length] <= 5; - }]; + return [[evaluatedObject firstName] length] <= 5; +}]; // ["Alice Smith", "Bob Jones"] NSLog(@"Short Names: %@", [people filteredArrayUsingPredicate:shortNamePredicate]); -~~~ +``` ...Alright, that whole dig on `predicateWithBlock:` as being the lazy way out wasn't _entirely_ charitable. diff --git a/2013-07-22-uimenucontroller.md b/2013-07-22-uimenucontroller.md index 3fb5a3b3..6052e0fa 100644 --- a/2013-07-22-uimenucontroller.md +++ b/2013-07-22-uimenucontroller.md @@ -1,6 +1,6 @@ --- title: UIMenuController -author: Mattt Thompson +author: Mattt category: Cocoa tags: nshipster excerpt: "Mobile usability today is truly quite remarkable—especially considering how far it's come in just the last decade. What was once a clumsy technology relegated to the tech elite has now become the primary mode of computation for a significant portion of the general population." @@ -15,7 +15,7 @@ Yet despite its advances, one can't help but feel occasionally... trapped. All too often, there will be information on the screen that you _just can't access_. Whether its flight information stuck in a table view cell or an unlinked URL, users are forced to solve problems creatively for lack of a provided solution. -In the past, we've mentioned [localization](http://nshipster.com/nslocalizedstring) and [accessibility](http://nshipster.com/uiaccessibility) as two factors that distinguish great apps from the rest of the pack. This week, we'll add another item to that list: **Edit Actions**. +In the past, we've mentioned [localization](https://nshipster.com/nslocalizedstring) and [accessibility](https://nshipster.com/uiaccessibility) as two factors that distinguish great apps from the rest of the pack. This week, we'll add another item to that list: **Edit Actions**. ### Copy, Cut, Paste, Delete, Select @@ -23,7 +23,7 @@ iOS 3's killer feature was undoubtedly push notifications, but the ability to co This may be due to how cumbersome it is to implement. Let's look at a simple implementation, and then dive into some specifics about the APIs. First the label itself: -~~~{swift} +```swift class HipsterLabel : UILabel { override func canBecomeFirstResponder() -> Bool { return true @@ -39,8 +39,8 @@ class HipsterLabel : UILabel { UIPasteboard.generalPasteboard().string = text } } -~~~ -~~~{objective-c} +``` +```objc // HipsterLabel.h @interface HipsterLabel : UILabel @end @@ -65,11 +65,11 @@ class HipsterLabel : UILabel { } @end -~~~ +``` And with that out of the way, the view controller that uses it: -~~~{swift} +```swift override func viewDidLoad() { super.viewDidLoad() @@ -93,8 +93,8 @@ func handleLongPressGesture(recognizer: UIGestureRecognizer) { recognizerView.becomeFirstResponder() } } -~~~ -~~~{objective-c} +``` +```objc - (void)viewDidLoad { HipsterLabel *label = ...; label.userInteractionEnabled = YES; @@ -114,7 +114,7 @@ func handleLongPressGesture(recognizer: UIGestureRecognizer) { [menuController setMenuVisible:YES animated:YES]; } } -~~~ +``` So, to recap, in order to allow a label's text to be copied, the following must happen: diff --git a/2013-07-31-nshipster-quiz-3.md b/2013-07-31-nshipster-quiz-3.md index 149ca87b..333cbacb 100644 --- a/2013-07-31-nshipster-quiz-3.md +++ b/2013-07-31-nshipster-quiz-3.md @@ -1,13 +1,13 @@ --- title: "NSHipster Quiz #3" -author: Mattt Thompson +author: Mattt category: Trivia excerpt: "Test your mettle as NSHipster Pub Quiz goes on the road, to New York City!" status: swift: n/a --- -NSHipster Pub Quiz came to New York City on July 30th. Like our [first](http://nshipster.com/nshipster-quiz-1/) and [second](http://nshipster.com/nshipster-quiz-2/) quizzes, questions ranged from random Apple trivia to obscure framework questions—this time, with a particular focus on hardware rumors and questions about iOS [REDACTED]. +NSHipster Pub Quiz came to New York City on July 30th. Like our [first](https://nshipster.com/nshipster-quiz-1/) and [second](https://nshipster.com/nshipster-quiz-2/) quizzes, questions ranged from random Apple trivia to obscure framework questions—this time, with a particular focus on hardware rumors and questions about iOS [REDACTED]. The event was hosted by [Meetup](http://meetup.com), and sponsored by [Heroku](https://heroku.com). Dozens of the best and brightest minds in Objective-C attended the event, with the team "The Forstall Five" ([@mb](https://twitter.com/mb), [@bcapps](https://twitter.com/bcapps), [@ethicalpaul](https://twitter.com/ethicalpaul), [@grantjbutler](https://twitter.com/grantjbutler), and [@conbrolution](https://twitter.com/conbrolution)) taking first place. @@ -62,43 +62,43 @@ With over 1 Million iOS & Mac Apps on the App Store, it's clear that the true se - 1. What is the name of this iOS game? -![Question 1]({{ site.asseturl }}/quiz-3/question-1.png) +![Question 1]({% asset quiz-3/question-1.png @path %}) - 2. What is the name of this iOS game? -![Question 2]({{ site.asseturl }}/quiz-3/question-2.png) +![Question 2]({% asset quiz-3/question-2.png @path %}) - 3. What is the name of this iOS app? -![Question 3]({{ site.asseturl }}/quiz-3/question-3.png) +![Question 3]({% asset quiz-3/question-3.png @path %}) - 4. What is the name of this popular iOS app? -![Question 4]({{ site.asseturl }}/quiz-3/question-4.png) +![Question 4]({% asset quiz-3/question-4.png @path %}) - 5. While not on the App Store, jailbreakers will know this icon well. What's its name? -![Question 5]({{ site.asseturl }}/quiz-3/question-5.png) +![Question 5]({% asset quiz-3/question-5.png @path %}) - 6. Which classic Mac app sports this delightful moving truck? -![Question 6]({{ site.asseturl }}/quiz-3/question-6.png) +![Question 6]({% asset quiz-3/question-6.png @path %}) - 7. Which indispensible development tool has this incongruous icon? -![Question 7]({{ site.asseturl }}/quiz-3/question-7.png) +![Question 7]({% asset quiz-3/question-7.png @path %}) - 8. Which app sports this sleek icon? -![Question 8]({{ site.asseturl }}/quiz-3/question-8.png) +![Question 8]({% asset quiz-3/question-8.png @path %}) - 9. Which app is represented by this delightful mail bag? -![Question 9]({{ site.asseturl }}/quiz-3/question-9.png) +![Question 9]({% asset quiz-3/question-9.png @path %}) - 10. Which (unfortunately stalled) app has this beautiful icon? -![Question 10]({{ site.asseturl }}/quiz-3/question-10.png) +![Question 10]({% asset quiz-3/question-10.png @path %}) Round 4: [REDACTED] @@ -131,10 +131,10 @@ Round 1: General Knowledge 3. [iPhone 5C](http://www.latimes.com/business/technology/la-fi-tn-the-iphone-5c-apple-color-plasticspotify-20130730,0,1720605.story) 4. [Aaron Sorkin](http://tech.fortune.cnn.com/2012/11/16/apple-sorkin-jobs-movie/) 5. [The Netherlands](http://appleinsider.com/articles/13/07/02/dutch-steve-jobs-schools-to-use-apples-ipad-for-entire-education-experience) -6. [Douglas Engelbart](http://en.wikipedia.org/wiki/Douglas_Engelbart) +6. [Douglas Engelbart](https://en.wikipedia.org/wiki/Douglas_Engelbart) 7. [HopStop.com](http://www.macworld.com/article/2044904/apple-finds-its-way-to-hopstop-acquisition.html) -8. [Number of Stations (468)](http://en.wikipedia.org/wiki/New_York_City_Subway) -9. [Jane Jacobs](http://en.wikipedia.org/wiki/The_Death_and_Life_of_Great_American_Cities) +8. [Number of Stations (468)](https://en.wikipedia.org/wiki/New_York_City_Subway) +9. [Jane Jacobs](https://en.wikipedia.org/wiki/The_Death_and_Life_of_Great_American_Cities) 10. [HBO Go, WatchESPN, Sky News, Qello, & CrunchyRoll](http://www.apple.com/pr/library/2013/06/19HBO-GO-WatchESPN-Come-to-Apple-TV.html) Round 2: Public, Private, or Fake? diff --git a/2013-08-05-documentation.md b/2013-08-05-objective-c-documentation.md similarity index 86% rename from 2013-08-05-documentation.md rename to 2013-08-05-objective-c-documentation.md index bcea59b0..cf2fb8e9 100644 --- a/2013-08-05-documentation.md +++ b/2013-08-05-objective-c-documentation.md @@ -1,17 +1,17 @@ --- -title: Documentation -author: Mattt Thompson +title: Objective-C Documentation +author: Mattt category: Objective-C excerpt: "There's an adage among Cocoa developers that Objective-C's verbosity lends its code to being effectively self-documenting. Between longMethodNamesWithNamedParameters: and the explicit typing of those parameters, Objective-C methods don't leave much to the imagination." status: - swift: n/a + swift: n/a --- There's an adage among Cocoa developers that Objective-C's verbosity lends its code to being effectively self-documenting. Between `longMethodNamesWithNamedParameters:` and the explicit typing of those parameters, Objective-C methods don't leave much to the imagination. But even self-documenting code can be improved with documentation, and just a small amount of effort can yield significant benefit to others. -**Listen**—I know programmers don't like to be told what to do, and prescriptive arguments of "thou shalt" and "thou shalt not" have the [rhetorical impact of a trombone](http://www.youtube.com/watch?v=ss2hULhXf04), so I'll cut to the chase: +**Listen**—I know programmers don't like to be told what to do, and prescriptive arguments of "thou shalt" and "thou shalt not" have the [rhetorical impact of a trombone](https://www.youtube.com/watch?v=ss2hULhXf04), so I'll cut to the chase: Do you like Apple's documentation? Don't you want that [for your own libraries?](http://cocoadocs.org/docsets/AFNetworking/1.3.1/Classes/AFHTTPClient.html) Follow just a few simple conventions, and your code can get the documentation it deserves. @@ -19,7 +19,7 @@ Do you like Apple's documentation? Don't you want that [for your own libraries?] Every modern programming language has comments: non-executable natural language annotations denoted by a special character sequence, such as `//`, `/* */`, `#`, and `--`. Documentation provides auxiliary explanation and context to code using specially-formatted comments, which can be extracted and parsed by a build tool. -In Objective-C, the documentation tool of choice is [`appledoc`](https://github.com/tomaz/appledoc). Using a [Javadoc](http://en.wikipedia.org/wiki/Javadoc)-like syntax, `appledoc` is able to generate HTML and Xcode-compatible `.docset` docs from `.h` files that [look nearly identical](http://cocoadocs.org/docsets/AFNetworking/1.3.1/Classes/AFHTTPClient.html) to [Apple's official documentation](https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/NSArray_Class/NSArray.html). +In Objective-C, the documentation tool of choice is [`appledoc`](https://github.com/tomaz/appledoc). Using a [Javadoc](https://en.wikipedia.org/wiki/Javadoc)-like syntax, `appledoc` is able to generate HTML and Xcode-compatible `.docset` docs from `.h` files that [look nearly identical](http://cocoadocs.org/docsets/AFNetworking/1.3.1/Classes/AFHTTPClient.html) to [Apple's official documentation](https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/NSArray_Class/NSArray.html). > [Doxygen](http://www.stack.nl/~dimitri/doxygen/), used primarily for C++, is another viable option for Objective-C, but is generally dispreffered by the iOS / OS X developer community. @@ -45,7 +45,7 @@ Each method should similarly begin with a concise description of its functionali Properties are often described in a single sentence, and should include what its default value is. -Related properties and methods should be grouped by an `@name` declaration, which functions similarly to a [`#pragma mark`](http://nshipster.com/pragma/), and can be used with the triple-slash (`///`) comment variant. +Related properties and methods should be grouped by an `@name` declaration, which functions similarly to a [`#pragma mark`](https://nshipster.com/pragma/), and can be used with the triple-slash (`///`) comment variant. Try reading other documentation before writing some yourself, in order to get a sense of the correct tone and style. When in doubt about terminology or verbiage, follow the lead of the closest thing you can find from Apple's official docs. diff --git a/2013-08-12-random.md b/2013-08-12-random.md index e98135ba..94904a39 100644 --- a/2013-08-12-random.md +++ b/2013-08-12-random.md @@ -1,6 +1,6 @@ --- title: rand(3) / random(3) / arc4random(3) / et al. -author: Mattt Thompson +author: Mattt category: Objective-C excerpt: "What passes for randomness is merely a hidden chain of causality. Of course, app developers could care less about philosophy—what they want is code. Thus, our goal this week: to clear up all of the lingering questions and misunderstandings about doing random things in Objective-C" status: @@ -13,7 +13,7 @@ In a mechanical universe of material interactions expressed through mathematical We can be sure of one thing, however: in the closed, digital universe of CPU cycles, processes, and threads, there is no true randomness, only _pseudorandomness_. -Pseudorandomness, is often implemented in a way very similar to a [cryptographic hash](http://en.wikipedia.org/wiki/Cryptographic_hash_function), as a deterministic function that returns a value based on the current time (salted, of course, by some initial seed value). Also like hash functions, there are a number of PRNG, or pseudorandom number generators, each of which are optimized for particular performance characteristics: uniformity, periodicity, and computational complexity. +Pseudorandomness, is often implemented in a way very similar to a [cryptographic hash](https://en.wikipedia.org/wiki/Cryptographic_hash_function), as a deterministic function that returns a value based on the current time (salted, of course, by some initial seed value). Also like hash functions, there are a number of PRNG, or pseudorandom number generators, each of which are optimized for particular performance characteristics: uniformity, periodicity, and computational complexity. Of course, for app developers, all of this is an academic exercise. And rather than bore you with any more high-minded, long-winded treatises on the philosophical nature of randomness, we're going to tackle this one FAQ-style. @@ -29,24 +29,24 @@ Specifically, to generate a random number between `0` and `N - 1`, use `arc4rand ### Random `int` between `0` and `N - 1` -~~~{objective-c} +```objc NSUInteger r = arc4random_uniform(N); -~~~ +``` ### Random `int` between `1` and `N` -~~~{objective-c} +```objc NSUInteger r = arc4random_uniform(N) + 1; -~~~ +``` ### Random `double` between `0` and `1` If you are generating a random `double` or `float`, another good option is the more obscure `rand48` family of functions, including `drand48(3)`. -~~~{objective-c} +```objc srand48(time(0)); double r = drand48(); -~~~ +``` > `rand48` functions, unlike `arc4random` functions, require an initial value to be seeded before generating random numbers. This seed function, `srand48(3)`, should only be run once. @@ -54,18 +54,18 @@ double r = drand48(); Use `arc4random_uniform(3)` to generate a random number in the range of a non-empty array. -~~~{objective-c} +```objc if ([array count] > 0) { id obj = array[arc4random_uniform([array count])]; } -~~~ +``` ## How Do I Randomly Order an `NSArray`? -~~~{objective-c} +```objc NSMutableArray *mutableArray = [NSMutableArray arrayWithArray:array]; NSUInteger count = [mutableArray count]; -// See http://en.wikipedia.org/wiki/Fisher–Yates_shuffle +// See https://en.wikipedia.org/wiki/Fisher–Yates_shuffle if (count > 1) { for (NSUInteger i = count - 1; i > 0; --i) { [mutableArray exchangeObjectAtIndex:i withObjectAtIndex:arc4random_uniform((int32_t)(i + 1))]; @@ -73,13 +73,13 @@ if (count > 1) { } NSArray *randomArray = [NSArray arrayWithArray:mutableArray]; -~~~ +``` > This code is borrowed from [TTTRandomizedEnumerator](https://github.com/mattt/TTTRandomizedEnumerator), which also provides randomized enumerators for `NSSet`, `NSOrderedSet`, and `NSDictionary`. ## How Do I Generate a Random String? -If you're looking to generate "[lorem ipsum](http://en.wikipedia.org/wiki/Lorem_ipsum)"-style sentences, try constructing a [Markov Chain](http://en.wikipedia.org/wiki/Markov_chain) from a [corpus](http://en.wikipedia.org/wiki/Text_corpus). +If you're looking to generate "[lorem ipsum](https://en.wikipedia.org/wiki/Lorem_ipsum)"-style sentences, try constructing a [Markov Chain](https://en.wikipedia.org/wiki/Markov_chain) from a [corpus](https://en.wikipedia.org/wiki/Text_corpus). Otherwise, if you're looking to just get random letters, try one of the following methods: @@ -87,22 +87,22 @@ Otherwise, if you're looking to just get random letters, try one of the followin If you are operating on a known, contiguous range of Unicode characters, such as the lowercase letters (`U+0061` — `U+007A`), you can do a simple conversion from a `char`: -~~~{objective-c} +```objc NSString *letter = [NSString stringWithFormat:@"%c", arc4random_uniform(26) + 'a']; -~~~ +``` ### Pick a Random Character From an `NSString` Otherwise, a simple way to pick random letters from a set of your choosing is to simply create a string containing all of the possible letters: -~~~{objective-c} +```objc NSString *vowels = @"aeiouy"; NSString *letter = [vowels substringWithRange:NSMakeRange(arc4random_uniform([vowels length]), 1)]; -~~~ +``` ## Why Should I Use `arc4random(3)` instead of `rand(3)` or `random(3)`? -> C functions are typically denoted with a number `3` inside of parentheses, following the organizational convention of [`man` pages](http://en.wikipedia.org/wiki/Man_page#Manual_sections). +> C functions are typically denoted with a number `3` inside of parentheses, following the organizational convention of [`man` pages](https://en.wikipedia.org/wiki/Man_page#Manual_sections). - `arc4random` does not require an initial seed (with `srand` or `srandom`), making it that much easier to use. - `arc4random` has a range up to `0x100000000 (4294967296)`, whereas `rand` and `random` top out at `RAND_MAX = 0x7fffffff (2147483647)`. diff --git a/2013-08-19-nshashtable-and-nsmaptable.md b/2013-08-19-nshashtable-and-nsmaptable.md index 1cbe0620..65eed7c8 100644 --- a/2013-08-19-nshashtable-and-nsmaptable.md +++ b/2013-08-19-nshashtable-and-nsmaptable.md @@ -1,6 +1,6 @@ --- title: "NSHashTable & NSMapTable" -author: Mattt Thompson +author: Mattt category: Cocoa tags: nshipster excerpt: "NSSet and NSDictionary, along with NSArray are the workhorse collection classes of Foundation. Unlike other standard libraries, implementation details are hidden from developers, allowing them to write simple code and trust that it will be (reasonably) performant." @@ -9,11 +9,11 @@ status: reviewed: September 11, 2015 --- -`NSSet` and `NSDictionary`, along with `NSArray` are the workhorse collection classes of Foundation. Unlike [ other standard libraries](http://en.wikipedia.org/wiki/Java_collections_framework), implementation details are [hidden](http://ridiculousfish.com/blog/posts/array.html) from developers, allowing them to write simple code and trust that it will be (reasonably) performant. +`NSSet` and `NSDictionary`, along with `NSArray` are the workhorse collection classes of Foundation. Unlike [ other standard libraries](https://en.wikipedia.org/wiki/Java_collections_framework), implementation details are [hidden](http://ridiculousfish.com/blog/posts/array.html) from developers, allowing them to write simple code and trust that it will be (reasonably) performant. However, even the best abstractions break down; their underlying assumptions overturned. In these cases, developers either venture further down the abstraction, or, if available use a more general-purpose solution. -For `NSSet` and `NSDictionary`, the breaking assumption was in the memory behavior when storing objects in the collection. For `NSSet`, objects are a strongly referenced, as are `NSDictionary` values. Keys, on the other hand, are copied by `NSDictionary`. If a developer wanted to store a weak value, or use a non-``-conforming object as a key, they could be clever and use [`NSValue +valueWithNonretainedObject`](http://nshipster.com/nsvalue/). Or, as of iOS 6 (and as far back as OS X Leopard), they could use `NSHashTable` or `NSMapTable`, the more general-case counterparts to `NSSet` or `NSDictionary`, respectively. +For `NSSet` and `NSDictionary`, the breaking assumption was in the memory behavior when storing objects in the collection. For `NSSet`, objects are a strongly referenced, as are `NSDictionary` values. Keys, on the other hand, are copied by `NSDictionary`. If a developer wanted to store a weak value, or use a non-``-conforming object as a key, they could be clever and use [`NSValue +valueWithNonretainedObject`](https://nshipster.com/nsvalue/). Or, as of iOS 6 (and as far back as OS X Leopard), they could use `NSHashTable` or `NSMapTable`, the more general-case counterparts to `NSSet` or `NSDictionary`, respectively. So without further ado, here's everything you need to know about two of the more obscure members of Foundation's collection classes: @@ -29,29 +29,29 @@ So without further ado, here's everything you need to know about two of the more ### Usage -~~~{swift} +```swift let hashTable = NSHashTable(options: .CopyIn) hashTable.addObject("foo") hashTable.addObject("bar") hashTable.addObject(42) hashTable.removeObject("bar") print("Members: \(hashTable.allObjects)") -~~~ -~~~{objective-c} +``` +```objc NSHashTable *hashTable = [NSHashTable hashTableWithOptions:NSPointerFunctionsCopyIn]; [hashTable addObject:@"foo"]; [hashTable addObject:@"bar"]; [hashTable addObject:@42]; [hashTable removeObject:@"bar"]; NSLog(@"Members: %@", [hashTable allObjects]); -~~~ +``` -`NSHashTable` objects are initialized with an option for any of the following behaviors. Deprecated enum values are due to `NSHashTable` being ported from Garbage-Collected OS X to ARC-ified iOS. Other values are aliased to options defined by [NSPointerFunctions](http://developer.apple.com/library/ios/DOCUMENTATION/Cocoa/Reference/Foundation/Classes/NSPointerFunctions_Class/Introduction/Introduction.html), which will be covered next week on NSHipster. +`NSHashTable` objects are initialized with an option for any of the following behaviors. Deprecated enum values are due to `NSHashTable` being ported from Garbage-Collected OS X to ARC-ified iOS. Other values are aliased to options defined by [NSPointerFunctions](https://developer.apple.com/library/ios/DOCUMENTATION/Cocoa/Reference/Foundation/Classes/NSPointerFunctions_Class/Introduction/Introduction.html), which will be covered next week on NSHipster. > - `NSHashTableStrongMemory`: Equal to `NSPointerFunctionsStrongMemory`. This is the default behavior, equivalent to `NSSet` member storage. > - `NSHashTableWeakMemory`: Equal to `NSPointerFunctionsWeakMemory`. Uses weak read and write barriers. Using `NSPointerFunctionsWeakMemory`, object references will turn to `NULL` on last release. > - `NSHashTableZeroingWeakMemory`: This option has been deprecated. Instead use the `NSHashTableWeakMemory` option. -> - `NSHashTableCopyIn`: Use the memory acquire function to allocate and copy items on input (see [`NSPointerFunction -acquireFunction`](http://developer.apple.com/library/ios/DOCUMENTATION/Cocoa/Reference/Foundation/Classes/NSPointerFunctions_Class/Introduction/Introduction.html#//apple_ref/occ/instp/NSPointerFunctions/acquireFunction)). Equal to `NSPointerFunctionsCopyIn`. +> - `NSHashTableCopyIn`: Use the memory acquire function to allocate and copy items on input (see [`NSPointerFunction -acquireFunction`](https://developer.apple.com/library/ios/DOCUMENTATION/Cocoa/Reference/Foundation/Classes/NSPointerFunctions_Class/Introduction/Introduction.html#//apple_ref/occ/instp/NSPointerFunctions/acquireFunction)). Equal to `NSPointerFunctionsCopyIn`. > - `NSHashTableObjectPointerPersonality`: Use shifted pointer for the hash value and direct comparison to determine equality; use the description method for a description. Equal to `NSPointerFunctionsObjectPointerPersonality`. ## `NSMapTable` @@ -70,35 +70,35 @@ NSLog(@"Members: %@", [hashTable allObjects]); Instances where one might use `NSMapTable` include non-copyable keys and storing weak references to keyed delegates or another kind of weak object. -~~~{swift} +```swift let delegate: AnyObject = ... let mapTable = NSMapTable(keyOptions: .StrongMemory, valueOptions: .WeakMemory) mapTable.setObject(delegate, forKey: "foo") print("Keys: \(mapTable.keyEnumerator().allObjects)") -~~~ -~~~{objective-c} +``` +```objc id delegate = ...; NSMapTable *mapTable = [NSMapTable mapTableWithKeyOptions:NSMapTableStrongMemory valueOptions:NSMapTableWeakMemory]; [mapTable setObject:delegate forKey:@"foo"]; NSLog(@"Keys: %@", [[mapTable keyEnumerator] allObjects]); -~~~ +``` `NSMapTable` objects are initialized with options specifying behavior for both keys and values, using the following enum values: > - `NSMapTableStrongMemory`: Specifies a strong reference from the map table to its contents. > - `NSMapTableWeakMemory`: Uses weak read and write barriers appropriate for ARC or GC. Using `NSPointerFunctionsWeakMemory`, object references will turn to `NULL` on last release. Equal to `NSMapTableZeroingWeakMemory`. > - `NSHashTableZeroingWeakMemory`: This option has been superseded by the `NSMapTableWeakMemory` option. -> - `NSMapTableCopyIn`: Use the memory acquire function to allocate and copy items on input (see acquireFunction (see [`NSPointerFunction -acquireFunction`](http://developer.apple.com/library/ios/DOCUMENTATION/Cocoa/Reference/Foundation/Classes/NSPointerFunctions_Class/Introduction/Introduction.html#//apple_ref/occ/instp/NSPointerFunctions/acquireFunction)). Equal to NSPointerFunctionsCopyIn. +> - `NSMapTableCopyIn`: Use the memory acquire function to allocate and copy items on input (see acquireFunction (see [`NSPointerFunction -acquireFunction`](https://developer.apple.com/library/ios/DOCUMENTATION/Cocoa/Reference/Foundation/Classes/NSPointerFunctions_Class/Introduction/Introduction.html#//apple_ref/occ/instp/NSPointerFunctions/acquireFunction)). Equal to NSPointerFunctionsCopyIn. > - `NSMapTableObjectPointerPersonality`: Use shifted pointer hash and direct equality, object description. Equal to `NSPointerFunctionsObjectPointerPersonality`. ### Subscripting -`NSMapTable` doesn't implement [object subscripting](http://nshipster.com/object-subscripting/), but it can be trivially added in a category. `NSDictionary`'s `NSCopying` requirement for keys belongs to `NSDictionary` alone: +`NSMapTable` doesn't implement [object subscripting](https://nshipster.com/object-subscripting/), but it can be trivially added in a category. `NSDictionary`'s `NSCopying` requirement for keys belongs to `NSDictionary` alone: -~~~{swift} +```swift extension NSMapTable { subscript(key: AnyObject) -> AnyObject? { get { @@ -114,9 +114,9 @@ extension NSMapTable { } } } -~~~ +``` -~~~{objective-c} +```objc @implementation NSMapTable (NSHipsterSubscripting) - (id)objectForKeyedSubscript:(id)key @@ -134,7 +134,7 @@ extension NSMapTable { } @end -~~~ +``` --- diff --git a/2013-08-26-equality.md b/2013-08-26-equality.md index 9dcb89b2..a03800ac 100644 --- a/2013-08-26-equality.md +++ b/2013-08-26-equality.md @@ -1,79 +1,111 @@ --- title: Equality -author: Mattt Thompson +author: Mattt category: Objective-C tags: nshipster -excerpt: "The concept of equality is a central point of debate and inquiry in philosophy and mathematics, with far-reaching implications for matters of ethics, justice, and public policy. It is the task of programmers to reconcile our logical and physical understanding of equality with the semantic domains we model." +excerpt: > + The concept of equality is a central topic in philosophy and mathematics, + with far-reaching implications for matters of + ethics, justice, and public policy. + Our task as programmers is to reconcile + our logical and physical understanding of equality + with the domains we model. +revisions: + "2013-08-26": Original publication + "2018-08-15": Updated and expanded status: - swift: n/a + swift: n/a --- -The concept of equality is a central point of debate and inquiry in philosophy and mathematics, with far-reaching implications for matters of ethics, justice, and public policy. +The concept of equality is a central topic in philosophy and mathematics, +with far-reaching implications for matters of ethics, justice, and public policy. -From an empiricist perspective of the universe, two objects are equal if they are indistinguishable from one another in measurable observations. On a human scale, egalitarians hold that individuals should be considered equal members of the societal, economic, political, and judicial systems they inhabit. +From an empiricist perspective of the universe, +two objects are equal if they're indistinguishable by measurable observation. +On a human scale, +egalitarians hold that individuals should be considered equal members of the +societal, economic, political, and judicial systems they inhabit. -It is the task of programmers to reconcile our logical and physical understanding of equality with the semantic domains we model. There is a subtlety to the question of equality, too often overlooked. Jumping into implementation without sufficient understanding of semantics can lead to unnecessary work that produces incorrect results. Though an understanding of the mathematical and logical system underpinning is equally essential to making things work as modeled. +Our task as programmers is to reconcile +our logical and physical understanding of equality +with the domains we model. +And to do this correctly, +we must start from a place of understanding. -While the temptation for all technical blog posts is to skim for headings and code samples, please take a few minutes to read and understand all of this. **Copying relevant-looking code verbatim without knowing why its there may lead to incorrect behavior**. With all seriousness, equality is one of those topics—in Objective-C in particular—where there is still [a great deal of confusion](http://stackoverflow.com/questions/254281/best-practices-for-overriding-isequal-and-hash). +So I invite you to take a moment to consider these broader questions; +resist the urge to skim this article +in search of relevant-looking code to copy-paste verbatim. +Equality in Objective-C is a topic that remains a frequent source of confusion +and deserves our full and undivided attention. ## Equality & Identity -First and foremost, it is important to make a distinction between _equality_ and _identity_. +First and foremost, +let's make a distinction between +equality and identity. -Two objects may be _equal_ or _equivalent_ to one another, if they share a common set of observable properties. Yet, those two objects may still be thought to be _distinct_, each with their own _identity_. In programming, an object's identity is tied to its memory address. +Two objects may be _equal_ or _equivalent_ to one another +if they share a common set of observable properties. +Yet those two objects may be thought to be _distinct_, +each with their own _identity_. -`NSObject` tests equality with another object with the method `isEqual:`. In its base implementation, an equality check is essentially a test for identity. Two `NSObject`s are considered equal if they point to the same memory address. +In Objective-C, an object's identity is tied to its memory address. +When you use the `==` operator to compare two objects in Objective-C, +you're checking to see if they point to the same location in memory. -~~~{objective-c} -@implementation NSObject (Approximate) -- (BOOL)isEqual:(id)object { - return self == object; -} -@end -~~~ +`NSObject` and its subclasses designate the `isEqual:` method +to determine equality between two objects. +In its base implementation, +an equality check simply tests for equal identity: -For container classes like `NSArray`, `NSDictionary`, and `NSString`, the expected and indeed more useful behavior would be to do a deep equality comparison, to test that each member in the collection is equal. +```objc +NSObject *a = [NSObject new]; +NSObject *b = [NSObject new]; -Subclasses of `NSObject` implementing their own `isEqual:` method are expected to do the following: +BOOL objectsHaveSameIdentity = (a == b); // NO +BOOL objectsAreEqual = ([a isEqual:b]); // NO +``` -- Implement a new `isEqualTo__ClassName__:` method, which performs the meaningful value comparison. -- Override `isEqual:` to make class and object identity checks, falling back on the aforementioned value comparison method. -- Override `hash`, which will be described in the next section. +However, some `NSObject` subclasses override `isEqual:` +and thereby redefine the criteria for equality: -Here's an idea of how `NSArray` might do this (ignoring, for this example, that as a [class cluster](https://developer.apple.com/library/ios/documentation/general/conceptual/CocoaEncyclopedia/ClassClusters/ClassClusters.html), the actual implementation would be significantly more complicated): +An `NSValue` object is a wrapper around an underlying value. +If you construct two `NSValue` objects from the same value, +they'll return `NO` when compared with the `==` operator, +but `YES` when compared using the `isEqual:` method: -~~~{objective-c} -@implementation NSArray (Approximate) -- (BOOL)isEqualToArray:(NSArray *)array { - if (!array || [self count] != [array count]) { - return NO; - } +```objc +NSPoint point = NSMakePoint(2.0, 3.0); +NSValue *a = [NSValue valueWithPoint:point]; +NSValue *b = [NSValue valueWithPoint:point]; - for (NSUInteger idx = 0; idx < [array count]; idx++) { - if (![self[idx] isEqual:array[idx]]) { - return NO; - } - } +BOOL valuesHaveSameIdentity = (a == b); // NO +BOOL valuesAreEqual = ([a isEqual:b]); // YES +``` - return YES; -} +`NSObject` and `NSValue` have different semantics for equality, +and understanding the difference between them +is the key to understanding how equality works in most programming languages. -- (BOOL)isEqual:(id)object { - if (self == object) { - return YES; - } +### Value vs. Reference Semantics - if (![object isKindOfClass:[NSArray class]]) { - return NO; - } +If the most important thing about an object is its **state**, +then it's known as a value type, +and its observable properties are used to determine equality. - return [self isEqualToArray:(NSArray *)object]; -} -@end -~~~ +If the most important thing about an object is its **identity**, +then it's known as a reference type, +and its memory address is used to determine equality. + +The naming of `NSValue` is therefore appropriate +because objects of that type follow value semantics +when determining equality in `isEqual:`. -The following `NSObject` subclasses in Foundation have custom equality implementations, with the corresponding method: +You'll find plenty of other value types throughout Foundation --- +just look for their telltale `isEqualTo<#ClassName#>:` method. +For example: +- `NSArray -isEqualToArray:` - `NSAttributedString -isEqualToAttributedString:` - `NSData -isEqualToData:` - `NSDate -isEqualToDate:` @@ -85,141 +117,342 @@ The following `NSObject` subclasses in Foundation have custom equality implement - `NSSet -isEqualToSet:` - `NSString -isEqualToString:` - `NSTimeZone -isEqualToTimeZone:` -- `NSValue -isEqualToValue:` -When comparing two instances of any of these classes, one is encouraged to use these high-level methods rather than `isEqual:`. +> When comparing two instances of any of these classes, +> use these high-level methods rather than `isEqual:`. -However, our theoretical implementation is yet incomplete. Let's turn our attention now to `hash` (after a quick detour to clear something up about `NSString`: +{% info %} -### The Curious Case of `NSString` Equality +The `isEqualTo<#ClassName#>:` methods *don't* accept `nil` as a parameter, whereas `isEqual:` does (and returns `NO` if passed `nil`). Also, watch out for the `-isEqualTo:` category method declared in `NSScriptWhoseTests.h`, which is unrelated despite its similar name. -As an interesting aside, consider the following: - -~~~{objective-c} -NSString *a = @"Hello"; -NSString *b = @"Hello"; -BOOL wtf = (a == b); // YES -~~~ +{% endinfo %} -Let it be perfectly clear that the correct way to compare `NSString` objects is to use `-isEqualToString:`. **Under no circumstances should you compare `NSString` with the `==` operator.** +Types that encapsulate a single value, +such as `NSDate`, +perform an equality comparison of that value. +In the case of `NSDate`, +which represents a point in time relative to an absolute reference date +(1 Jan 2001 00:00:00 GMT), +objects are compared using their +[offset value](https://developer.apple.com/documentation/corefoundation/cfabsolutetime?language=objc). -So what's going on here? Why does this work, when the same code for `NSArray` or `NSDictionary` literals wouldn't work? +For container classes like `NSArray` and `NSDictionary`, +deep equality comparison is performed +by checking that each member-wise pair in the collections are equal to each other. +Here's an idea of how `NSArray` might implement `isEqualToArray:`, +and how that relates to its implementation of `isEqual:` +(ignoring for a moment that, as a +[class cluster](https://developer.apple.com/library/ios/documentation/general/conceptual/CocoaEncyclopedia/ClassClusters/ClassClusters.html), +the actual implementation would be significantly more complicated): -It all has to do with an optimization technique known as [string interning](http://en.wikipedia.org/wiki/String_interning), whereby one copy of immutable string value is copied for each distinct value. `NSString *a` and `*b` point to the same copy of the interned string value `@"Hello"`. _Note that this only works for statically defined immutable strings._ +```objc +@implementation NSArray // Simplified +- (BOOL)isEqualToArray:(NSArray *)array { + if (!array || [self count] != [array count]) { + return NO; + } -Interestingly enough, Objective-C selector names are also stored as interned strings in a shared string pool. + for (NSUInteger idx = 0; idx < [array count]; idx++) { + if (![self[idx] isEqual:array[idx]]) { + return NO; + } + } -`themoreyouknow.gif`. + return YES; +} -## Hashing +- (BOOL)isEqual:(nullable id)object { + if (object == nil) { + return NO; + } -The primary use case of object equality tests for everyday object-oriented programming is to determine collection membership. To keep this fast, subclasses with custom equality implementations are expected to implement `hash` as well: + if (self == object) { + return YES; + } -- Object equality is _commutative_ (`[a isEqual:b]` ⇒ `[b isEqual:a]`) -- If objects are equal, then their `hash` values must also be equal (`[a isEqual:b]` ⇒ `[a hash] == [b hash]`) -- However, the converse does not hold: two objects need not be equal in order for their hash values to be equal (`[a hash] == [b hash]` ¬⇒ `[a isEqual:b]`) + if (![object isKindOfClass:[NSArray class]]) { + return NO; + } -Now for a quick flashback to Computer Science 101: + return [self isEqualToArray:(NSArray *)object]; +} +@end +``` ---- +### String Interning -A [hash table](http://en.wikipedia.org/wiki/Hash_table) is a fundamental data structure in programming, and it's what enables `NSSet` & `NSDictionary` to have fast (`O(1)`) lookup of elements. +After learning about the differences between reference and value semantics, +and how they change the behavior of `==` and `isEqual:`, +you may be confused by the following behavior: -We can best understand hash tables by contrasting them to arrays: +```objc +NSString *a = @"Hello"; +NSString *b = @"Hello"; -**Arrays** store elements in sequential indexes, such that an Array of size `n` will have slots at positions `0`, `1`, up to `n - 1`. To determine where an element is stored in the array (if at all), each position would have to be checked one-by-one (unless the array happens to be sorted, but that's another story). +BOOL valuesHaveSameIdentity = (a == b); // YES (?) +BOOL valuesAreEqual = ([a isEqual:b]); // YES +``` + +_What?_ +`NSString` is a value type, +so why does `==` return `YES` for what should be two different objects? + +It all has to do with an optimization technique known as +[string interning](https://en.wikipedia.org/wiki/String_interning), +whereby one copy of immutable string value is copied for each distinct value. +`NSString *a` and `NSString *b` +point to the same copy of the interned string value `@"Hello"`. + +Objective-C selector names are also stored as interned strings in a shared pool. +This is an important optimization +for a language that operates by passing messages back and forth; +being able to quickly check strings by pointer equality +has a huge impact on runtime performance. + +> Note that this only works for statically-defined, immutable strings. + +### Tagged Pointers + +"Fair enough," you might say to yourself at this point. +"Strings are important and complicated, +so I understand why things may not work as I originally expected." + +Unfortunately, +your understanding would be further confounded +when `NSDate` doesn't work as you expect, either: + +```objc +NSTimeInterval timeInterval = 556035120; +NSDate *a = [NSDate dateWithTimeIntervalSinceReferenceDate:timeInterval]; +NSDate *b = [NSDate dateWithTimeIntervalSinceReferenceDate:timeInterval]; + +BOOL valuesHaveSameIdentity = (a == b); // YES (?) +BOOL valuesAreEqual = ([a isEqual:b]); // YES +``` + +_Seriously?_ +We spent all that time explaining the difference between `==` and `isEqual:` +only to learn that it's all a lie? + +Well... kinda. +Not so much a lie as an omission. + +What you're seeing here is another optimization technique at work, +known as +[pointer tagging](https://en.wikipedia.org/wiki/Tagged_pointer). + +The Objective-C runtime, +when running in 64-bit mode, +represents object pointers using 64-bit integers. +Normally, this integer value points to an address in memory +where the object is stored. +But as an optimization, +some small values can be stored directly in the pointer itself. +If the least-significant bit is set to `1`, +a pointer is considered to be tagged; +the runtime reads the next 3 bits to determine the tagged class +and then initializes a value of that class using the next 60 bits. + +If we run the `NSDate` comparison code again with the debugger turned on, +we can confirm that `a` and `b` are both instances of `__NSTaggedDate *` +with odd pointer values (i.e. their least-significant digit is `1`). + +> As an interesting tie-in to our previous section, +> `NSString` gained support for tagged pointers in macOS 10.10 & iOS 8. +> [Mike Ash](https://github.com/mikeash) +> has [a fascinating write-up](https://mikeash.com/pyblog/friday-qa-2015-07-31-tagged-pointer-strings.html) +> of how that works. + +Only a handful of Foundation types implement tagged pointers, +so don't expect your own objects to magically get this behavior. -**Hash Tables** take a slightly different approach. Rather than storing elements sequentially (`0`, `1`, `...`, `n-1`), a hash table allocates `n` positions in memory, and uses a function to calculate a position within that range. A hash function is [deterministic](http://en.wikipedia.org/wiki/Deterministic_algorithm), and a _good_ hash function generates values in a relatively [uniform distribution](http://en.wikipedia.org/wiki/Uniform_distribution_%28discrete%29) without being too computationally expensive. A _hash collision_ occurs when two different objects calculate the same hash value. When this happens, the hash table will seek from the point of collision and place the new object in the first available place. As a hash table becomes more congested, the likelihood of collision increases, which leads to more time spent looking for a free space (hence why a hash function with a uniform distribution is so desireable). +## Hashing ---- +One of the most important applications of object equality +is to determine collection membership. +In order to keep this fast for `NSDictionary` and `NSSet` collections, +subclasses with custom equality implementations +are expected to implement the `hash` method +in a way that satisfies the following criteria: + +- Object equality is _commutative_ + (`[a isEqual:b]` ⇒ `[b isEqual:a]`) +- If objects are equal, + then their `hash` values must also be equal + (`[a isEqual:b]` ⇒ `[a hash] == [b hash]`) +- However, the converse does not hold: + two objects can have the same hash values, + but not be equal to one another + (`[a hash] == [b hash]` ¬⇒ `[a isEqual:b]`) -One of the most common misconceptions about implementing a custom `hash` function comes from [affirming the consequent](http://en.wikipedia.org/wiki/Affirming_the_consequent), thinking that `hash` values _must_ be distinct. This often leads to [needlessly complicated implementations involving the magical incantation of prime numbers copied from Java textbooks](http://stackoverflow.com/a/254380/157142). In reality, a simple [`XOR`](http://en.wikipedia.org/wiki/Exclusive_or) over the hash values of critical properties is sufficient 99% of the time. +Now for a quick flashback to Computer Science 101: -The trick is in thinking about what the critical value of an object is. +--- -For an `NSDate`, the time interval since a reference date would be sufficient: +A [hash table](https://en.wikipedia.org/wiki/Hash_table) +is a fundamental data structure in programming. + +We can best understand hash tables by contrasting them to lists: + +**Lists** store elements sequentially. +If you want to see whether a particular object is contained by a list, +you must check each element in the list sequentially +until you either find what you're looking for or run out of items. +Therefore, the amount of time it takes to perform a lookup +has a linear relationship to the number of elements in the list (`O(n)`). +`NSArray` is the primary list type in Foundation. + +**Hash tables** take a slightly different approach. +Rather than storing elements sequentially, +a hash table allocates a fixed number of positions in memory +and uses a function to calculate the position within that range +for each object when it's inserted. +A hash function is +[deterministic](https://en.wikipedia.org/wiki/Deterministic_algorithm), +and a _good_ hash function generates values in a relatively +[uniform distribution](https://en.wikipedia.org/wiki/Uniform_distribution_%28discrete%29) +without being too computationally expensive. +Ideally, the amount of time it takes +to find an element in a hash table is constant (`O(1)`), +independent of how many elements are stored. +`NSSet` and `NSDictionary` are the primary collections in Foundation +that implement hash tables. + +> There is one important caveat to these performance characteristics, though: +> If two different objects +> produce the same hash value, +> the hash table seeks from the calculated index +> and places the new object in the first available spot. +> We call this a hash collision. +> As a hash table becomes more congested, +> the likelihood of collision increases, +> which leads to more time spent looking for a free space +> (hence why a hash function with a uniform distribution is so desirable). + +## Best Practices when Implementing Value Types + +If you're implementing a custom type +and want it to follow value semantics, +do the following: + +- Implement a new `isEqualTo<#ClassName#>:` method + to test for value equality. +- Override the `isEqual:` method, + starting with early nil check and class and object identity checks + and falling back on the aforementioned value equality test. +- Override the `hash` method such that equal objects + produce the same hash value. + +As an example, +consider the following `Color` type, +which represents a color using floating-point values for +red, green, and blue intensities between `0` and `1`: + +```objc +@interface Color: NSObject +@property NSNumber *red; +@property NSNumber *green; +@property NSNumber *blue; +@end +``` -~~~{objective-c} -@implementation NSDate (Approximate) -- (NSUInteger)hash { - return (NSUInteger)abs([self timeIntervalSinceReferenceDate]); -} -~~~ +### Implementing `isEqualTo<#ClassName#>:` -For a `UIColor`, a bit-shifted sum of RGB components is a convenient calculation: +The `isEqualTo<#ClassName#>:` method should be publicly declared +and provide a test for value equality with another object of the same type. -~~~{objective-c} -@implementation UIColor (Approximate) -- (NSUInteger)hash { - CGFloat red, green, blue; - [self getRed:&red green:&green blue:&blue alpha:nil]; - return ((NSUInteger)(red * 255) << 16) + ((NSUInteger)(green * 255) << 8) + (NSUInteger)(blue * 255); +```objc +- (BOOL)isEqualToColor:(Color *)color { + return [self.red isEqualToNumber:color.red] && + [self.green isEqualToNumber:color.green] && + [self.blue isEqualToNumber:color.blue]; } -@end -~~~ +``` -## Implementing `-isEqual:` and `hash` in a Subclass +Implementations of this method typically perform member-wise comparison +between the receiver and the passed argument +for each of the properties of that type. +In the case of a `Color`, that means checking the +`red`, `green` , and `blue` properties of each color for equality. -Bringing it all together, here's how one might override the default equality implementation in a subclass: +> Be sure to use the corresponding value equality method +> for each of the properties. -~~~{objective-c} -@interface Person -@property NSString *name; -@property NSDate *birthday; +### Implementing `isEqual:` -- (BOOL)isEqualToPerson:(Person *)person; -@end +The `isEqual:` method should delegate to +the `isEqualTo<#ClassName#>:` method +after testing for nil argument, pointer equality, +and checking for type identity: -@implementation Person +```objc +- (BOOL)isEqual:(nullable id)object { + if (object == nil) { + return NO; + } -- (BOOL)isEqualToPerson:(Person *)person { - if (!person) { - return NO; - } + if (self == object) { + return YES; + } - BOOL haveEqualNames = (!self.name && !person.name) || [self.name isEqualToString:person.name]; - BOOL haveEqualBirthdays = (!self.birthday && !person.birthday) || [self.birthday isEqualToDate:person.birthday]; + if (![object isKindOfClass:[Color class]]) { + return NO; + } - return haveEqualNames && haveEqualBirthdays; + return [self isEqualToColor:(Color *)object]; } +``` -#pragma mark - NSObject +### Implementing `hash` -- (BOOL)isEqual:(id)object { - if (self == object) { - return YES; - } +A common misconception about custom `hash` implementations comes from +[affirming the consequent](https://en.wikipedia.org/wiki/Affirming_the_consequent): +thinking that `hash` values _must_ be distinct. +Although an ideal hash function would produce all distinct values, +this is significantly more difficult than what's required --- +which is, if you'll recall: - if (![object isKindOfClass:[Person class]]) { - return NO; - } +> - Override the `hash` method such that equal objects +> produce the same hash value. - return [self isEqualToPerson:(Person *)object]; -} +A simple way to satisfy this requirement is to simply +[`XOR`](https://en.wikipedia.org/wiki/Exclusive_or) +over the hash values of the properties that determine equality. +```objc - (NSUInteger)hash { - return [self.name hash] ^ [self.birthday hash]; + return [self.red hash] ^ [self.green hash] ^ [self.blue hash]; } -~~~ - -> For the curious and pedantic, see [this post from Mike Ash](http://www.mikeash.com/pyblog/friday-qa-2010-06-18-implementing-equality-and-hashing.html) for an explanation of how `hash` implementations might be improved by bit-shifting or rotating composite values that may overlap. - -## Don't Overthink It - -While all of this has been an interesting exercise in epistemology and computer science, there is a lingering pragmatic detail: - -**You don't usually need to implement this.** - -There are many situations where the default identity check (two variables point to the same address in memory) is desirable behavior. This comes as a consequence of the limitations of data modeling. - -Take, for instance, the previous example of the `Person` class. It's not inconceivable that two individuals would share a common name _and_ birthday. In reality, this crisis of identity would be resolved by additional information, whether it's a system-dependent identifier like a Social Security Number, their parents' identities, or any other physical attributes. - -> Yet even that additional information is not entirely foolproof. After all, that person could be cloned, teleported, or whisked away into a parallel universe. Unlikely? Sure. But much of the challenge in modeling systems is dealing with imperfect assumptions. Just saying. - -Ultimately, it's up to the abstraction to isolate the significant, identifying features that the system cares about, and disregard the rest. The developer can then decide whether objects will be used in such a way that set membership calculations should care about. In a program that only records `name` and `birthday`, it may perfectly correct to treat congruent instances as distinct entities. +``` + +Yes, this approach results in collisions +for objects with the same values for different properties +(for example, cyan and yellow produce the same hash value, +because each has color channels with intensity equal to `1`). +However, it may be good enough for what you're doing. + +Unless you have reason to believe that a better `hash` implementation +would improve performance in a meaningful way, +you're probably better off focusing your time elsewhere. +(That's not to say that _all_ optimizations are premature, +but rather that complicated hash functions frequently are). + +For the curious and pedantic, +Mike Ash has +[another blog post](http://www.mikeash.com/pyblog/friday-qa-2010-06-18-implementing-equality-and-hashing.html) +with suggestions for improving hash functions using techniques like +bit-shifting and rotating composite values that may overlap. --- -Hopefully, after all of this explanation, we all stand with equal footing on this slippery subject. - -As humans, we strive to understand and implement equality in our society and economy; in the laws and leaders that govern us; in the understanding that we extend to one another as we journey through existence. May we continue towards that ideal, where individuals are judged by the contents of their character, just as we judge a variable by the contents of its memory address. +Hopefully, after all of this explanation, +we can all stand with an equal footing on this slippery subject. + +As humans, +we strive to understand and implement equality in our society and economy; +in the laws and leaders that govern us, and +in the understanding that we extend to one another +as we journey through existence. +May we continue towards that ideal, +where individuals are judged by the contents of their character, +just as we judge a variable by the contents of its memory address. diff --git a/2013-09-02-xcode-snippets.md b/2013-09-02-xcode-snippets.md index 345be52f..844141bd 100644 --- a/2013-09-02-xcode-snippets.md +++ b/2013-09-02-xcode-snippets.md @@ -1,6 +1,6 @@ --- title: Xcode Snippets -author: Mattt Thompson +author: Mattt category: Xcode excerpt: "iOS development all but requires the use of Xcode. And if we're resigned to use an IDE in our development workflow, we might as well make the most of it, right? So this week on NSHipster, we're going to talk about one of the more powerful yet underused features of Xcode: Code Snippets" status: @@ -9,7 +9,7 @@ status: iOS development all but requires the use of Xcode. To its credit, Xcode has improved pretty consistently over the last couple of years. Sure, [it still has its... quirks](http://www.textfromxcode.com), but hey—things could be [much, much worse](http://www.eclipse.org). -Working in an IDE may not be as cool as working in your favorite [decades-old editor](http://en.wikipedia.org/wiki/Vim_(text_editor)) (or [that other one](http://en.wikipedia.org/wiki/Emacs)), but you know what is cool? [Autocompletion](http://www.textfromxcode.com/post/24542673087). Not to mention [Build & Analyze](http://clang-analyzer.llvm.org/xcode.html), [Breakpoints](https://developer.apple.com/library/ios/recipes/xcode_help-source_editor/Creating,Disabling,andDeletingBreakpoints/Creating,Disabling,andDeletingBreakpoints.html), and [Instruments](https://developer.apple.com/library/ios/DOCUMENTATION/DeveloperTools/Conceptual/InstrumentsUserGuide/InstrumentsQuickStart/InstrumentsQuickStart.html). +Working in an IDE may not be as cool as working in your favorite [decades-old editor](https://en.wikipedia.org/wiki/Vim_(text_editor)) (or [that other one](https://en.wikipedia.org/wiki/Emacs)), but you know what is cool? [Autocompletion](http://www.textfromxcode.com/post/24542673087). Not to mention [Build & Analyze](http://clang-analyzer.llvm.org/xcode.html), [Breakpoints](https://developer.apple.com/library/ios/recipes/xcode_help-source_editor/Creating,Disabling,andDeletingBreakpoints/Creating,Disabling,andDeletingBreakpoints.html), and [Instruments](https://developer.apple.com/library/ios/DOCUMENTATION/DeveloperTools/Conceptual/InstrumentsUserGuide/InstrumentsQuickStart/InstrumentsQuickStart.html). This is all to say: if we're resigned to use an IDE in our development workflow, we might as well make the most of it, right? So this week on NSHipster, we're going to talk about one of the more powerful yet underused features of Xcode: **Code Snippets**. @@ -21,21 +21,21 @@ From `@interface` declarations to `if (!self) return nil;` incantations, there i To see the available code snippets, show the Utilities panel, to the right of your editor. On the bottom half the Utilities panel, there will be a horizontal divider with 4 icons. -![Utilities Divider]({{ site.asseturl }}/xcode-snippet-utilities-divider.png) +![Utilities Divider]({% asset xcode-snippet-utilities-divider.png @path %}) Click the `{ }` icon to show the Code Snippets Library. -![Utilities Panel]({{ site.asseturl }}/xcode-snippet-utilties-panel.png) +![Utilities Panel]({% asset xcode-snippet-utilties-panel.png @path %}) There are two ways to insert a snippet into your code: You can drag and drop from the code snippets library into your editor: -![Drag-and-Drop]({{ site.asseturl }}/xcode-snippet-drag-and-drop.gif) +![Drag-and-Drop]({% asset xcode-snippet-drag-and-drop.gif @path %}) ...or for snippets that include a text completion shortcut, you can start typing that: -![Text Completion Shortcut]({{ site.asseturl }}/xcode-snippet-text-completion-shortcut.gif) +![Text Completion Shortcut]({% asset xcode-snippet-text-completion-shortcut.gif @path %}) To get a sense of what you can do with snippets, here's an overview of the ones built-in to Xcode: @@ -46,7 +46,7 @@ To get a sense of what you can do with snippets, here's an overview of the ones - Objective-C declarations for `@interface` (including for class extensions and categories), `@implementation`, `@protocol` - Objective-C boilerplate for KVO, including the relatively obscure `keyPathsForValuesAffecting`, used for [registering dependent keys](https://developer.apple.com/library/ios/DOCUMENTATION/Cocoa/Conceptual/KeyValueObserving/Articles/KVODependentKeys.html) - Objective-C boilerplate for Core Data fetches, property accessors, and property validation -- Objective-C idioms for enumerating [`NSIndexSet`](http://nshipster.com/nsindexset/) +- Objective-C idioms for enumerating [`NSIndexSet`](https://nshipster.com/nsindexset/) - Objective-C incantation for `init`, `initWithCoder:` and `initWithFrame:` method implementations - Objective-C `@try` / `@catch` / `@finally` and `@autorelease` blocks - GCD idioms for `dispatch_once` and `dispatch_after` @@ -57,11 +57,11 @@ Of course, what really makes snippets such a powerful feature is the ability to The process of creating a snippet is actually pretty unintuitive and difficult to explain. It uses an obscure OS X system feature that allows users to create a "Text Clipping" by dragging and dropping selected text. Much easier to just show it in action: -![Text Completion Shortcut]({{ site.asseturl }}/xcode-snippet-create.gif) +![Text Completion Shortcut]({% asset xcode-snippet-create.gif @path %}) After being added to the code snippet library, a user-defined snippet can be edited by double-clicking its listing: -![Text Completion Shortcut]({{ site.asseturl }}/xcode-snippet-editor.png) +![Text Completion Shortcut]({% asset xcode-snippet-editor.png @path %}) Each snippet has the following fields: @@ -87,7 +87,7 @@ Each snippet has the following fields: Something you may have noticed in using other Xcode snippets are placeholder tokens: -![Placeholder Token]({{ site.asseturl }}/xcode-snippet-token.png) +![Placeholder Token]({% asset xcode-snippet-token.png @path %}) In Xcode, placeholder tokens are delimited by `<#` and `#>`, with the placeholder text in the middle. Go ahead—try typing that into Xcode, and watch as the text between the octothorp tags magically transforms right in front of your eyes. diff --git a/2013-09-09-network-link-conditioner.md b/2013-09-09-network-link-conditioner.md index a0e01410..f5bc26cd 100644 --- a/2013-09-09-network-link-conditioner.md +++ b/2013-09-09-network-link-conditioner.md @@ -1,70 +1,178 @@ --- title: Network Link Conditioner -author: Mattt Thompson +author: Mattt category: Xcode tag: popular -excerpt: "Product design is about empathy. Knowing what a user wants, what they like, what they dislike, what causes them frustration, and learning to understand and embody those motivations in design decisions—this is what it takes to make something insanely great." +excerpt: >- + App developers often forget to test how their apps perform + under less-than-ideal networking environments. + Learn how you can use the Network Link conditioner + to simulate a spotty Internet connection on your device. +revisions: + "2018-07-18": Updated for Xcode 10 + "2019-07-29": Added note about installation problems in macOS 10.14 status: - swift: n/a + swift: n/a --- -Product design is about empathy. Knowing what a user wants, what they like, what they dislike, what causes them frustration, and learning to understand and embody those motivations in design decisions—this is what it takes to make something insanely great. +Product design is about empathy. +Knowing what a user wants, +what they like, +what they dislike, +what causes them frustration, +and learning to understand and embody those motivations --- +this is what it takes to make something insanely great. + +And so we invest in reaching beyond our own operational model of the world. +We tailor our experience to +[different locales](/nslocalizedstring/). +We consider the usability implications of +[screen readers or other assistive technologies](/uiaccessibility/). +We [continuously evaluate](/unit-testing/) +our implementation against these expectations. + +There is, however, +one critical factor that app developers often miss: +**network condition**, +or more specifically, +the latency and bandwidth of an Internet connection. + +For something so essential to user experience, +it's unfortunate that most developers take an ad-hoc approach +to field-testing their apps under different conditions +(if at all). + +This week on NSHipster, +we'll be talking about the +[Network Link Conditioner](https://developer.apple.com/download/more/?q=Additional%20Tools), +a utility that allows macOS and iOS devices +to accurately and consistently simulate adverse networking environments. -And so we invest in reaching beyond our own operational model of the world. We tailor our experience for [different locales](http://nshipster.com/nslocalizedstring/). We consider the usability implications of [screen readers or other assistive technologiess](http://nshipster.com/uiaccessibility/). We [continuously evaluate](http://nshipster.com/unit-testing/) our implementation against these expectations. +## Installation -There is, though, one critical factor that app developers often miss the first time around, and that is **network condition**, or more specifically the latency and bandwidth of an Internet connection. For something so essential to a user's experience with a product, it's unfortunate that most developers take an ad-hoc approach to field testing different kinds of environments, if at all. +Network Link Conditioner can be found +in the "Additional Tools for Xcode" package. +You can download this from the +[Downloads for Apple Developers](https://developer.apple.com/download/more/?q=Additional%20Tools) +page. -This week on NSHipster, we'll be talking about the [Network Link Conditioner](https://developer.apple.com/downloads/index.action?q=Hardware%20IO%20Tools), a utility that allows Mac and iOS devices to accurately and consistently simulate adverse networking environments. +Search for "Additional Tools" +and select the appropriate release of the package. -## Installation + + + Additional Tools - Hardware + -Network Link Conditioner can be found in the "Hardware IO Tools for Xcode" package. This can be downloaded from the [Apple Developer Downloads](https://developer.apple.com/downloads/index.action?q=Hardware%20IO%20Tools) page. +Once the download has finished, +open the DMG, +navigate to the "Hardware" directory, +and double-click "Network Link Condition.prefPane". -![Download]({{ site.asseturl }}/network-link-conditioner-download.png) + + + Install Network Link Conditioner + -Search for "Hardware IO Tools for Xcode", and select the appropriate release of the package. +Click on the Network Link Conditioner preference pane +at the bottom of System Preferences. -![Package]({{ site.asseturl }}/network-link-conditioner-dmg.png) + + {% comment %}{% endcomment %} + Network Link Conditioner + -Once the download has finished, open the DMG and double-click "Network Link Condition.prefPane" to install. +{% error %} -![System Preferences]({{ site.asseturl }}/network-link-conditioner-install.png) +When you first install Network Link Conditioner on macOS 10.14, +everything works as expected. +But if you close and reopen System Preferences, +the preference pane no longer appears, +and attempting to reinstall results in the following error message: -From now on, you can enable the Network Link Conditioner from its preference pane at the bottom of System Preferences. +
-![Network Link Conditioner]({{ site.asseturl }}/network-link-conditioner-system-preference.png) + + + Network Link Conditioner installation error message + -When enabled, the Network Link Conditioner can change the network environment of the iPhone Simulator according to one of the built-in presets: + +
+ +As a workaround, +you can move the preference pane +from your user `PreferencePanes` directory to the system-level directory +by entering the following command in `Terminal.app` +(you'll be prompted for your password): + +```terminal +$ sudo mv ~/Library/PreferencePanes/Network\ Link\ Conditioner.prefPane /Library/PreferencePanes/ +``` + +Once you've done this, +Network Link Conditioner will appear +the next time you open System Preferences. + +{% enderror %} + +## Controlling Bandwidth, Latency, and Packet Loss + +Enabling the Network Link Conditioner +changes the network environment system-wide +according to the selected configuration, +limiting uplink or download +[bandwidth](https://en.wikipedia.org/wiki/Bandwidth_%28computing%29), +[latency](https://en.wikipedia.org/wiki/Latency_%28engineering%29%23Communication_latency), and rate of +[packet loss](https://en.wikipedia.org/wiki/Packet_loss). + +You can choose from one of the following presets: + +- 100% Loss - 3G - DSL -- WiFi +- EDGE - High Latency DNS +- LTE - Very Bad Network -- 100% Loss +- WiFi +- WiFi 802.11ac -Each preset can set a limit for uplink or downlink [bandwidth](http://en.wikipedia.org/wiki/Bandwidth_%28computing%29), [latency](http://en.wikipedia.org/wiki/Latency_%28engineering%29%23Communication_latency), and rate of [packet loss](http://en.wikipedia.org/wiki/Packet_loss) (when any value is set to 0, that value is unchanged from your computer's network environment). +...or create your own according to your particular requirements. -![Preset]({{ site.asseturl }}/network-link-conditioner-preset.png) +![Preset]({% asset network-link-conditioner-preset.png @path %}) -You can also create your own preset, if you wish to simulate a particular combination of factors simultaneously. +--- -Try running your app in the simulator with the Network Link Conditioner enabled under various presets and see what happens. How does network latency affect your app startup? What effect does bandwidth have on table view scroll performance? Does your app work at all with 100% packet loss? +Now try running your app with the Network Link Conditioner enabled: -> If your app uses [Reachability](https://developer.apple.com/library/ios/samplecode/Reachability/Introduction/Intro.html) to detect network availability, you may experience some unexpected results while using the Network Link Conditioner. As such, any reachability behavior under Airplane mode or WWan / WiFi distinctions is something that should be tested separately from network conditioning. +How does network latency affect your app startup?
+What effect does bandwidth have on table view scroll performance?
+Does your app work at all with 100% packet loss? ## Enabling Network Link Conditioner on iOS Devices -While the preference pane works well for developing on the simulator, it's also important to test on actual devices. Fortunately, as of iOS 6, the Network Link Conditioner is available on the devices themselves. +Although the preference pane works well for developing on the simulator, +it's also important to test on a real device. +Fortunately, +the Network Link Conditioner is available for iOS as well. -To enable it, you need to set up your device for development: +To use the Network Link Conditioner on iOS, +set up your device for development: -1. Connect your iPhone or iPad to your Mac -2. In Xcode, go to Window > Organizer (⇧⌘2) -3. Select your device in the sidebar -4. Click "Use for Development" +1. Connect your iOS device to your Mac +2. In Xcode, navigate to Window > Devices & Simulators +3. Select your device in the sidebar +4. Click "Use for Development" -![iOS Devices]({{ site.asseturl }}/network-link-conditioner-ios.png) +![iOS Devices]({% asset network-link-conditioner-ios.png @path %}) -Now you'll have access to the Developer section of the Settings app, where you'll find the Network Link Conditioner (just don't forget to turn it off after you're done testing!). +Now you'll have access to the Developer section of the Settings app. +You can enable and configure the Network Link Conditioner +on your iOS device under Settings > Developer > Networking. +(Just remember to turn it off after you're done testing!). diff --git a/2013-09-16-afnetworking-2.md b/2013-09-16-afnetworking-2.md index 34048ae9..5cf16dee 100644 --- a/2013-09-16-afnetworking-2.md +++ b/2013-09-16-afnetworking-2.md @@ -1,10 +1,11 @@ --- title: AFNetworking 2.0 -author: Mattt Thompson +author: Mattt category: Open Source excerpt: "AFNetworking is one of the most widely used open source projects for iOS and OS X development. It's about as mainstream as it gets. But have you heard about the sequel?" +retired: true status: - swift: n/a + swift: n/a --- [AFNetworking](http://afnetworking.com) is one of the most widely used open source projects for iOS and OS X development. It powers thousands of popular and critically acclaimed apps, and serves as the foundation for dozens of other great open source libraries and frameworks. With thousands of stars and forks, and hundreds of contributors, the project is also among the most active and influential in the community. @@ -20,13 +21,13 @@ This week on NSHipster: an exclusive look at the future of AFNetworking. ## AFNetworking's Big Ideas -Started in May 2011 as a humble extension of an [Apple code sample](https://developer.apple.com/library/ios/samplecode/mvcnetworking/Introduction/Intro.html) from a [doomed location-based social network](http://en.wikipedia.org/wiki/Gowalla), AFNetworking's success was a product of timing more than anything. At a time when [ASIHTTPRequest](https://github.com/pokeb/asi-http-request) was the de facto networking solution, AFNetworking's core ideas caught on among developers looking for a more modern solution. +Started in May 2011 as a humble extension of an [Apple code sample](https://developer.apple.com/library/ios/samplecode/mvcnetworking/Introduction/Intro.html) from a [doomed location-based social network](https://en.wikipedia.org/wiki/Gowalla), AFNetworking's success was a product of timing more than anything. At a time when [ASIHTTPRequest](https://github.com/pokeb/asi-http-request) was the de facto networking solution, AFNetworking's core ideas caught on among developers looking for a more modern solution. ### NSURLConnection + NSOperation `NSURLConnection` is the backbone of the Foundation URL Loading system. An `NSURLConnection` loads an `NSURLRequest` object asynchronously, calling methods on its delegates as the `NSURLResponse` / `NSHTTPURLResponse` and its associated `NSData` is sent to and loaded from the server; the delegate may also implement behavior for handling an `NSURLAuthenticationChallenge`, a redirect responses, or determine how the associated `NSCachedURLResponse` is stored to the shared `NSURLCache`. -[`NSOperation`](http://nshipster.com/nsoperation) is an abstract class that models a single unit of computation, with useful constructs like state, priority, dependencies, and cancellation. +[`NSOperation`](https://nshipster.com/nsoperation) is an abstract class that models a single unit of computation, with useful constructs like state, priority, dependencies, and cancellation. The first major breakthrough of AFNetworking was combining the two. `AFURLConnectionOperation`, an `NSOperation` subclass conforms to `NSURLConnectionDelegate` methods, and tracks the state of the request from start to finish, while storing intermediary state, such as request, response, and response data. @@ -52,8 +53,8 @@ With its second major release, AFNetworking aims to reconcile many of the quirks ### Motivations -- **NSURLSession Compatibility** - `NSURLSession` is a replacement for `NSURLConnection` introduced in iOS 7. `NSURLConnection` isn't deprecated, and likely won't be for some time, but `NSURLSession` is the future of networking in Foundation, and it's a bright future at that, addressing many of the shortcomings of its predecessor. (See WWDC 2013 Session 705 "What’s New in Foundation Networking" for a good overview). Some had initially speculated that `NSURLSession` would obviate the need for AFNetworking; although there is overlap, there is still much that a higher-level abstraction can provide. __AFNetworking 2.0 does just this, embracing and extending `NSURLSession` to pave over some of the rough spots, and maximize its usefulness.__ -- **Modularity** - One of the major criticisms of AFNetworking is how bulky it is. Although its architecture lent itself well to modularity on a class level, its packaging didn't allow for individual features to be selected à la carte. Over time, `AFHTTPClient` in particular became overburdened in its responsibilities (creating requests, serializing query string parameters, determining response parsing behavior, creating and managing operations, monitoring network reachability). __In AFNetworking 2.0, you can pick and choose only the components you need using [CocoaPods subspecs](https://github.com/CocoaPods/CocoaPods/wiki/The-podspec-format#subspecs).__ +- **NSURLSession Compatibility** - `NSURLSession` is a replacement for `NSURLConnection` introduced in iOS 7. `NSURLConnection` isn't deprecated, and likely won't be for some time, but `NSURLSession` is the future of networking in Foundation, and it's a bright future at that, addressing many of the shortcomings of its predecessor. (See WWDC 2013 Session 705 "What’s New in Foundation Networking" for a good overview). Some had initially speculated that `NSURLSession` would obviate the need for AFNetworking; although there is overlap, there is still much that a higher-level abstraction can provide. **AFNetworking 2.0 does just this, embracing and extending `NSURLSession` to pave over some of the rough spots, and maximize its usefulness.** +- **Modularity** - One of the major criticisms of AFNetworking is how bulky it is. Although its architecture lent itself well to modularity on a class level, its packaging didn't allow for individual features to be selected à la carte. Over time, `AFHTTPClient` in particular became overburdened in its responsibilities (creating requests, serializing query string parameters, determining response parsing behavior, creating and managing operations, monitoring network reachability). **In AFNetworking 2.0, you can pick and choose only the components you need using [CocoaPods subspecs](https://github.com/CocoaPods/CocoaPods/wiki/The-podspec-format#subspecs).** ### Meet the Cast @@ -65,7 +66,7 @@ With its second major release, AFNetworking aims to reconcile many of the quirks #### `NSURLSession` Components _(iOS 7)_ -- `AFURLSessionManager` - A class that creates and manages an `NSURLSession` object based on a specified `NSURLSessionConfiguration` object, as well as data, download, and upload tasks for that session, implementing the delegate methods for both the session and its associated tasks. Because of the odd gaps in `NSURLSession`'s API design, __any code working with `NSURLSession` would be improved by `AFURLSessionManager`__. +- `AFURLSessionManager` - A class that creates and manages an `NSURLSession` object based on a specified `NSURLSessionConfiguration` object, as well as data, download, and upload tasks for that session, implementing the delegate methods for both the session and its associated tasks. Because of the odd gaps in `NSURLSession`'s API design, **any code working with `NSURLSession` would be improved by `AFURLSessionManager`**. - `AFHTTPSessionManager` - A subclass of `AFURLSessionManager` that encapsulates the common patterns of communicating with an web service over HTTP, backed by `NSURLSession` by way of `AFURLSessionManager`. --- @@ -87,7 +88,7 @@ One of the breakthroughs of AFNetworking 2.0's new architecture is use of serial Thanks to the contributions of [Dustin Barker](https://github.com/dstnbrkr), [Oliver Letterer](https://github.com/OliverLetterer), and [Kevin Harwood](https://github.com/kcharwood) and others, AFNetworking comes with built-in support for [SSL pinning](http://blog.lumberlabs.com/2012/04/why-app-developers-should-care-about.html), which is critical for apps that deal with sensitive information. -- `AFSecurityPolicy` - A class that evaluates the server trust of secure connections against its specified pinned certificates or public keys. tl;dr Add your server certificate to your app bundle to help prevent against [man-in-the-middle attacks](http://en.wikipedia.org/wiki/Man-in-the-middle_attack). +- `AFSecurityPolicy` - A class that evaluates the server trust of secure connections against its specified pinned certificates or public keys. tl;dr Add your server certificate to your app bundle to help prevent against [man-in-the-middle attacks](https://en.wikipedia.org/wiki/Man-in-the-middle_attack). #### Reachability @@ -97,9 +98,9 @@ Another piece of functionality now decoupled from `AFHTTPClient` is network reac #### Real-time -- `AFEventSource` - An Objective-C implementation of the [`EventSource` DOM API](http://en.wikipedia.org/wiki/Server-sent_events). A persistent HTTP connection is opened to a host, which streams events to the event source, to be dispatched to listeners. Messages streamed to the event source formatted as [JSON Patch](http://tools.ietf.org/html/rfc6902) documents are translated into arrays of `AFJSONPatchOperation` objects. These patch operations can be applied to the persistent data set fetched from the server. +- `AFEventSource` - An Objective-C implementation of the [`EventSource` DOM API](https://en.wikipedia.org/wiki/Server-sent_events). A persistent HTTP connection is opened to a host, which streams events to the event source, to be dispatched to listeners. Messages streamed to the event source formatted as [JSON Patch](http://tools.ietf.org/html/rfc6902) documents are translated into arrays of `AFJSONPatchOperation` objects. These patch operations can be applied to the persistent data set fetched from the server. -~~~{objective-c} +```objc NSURL *URL = [NSURL URLWithString:@"http://example.com"]; AFHTTPSessionManager *manager = [[AFHTTPSessionManager alloc] initWithBaseURL:URL]; [manager GET:@"/resources" parameters:nil success:^(NSURLSessionDataTask *task, id responseObject) { @@ -117,7 +118,7 @@ AFHTTPSessionManager *manager = [[AFHTTPSessionManager alloc] initWithBaseURL:UR } } error:nil]; } failure:nil]; -~~~ +``` #### UIKit Extensions @@ -125,10 +126,10 @@ All of the UIKit categories in AFNetworking 2.0 have been extracted and expanded - `AFNetworkActivityIndicatorManager`: Automatically start and stop the network activity indicator in the status bar as request operations and tasks begin and finish loading. - `UIImageView+AFNetworking`: Adds `imageResponseSerializer` property, which makes it easy to automatically resize or apply a filter to images loaded remotely to an image view. For example, [`AFCoreImageSerializer`](https://github.com/AFNetworking/AFCoreImageSerializer) could be used to apply Core Image filters to the response image before being displayed. -- `UIButton+AFNetworking` *(New)*: Similar to `UIImageView+AFNetworking`, loads `image` and `backgroundImage` from remote source. -- `UIActivityIndicatorView+AFNetworking` *(New)*: Automatically start and stop a `UIActivityIndicatorView` according to the state of a specified request operation or session task. -- `UIProgressView+AFNetworking` *(New)*: Automatically track the upload or download progress of a specified request operation or session task. -- `UIWebView+AFNetworking` *(New)*: Provides a more sophisticated API for loading URL requests, with support for progress callbacks and content transformation. +- `UIButton+AFNetworking` _(New)_: Similar to `UIImageView+AFNetworking`, loads `image` and `backgroundImage` from remote source. +- `UIActivityIndicatorView+AFNetworking` _(New)_: Automatically start and stop a `UIActivityIndicatorView` according to the state of a specified request operation or session task. +- `UIProgressView+AFNetworking` _(New)_: Automatically track the upload or download progress of a specified request operation or session task. +- `UIWebView+AFNetworking` _(New)_: Provides a more sophisticated API for loading URL requests, with support for progress callbacks and content transformation. --- @@ -138,9 +139,9 @@ Thus concludes our whirlwind tour of AFNetworking 2.0. New features for the next You can start playing around with AFNetworking 2.0 by putting the following in your [`Podfile`](http://cocoapods.org): -~~~{ruby} +```ruby platform :ios, '7.0' pod "AFNetworking", "2.5.0" -~~~ +``` For anyone coming over to AFNetworking from the current 1.x release, you may find [the AFNetworking 2.0 Migration Guide](https://github.com/AFNetworking/AFNetworking/wiki/AFNetworking-2.0-Migration-Guide) especially useful. diff --git a/2013-09-23-ios7.md b/2013-09-23-ios7.md index 3c7244a0..6743d0db 100644 --- a/2013-09-23-ios7.md +++ b/2013-09-23-ios7.md @@ -1,7 +1,7 @@ --- title: "iOS 7" -author: Mattt Thompson -category: "" +author: Mattt +category: Miscellaneous excerpt: "With the NDA finally lifted, we can finally talk about all of the amazing new APIs in iOS 7." status: swift: 2.0 @@ -16,27 +16,27 @@ We'll be going over many of the new features iOS 7 in depth over the coming week ## NSData (NSDataBase64Encoding) -[Base64](http://en.wikipedia.org/wiki/Base64) is a general term for encoding binary data as ASCII text. This is used all over the place on the web, since many core technologies are designed to support text, but not raw binary. For instance, CSS can embed images with [inline `data://` URIs](http://en.wikipedia.org/wiki/Data_URI_scheme), which are often Base64-encoded. Another example is [Basic `Authentication` headers](http://en.wikipedia.org/wiki/Basic_access_authentication), which Base64-encodes its username/password pair, which is marginally better than having them completely in the clear. +[Base64](https://en.wikipedia.org/wiki/Base64) is a general term for encoding binary data as ASCII text. This is used all over the place on the web, since many core technologies are designed to support text, but not raw binary. For instance, CSS can embed images with [inline `data://` URIs](https://en.wikipedia.org/wiki/Data_URI_scheme), which are often Base64-encoded. Another example is [Basic `Authentication` headers](https://en.wikipedia.org/wiki/Basic_access_authentication), which Base64-encodes its username/password pair, which is marginally better than having them completely in the clear. For the longest time, this boringly essential function was completely MIA, leaving thousands of developers to copy-paste random code snippets from forum threads. It was an omission as conspicuous and annoying as JSON pre-iOS 5. But no longer! iOS 7 finally bakes-in Base64: -~~~{swift} +```swift let string = "Lorem ipsum dolor sit amet." if let data = string.dataUsingEncoding(NSUTF8StringEncoding) { let base64EncodedString = data.base64EncodedStringWithOptions([]) print(base64EncodedString) // TG9yZW0gaXBzdW0gZG9sYXIgc2l0IGFtZXQu } -~~~ +``` -~~~{objective-c} +```objc NSString *string = @"Lorem ipsum dolor sit amet."; NSString *base64EncodedString = [[string dataUsingEncoding:NSUTF8StringEncoding] base64EncodedStringWithOptions:0]; NSLog(@"%@", base64EncodedString); // TG9yZW0gaXBzdW0gZG9sYXIgc2l0IGFtZXQu -~~~ +``` ## NSURLComponents & NSCharacterSet (NSURLUtilities) @@ -44,24 +44,24 @@ Foundation is blessed with a wealth of functionality for working with URIs. Unfo `NSURLComponents` dramatically improves this situation. Think of it as `NSMutableURL`: -~~~{swift} -if let components = NSURLComponents(string: "http://nshipster.com") { +```swift +if let components = NSURLComponents(string: "https://nshipster.com") { components.path = "/iOS7" components.query = "foo=bar" print(components.scheme!) // http - print(components.URL!) // http://nshipster.com/iOS7?foo=bar + print(components.URL!) // https://nshipster.com/iOS7?foo=bar } -~~~ +``` -~~~{objective-c} -NSURLComponents *components = [NSURLComponents componentsWithString:@"http://nshipster.com"]; +```objc +NSURLComponents *components = [NSURLComponents componentsWithString:@"https://nshipster.com"]; components.path = @"/iOS7"; components.query = @"foo=bar"; NSLog(@"%@", components.scheme); // http -NSLog(@"%@", [components URL]); // http://nshipster.com/iOS7?foo=bar -~~~ +NSLog(@"%@", [components URL]); // https://nshipster.com/iOS7?foo=bar +``` Each property for URL components also has a `percentEncoded*` variation (e.g. `user` & `percentEncodedUser`), which forgoes any additional URI percent encoding of special characters. @@ -82,23 +82,23 @@ Anything with a notion of completed and total units is a candidate for `NSProgre `NSProgress` can be used to simply report overall progress in a localized way: -~~~{swift} +```swift let progress = NSProgress(totalUnitCount: 100) progress.completedUnitCount = 42; print(progress.localizedDescription) // 42% completed -~~~ +``` -~~~{objective-c} +```objc NSProgress *progress = [NSProgress progressWithTotalUnitCount:100]; progress.completedUnitCount = 42; NSLog(@"%@", [progress localizedDescription]); // 42% completed -~~~ +``` ...or it can be given a handler for stopping work entirely: -~~~{swift} +```swift let timer = NSTimer(timeInterval: 1.0, target: self, selector: "incrementCompletedUnitCount:", userInfo: nil, repeats: true) @@ -107,9 +107,9 @@ progress.cancellationHandler = { } progress.cancel() -~~~ +``` -~~~{objective-c} +```objc NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(incrementCompletedUnitCount:) userInfo:nil @@ -120,7 +120,7 @@ progress.cancellationHandler = ^{ }; [progress cancel]; -~~~ +``` `NSProgress` makes a lot more sense in the context of OS X Mavericks, but for now, it remains a useful class for encapsulating the shared patterns of work units. @@ -130,19 +130,19 @@ Rejoice! The `NSRangeException`-dodging convenience of `-lastObject` has finally Behold! -~~~{swift} +```swift let array = [1, 2, 3] as NSArray print("First Object: \(array.firstObject)") // First Object: Optional(1) print("Last Object: \(array.lastObject)") // Last Object: Optional(3) -~~~ +``` -~~~{objective-c} +```objc NSArray *array = @[@1, @2, @3]; NSLog(@"First Object: %@", [array firstObject]); // First Object: 1 NSLog(@"Last Object: %@", [array lastObject]); // Last Object: 3 -~~~ +``` Refreshing! @@ -154,7 +154,7 @@ Since iOS 5, the Core Image framework has provided facial detection and recognit In yet another free app idea, here's a snippet that might be used by a camera that only saves pictures of smiling faces: -~~~{swift} +```swift import CoreImage let smileDetector = CIDetector(ofType: CIDetectorTypeFace, context: context, @@ -167,9 +167,9 @@ if let feature = features.first as? CIFaceFeature where feature.hasSmile { } else { label.text = "Say Cheese!" } -~~~ +``` -~~~{objective-c} +```objc @import CoreImage; CIDetector *smileDetector = [CIDetector detectorOfType:CIDetectorTypeFace @@ -182,13 +182,13 @@ if (([features count] > 0) && (((CIFaceFeature *)features[0]).hasSmile)) { } else { self.label.text = @"Say Cheese!" } -~~~ +``` ## AVCaptureMetaDataOutput Scan UPCs, QR codes, and barcodes of all varieties with `AVCaptureMetaDataOutput`, new to iOS 7. All you need to do is set it up as the output of an `AVCaptureSession`, and implement the `captureOutput:didOutputMetadataObjects:fromConnection:` method accordingly: -~~~{swift} +```swift import AVFoundation let session = AVCaptureSession() @@ -226,9 +226,9 @@ func captureOutput( print("QRCode: \(QRCode)") } -~~~ +``` -~~~{objective-c} +```objc @import AVFoundation; AVCaptureSession *session = [[AVCaptureSession alloc] init]; @@ -267,7 +267,7 @@ didOutputMetadataObjects:(NSArray *)metadataObjects NSLog(@"QR Code: %@", QRCode); } -~~~ +``` `AVFoundation` supports every code you've heard of (and probably a few that you haven't): @@ -288,46 +288,46 @@ If nothing else, `AVCaptureMetaDataOutput` makes it possible to easily create a Even though the number of people who have actually read something saved for later is only marginally greater than [the number of people who have ever used a QR code](http://picturesofpeoplescanningqrcodes.tumblr.com), it's nice that iOS 7 adds a way to add items to the Safari reading list with the new Safari Services framework. -~~~{swift} +```swift import SafariServices -let url = NSURL(string: "http://nshipster.com/ios7")! +let url = NSURL(string: "https://nshipster.com/ios7")! try? SSReadingList.defaultReadingList()?.addReadingListItemWithURL(url, title: "NSHipster", previewText: "...") -~~~ +``` -~~~{objective-c} +```objc @import SafariServices; -NSURL *URL = [NSURL URLWithString:@"http://nshipster.com/ios7"]; +NSURL *URL = [NSURL URLWithString:@"https://nshipster.com/ios7"]; [[SSReadingList defaultReadingList] addReadingListItemWithURL:URL title:@"NSHipster" previewText:@"..." error:nil]; -~~~ +``` ## AVSpeechSynthesizer -Text-to-Speech has been the killer feature of computers for [accessibility](http://nshipster.com/uiaccessibility/) and [pranking](http://nshipster.com/icloud/) enthusiasts since its inception in the late 1960s. +Text-to-Speech has been the killer feature of computers for [accessibility](https://nshipster.com/uiaccessibility/) and [pranking](https://nshipster.com/icloud/) enthusiasts since its inception in the late 1960s. -iOS 7 brings the power of Siri with the convenience of a [Speak & Spell](http://en.wikipedia.org/wiki/Speak_%26_Spell_%28toy%29) in a new class `AVSpeechSynthesizer`: +iOS 7 brings the power of Siri with the convenience of a [Speak & Spell](https://en.wikipedia.org/wiki/Speak_%26_Spell_%28toy%29) in a new class `AVSpeechSynthesizer`: -~~~{swift} +```swift import AVFoundation let synthesizer = AVSpeechSynthesizer() let utterance = AVSpeechUtterance(string: "Just what do you think you're doing, Dave?") utterance.rate = AVSpeechUtteranceMinimumSpeechRate // Tell it to me slowly synthesizer.speakUtterance(utterance) -~~~ +``` -~~~{objective-c} +```objc @import AVFoundation; AVSpeechSynthesizer *synthesizer = [[AVSpeechSynthesizer alloc] init]; AVSpeechUtterance *utterance = [AVSpeechUtterance speechUtteranceWithString:@"Just what do you think you're doing, Dave?"]; utterance.rate = AVSpeechUtteranceMinimumSpeechRate; // Tell it to me slowly [synthesizer speakUtterance:utterance]; -~~~ +``` ## MKDistanceFormatter @@ -335,7 +335,7 @@ Finally, we end our showcase of iOS 7's new and noteworthy APIs with another cla As advertised, `MKDistanceFormatter` provides a way to convert distances into localized strings using either imperial or metric units: -~~~{swift} +```swift import CoreLocation import MapKit @@ -346,9 +346,9 @@ let distance = portland.distanceFromLocation(sanFrancisco) let formatter = MKDistanceFormatter() formatter.units = .Imperial print(formatter.stringFromDistance(distance)) // 535 miles -~~~ +``` -~~~{objective-c} +```objc @import CoreLocation; @import MapKit; @@ -359,7 +359,7 @@ CLLocationDistance distance = [portland distanceFromLocation:sanFrancisco]; MKDistanceFormatter *formatter = [[MKDistanceFormatter alloc] init]; formatter.units = MKDistanceFormatterUnitsImperial; NSLog(@"%@", [formatter stringFromDistance:distance]); // 535 miles -~~~ +``` --- diff --git a/2013-09-30-xcode-key-bindings-and-gestures.md b/2013-09-30-xcode-key-bindings-and-gestures.md index 49ea63fc..b2df63e4 100644 --- a/2013-09-30-xcode-key-bindings-and-gestures.md +++ b/2013-09-30-xcode-key-bindings-and-gestures.md @@ -1,6 +1,6 @@ --- title: Xcode Key Bindings & Gestures -author: Mattt Thompson +author: Mattt category: Xcode tag: popular excerpt: "Xcode key bindings and gestures not only shave off seconds of precious work, but make you look more confident, competent, and cromulent in the process." @@ -12,13 +12,13 @@ The extent to which programming-as-craft is compared to manual disciplines like Here at NSHipster, the advice is simple and only slightly allegorical: "Xcode is your mustache, so keep it trimmed, waxed to a sharp point, and free of bugs." -Anyway, a few weeks ago, we looked at how [Xcode Snippets](http://nshipster.com/xcode-snippets/) can make you more productive by reducing the amount of boilerplate code you have to type out. This week, we're going to pick up on that thread and cover the essential key bindings and gestures. +Anyway, a few weeks ago, we looked at how [Xcode Snippets](https://nshipster.com/xcode-snippets/) can make you more productive by reducing the amount of boilerplate code you have to type out. This week, we're going to pick up on that thread and cover the essential key bindings and gestures. Xcode key bindings and gestures not only shave off seconds of precious work, but make you look more confident, competent, and cromulent in the process. Learn the following tricks of the trade and join the elite set of Xcode power users. --- -> For your reference, here is a legend of the common modifier key symbols (as well as a symbol for click [shamelessly borrowed from the International Phonetic Alphabet](http://en.wikipedia.org/wiki/Click_consonant)): +> For your reference, here is a legend of the common modifier key symbols (as well as a symbol for click [shamelessly borrowed from the International Phonetic Alphabet](https://en.wikipedia.org/wiki/Click_consonant)): @@ -32,18 +32,18 @@ Xcode key bindings and gestures not only shave off seconds of precious work, but - - - - - + + + + +
ʘʘ
## Open Quickly (`⇧⌘O`) -![Open Quickly]({{ site.asseturl }}/xcode-shortcuts-quick-open.png) +![Open Quickly]({% asset xcode-shortcuts-quick-open.png @path %}) Learn to rely less on the Project Navigator by learning to love Open Quickly. There's a lot to love, too—with support for partial case- and position-insensitive matches, Xcode does a great job of finding what you want with just a minimal amount of input on your part. @@ -51,7 +51,7 @@ Learn to rely less on the Project Navigator by learning to love Open Quickly. Th ## Quick Documentation (`⌥ʘ` on Symbol / Three-Finger Tap)
Open Documentation (`⌥ʘʘ` on Symbol) -![Quick Documentation]({{ site.asseturl }}/xcode-shortcuts-quick-documentation.gif) +![Quick Documentation]({% asset xcode-shortcuts-quick-documentation.gif @path %}) Quick Documentation is probably the first Xcode shortcut developers should learn. Just alt-click (or three-finger tap) any class, variable, or constant value, and Xcode will give you a quick rundown of what you're looking at. Alt-double-click to bring up the documentation window, opened to the relevant entry. @@ -61,7 +61,7 @@ Also well-know to an expert Xcoder's workflow is Jump to Definition, which opens ## Jump to Next Counterpart (`^⌘↑` / `^⌘↓` / Three-Finger Vertical Swipe) -![Jump to Next Counterpart]({{ site.asseturl }}/xcode-shortcuts-counterpart.gif) +![Jump to Next Counterpart]({% asset xcode-shortcuts-counterpart.gif @path %}) Last, but certainly not least, there's Jump to Next Counterpart, which is very likely the shortcut used the most on any given day. Quickly switch between a `.h` header and it's corresponding `.m` implementation with a simple three-finger swipe up or down (or `^⌘↑` / `^⌘↓` if you feel so inclined). @@ -69,23 +69,23 @@ Last, but certainly not least, there's Jump to Next Counterpart, which is very l ## Comment Selection / Current Line (`⌘/`) -![Comment Selection]({{ site.asseturl }}/xcode-shortcuts-comment.gif) +![Comment Selection]({% asset xcode-shortcuts-comment.gif @path %}) Sure, you _could_ be debugging the "right way" by setting breakpoints and being clever with your code paths, but there's quite so refreshingly simple and powerful as phasing code in and out of computational existence with a comment. Add or remove `//` comments to the current line or selection. ## Show Standard Editor (`⌘↵`)
Show Assistant Editor (`⌥⌘↵`)
Show Version Editor (`⌥⇧⌘↵`) -![Editors]({{ site.asseturl }}/xcode-shortcuts-editors.gif) +![Editors]({% asset xcode-shortcuts-editors.gif @path %}) For how useful the Assistant Editor can be, surprisingly few developers can actually remember the key combo to turn it on and off. But now with `⌘↵` and `⌥⌘↵` fresh in your mind, you'll be helping Xcode help yourself more often. -![Assistant Editor Position]({{ site.asseturl }}/xcode-shortcuts-assistant-editor-position.png) +![Assistant Editor Position]({% asset xcode-shortcuts-assistant-editor-position.png @path %}) As an aside, if you're not big on how editors are stacking, a different horizontal or vertical arrangement can be chosen in View > Assistant Editor. --- -![Panels]({{ site.asseturl }}/xcode-shortcuts-panels.gif) +![Panels]({% asset xcode-shortcuts-panels.gif @path %}) Sandwiching the editors on the left and right flanks, the Navigator and Utilities panels encircle your code in their loving embrace. Learning how to get them to show what's useful and GTFO when needed are critical for inner peace and maximum productivity. @@ -122,7 +122,7 @@ Sandwiching the editors on the left and right flanks, the Navigator and Utilitie ## Show / Hide Debug Area (`⇧⌘Y`)
Activate Console (`⇧⌘C`) -![Show / Hide Debug Area]({{ site.asseturl }}/xcode-shortcuts-debug-area.gif) +![Show / Hide Debug Area]({% asset xcode-shortcuts-debug-area.gif @path %}) Anyone miss the option in Xcode 3 to have a detached debugger window? Yeah, me too. @@ -132,19 +132,19 @@ Knowing how to toggle the debug area and activate the console in a single keystr ## Find (`⌘F`) /
Find & Replace (`⌥⌘F`) /
Find in Project (`⇧⌘F`) /
Find & Replace in Project (`⌥⇧⌘F`) -![Find]({{ site.asseturl }}/xcode-shortcuts-find.gif) +![Find]({% asset xcode-shortcuts-find.gif @path %}) For when Xcode's refactoring capabilities come up short... which is to say: often. On the plus side, Xcode allows reference, definition, and regular expression search in addition to literal text. ## Spelling & Grammar (`⌘:`) -![Spelling & Grammar]({{ site.asseturl }}/xcode-shortcuts-spelling-and-grammar.png) +![Spelling & Grammar]({% asset xcode-shortcuts-spelling-and-grammar.png @path %}) All-powerful as Clang is, it still can't help your nightmarish grammar and punctuation in your comments. Especially for anyone releasing code into the open-source wilds, do yourself a favor and give it a once-over with a built-in OS X spelling and grammar check. --- -![Xcode Shortcut Preferences]({{ site.asseturl }}/xcode-shortcuts-preferences.png) +![Xcode Shortcut Preferences]({% asset xcode-shortcuts-preferences.png @path %}) But, of course, the fun doesn't stop there! Like any respectable editor, Xcode allows you to customize the key bindings for every menu item and action across the app. diff --git a/2013-10-07-key-value-observing.md b/2013-10-07-key-value-observing.md index 18c7ffbc..8ffb2016 100644 --- a/2013-10-07-key-value-observing.md +++ b/2013-10-07-key-value-observing.md @@ -1,6 +1,6 @@ --- title: Key-Value Observing -author: Mattt Thompson +author: Mattt category: Cocoa tag: nshipster, popular excerpt: "Ask anyone who's been around the NSBlock a few times: Key-Value Observing has the _worst_ API in all of Cocoa. It's awkward, verbose, and confusing. And worst of all, its terrible API belies one of the most compelling features of the framework." @@ -32,16 +32,16 @@ The main value proposition of KVO is rather compelling: any object can subscribe ## Subscribing -Objects can have observers added for a particular key path, which, as described in [the KVC operators article](http://nshipster.com/kvc-collection-operators/), are dot-separated keys that specify a sequence of properties. Most of the time with KVO, these are just the top-level properties on the object. +Objects can have observers added for a particular key path, which, as described in [the KVC operators article](https://nshipster.com/kvc-collection-operators/), are dot-separated keys that specify a sequence of properties. Most of the time with KVO, these are just the top-level properties on the object. The method used to add an observer is `–addObserver:forKeyPath:options:context:`: -~~~{objective-c} +```objc - (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context -~~~ +``` > - `observer`: The object to register for KVO notifications. The observer must implement the key-value observing method observeValueForKeyPath:ofObject:change:context:. > - `keyPath`: The key path, relative to the receiver, of the property to observe. This value must not be `nil`. @@ -73,28 +73,28 @@ Another aspect of KVO that lends to its ugliness is the fact that there is no wa Instead, all changes for observers are funneled through a single method—`-observeValueForKeyPath:ofObject:change:context:`: -~~~{objective-c} +```objc - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context -~~~ +``` Those parameters are the same as what were specified in `–addObserver:forKeyPath:options:context:`, with the exception of `change`, which are populated from whichever `NSKeyValueObservingOptions` `options` were used. A typical implementation of this method looks something like this: -~~~{objective-c} +```objc - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { if ([keyPath isEqualToString:@"state"]) { - // ... + <#...#> } } -~~~ +``` Depending on how many kinds of objects are being observed by a single class, this method may also introduce `-isKindOfObject:` or `-respondsToSelector:` in order to definitively identify the kind of event being passed. However, the safest method is to do an equality check to `context`—especially when dealing with subclasses whose parents observe the same keypath. @@ -102,13 +102,13 @@ Depending on how many kinds of objects are being observed by a single class, thi What makes a good `context` value? Here's a suggestion: -~~~{objective-c} +```objc static void * XXContext = &XXContext; -~~~ +``` It's that simple: a static value that stores its own pointer. It means nothing on its own, which makes it rather perfect for ``: -~~~{objective-c} +```objc - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change @@ -120,7 +120,7 @@ It's that simple: a static value that stores its own pointer. It means nothing o } } } -~~~ +``` ### Better Key Paths @@ -128,13 +128,13 @@ Passing strings as key paths is strictly worse than using properties directly, a A clever workaround to this is to use `NSStringFromSelector` and a `@selector` literal value: -~~~{objective-c} +```objc NSStringFromSelector(@selector(isFinished)) -~~~ +``` Since `@selector` looks through all available selectors in the target, this won't prevent all mistakes, but it will catch most of them—including breaking changes made by Xcode automatic refactoring. -~~~{objective-c} +```objc - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change @@ -145,10 +145,10 @@ Since `@selector` looks through all available selectors in the target, this won' } } else if (...) { - // ... + <#...#> } } -~~~ +``` ## Unsubscribing @@ -160,7 +160,7 @@ Perhaps the most pronounced annoyance with KVO is how it gets you at the end. If Which causes one to rely on a rather unfortunate cudgel `@try` with an unhandled `@catch`: -~~~{objective-c} +```objc - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change @@ -175,7 +175,7 @@ Which causes one to rely on a rather unfortunate cudgel `@try` with an unhandled } } } -~~~ +``` Granted, _not_ handling a caught exception, as in this example, is waving the `[UIColor whiteColor]` flag of surrender. Therefore, one should only really use this technique when faced with intermittent crashes which cannot be remedied by normal book-keeping (whether due to race conditions or undocumented behavior from a superclass). @@ -189,11 +189,11 @@ But what about compound or derived values? Let's say you have an object with a ` Well, you can implement the method `keyPathsForValuesAffectingAddress` (or its less magical catch-all, `+keyPathsForValuesAffectingValueForKey:`): -~~~{objective-c} +```objc + (NSSet *)keyPathsForValuesAffectingAddress { return [NSSet setWithObjects:NSStringFromSelector(@selector(streetAddress)), NSStringFromSelector(@selector(locality)), NSStringFromSelector(@selector(region)), NSStringFromSelector(@selector(postalCode)), nil]; } -~~~ +``` --- diff --git a/2013-10-14-nserror.md b/2013-10-14-nserror.md index 4125051f..3f0fbd9c 100644 --- a/2013-10-14-nserror.md +++ b/2013-10-14-nserror.md @@ -1,6 +1,6 @@ --- title: NSError -author: Mattt Thompson +author: Mattt category: Cocoa tags: nshipster excerpt: "NSError is the unsung hero of the Foundation framework. Passed gallantly in and out of perilous method calls, it is the messenger by which we are able to contextualize our failures." @@ -51,7 +51,7 @@ Three are generally useful: Here's how to construct `NSError` with a `userInfo` dictionary: -~~~{objective-c} +```objc NSDictionary *userInfo = @{ NSLocalizedDescriptionKey: NSLocalizedString(@"Operation was unsuccessful.", nil), NSLocalizedFailureReasonErrorKey: NSLocalizedString(@"The operation timed out.", nil), @@ -60,19 +60,19 @@ NSDictionary *userInfo = @{ NSError *error = [NSError errorWithDomain:NSHipsterErrorDomain code:-57 userInfo:userInfo]; -~~~ +``` The advantage of encapsulating this information in an object like `NSError`, as opposed to, say, throwing exceptions willy-nilly, is that these error objects can be easily passed between different objects and contexts. For example, a controller that calls a method that populates an `NSError **` (as discussed in the next section) might pass that error into an alert view: -~~~{objective-c} +```objc [[[UIAlertView alloc] initWithTitle:error.localizedDescription message:error.localizedRecoverySuggestion delegate:nil cancelButtonTitle:NSLocalizedString(@"OK", nil) otherButtonTitles:nil, nil] show]; -~~~ +``` > As a brief non-sequitur: one clever hack used by C functions to communicate errors is to [encode 4-letter ASCII sequences in the 32 bit return type](https://github.com/mattt/Xcode-Snippets/blob/master/checkerror.m). It's no `localizedDescription`, but it's better than cross-referencing error codes from a table every time! @@ -96,7 +96,7 @@ There are two ways in which you will encounter `NSError`: as a consumer and as a As a consumer, you are primarily concerned with methods that have a final parameter of type `NSError **`. Again, this is to get around the single return value constraint of Objective-C; by passing a pointer to an uninitialized `NSError *` variable, that variable will be populated with any error the method encounters: -~~~{objective-c} +```objc NSError *error = nil; BOOL success = [[NSFileManager defaultManager] moveItemAtPath:@"/path/to/target" toPath:@"/path/to/destination" @@ -104,13 +104,13 @@ BOOL success = [[NSFileManager defaultManager] moveItemAtPath:@"/path/to/target" if (!success) { NSLog(@"%@", error); } -~~~ +``` > According to Cocoa conventions, methods returning `BOOL` to indicate success or failure are encouraged to have a final `NSError **` parameter if there are multiple failure conditions to distinguish between. A good guideline is whether you could imagine that `NSError` bubbling up, and being presented to the user. Another way `NSError` objects are passed is the inclusion of an `NSError *` argument in `completionHandler` blocks. This gets around both a constraint on single value returns as well as one on that value being returned synchronously. This has become especially popular with newer Foundation APIs, like `NSURLSession`: -~~~{objective-c} +```objc NSURL *URL = [NSURL URLWithString:@"http://example.com"]; NSURLRequest *request = [NSURLRequest requestWithURL:URL]; NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]]; @@ -119,10 +119,10 @@ NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConf if (!data) { NSLog(@"%@", error); } else { - // ... + <#...#> } }] resume]; -~~~ +``` ### Producing @@ -130,7 +130,7 @@ One would be well-advised to follow the same conventions for error handling as o To pass an error to an `NSError **` parameter, do the following: -~~~{objective-c} +```objc - (BOOL)validateObject:(id)object error:(NSError * __autoreleasing *)outError { @@ -148,7 +148,7 @@ To pass an error to an `NSError **` parameter, do the following: return success; } -~~~ +``` The root error, if one exists, should be returned as part of your custom error's `userInfo` dictionary as the value for `NSUnderlyingErrorKey`. diff --git a/2013-10-21-launch-arguments-and-environment-variables.md b/2013-10-21-launch-arguments-and-environment-variables.md index f3d39718..0c646a0b 100644 --- a/2013-10-21-launch-arguments-and-environment-variables.md +++ b/2013-10-21-launch-arguments-and-environment-variables.md @@ -1,10 +1,10 @@ --- -title: "Launch Arguments &
Environment Variables" -author: Mattt Thompson +title: "Launch Arguments &
Environment Variables" +author: Mattt category: Xcode excerpt: "There are a number of options that can be passed into a target's scheme to enable useful debugging behavior, but like a fast food secret menu, they're obscure and widely unknown." status: - swift: n/a + swift: n/a --- Walk into any American fast food establishment, and you'll be greeted with a colorful, back-lit display of specials, set menus, and other a la carte items. But as those in-the-know are quick to point out, larger chains often have a _secret_ menu, passed down by oral tradition between line cook workers and patrons over the generations. @@ -19,15 +19,15 @@ Which brings us to Xcode Launch Arguments & Environment Variables. There are a n So this week on NSHipster, we'll take a look at the hidden world of Xcode runtime configuration, so that you, dear reader, may also saunter up to the great lunch counter of Objective-C and order to your heart's content. -* * * +--- To enable launch arguments and set environment variables for your app, select your target from the Xcode toolbar and select "Edit Scheme..." -![Edit Scheme...]({{ site.asseturl }}/launch-arguments-edit-scheme.png) +![Edit Scheme...]({% asset launch-arguments-edit-scheme.png @path %}) On the left side of the panel, select "Run [AppName].app", and select the "Arguments" segment on the right side. There will be two drop-downs, for "Arguments Passed on Launch" and "Environment Variables". -![Edit Scheme Panel]({{ site.asseturl }}/launch-arguments-edit-scheme-panel.png) +![Edit Scheme Panel]({% asset launch-arguments-edit-scheme-panel.png @path %}) For the purposes of debugging an app target, launch arguments and environment variables can be thought to be equivalent—both change the runtime behavior by defining certain values. In practice, the main difference between the two is that launch arguments begin with a dash (`-`) and don't have a separate field for argument values. @@ -39,13 +39,15 @@ Any argument passed on launch will override the current value in `NSUserDefaults Getting localization right is a challenging and time-consuming task in and of itself. Fortunately, there are a few launch arguments that make the process _much_ nicer. -> For more information about localization, check out our article about [`NSLocalizedString`](http://nshipster.com/nslocalizedstring/). +> For more information about localization, check out our article about [`NSLocalizedString`](https://nshipster.com/nslocalizedstring/). #### NSDoubleLocalizedStrings To simulate German's UI-breaking _götterdämmere Weltanschauung_ of long-compound-words-unbroken-by-breakable-whitespace, there's `NSDoubleLocalizedStrings`. -According to [IBM's Globalization Guidelines](http://www-01.ibm.com/software/globalization/guidelines/a3.html), we can expect translations from English to many European languages to be double or even triple the physical space of the source: +According to [IBM's Globalization Guidelines _(Archived)_](https://web.archive.org/web/20180704133337/http://www-01.ibm.com/software/globalization/guidelines/a3.html), we can expect translations from English to many European languages to be double or even triple the physical space of the source: + +{::nomarkdown} @@ -53,6 +55,7 @@ According to [IBM's Globalization Guidelines](http://www-01.ibm.com/software/glo + @@ -62,14 +65,15 @@ According to [IBM's Globalization Guidelines](http://www-01.ibm.com/software/glo
Number of Characters in Text Additional Physical Space Required
≤ 10100% to 200%
11 – 2080% to 100%
7030%
+{:/} While you're waiting for the first batch of translations to come back, or are merely curious to see how badly your UI breaks under linguistic pressure, specify the following launch argument: -~~~ +``` -NSDoubleLocalizedStrings YES -~~~ +``` -![NSDoubleLocalizedStrings - Before & After]({{ site.asseturl }}/launch-arguments-nsdoublelocalizedstrings.png) +![NSDoubleLocalizedStrings - Before & After]({% asset launch-arguments-nsdoublelocalizedstrings.png @path %}) #### NSShowNonLocalizedStrings @@ -77,9 +81,9 @@ Project managers screaming at you to get localization finished? Now you can conf If you pass the `NSShowNonLocalizedStrings` launch argument, any unlocalized string will SCREAM AT YOU IN CAPITAL LETTERS. HOW DELIGHTFUL! -~~~ +``` -NSShowNonLocalizedStrings YES -~~~ +``` #### AppleLanguages @@ -87,9 +91,9 @@ Perhaps the most useful launch argument of all, however, is `AppleLanguages`. Normally, one would have to manually go through Settings > General > International > Language and wait for the Simulator or Device to restart. But the same can be accomplished much more simply with the following launch argument: -~~~ +``` -AppleLanguages (es) -~~~ +``` > The value for `AppleLanguages` can either be the name of the language ("Spanish"), or its language code (`es`), but since localization files are keyed by their ISO 639 code, using the code is preferable to the actual name of the language. @@ -103,19 +107,19 @@ Most Core Data stacks use SQLite as a persistent store, so if your app is anythi Set the following launch argument: -~~~ +``` -com.apple.CoreData.SQLDebug 3 -~~~ +``` ...and let the spice flow. -~~~ +``` CoreData: sql: pragma cache_size=1000 CoreData: sql: SELECT Z_VERSION, Z_UUID, Z_PLIST FROM Z_METADATA CoreData: sql: SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZAUTHOR, t0.ZTITLE, t0.ZCOPYRIGHT FROM ZBOOK t0 ORDER BY t0.ZAUTHOR, t0.ZTITLE CoreData: annotation: sql connection fetch time: 0.0001s CoreData: annotation: total fetch execution time: 0.0010s for 20 rows. -~~~ +``` `com.apple.CoreData.SQLDebug` takes a value between `1` and `3`; the higher the value, the more verbose the output. Adjust according to taste. @@ -123,19 +127,19 @@ CoreData: annotation: total fetch execution time: 0.0010s for 20 rows. Want your debug statements to be _even spicier_? Toss `com.apple.CoreData.SyntaxColoredLogging` into the mix and brace yourself for an explosion of color: -~~~ +``` -com.apple.CoreData.SyntaxColoredLogging YES -~~~ +``` #### Migration Debug In any other persistence layer, migrations are a blessing. Yet, for some reason, Core Data manages to make them into something out of a nightmare. When things go wrong and you have no one to blame except your own ignorant self, unworthy of such an intuitive and well-designed ORM graph persistence framework, then here's an argument you'll want to pass at launch: -~~~ +``` -com.apple.CoreData.MigrationDebug -~~~ +``` -* * * +--- ## Environment Variables @@ -151,16 +155,19 @@ Over-played in popular media, under-played in Objective-C, everyone can agree th Setting `NSZombie`-related environment variables allows you to control the _BRAAAAINS!_ of your app. To be more specific, when objects are deallocated, they become "zombified", able to communicate any messages that are passed after they have been freed. This can be useful for tracing any errant `EXC_BAD_ACCESS` exceptions you get during execution. +{::nomarkdown} + - - + +
NameEffect
NSZombieEnabledIf set to YES, deallocated objects are 'zombified'; this allows you to quickly debug problems where you send a message to an object that has already been freed.
NSDeallocateZombiesIf set to YES, the memory for 'zombified' objects is actually freed.
NSZombieEnabledIf set to YES, deallocated objects are 'zombified'; this allows you to quickly debug problems where you send a message to an object that has already been freed.
NSDeallocateZombiesIf set to YES, the memory for 'zombified' objects is actually freed.
+{:/} ### Memory Allocator @@ -170,37 +177,42 @@ The memory allocator includes several debugging hooks that can be enabled by env Here are some of the most useful ones: +{::nomarkdown} + - - - - + + + +
NameEffect
MallocScribbleFill allocated memory with 0xAA and scribble deallocated memory with 0x55.
MallocGuardEdgesAdd guard pages before and after large allocations.
MallocStackLoggingRecord backtraces for each memory block to assist memory debugging tools; if the block is allocated and then immediately freed, both entries are removed from the log, which helps reduce the size of the log.
MallocStackLoggingNoCompactSame as MallocStackLogging but keeps all log entries.
MallocScribbleFill allocated memory with 0xAA and scribble deallocated memory with 0x55.
MallocGuardEdgesAdd guard pages before and after large allocations.
MallocStackLoggingRecord backtraces for each memory block to assist memory debugging tools; if the block is allocated and then immediately freed, both entries are removed from the log, which helps reduce the size of the log.
MallocStackLoggingNoCompactSame as MallocStackLogging but keeps all log entries.
+{:/} ### I/O Buffering Although unlikely, you may come across a situation where you want logging to `stdout` to be unbuffered (ensuring that the output has been written before continuing). You can set that with the `NSUnbufferedIO` environment variable: +{::nomarkdown} + - +
NameEffect
NSUnbufferedIOIf set to YES, Foundation will use unbuffered I/O for stdout (stderr is unbuffered by default).
NSUnbufferedIOIf set to YES, Foundation will use unbuffered I/O for stdout (stderr is unbuffered by default).
+{:/} -* * * +--- -Just as secret menus are bound by the implications of Gödel's Incompleteness Theorem, it is impossible to document all of the secret incantations to get special treatment in Xcode. However, perhaps you can find a few more (and learn a _ton_ about runtime internals) by perusing Apple's [Technical Note TN2239: iOS Debugging Magic][TN2239] and [Technical Note TN2124: OS X Debugging Magic][TN2124]. +Just as secret menus are bound by the implications of Gödel's Incompleteness Theorem, it is impossible to document all of the secret incantations to get special treatment in Xcode. However, perhaps you can find a few more (and learn a _ton_ about runtime internals) by perusing Apple's [Technical Note TN2239: iOS Debugging Magic][tn2239] and [Technical Note TN2124: OS X Debugging Magic][tn2124]. Hopefully, though, the secret knowledge you've been exposed to in this article will sustain you in your app endeavors. Use them wisely, and pass them onto your coworkers like an urban legend or juicy rumor. - -[TN2239]: https://developer.apple.com/library/ios/technotes/tn2239/_index.html -[TN2124]: https://developer.apple.com/library/mac/technotes/tn2124/_index.html +[tn2239]: https://developer.apple.com/library/ios/technotes/tn2239/_index.html +[tn2124]: https://developer.apple.com/library/mac/technotes/tn2124/_index.html diff --git a/2013-10-28-nshipster-quiz-4.md b/2013-10-28-nshipster-quiz-4.md index 9d9a8769..8a5caf70 100644 --- a/2013-10-28-nshipster-quiz-4.md +++ b/2013-10-28-nshipster-quiz-4.md @@ -1,6 +1,6 @@ --- title: "NSHipster Quiz #4" -author: Mattt Thompson +author: Mattt category: Trivia excerpt: "The fourth and final quiz of the year. Do you have what it takes to be the `NSArray -firstObject` among your peers?" status: @@ -9,7 +9,7 @@ status: The fourth and final NSHipster pub quiz of the year was held in the beautiful city of Amsterdam on October 22nd, with help from the good folks at [Appsterdam](http://appsterdam.rs), [The Big Nerd Ranch](http://www.bignerdranch.com/), and [Heroku](http://www.heroku.com). -The competition was fierce, but ultimately the team of [Mike Lee](https://twitter.com/bmf), [Judy Chen](https://twitter.com/judykitteh), [Eloy Dúran](https://twitter.com/alloy), [Alexander Repty](https://twitter.com/arepty), [Maxie Ketschau-Repty](https://twitter.com/Yumyoko), and [Sernin van de Krol](https://twitter.com/paneidos) were victorious. This was, by design, to be the highest-scoring of any pub quiz, with generous portions of extra points, and Team "[Graceful Hoppers](http://en.wikipedia.org/wiki/Grace_Hopper)" came through with an impressive 53 points (which, interestingly enough, only edged out the 2nd place team by ½ of a point). +The competition was fierce, but ultimately the team of [Mike Lee](https://twitter.com/bmf), [Judy Chen](https://twitter.com/judykitteh), [Eloy Dúran](https://twitter.com/alloy), [Alexander Repty](https://twitter.com/arepty), [Maxie Ketschau-Repty](https://twitter.com/Yumyoko), and [Sernin van de Krol](https://twitter.com/paneidos) were victorious. This was, by design, to be the highest-scoring of any pub quiz, with generous portions of extra points, and Team "[Graceful Hoppers](https://en.wikipedia.org/wiki/Grace_Hopper)" came through with an impressive 53 points (which, interestingly enough, only edged out the 2nd place team by ½ of a point). As always, you can play along at home or at work with your colleagues. Here are the rules: @@ -53,25 +53,25 @@ With the fluff out of the way, it's now time to dive into some hardcore Cocoa fu 9. Name the 4 Classes that conform to `` (1 pt. each) 10. What is the name of the method called by the following code: -~~~{objective-c} +```objc array[1] = @"foo"; -~~~ +``` Round 3: Picture Round - Indie Devs ----------------------------------- Following another tradition of the NSHipster quiz is everybody's favorite: the Picture Round! This time, the theme is indie developers. Earn up to 3 points for each set of pictures by naming the **founder**, the **name of the company** they're known for, and the **name of their flagship app** represented by the icon. -1. ![Question 1]({{ site.asseturl }}/quiz-4/nshipster-quiz-4-question-1.png) -2. ![Question 2]({{ site.asseturl }}/quiz-4/nshipster-quiz-4-question-2.png) -3. ![Question 3]({{ site.asseturl }}/quiz-4/nshipster-quiz-4-question-3.png) -4. ![Question 4]({{ site.asseturl }}/quiz-4/nshipster-quiz-4-question-4.png) -5. ![Question 5]({{ site.asseturl }}/quiz-4/nshipster-quiz-4-question-5.png) -6. ![Question 6]({{ site.asseturl }}/quiz-4/nshipster-quiz-4-question-6.png) -7. ![Question 7]({{ site.asseturl }}/quiz-4/nshipster-quiz-4-question-7.png) -8. ![Question 8]({{ site.asseturl }}/quiz-4/nshipster-quiz-4-question-8.png) -9. ![Question 9]({{ site.asseturl }}/quiz-4/nshipster-quiz-4-question-9.png) -10. ![Question 10]({{ site.asseturl }}/quiz-4/nshipster-quiz-4-question-10.png) +1. ![Question 1]({% asset quiz-4/nshipster-quiz-4-question-1.png @path %}) +2. ![Question 2]({% asset quiz-4/nshipster-quiz-4-question-2.png @path %}) +3. ![Question 3]({% asset quiz-4/nshipster-quiz-4-question-3.png @path %}) +4. ![Question 4]({% asset quiz-4/nshipster-quiz-4-question-4.png @path %}) +5. ![Question 5]({% asset quiz-4/nshipster-quiz-4-question-5.png @path %}) +6. ![Question 6]({% asset quiz-4/nshipster-quiz-4-question-6.png @path %}) +7. ![Question 7]({% asset quiz-4/nshipster-quiz-4-question-7.png @path %}) +8. ![Question 8]({% asset quiz-4/nshipster-quiz-4-question-8.png @path %}) +9. ![Question 9]({% asset quiz-4/nshipster-quiz-4-question-9.png @path %}) +10. ![Question 10]({% asset quiz-4/nshipster-quiz-4-question-10.png @path %}) Round 4: NSAnagram ------------------ diff --git a/2013-11-04-xctool.md b/2013-11-04-xctool.md index d71482b1..3749c0b2 100644 --- a/2013-11-04-xctool.md +++ b/2013-11-04-xctool.md @@ -1,10 +1,11 @@ --- title: xctool -author: Mattt Thompson +author: Mattt category: Open Source excerpt: "Control the build system, and you control the destiny of the language, its ecosystem, and community." +retired: true status: - swift: n/a + swift: n/a --- > Control the build system, and you control the destiny of the language, its ecosystem, and community. @@ -13,11 +14,13 @@ Objective-C has changed so very much in such a very short timespan. In just a fe [CocoaPods](http://cocoapods.org), in particular, exemplifies the compounding influences of technology and community. Now two years into the project, there are over 2,700 community-submitted libraries and frameworks that can be integrated into your own project with a simple `pod install` command. -Dependency management is just one of the many aspects of iOS and OS X development that have been dramatically improved by the community. Other examples include [provisioning and distribution automation](http://nomad-cli.com), [bug reporting](http://www.quickradar.com), and [documentation](http://cocoadocs.org). +Dependency management is just one of the many aspects of iOS and OS X development that have been dramatically improved by the community. Other examples include [provisioning and distribution automation](http://nomad-cli.com), +[bug reporting](https://nshipster.com/bug-reporting), +and [documentation](http://cocoadocs.org). But this week, our focus is on a tool that redefines the actual build process itself, to serve as the foundation for a new generation of tooling and integration: `xctool`. -* * * +--- [`xctool`](https://github.com/facebook/xctool) is an open source project by [Fred Potter](https://github.com/fpotter) that came from his work on build system automation at [Facebook](https://github.com/facebook). It is a drop-in replacement for [`xcodebuild`](https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man1/xcodebuild.1.html), the utility underlying Xcode.app itself. @@ -31,13 +34,15 @@ Rather than belabor the point by enumerating all of the flaws in this decade-old The first you'll notice about `xctool` is its gorgeous, colorized output. -![xctool in Action]({{ site.asseturl }}/xctool-example.gif) +![xctool in Action]({% asset xctool-example.gif @path %}) As consumers of Apple hardware and software ourselves, the role of design cannot be under-stated. In this respect, `xctool` absolutely nails it. Every step of the build process is neatly organized and reported in a way that is understandable and visually appealing, with ANSI colorization and a splash of Unicode ornamentation. But xctool's beauty is not just skin-deep: build progress can also be reported in formats that can be read by other tools: - xctool -reporter plain:output.txt build +```terminal +$ xctool -reporter plain:output.txt build +``` #### Reporters @@ -62,13 +67,13 @@ To add Travis CI to your own Objective-C project, [create an account and webserv #### .travis.yml -~~~{yaml} +```yaml language: objective-c before_install: - - brew update - - brew install xctool + - brew update + - brew install xctool script: xctool -workspace MyApp.xcworkspace -scheme MyApp test -~~~ +``` ### OCLint @@ -78,9 +83,9 @@ Remember `xctool`'s `json-compilation-database` reporter option? Well, that outp At the time of writing, there is still a ways to go before this becomes widely adopted, but my hope is that, now that the cat is out of the bag, some enterprising individuals might work together to make an insanely great experience around this promising tool. -* * * +--- -Just as with the growth population within cities, infrastructure makes all of difference. One way or another, whether by local [government mandate](http://en.wikipedia.org/wiki/Commissioners'_Plan_of_1811), [emergent self-organization](http://en.wikipedia.org/wiki/Kowloon_Walled_City), or somewhere in-between, environments are changed to accommodate this growth. +Just as with the growth population within cities, infrastructure makes all of difference. One way or another, whether by local [government mandate](https://en.wikipedia.org/wiki/Commissioners'_Plan_of_1811), [emergent self-organization](https://en.wikipedia.org/wiki/Kowloon_Walled_City), or somewhere in-between, environments are changed to accommodate this growth. Objective-C has and continues to undergo rapid growth based on the popularity of iOS. It is up to the community, in working with (and sometimes against) Apple to create infrastructure necessary to integrate so many new developers. How successful we are in this respect determines how we understand and communicate our roles and responsibilities as professional developers. diff --git a/2013-11-11-nsformatter.md b/2013-11-11-nsformatter.md deleted file mode 100644 index bce157e7..00000000 --- a/2013-11-11-nsformatter.md +++ /dev/null @@ -1,670 +0,0 @@ ---- -title: NSFormatter -author: Mattt Thompson -category: Cocoa -tags: nshipster, popular -excerpt: "Conversion is the tireless errand of software development. Most programming tasks boil down to some variation of transforming data into something more useful." -revisions: - "2014-06-30": Converted examples to Swift; added iOS 8 & OS X Yosemite formatter classes. - "2015-02-17": Converted remaining examples to Swift; reintroduced Objective-C examples; added Objective-C examples for new formatter classes. -status: - swift: 2.0 - reviewed: September 8, 2015 ---- - -Conversion is the tireless errand of software development. Most programming tasks boil down to some variation of transforming data into something more useful. - -In the case of user-facing software, converting data into human-readable form is an essential task, and a complex one at that. A user's preferred language, locale, calendar, or currency can all factor into how information should be displayed, as can other constraints, such as a label's dimensions. - -All of this is to say that sending `-description` to an object just isn't going to cut it in most circumstances. Even `+stringWithFormat:` is going to ultimately disappoint. No, the real tool for this job is `NSFormatter`. - -* * * - -`NSFormatter` is an abstract class for transforming data into a textual representation. It can also interpret valid textual representations back into data. - -Its origins trace back to `NSCell`, which is used to display information and accept user input in tables, form fields, and other views in AppKit. Much of the API design of `NSFormatter` reflects this. - -Foundation provides a number of concrete subclasses for `NSFormatter` (in addition to a single `NSFormatter` subclass provided in the MapKit framework): - -| Class | Availability | -|------------------------------|--------------------------------| -| `NSNumberFormatter` | iOS 2.0 / OS X Cheetah | -| `NSDateFormatter` | iOS 2.0 / OS X Cheetah | -| `NSByteCountFormatter` | iOS 6.0 / OS X Mountain Lion | -| `NSDateComponentsFormatter` | iOS 8.0 / OS X Yosemite | -| `NSDateIntervalFormatter` | iOS 8.0 / OS X Yosemite | -| `NSEnergyFormatter` | iOS 8.0 / OS X Yosemite | -| `NSMassFormatter` | iOS 8.0 / OS X Yosemite | -| `NSLengthFormatter` | iOS 8.0 / OS X Yosemite | -| `MKDistanceFormatter` | iOS 7.0 / OS X Mavericks | - -As some of the oldest members of the Foundation framework, `NSNumberFormatter` and `NSDateFormatter` are astonishingly well-suited to their respective domains, in that way only decade-old software can. This tradition of excellence is carried by the most recent incarnations as well. - -> iOS 8 & OS X Yosemite more than _doubled_ the number of system-provided formatter classes, which is pretty remarkable. - -## NSNumberFormatter - -`NSNumberFormatter` handles every aspect of number formatting imaginable, from mathematical and scientific notation, to currencies and percentages. Nearly everything about the formatter can be customized, whether it's the currency symbol, grouping separator, number of significant digits, rounding behavior, fractions, character for infinity, string representation for `0`, or maximum / minimum values. It can even write out numbers in several languages! - -### Number Styles - -When using an `NSNumberFormatter`, the first order of business is to determine what kind of information you're displaying. Is it a price? Is this a whole number, or should decimal values be shown? - -`NSNumberFormatter` can be configured for any one of the following formats, with the `numberStyle` property. - -To illustrate the differences between each style, here is how the number `12345.6789` would be displayed for each: - -#### `NSNumberFormatterStyle` - -| Formatter Style | Output | -|--------------------|---------------------------------------------------------------------| -| `NoStyle` | 12346 | -| `DecimalStyle` | 12345.6789 | -| `CurrencyStyle` | $12345.68 | -| `PercentStyle` | 1234567% | -| `ScientificStyle` | 1.23456789E4 | -| `SpellOutStyle` | twelve thousand three hundred forty-five point six seven eight nine | - -### Locale Awareness - -By default, `NSNumberFormatter` will format according to the current locale settings, which determines things like currency symbol ($, £, €, etc.) and whether to use "," or "." as the decimal separator. - -~~~{swift} -let formatter = NSNumberFormatter() -formatter.numberStyle = NSNumberFormatterStyle.CurrencyStyle - -for identifier in ["en_US", "fr_FR", "ja_JP"] { - formatter.locale = NSLocale(localeIdentifier: identifier) - print("\(identifier) \(formatter.stringFromNumber(1234.5678))") -} -~~~ - -~~~{objective-c} -NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init]; -[numberFormatter setNumberStyle:NSNumberFormatterCurrencyStyle]; - -for (NSString *identifier in @[@"en_US", @"fr_FR", @"ja_JP"]) { - numberFormatter.locale = [NSLocale localeWithLocaleIdentifier:identifier]; - NSLog(@"%@: %@", identifier, [numberFormatter stringFromNumber:@(1234.5678)]); -} -~~~ - -| Locale | Formatted Number | -|---------|---------------------| -| `en_US` | $1,234.57 | -| `fr_FR` | 1 234,57 € | -| `ja_JP` | ¥1,235 | - -> All of those settings can be overridden on an individual basis, but for most apps, the best strategy would be deferring to the locale's default settings. - -### Rounding & Significant Digits - -In order to prevent numbers from getting annoyingly pedantic (_"thirty-two point three three, repeating, of course..."_), make sure to get a handle on `NSNumberFormatter`'s rounding behavior. - -The easiest way to do this, would be to set the `usesSignificantDigits` property to `false`, and then set minimum and maximum number of significant digits appropriately. For example, a number formatter used for approximate distances in directions, would do well with significant digits to the tenths place for miles or kilometers, but only the ones place for feet or meters. - -> For anything more advanced, an `NSDecimalNumberHandler` object can be passed as the `roundingBehavior` property of a number formatter. - -## NSDateFormatter - -`NSDateFormatter` is the be all and end all of getting textual representations of both dates and times. - -### Date & Time Styles - -The most important properties for an `NSDateFormatter` object are its `dateStyle` and `timeStyle`. Like `NSNumberFormatter numberStyle`, these styles provide common preset configurations for common formats. In this case, the various formats are distinguished by their specificity (more specific = longer). - -Both properties share a single set of `enum` values: - -#### `NSDateFormatterStyle` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
StyleDescriptionExamples
DateTime
NoStyleSpecifies no style.
ShortStyleSpecifies a short style, typically numeric only.11/23/373:30pm
MediumStyleSpecifies a medium style, typically with abbreviated text.Nov 23, 19373:30:32pm
LongStyleSpecifies a long style, typically with full text.November 23, 19373:30:32pm
FullStyleSpecifies a full style with complete details.Tuesday, April 12, 1952 AD3:30:42pm PST
- -`dateStyle` and `timeStyle` are set independently. For example, to display just the time, an `NSDateFormatter` would be configured with a `dateStyle` of `NoStyle`: - -~~~{swift} -let formatter = NSDateFormatter() -formatter.dateStyle = .NoStyle -formatter.timeStyle = .MediumStyle - -let string = formatter.stringFromDate(NSDate()) -// 10:42:21am -~~~ - -~~~{objective-c} -NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; -[formatter setDateStyle:NSDateFormatterNoStyle]; -[formatter setTimeStyle:NSDateFormatterMediumStyle]; - -NSLog(@"%@", [formatter stringFromDate:[NSDate date]]); -// 12:11:19pm -~~~ - -Whereas setting both to `LongStyle` yields the following: - -~~~{swift} -let formatter = NSDateFormatter() -formatter.dateStyle = .LongStyle -formatter.timeStyle = .LongStyle - -let string = formatter.stringFromDate(NSDate()) -// Monday June 30, 2014 10:42:21am PST -~~~ - -~~~{objective-c} -NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; -[formatter setDateStyle:NSDateFormatterLongStyle]; -[formatter setTimeStyle:NSDateFormatterLongStyle]; - -NSLog(@"%@", [formatter stringFromDate:[NSDate date]]); -// Monday, November 11, 2013 12:11:19pm PST -~~~ - -As you might expect, each aspect of the date format can alternatively be configured individually, a la carte. For any aspiring time wizards `NSDateFormatter` has a bevy of different knobs and switches to play with. - -### Relative Formatting - -As of iOS 4 / OS X Snow Leopard, `NSDateFormatter` supports relative date formatting for certain locales with the `doesRelativeDateFormatting` property. Setting this to `true` would format the date of `NSDate()` to "Today". - -## NSByteCounterFormatter - -For apps that work with files, data in memory, or information downloaded from a network, `NSByteCounterFormatter` is a must-have. All of the great information unit formatting behavior seen in Finder and other OS X apps is available with `NSByteCounterFormatter`, without any additional configuration. - -`NSByteCounterFormatter` takes a raw number of bytes and formats it into more meaningful units. For example, rather than bombarding a user with a ridiculous quantity, like "8475891734 bytes", a formatter can make a more useful approximation of "8.48 GB": - -~~~{swift} -let formatter = NSByteCountFormatter() -let byteCount = 8475891734 -let string = formatter.stringFromByteCount(Int64(byteCount)) -// 8.48 GB -~~~ -~~~{objective-c} -NSByteCountFormatter *formatter = [[NSByteCountFormatter alloc] init]; -long long byteCount = 8475891734; -NSLog(@"%@", [formatter stringFromByteCount:byteCount]); -// 8.48 GB -~~~ - -By default, specifying a `0` byte count will yield a localized string like "Zero KB". For a more consistent format, set `allowsNonnumericFormatting` to `false`: - -~~~{swift} -let formatter = NSByteCountFormatter() -let byteCount = 0 - -formatter.stringFromByteCount(Int64(byteCount)) -// Zero KB - -formatter.allowsNonnumericFormatting = false -formatter.stringFromByteCount(Int64(byteCount)) -// 0 bytes -~~~ -~~~{objective-c} -NSByteCountFormatter *formatter = [[NSByteCountFormatter alloc] init]; -long long byteCount = 0; - -NSLog(@"%@", [formatter stringFromByteCount:byteCount]); -// Zero KB - -formatter.allowsNonnumericFormatting = NO; -NSLog(@"%@", [formatter stringFromByteCount:byteCount]); -// 0 bytes -~~~ - -### Count Style - -One might think that dealing with bytes in code (which is, you know, _a medium of bytes_) would be a piece of cake, but in reality, even determining how many bytes are in a kilobyte remains a contentious and confusing matter. ([Obligatory XKCD link](http://xkcd.com/394/)) - -In [SI](http://en.wikipedia.org/wiki/International_System_of_Units), the "kilo" prefix multiplies the base quantity by 1000 (i.e. 1km == 1000 m). However, being based on a binary number system, a more convenient convention has been to make a kilobyte equal to 210 bytes instead (i.e. 1KB == 1024 bytes). While the 2% difference is negligible at lower quantities, this confusion has significant implications when, for example, determining how much space is available on a 1TB drive (either 1000GB or 1024 GB). - -To complicate matters further, this binary prefix was codified into the [Kibibyte](http://en.wikipedia.org/wiki/Kibibyte) standard by the [IEC](http://en.wikipedia.org/wiki/International_Electrotechnical_Commission) in 1998... which is summarily ignored by the [JEDEC](http://en.wikipedia.org/wiki/JEDEC_memory_standards#Unit_prefixes_for_semiconductor_storage_capacity), the trade and engineering standardization organization representing the actual manufacturers of storage media. The result is that one can represent information as either `1kB`, `1KB`, or `1KiB`. ([Another obligatory XKCD link](http://xkcd.com/927/)) - -Rather than get caught up in all of this, simply use the most appropriate count style for your particular use case: - -#### NSByteCountFormatterCountStyle - -| `File` | Specifies display of file byte counts. The actual behavior for this is platform-specific; on OS X Mountain Lion, this uses the binary style, but that may change over time. | -| `Memory` | Specifies display of memory byte counts. The actual behavior for this is platform-specific; on OS X Mountain Lion, this uses the binary style, but that may change over time. | - -In most cases, it is better to use `File` or `Memory`, however decimal or binary byte counts can be explicitly specified with either of the following values: - -| `Decimal` | Causes 1000 bytes to be shown as 1 KB. | -| `Binary` | Causes 1024 bytes to be shown as 1 KB. | - -## Date & Time Interval Formatters - -`NSDateFormatter` is great for points in time, but when it comes to dealing with date or time ranges, Foundation was without any particularly great options. That is, until the introduction of `NSDateComponentsFormatter` & `NSDateIntervalFormatter`. - -## NSDateComponentsFormatter - -As the name implies, `NSDateComponentsFormatter` works with `NSDateComponents`, which was covered in [a previous NSHipster article](http://nshipster.com/nsdatecomponents/). An `NSDateComponents` object is a container for representing a combination of discrete calendar quantities, such as "1 day and 2 hours". `NSDateComponentsFormatter` provides localized representations of `NSDateComponents` objects in several different formats: - -~~~{swift} -let formatter = NSDateComponentsFormatter() -formatter.unitsStyle = .Full - -let components = NSDateComponents() -components.day = 1 -components.hour = 2 - -let string = formatter.stringFromDateComponents(components) -// 1 day, 2 hours -~~~ -~~~{objective-c} -NSDateComponentsFormatter *formatter = [[NSDateComponentsFormatter alloc] init]; -formatter.unitsStyle = NSDateComponentsFormatterUnitsStyleFull; - -NSDateComponents *components = [[NSDateComponents alloc] init]; -components.day = 1; -components.hour = 2; - -NSLog(@"%@", [formatter stringFromDateComponents:components]); -// 1 day, 2 hours -~~~ - -#### `NSDateComponentsFormatterUnitsStyle` - -| Unit Style | Example | -|-----------------|-------------------------------------------------------------------| -| `Positional` | "1:10" | -| `Abbreviated` | "1h 10m" | -| `Short` | "1hr 10min" | -| `Full` | "1 hour, 10 minutes" | -| `SpellOut` | "One hour, ten minutes" | - -### NSDateIntervalFormatter - -Like `NSDateComponentsFormatter`, `NSDateIntervalFormatter` deals with ranges of time, but specifically for time intervals between a start and end date: - -~~~{swift} -let formatter = NSDateIntervalFormatter() -formatter.dateStyle = .NoStyle -formatter.timeStyle = .ShortStyle - -let fromDate = NSDate() -let toDate = fromDate.dateByAddingTimeInterval(10000) - -let string = formatter.stringFromDate(fromDate, toDate: toDate) -// 5:49 - 8:36 PM -~~~ -~~~{objective-c} -NSDateIntervalFormatter *formatter = [[NSDateIntervalFormatter alloc] init]; -formatter.dateStyle = NSDateIntervalFormatterNoStyle; -formatter.timeStyle = NSDateIntervalFormatterShortStyle; - -NSDate *fromDate = [NSDate date]; -NSDate *toDate = [fromDate dateByAddingTimeInterval:10000]; - -NSLog(@"%@", [formatter stringFromDate:fromDate toDate:toDate]); -// 5:49 - 8:36 PM -~~~ - -#### `NSDateIntervalFormatterStyle` - -| Formatter Style | Time Output | Date Output | -|---------------------|-------------------------------------|-------------------------------| -| `NoStyle` | | | -| `ShortStyle` | 5:51 AM - 7:37 PM | 6/30/14 - 7/11/14 | -| `MediumStyle` | 5:51:49 AM - 7:38:29 PM | Jun 30, 2014 - Jul 11, 2014 | -| `LongStyle` | 6:02:54 AM GMT-8 - 7:49:34 PM GMT-8 | June 30, 2014 - July 11, 2014 | -| `FullStyle` | 6:03:28 PM Pacific Standard Time - 7:50:08 PM Pacific Standard Time | Monday, June 30, 2014 - Friday, July 11, 2014 | - -`NSDateIntervalFormatter` and `NSDateComponentsFormatter` are useful for displaying regular and pre-defined ranges of times such as the such as the opening hours of a business, or frequency or duration of calendar events. - -> In the case of displaying business hours, such as "Mon – Fri: 8:00 AM – 10:00 PM", use the `weekdaySymbols` of an `NSDateFormatter` to get the localized names of the days of the week. - -## Mass, Length, & Energy Formatters - -Prior to iOS 8 / OS X Yosemite, working with physical quantities was left as an exercise to the developer. However, with the introduction of HealthKit, this functionality is now provided in the standard library. - -### NSMassFormatter - -Although the fundamental unit of physical existence, mass is pretty much relegated to tracking the weight of users in HealthKit. - -> Yes, mass and weight are different, but this is programming, not science class, so stop being pedantic. - -~~~{swift} -let massFormatter = NSMassFormatter() -let kilograms = 60.0 -print(massFormatter.stringFromKilograms(kilograms)) // "132 lb" -~~~ -~~~{objective-c} -NSMassFormatter *massFormatter = [[NSMassFormatter alloc] init]; -double kilograms = 60; -NSLog(@"%@", [massFormatter stringFromKilograms:kilograms]); // "132 lb" -~~~ - -### NSLengthFormatter - -`NSLengthFormatter` can be thought of as a more useful version of `MKDistanceFormatter`, with more unit options and formatting options. - -~~~{swift} -let lengthFormatter = NSLengthFormatter() -let meters = 5_000.0 -print(lengthFormatter.stringFromMeters(meters)) // "3.107 mi" -~~~ -~~~{objective-c} -NSLengthFormatter *lengthFormatter = [[NSLengthFormatter alloc] init]; -double meters = 5000; -NSLog(@"%@", [lengthFormatter stringFromMeters:meters]); // "3.107 mi" -~~~ - -### NSEnergyFormatter - -Rounding out the new `NSFormatter` subclasses added for HealthKit is `NSEnergyFormatter`, which formats energy in Joules, the raw unit of work for exercises, and Calories, which is used when working with nutrition information. - -~~~{swift} -let energyFormatter = NSEnergyFormatter() -energyFormatter.forFoodEnergyUse = true - -let joules = 10_000.0 -print(energyFormatter.stringFromJoules(joules)) // "2.39 Cal" -~~~ -~~~{objective-c} -NSEnergyFormatter *energyFormatter = [[NSEnergyFormatter alloc] init]; -energyFormatter.forFoodEnergyUse = YES; - -double joules = 10000; -NSLog(@"%@", [energyFormatter stringFromJoules:joules]); // "2.39 Cal" -~~~ - ---- - -## Re-Using Formatter Instances - -Perhaps the most critical detail to keep in mind when using formatters is that they are _extremely_ expensive to create. Even just an `alloc init` of an `NSNumberFormatter` in a tight loop is enough to bring an app to its knees. - -Therefore, it's strongly recommended that formatters be created once, and re-used as much as possible. - -If it's just a single method using a particular formatter, a static instance is a good strategy: - -~~~{swift} -// a nested struct can have a static property, -// created only the first time it's encountered. -func fooWithNumber(number: NSNumber) { - struct NumberFormatter { - static let formatter: NSNumberFormatter = { - let formatter = NSNumberFormatter() - formatter.numberStyle = .DecimalStyle - return formatter - }() - } - - let string = NumberFormatter.formatter.stringFromNumber(number) - // ... -} -~~~ - -~~~{objective-c} -// dispatch_once guarantees that the specified block is called -// only the first time it's encountered. -- (void)fooWithNumber:(NSNumber *)number { - static NSNumberFormatter *_numberFormatter = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - _numberFormatter = [[NSNumberFormatter alloc] init]; - [_numberFormatter setNumberStyle:NSNumberFormatterDecimalStyle]; - }); - - NSString *string = [_numberFormatter stringFromNumber:number]; - - // ... -} -~~~ - -If the formatter is used across several methods in the same class, that static instance can be refactored into a singleton method in Objective-C or a static type property in Swift: - -~~~{swift} -static let numberFormatter: NSNumberFormatter = { - let formatter = NSNumberFormatter() - formatter.numberStyle = .DecimalStyle - return formatter -}() -~~~ - -~~~{objective-c} -+ (NSNumberFormatter *)numberFormatter { - static NSNumberFormatter *_numberFormatter = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - _numberFormatter = [[NSNumberFormatter alloc] init]; - [_numberFormatter setNumberStyle:NSNumberFormatterDecimalStyle]; - }); - - return _numberFormatter; -} -~~~ - -If the same formatter is privately implemented across several classes, one could either expose it publicly in one of the classes, or implement the static singleton method in a category on `NSNumberFormatter`. - -> Prior to iOS 7 and OS X Mavericks, `NSDateFormatter` & `NSNumberFormatter` were not thread safe. Under these circumstances, the safest way to reuse formatter instances was with a thread dictionary: - -~~~{objective-c} -NSMutableDictionary *threadDictionary = [[NSThread currentThread] threadDictionary]; -NSDateFormatter *dateFormatter = threadDictionary[@"dateFormatter"]; -if (!dateFormatter) { - dateFormatter = [[NSDateFormatter alloc] init]; - dateFormatter.locale = [NSLocale currentLocale]; - dateFormatter.dateStyle = NSDateFormatterLongStyle; - dateFormatter.timeStyle = NSDateFormatterShortStyle; - threadDictionary[@"dateFormatter"] = dateFormatter; -} - -return dateFormatter; -~~~ - -## Formatter Context - -Another addition to formatters in iOS 8 & OS X Yosemite is the idea of formatter _contexts_. This allows the formatted output to be correctly integrated into the localized string. The most salient application of this is the letter casing of formatted output at different parts of a sentence in western locales, such as English. For example, when appearing at the beginning of a sentence or by itself, the first letter of formatted output would be capitalized, whereas it would be lowercase in the middle of a sentence. - -A `context` property is available for `NSDateFormatter`, `NSNumberFormatter`, `NSDateComponentsFormatter`, and `NSByteCountFormatter`, with the following values: - -| Formatting Context | Output | -|-------------------------|-----------------| -| `Standalone` | "About 2 hours" | -| `ListItem` | "About 2 hours" | -| `BeginningOfSentence` | "About 2 hours" | -| `MiddleOfSentence` | "about 2 hours" | -| `Dynamic` | _(Depends)_ | - -In cases where localizations may change the position of formatted information within a string, the `Dynamic` value will automatically change depending on where it appears in the text. - -- - - - -## Third Party Formatters - -An article on `NSFormatter` would be remiss without mention of some of the third party subclasses that have made themselves mainstays of everyday app development: [ISO8601DateFormatter](http://boredzo.org/iso8601dateformatter/) & [FormatterKit](https://github.com/mattt/FormatterKit) - -### ISO8601DateFormatter - -Created by [Peter Hosey](https://twitter.com/boredzo), [ISO8601DateFormatter](https://github.com/boredzo/iso-8601-date-formatter) has become the de facto way of dealing with [ISO 8601 timestamps](http://en.wikipedia.org/wiki/ISO_8601), used as the interchange format for dates by webservices ([Yet another obligatory XKCD link](http://xkcd.com/1179/)). - -Although Apple provides [official recommendations on parsing internet dates](https://developer.apple.com/library/ios/qa/qa1480/_index.html), the reality of formatting quirks across makes the suggested `NSDateFormatter`-with-`en_US_POSIX`-locale approach untenable for real-world usage. `ISO8601DateFormatter` offers a simple, robust interface for dealing with timestamps: - -~~~{swift} -let formatter = ISO8601DateFormatter() -let timestamp = "2014-06-30T08:21:56+08:00" -let date = formatter.dateFromString(timestamp) -~~~ - -> Although not an `NSFormatter` subclass, [TransformerKit](https://github.com/mattt/TransformerKit) offers an extremely performant alternative for parsing and formatting both ISO 8601 and RFC 2822 timestamps. - -### FormatterKit - -[FormatterKit](https://github.com/mattt/FormatterKit) has great examples of `NSFormatter` subclasses for use cases not currently covered by built-in classes, such as localized addresses, arrays, colors, locations, and ordinal numbers, and URL requests. It also boasts localization in 23 different languages, making it well-suited to apps serving every major market. - -#### TTTAddressFormatter - -~~~{swift} -let formatter = TTTAddressFormatter() -formatter.locale = NSLocale(localeIdentifier: "en_GB") - -let street = "221b Baker St" -let locality = "Paddington" -let region = "Greater London" -let postalCode = "NW1 6XE" -let country = "United Kingdom" - -let string = formatter.stringFromAddressWithStreet(street: street, locality: locality, region: region, postalCode: postalCode, country: country) -// 221b Baker St / Paddington / Greater London / NW1 6XE / United Kingdom -~~~ -~~~{objective-c} -TTTAddressFormatter *formatter = [[TTTAddressFormatter alloc] init]; -[formatter setLocale:[NSLocale localeWithLocaleIdentifier:@"en_GB"]]; - -NSString *street = @"221b Baker St"; -NSString *locality = @"Paddington"; -NSString *region = @"Greater London"; -NSString *postalCode = @"NW1 6XE"; -NSString *country = @"United Kingdom"; - -NSLog(@"%@", [formatter stringFromAddressWithStreet:street locality:locality region:region postalCode:postalCode country:country]); -// 221b Baker St / Paddington / Greater London / NW1 6XE / United Kingdom -~~~ - -#### TTTArrayFormatter - -~~~{swift} -let formatter = TTTArrayFormatter() -formatter.usesAbbreviatedConjunction = true // Use '&' instead of 'and' -formatter.usesSerialDelimiter = true // Omit Oxford Comma - -let array = ["Russel", "Spinoza", "Rawls"] -let string = formatter.stringFromArray(array) -// "Russell, Spinoza & Rawls" -~~~ -~~~{objective-c} -TTTArrayFormatter *formatter = [[TTTArrayFormatter alloc] init]; -formatter.usesAbbreviatedConjunction = YES; // Use '&' instead of 'and' -formatter.usesSerialDelimiter = YES; // Omit Oxford Comma - -NSArray *array = @[@"Russel", @"Spinoza", @"Rawls"]; -NSLog(@"%@", [formatter stringFromArray:array]); -// "Russell, Spinoza & Rawls" -~~~ - -#### TTTColorFormatter - -~~~{swift} -let formatter = TTTColorFormatter() -let color = UIColor.orangeColor() -let hex = formatter.hexadecimalStringFromColor(color); -// #ffa500 -~~~ -~~~{objective-c} -TTTColorFormatter *formatter = [[TTTColorFormatter alloc] init]; -UIColor *color = [UIColor orangeColor]; -NSLog(@"%@", [formatter hexadecimalStringFromColor:color]); -// #ffa500 -~~~ - -#### TTTLocationFormatter - -~~~{swift} -let formatter = TTTLocationFormatter() -formatter.numberFormatter.maximumSignificantDigits = 4 -formatter.bearingStyle = TTTBearingAbbreviationWordStyle -formatter.unitSystem = TTTImperialSystem - -let pittsburgh = CLLocation(latitude: 40.4405556, longitude: -79.9961111) -let austin = CLLocation(latitude: 30.2669444, longitude: -97.7427778) -let string = formatter.stringFromDistanceAndBearingFromLocation(pittsburgh, toLocation: austin) -// "1,218 miles SW" -~~~ -~~~{objective-c} -TTTLocationFormatter *formatter = [[TTTLocationFormatter alloc] init]; -formatter.numberFormatter.maximumSignificantDigits = 4; -formatter.bearingStyle = TTTBearingAbbreviationWordStyle; -formatter.unitSystem = TTTImperialSystem; - -CLLocation *pittsburgh = [[CLLocation alloc] initWithLatitude:40.4405556 longitude:-79.9961111]; -CLLocation *austin = [[CLLocation alloc] initWithLatitude:30.2669444 longitude:-97.7427778]; -NSLog(@"%@", [formatter stringFromDistanceAndBearingFromLocation:pittsburgh toLocation:austin]); -// "1,218 miles SW" -~~~ - -#### TTTOrdinalNumberFormatter - -~~~{swift} -let formatter = TTTOrdinalNumberFormatter() -formatter.locale = NSLocale(localeIdentifier: "fr_FR") -formatter.grammaticalGender = TTTOrdinalNumberFormatterMaleGender -let string = NSString(format: NSLocalizedString("You came in %@ place", comment: ""), formatter.stringFromNumber(2)) -// "Vous êtes arrivé à la 2e place!" -~~~ -~~~{objective-c} -TTTOrdinalNumberFormatter *formatter = [[TTTOrdinalNumberFormatter alloc] init]; -[formatter setLocale:[NSLocale localeWithLocaleIdentifier:@"fr_FR"]]; -[formatter setGrammaticalGender:TTTOrdinalNumberFormatterMaleGender]; -NSLog(@"%@", [NSString stringWithFormat:NSLocalizedString(@"You came in %@ place!", nil), [formatter stringFromNumber:@2]]); -// "Vous êtes arrivé à la 2e place!" -~~~ - -#### TTTURLRequestFormatter - -~~~{swift} -let request = NSMutableURLRequest(URL: NSURL(string: "http://nshipster.com")!) -request.HTTPMethod = "GET" -request.addValue("text/html", forHTTPHeaderField: "Accept") - -let command = TTTURLRequestFormatter.cURLCommandFromURLRequest(request) -// curl -X GET "https://nshipster.com/" -H "Accept: text/html" -~~~ -~~~{objective-c} -NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:@"http://www.example.com/"]]; -[request setHTTPMethod:@"GET"]; -[request addValue:@"text/html" forHTTPHeaderField:@"Accept"]; -NSLog(@"%@", [TTTURLRequestFormatter cURLCommandFromURLRequest:request]); -// curl -X GET "https://nshipster.com/" -H "Accept: text/html" -~~~ - -* * * - -If your app deals in numbers or dates (or time intervals or bytes or distance or length or energy or mass), `NSFormatter` is indispensable. Actually, if your app doesn't… then what _does_ it do, exactly? - -Presenting useful information to users is as much about content as presentation. Invest in learning all of the secrets of `NSNumberFormatter`, `NSDateFormatter`, and the rest of the Foundation formatter crew to get everything exactly how you want them. - -And if you find yourself with formatting logic scattered across your app, consider creating your own `NSFormatter` subclass to consolidate all of that business logic in one place. diff --git a/2013-11-18-filemanager.md b/2013-11-18-filemanager.md new file mode 100644 index 00000000..4f845b1f --- /dev/null +++ b/2013-11-18-filemanager.md @@ -0,0 +1,460 @@ +--- +title: FileManager +author: Mattt +category: Cocoa +tags: nshipster +excerpt: >- + `FileManager` offers a convenient way to create, read, move, copy, and delete + both files and directories, + whether they're on local or networked drives or iCloud ubiquitous containers. +revisions: + "2013-11-18": Original publication + "2018-10-29": Updated for Swift 4.2 +status: + swift: 4.2 + reviewed: October 29, 2018 +--- + +One of the most rewarding experiences you can have as a developer +is to teach young people how to program. +If you ever grow jaded by how fundamentally broken all software is, +there's nothing like watching a concept like recursion +_click_ for the first time +to offset your world-weariness. + +My favorite way to introduce the concept of programming +is to set out all the ingredients for a peanut butter and jelly sandwich +and ask the class give me instructions for assembly +as if I were a robot 🤖. +The punchline is that the computer +takes every instruction as _literally_ as possible, +often with unexpected results. +Ask the robot to "put peanut butter on the bread", +and you may end up with an unopened jar of Jif +flattening a sleeve of Wonder Bread. +Forget to specify which part of the bread to put the jelly on? +Don't be surprised if it ends up on the outer crust. +And so on. +_Kids love it._ + +The lesson of breaking down a complex process into discrete steps +is a great encapsulation of programming. +And the malicious compliance from lack of specificity +echoes the analogy of "programming as wish making" +from [our article about `numericCast(_:)`](/numericcast). + +But let's take the metaphor a step further, +and imagine that instead of commanding a single robot to +([`sudo`](https://xkcd.com/149/)) +make a sandwich, +you're writing instructions for a thousand different robots. +Big and small, fast and slow; +some have 4 arms instead of 2, +others hover in the air, +maybe a few read everything in reverse. +Consider what would happen if multiple robots tried to make a sandwich +at the same time. +Or imagine that your instructions +might be read by robots that won't be built for another 40 years, +by which time peanut butter is packaged in capsules +and jelly comes exclusively as a gas. + +That's kind of what it's like to interact with a file system. + +The only chance we have at making something that works +is to leverage the power of abstraction. +On Apple platforms, +this functionality is provided by the Foundation framework +by way of `FileManager`. + +We can't possibly cover everything there is to know +about working with file systems in a single article, +so this week, +let's take a look at the operations you're most likely to perform +when building an app. + +--- + +`FileManager` offers a convenient way to create, read, move, copy, and delete +both files and directories, +whether they're on local or networked drives or iCloud ubiquitous containers. + +The common currency for all of these operations are paths and file URLs. + +## Paths and File URLs + +Objects on the file system can be identified in a few different ways. +For example, each of the following represents +the location of the same text document: + +- Path: `/Users/NSHipster/Documents/article.md` +- File URL: `file:///Users/NSHipster/Documents/article.md` +- File Reference URL: `file:///.file/id=1234567.7654321/` + +Paths are slash-delimited (`/`) strings that designate a location +in the directory hierarchy. +File URLs are URLs with a `file://` scheme in addition to a file path. +File Reference URLs identify the location of a file +using a unique identifier separate from any directory structure. + +Of those, +you'll mostly deal with the first two, +which identify files and directories using a relational path. +That path may be absolute +and provide the full location of a resource from the root directory, +or it may be relative +and show how to get to a resource from a given starting point. +Absolute URLs begin with `/`, +whereas relative URLs begin with +`./` (the current directory), +`../` (the parent directory), or +`~` (the current user's home directory). + +`FileManager` has methods that accept both paths and URLs --- +often with variations of the same method for both. +In general, the use of URLs is preferred to paths, +as they're more flexible to work with. +(it's also easier to convert from a URL to a path than vice versa). + +## Locating Files and Directories + +The first step to working with a file or directory +is locating it on the file system. +Standard locations vary across different platforms, +so rather than manually constructing paths like `/System` or `~/Documents`, +you use the `FileManager` method `url(for:in:appropriateFor:create:)` +to locate the appropriate location for what you want. + +The first parameter takes one of the values specified by +[`FileManager.SearchPathDirectory`](https://developer.apple.com/documentation/foundation/filemanager/searchpathdirectory). +These determine what kind of standard directory you're looking for, +like "Documents" or "Caches". + +The second parameter passes a +[`FileManager.SearchPathDomainMask`](https://developer.apple.com/documentation/foundation/filemanager/searchpathdomainmask) value, +which determines the scope of where you're looking for. +For example, +`.applicationDirectory` might refer to `/Applications` in the local domain +and `~/Applications` in the user domain. + +```swift +let documentsDirectoryURL = + try FileManager.default.url(for: .documentDirectory, + in: .userDomainMask, + appropriateFor: nil, + create: false) +``` + +```objc +NSFileManager *fileManager = [NSFileManager defaultManager]; +NSString *documentsPath = + [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject]; +NSString *filePath = [documentsPath stringByAppendingPathComponent:@"file.txt"]; +``` + +{% info %} + +Files and directories can have alternate names from what's encoded by the path. + +For example, most macOS system directories are localized; +although a user's photos located at `~/Photos`, +the `~/Photos/.localized` file can change how the folder is named in Finder +and Open / Save panels. +An app can also provide locale-specific names for itself +and directories it creates. + +Another example of this is when files +are configured to hide their file extension. +(You've probably encountered this at some point, +with some degree of bewilderment). + +Long story short, when displaying the name of a file or directory to the user, +don't simply take the last path component. +Instead, call the method `displayName(atPath:)`: + +```swift +let directoryURL: URL = <#/path/to/directory#> + +// Bad +let filename = directoryURL.pathComponents.last + +// Good +let filename = FileManager.default.displayName(atPath: url.path) +``` + +{% endinfo %} + +## Determining Whether a File Exists + +You might check to see if a file exists at a location before trying to read it, +or want to avoid overwriting an existing one. +To do this, call the `fileExists(atPath:)` method: + +```swift +let fileURL: URL = <#/path/to/file#> +let fileExists = FileManager.default.fileExists(atPath: fileURL.path) +``` + +```objc +NSURL *fileURL = <#/path/to/file#>; +NSFileManager *fileManager = [NSFileManager defaultManager]; +BOOL fileExists = [fileManager fileExistsAtPath:[fileURL path]]; +``` + +## Getting Information About a File + +The file system stores various pieces of metadata +about each file and directory in the system. +You can access them using the `FileManager` method `attributesOfItem(atPath:)`. +The resulting dictionary contains attributes keyed by `FileAttributeKey` values, +including `.creationDate`: + +```swift +let fileURL: URL = <#/path/to/file#> +let attributes = + FileManager.default.attributesOfItem(atPath: fileURL.path) +let creationDate = attributes[.creationDate] +``` + +```objc +NSURL *fileURL = <#/path/to/file#>; +NSFileManager *fileManager = [NSFileManager defaultManager]; + +NSError *error = nil; +NSDictionary *attributes = [fileManager attributesOfItemAtPath:[fileURL path] + error:&error]; +NSDate *creationDate = attributes[NSFileCreationDate]; +``` + +{% info %} +File attributes used to be keyed by string constants, +which made them hard to discover through autocompletion or documentation. +Fortunately, it's now easy to +[see everything that's available](https://developer.apple.com/documentation/foundation/fileattributekey). +{% endinfo %} + +## Listing Files in a Directory + +To list the contents of a directory, +call the `FileManager` method +`contentsOfDirectory(at:includingPropertiesForKeys:options:)`. +If you intend to access any metadata properties, +as described in the previous section +(for example, get the modification date of each file in a directory), +specify those here to ensure that those attributes are cached. +The `options` parameter of this method allows you to skip +hidden files and/or descendants. + +```swift +let directoryURL: URL = <#/path/to/directory#> +let contents = + try FileManager.default.contentsOfDirectory(at: directoryURL, + includingPropertiesForKeys: nil, + options: [.skipsHiddenFiles]) +for file in contents { + <#...#> +} +``` + +```objc +NSFileManager *fileManager = [NSFileManager defaultManager]; +NSURL *bundleURL = [[NSBundle mainBundle] bundleURL]; +NSArray *contents = [fileManager contentsOfDirectoryAtURL:bundleURL + includingPropertiesForKeys:@[] + options:NSDirectoryEnumerationSkipsHiddenFiles + error:nil]; + +NSPredicate *predicate = [NSPredicate predicateWithFormat:@"pathExtension == 'png'"]; +for (NSURL *fileURL in [contents filteredArrayUsingPredicate:predicate]) { + // Enumerate each .png file in directory +} +``` + +### Recursively Enumerating Files In A Directory + +If you want to go through each subdirectory at a particular location recursively, +you can do so by creating a `FileManager.DirectoryEnumerator` object +with the `enumerator(atPath:)` method: + +```swift +let directoryURL: URL = <#/path/to/directory#> + +if let enumerator = + FileManager.default.enumerator(atPath: directoryURL.path) +{ + for case let path as String in enumerator { + // Skip entries with '_' prefix, for example + if path.hasPrefix("_") { + enumerator.skipDescendants() + } + } +} +``` + +```objc +NSFileManager *fileManager = [NSFileManager defaultManager]; +NSURL *bundleURL = [[NSBundle mainBundle] bundleURL]; +NSDirectoryEnumerator *enumerator = [fileManager enumeratorAtURL:bundleURL + includingPropertiesForKeys:@[NSURLNameKey, NSURLIsDirectoryKey] + options:NSDirectoryEnumerationSkipsHiddenFiles + errorHandler:^BOOL(NSURL *url, NSError *error) +{ + if (error) { + NSLog(@"[Error] %@ (%@)", error, url); + return NO; + } + + return YES; +}]; + +NSMutableArray *mutableFileURLs = [NSMutableArray array]; +for (NSURL *fileURL in enumerator) { + NSString *filename; + [fileURL getResourceValue:&filename forKey:NSURLNameKey error:nil]; + + NSNumber *isDirectory; + [fileURL getResourceValue:&isDirectory forKey:NSURLIsDirectoryKey error:nil]; + + // Skip directories with '_' prefix, for example + if ([filename hasPrefix:@"_"] && [isDirectory boolValue]) { + [enumerator skipDescendants]; + continue; + } + + if (![isDirectory boolValue]) { + [mutableFileURLs addObject:fileURL]; + } +} +``` + +## Creating a Directory + +To create a directory, +call the method `createDirectory(at:withIntermediateDirectories:attributes:)`. +In Unix parlance, setting the `withIntermediateDirectories` parameter to `true` +is equivalent to passing the `-p` option to `mkdir`. + +```swift +try FileManager.default.createDirectory(at: directoryURL, + withIntermediateDirectories: true, + attributes: nil) +``` + +```objc +NSFileManager *fileManager = [NSFileManager defaultManager]; +NSString *documentsPath = + [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, + NSUserDomainMask, + YES) firstObject]; +NSString *imagesPath = [documentsPath stringByAppendingPathComponent:@"images"]; +if (![fileManager fileExistsAtPath:imagesPath]) { + NSError *error = nil; + [fileManager createDirectoryAtPath:imagesPath + withIntermediateDirectories:NO + attributes:nil + error:&error]; +} +``` + +## Deleting a File or Directory + +If you want to delete a file or directory, +call `removeItem(at:)`: + +```swift +let fileURL: URL = <#/path/to/file#> +try FileManager.default.removeItem(at: fileURL) +``` + +```objc +NSFileManager *fileManager = [NSFileManager defaultManager]; +NSString *documentsPath = + [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, + NSUserDomainMask, + YES) firstObject]; +NSString *filePath = [documentsPath stringByAppendingPathComponent:@"image.png"]; +NSError *error = nil; + +if (![fileManager removeItemAtPath:filePath error:&error]) { + NSLog(@"[Error] %@ (%@)", error, filePath); +} +``` + +## FileManagerDelegate + +`FileManager` may optionally set a delegate +to verify that it should perform a particular file operation. +This is a convenient way to audit all file operations in your app, +and a good place to factor out and centralize business logic, +such as which files to protect from deletion. + +There are four operations covered by the +[`FileManagerDelegate`](https://developer.apple.com/documentation/foundation/filemanagerdelegate) protocol: +moving, copying, removing, and linking items --- +each with variations for working with paths and URLs, +as well as how to proceed after an error occurs: + +If you were wondering when you might create your own `FileManager` +rather than using this shared instance, +this is it. + +From the documentation: + +> You should associate your delegate +> with a unique instance of the `FileManager` class, +> as opposed to the shared instance. + +```swift +class CustomFileManagerDelegate: NSObject, FileManagerDelegate { + func fileManager(_ fileManager: FileManager, + shouldRemoveItemAt URL: URL) -> Bool + { + // Don't delete PDF files + return URL.pathExtension != "pdf" + } +} + +// Maintain strong references to fileManager and delegate +let fileManager = FileManager() +let delegate = CustomFileManagerDelegate() + +fileManager.delegate = delegate +``` + +```objc +NSFileManager *fileManager = [[NSFileManager alloc] init]; +fileManager.delegate = delegate; + +NSURL *bundleURL = [[NSBundle mainBundle] bundleURL]; +NSArray *contents = [fileManager contentsOfDirectoryAtURL:bundleURL + includingPropertiesForKeys:@[] + options:NSDirectoryEnumerationSkipsHiddenFiles + error:nil]; + +for (NSString *filePath in contents) { + [fileManager removeItemAtPath:filePath error:nil]; +} + +// CustomFileManagerDelegate.m + +#pragma mark - NSFileManagerDelegate + +- (BOOL)fileManager:(NSFileManager *)fileManager +shouldRemoveItemAtURL:(NSURL *)URL +{ + return ![[[URL lastPathComponent] pathExtension] isEqualToString:@"pdf"]; +} +``` + +--- + +When you write an app that interacts with a file system, +you don't know if it's an HDD or SSD +or if it's formatted with APFS or HFS+ or something else entirely. +You don't even know where the disk is: +it could be internal or in a mounted peripheral, +it could be network-attached, or maybe floating around somewhere in the cloud. + +The best strategy for ensuring that things work +across each of the various permutations +is to work through `FileManager` and its related Foundation APIs. diff --git a/2013-11-18-nsfilemanager.md b/2013-11-18-nsfilemanager.md deleted file mode 100644 index f23274fb..00000000 --- a/2013-11-18-nsfilemanager.md +++ /dev/null @@ -1,227 +0,0 @@ ---- -title: NSFileManager -author: Mattt Thompson -category: Cocoa -tags: nshipster -excerpt: "File systems are a complex topic, with decades of history, vestigial complexities, and idiosyncrasies, and is well outside the scope of a single article. And since most applications don't often interact with the file system much beyond simple file operations, one can get away with only knowing the basics." -status: - swift: t.b.c. ---- - -`NSFileManager` is Foundation's high-level API for working with file systems. It abstracts Unix and Finder internals, providing a convenient way to create, read, move, copy, and delete files & directories on local or networked drives, as well as iCloud ubiquitous containers. - -File systems are a complex topic, with decades of history, vestigial complexities, and idiosyncrasies, and is well outside the scope of a single article. And since most applications don't often interact with the file system much beyond simple file operations, one can get away with only knowing the basics. - -What follows are some code samples for your copy-pasting pleasure. Use them as a foundation for understanding how to adjust parameters to your particular use case: - -## Common Tasks - -> Throughout the code samples is the magical incantation `NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)`. This may be tied with KVO as one of the worst APIs in Cocoa. Just know that this returns an array containing the user documents directory as the first object. Thank goodness for the inclusion of `NSArray -firstObject`. - -### Determining If A File Exists - -~~~{objective-c} -NSFileManager *fileManager = [NSFileManager defaultManager]; -NSString *documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject]; -NSString *filePath = [documentsPath stringByAppendingPathComponent:@"file.txt"]; -BOOL fileExists = [fileManager fileExistsAtPath:filePath]; -~~~ - -### Listing All Files In A Directory - -~~~{objective-c} -NSFileManager *fileManager = [NSFileManager defaultManager]; -NSURL *bundleURL = [[NSBundle mainBundle] bundleURL]; -NSArray *contents = [fileManager contentsOfDirectoryAtURL:bundleURL - includingPropertiesForKeys:@[] - options:NSDirectoryEnumerationSkipsHiddenFiles - error:nil]; - -NSPredicate *predicate = [NSPredicate predicateWithFormat:@"pathExtension == 'png'"]; -for (NSURL *fileURL in [contents filteredArrayUsingPredicate:predicate]) { - // Enumerate each .png file in directory -} -~~~ - -## Recursively Enumerating Files In A Directory - -~~~{objective-c} -NSFileManager *fileManager = [NSFileManager defaultManager]; -NSURL *bundleURL = [[NSBundle mainBundle] bundleURL]; -NSDirectoryEnumerator *enumerator = [fileManager enumeratorAtURL:bundleURL - includingPropertiesForKeys:@[NSURLNameKey, NSURLIsDirectoryKey] - options:NSDirectoryEnumerationSkipsHiddenFiles - errorHandler:^BOOL(NSURL *url, NSError *error) -{ - if (error) { - NSLog(@"[Error] %@ (%@)", error, url); - return NO; - } - - return YES; -}]; - -NSMutableArray *mutableFileURLs = [NSMutableArray array]; -for (NSURL *fileURL in enumerator) { - NSString *filename; - [fileURL getResourceValue:&filename forKey:NSURLNameKey error:nil]; - - NSNumber *isDirectory; - [fileURL getResourceValue:&isDirectory forKey:NSURLIsDirectoryKey error:nil]; - - // Skip directories with '_' prefix, for example - if ([filename hasPrefix:@"_"] && [isDirectory boolValue]) { - [enumerator skipDescendants]; - continue; - } - - if (![isDirectory boolValue]) { - [mutableFileURLs addObject:fileURL]; - } -} -~~~ - -### Creating a Directory - -~~~{objective-c} -NSFileManager *fileManager = [NSFileManager defaultManager]; -NSString *documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject]; -NSString *imagesPath = [documentsPath stringByAppendingPathComponent:@"images"]; -if (![fileManager fileExistsAtPath:imagesPath]) { - [fileManager createDirectoryAtPath:imagesPath withIntermediateDirectories:NO attributes:nil error:nil]; -} -~~~ - -### Deleting a File - -~~~{objective-c} -NSFileManager *fileManager = [NSFileManager defaultManager]; -NSString *documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject]; -NSString *filePath = [documentsPath stringByAppendingPathComponent:@"image.png"]; -NSError *error = nil; - -if (![fileManager removeItemAtPath:filePath error:&error]) { - NSLog(@"[Error] %@ (%@)", error, filePath); -} -~~~ - -### Determining the Creation Date of a File - -~~~{objective-c} -NSFileManager *fileManager = [NSFileManager defaultManager]; -NSString *documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject]; -NSString *filePath = [documentsPath stringByAppendingPathComponent:@"Document.pages"]; - -NSDate *creationDate = nil; -if ([fileManager fileExistsAtPath:filePath]) { - NSDictionary *attributes = [fileManager attributesOfItemAtPath:filePath error:nil]; - creationDate = attributes[NSFileCreationDate]; -} -~~~ - -There are a number of file attributes that are made accessible through `NSFileManager`, which can be fetched with `-attributesOfItemAtPath:error:`, and other methods: - -#### File Attribute Keys - -> - `NSFileAppendOnly`: The key in a file attribute dictionary whose value indicates whether the file is read-only. -> - `NSFileBusy`: The key in a file attribute dictionary whose value indicates whether the file is busy. -> - `NSFileCreationDate`: The key in a file attribute dictionary whose value indicates the file's creation date. -> - `NSFileOwnerAccountName`: The key in a file attribute dictionary whose value indicates the name of the file's owner. -> - `NSFileGroupOwnerAccountName`: The key in a file attribute dictionary whose value indicates the group name of the file's owner. -> - `NSFileDeviceIdentifier`: The key in a file attribute dictionary whose value indicates the identifier for the device on which the file resides. -> - `NSFileExtensionHidden`: The key in a file attribute dictionary whose value indicates whether the file's extension is hidden. -> - `NSFileGroupOwnerAccountID`: The key in a file attribute dictionary whose value indicates the file's group ID. -> - `NSFileHFSCreatorCode`: The key in a file attribute dictionary whose value indicates the file's HFS creator code. -> - `NSFileHFSTypeCode`: The key in a file attribute dictionary whose value indicates the file's HFS type code. -> - `NSFileImmutable`: The key in a file attribute dictionary whose value indicates whether the file is mutable. -> - `NSFileModificationDate`: The key in a file attribute dictionary whose value indicates the file's last modified date. -> - `NSFileOwnerAccountID`: The key in a file attribute dictionary whose value indicates the file's owner's account ID. -> - `NSFilePosixPermissions`: The key in a file attribute dictionary whose value indicates the file's Posix permissions. -> - `NSFileReferenceCount`: The key in a file attribute dictionary whose value indicates the file's reference count. -> - `NSFileSize`: The key in a file attribute dictionary whose value indicates the file's size in bytes. -> - `NSFileSystemFileNumber`: The key in a file attribute dictionary whose value indicates the file's filesystem file number. -> - `NSFileType`: The key in a file attribute dictionary whose value indicates the file's type. - - -> - `NSDirectoryEnumerationSkipsSubdirectoryDescendants`: Perform a shallow enumeration; do not descend into directories. -> - `NSDirectoryEnumerationSkipsPackageDescendants`: Do not descend into packages. -> - `NSDirectoryEnumerationSkipsHiddenFiles`: Do not enumerate hidden files. - -## NSFileManagerDelegate - -`NSFileManager` may optionally set a delegate to verify that it should perform a particular file operation. This allows the business logic of, for instance, which files to protect from deletion, to be factored out of the controller. - -There are four kinds of methods in the `` protocol, each with a variation for working with paths, as well as methods for error handling: - -- `-fileManager:shouldMoveItemAtURL:toURL:` -- `-fileManager:shouldCopyItemAtURL:toURL:` -- `-fileManager:shouldRemoveItemAtURL:` -- `-fileManager:shouldLinkItemAtURL:toURL:` - -If you were wondering when you might `alloc init` your own `NSFileManager` rather than using the shared instance, this is it. As per the documentation: - -> If you use a delegate to receive notifications about the status of move, copy, remove, and link operations, you should create a unique instance of the file manager object, assign your delegate to that object, and use that file manager to initiate your operations. - -~~~{objective-c} -NSFileManager *fileManager = [[NSFileManager alloc] init]; -fileManager.delegate = delegate; - -NSURL *bundleURL = [[NSBundle mainBundle] bundleURL]; -NSArray *contents = [fileManager contentsOfDirectoryAtURL:bundleURL - includingPropertiesForKeys:@[] - options:NSDirectoryEnumerationSkipsHiddenFiles - error:nil]; - -for (NSString *filePath in contents) { - [fileManager removeItemAtPath:filePath error:nil]; -} -~~~ - -#### CustomFileManagerDelegate.m - -~~~{objective-c} -#pragma mark - NSFileManagerDelegate - -- (BOOL)fileManager:(NSFileManager *)fileManager -shouldRemoveItemAtURL:(NSURL *)URL -{ - return ![[[URL lastPathComponent] pathExtension] isEqualToString:@"pdf"]; -} -~~~ - -## Ubiquitous Storage - -Documents can also be moved to iCloud. If you guessed that this would be anything but straight forward, you'd be 100% correct. - -This is another occasion when you'd `alloc init` your own `NSFileManager` rather than using the shared instance. Because `URLForUbiquityContainerIdentifier:` and `setUbiquitous:itemAtURL:destinationURL:error:` are blocking calls, this entire operation needs to be dispatched to a background queue. - -### Moving an Item to Ubiquitous Storage - -~~~{objective-c} -dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{ - NSFileManager *fileManager = [[NSFileManager alloc] init]; - NSString *documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject]; - NSURL *fileURL = [NSURL fileURLWithPath:[documentsPath stringByAppendingPathComponent:@"Document.pages"]]; - - // Defaults to first listed in entitlements when `nil`; should replace with real identifier - NSString *identifier = nil; - - NSURL *ubiquitousContainerURL = [fileManager URLForUbiquityContainerIdentifier:identifier]; - NSURL *ubiquitousFileURL = [ubiquitousContainerURL URLByAppendingPathComponent:@"Document.pages"]; - - NSError *error = nil; - BOOL success = [fileManager setUbiquitous:YES - itemAtURL:fileURL - destinationURL:ubiquitousFileURL - error:&error]; - if (!success) { - NSLog(@"[Error] %@ (%@) (%@)", error, fileURL, ubiquitousFileURL); - } -}); -~~~ - -> You can find more information about ubiquitous document storage in Apple's "iCloud File Management" document. - -* * * - -There's a lot to know about file systems, but as an app developer, it's mostly an academic exercise. Now don't get me wrong—academic exercises are great! But they don't ship code. `NSFileManager` allows you to ignore most of the subtlety of all of this and get things done. diff --git a/2013-12-02-nsnotification-and-nsnotificationcenter.md b/2013-12-02-nsnotification-and-nsnotificationcenter.md index baceb91a..779baa9d 100644 --- a/2013-12-02-nsnotification-and-nsnotificationcenter.md +++ b/2013-12-02-nsnotification-and-nsnotificationcenter.md @@ -1,6 +1,6 @@ --- title: "NSNotification &
NSNotificationCenter" -author: Mattt Thompson +author: Mattt category: Cocoa tags: popular excerpt: "Any idea is inextricably linked to how its communicated. A medium defines the form and scale of significance in such a way to shape the very meaning of an idea. Very truly, the medium is the message." @@ -39,7 +39,7 @@ This is as true of humans as it is within a computer process. In Cocoa, there ar
    -
  • Notifications
  • +
  • Notifications
@@ -59,7 +59,7 @@ This is as true of humans as it is within a computer process. In Cocoa, there ar -We've discussed the importance of how events are communicated in APIs previously in our [article on Key-Value Observing](http://nshipster.com/key-value-observing/). This week, we'll expand our look at the available options, with `NSNotificationCenter` & `NSNotification`. +We've discussed the importance of how events are communicated in APIs previously in our [article on Key-Value Observing](https://nshipster.com/key-value-observing/). This week, we'll expand our look at the available options, with `NSNotificationCenter` & `NSNotification`. * * * @@ -85,13 +85,13 @@ The `name` and `object` parameters of both methods are used to decide whether th > *See for yourself! An ordinary iOS app fires dozens of notifications just in the first second of being launched—many that you've probably never heard of before, nor will ever have to think about again. -~~~{swift} +```swift let center = NSNotificationCenter.defaultCenter() center.addObserverForName(nil, object: nil, queue: nil) { notification in print("\(notification.name): \(notification.userInfo ?? [:])") } -~~~ -~~~{objective-c} +``` +```objc NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; [center addObserverForName:nil object:nil @@ -100,7 +100,7 @@ NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; { NSLog(@"%@", notification.name); }]; -~~~ +``` ### Removing Observers @@ -118,23 +118,23 @@ Notification names are generally defined as string constants. Like any string co Keys for `userInfo` should likewise be defined as string constants. It's important to clearly document the expected kinds of values for each key, since the compiler can't enforce constraints on dictionaries the same way it can for an object. -~~~{swift} +```swift class FooController : UIViewController { enum Notifications { static let FooDidBar = "XXFooDidBarNotification" static let FooDidBazoom = "XXFooDidBazoomNotification" } - // ... + <#...#> } -~~~ -~~~{objective-c} +``` +```objc // Foo.h extern NSString * const XXFooDidBarNotification; // Foo.m NSString * const XXFooDidBarNotification = @"XXFooDidBarNotification"; -~~~ +``` Notifications are posted with `–postNotificationName:object:userInfo:` or its convenience method `–postNotificationName:object:`, which passes `nil` for `userInfo`. `–postNotification:` is also available, but it's generally preferable to have the notification object creation handled by the method itself. @@ -144,25 +144,25 @@ Since notification dispatch happens on the posting thread, it may be necessary t ## KVO != NSNotificationCenter -Something that often slips up developers is how similar the method signatures for [Key-Value Observing](http://nshipster.com/key-value-observing/) are to those of `NSNotificationCenter`: +Something that often slips up developers is how similar the method signatures for [Key-Value Observing](https://nshipster.com/key-value-observing/) are to those of `NSNotificationCenter`: #### Key-Value Observing -~~~{swift} +```swift func addObserver(observer: NSObject, forKeyPath keyPath: String, options: NSKeyValueObservingOptions, context: UnsafeMutablePointer) -~~~ -~~~{objective-c} +``` +```objc - (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context -~~~ +``` #### NSNotificationCenter -~~~{swift} +```swift func addObserver(observer: AnyObject, selector aSelector: Selector, name aName: String?, @@ -172,8 +172,8 @@ func addObserverForName(name: String?, object obj: AnyObject?, queue: NSOperationQueue?, usingBlock block: (NSNotification) -> Void) -> NSObjectProtocol -~~~ -~~~{objective-c} +``` +```objc - (void)addObserver:(id)notificationObserver selector:(SEL)notificationSelector name:(NSString *)notificationName @@ -183,7 +183,7 @@ func addObserverForName(name: String?, object:(id)obj queue:(NSOperationQueue *)queue usingBlock:(void (^)(NSNotification *))block -~~~ +``` **Key-Value Observing adds observers for keypaths, while NSNotificationCenter adds observers for notifications.** Keep this distinction clear in your mind, and proceed to use both APIs confidently. diff --git a/2013-12-09-multipeer-connectivity.md b/2013-12-09-multipeer-connectivity.md index 4aca63e3..0d6c5070 100644 --- a/2013-12-09-multipeer-connectivity.md +++ b/2013-12-09-multipeer-connectivity.md @@ -1,6 +1,6 @@ --- title: Multipeer Connectivity -author: Mattt Thompson +author: Mattt category: Cocoa tags: popular excerpt: "As consumer web technologies and enterprises race towards cloud infrastructure, there is a curious and significant counter-movement towards connected devices. The Multipeer Connectivity APIs, introduced in iOS 7, therefore may well be the most significant for the platform." @@ -26,15 +26,15 @@ Advertising makes a service known to other peers, while discovery is the inverse Each service is identified by a type, which is a short text string of ASCII letters, numbers, and dashes, up to 15 characters in length. By convention, a service name should begin with the app name, followed by a dash and a unique descriptor for that service (think of it as simplified `com.apple.*`-esque reverse-DNS notation): -~~~{objective-c} +```objc static NSString * const XXServiceType = @"xx-service"; -~~~ +``` Peers are uniquely identified by an `MCPeerID` object, which are initialized with a display name. This could be a user-specified nickname, or simply the current device name: -~~~{objective-c} +```objc MCPeerID *localPeerID = [[MCPeerID alloc] initWithDisplayName:[[UIDevice currentDevice] name]]; -~~~ +``` > Peers can be also be advertised or discovered manually using `NSNetService` or the Bonjour C APIs, but this is a rather advanced and specific concern. Additional information about manual peer management can be found in the `MCSession` documentation. @@ -44,20 +44,20 @@ Services are advertised by the `MCNearbyServiceAdvertiser`, which is initialized > Discovery information is sent as Bonjour `TXT` records encoded according to [RFC 6763](http://tools.ietf.org/html/rfc6763). -~~~{objective-c} +```objc MCNearbyServiceAdvertiser *advertiser = [[MCNearbyServiceAdvertiser alloc] initWithPeer:localPeerID discoveryInfo:nil serviceType:XXServiceType]; advertiser.delegate = self; [advertiser startAdvertisingPeer]; -~~~ +``` Events are handled by the advertiser's `delegate`, conforming to the `MCNearbyServiceAdvertiserDelegate` protocol. As an example implementation, consider a client that allows the user to choose whether to accept or reject incoming connection requests, with the option to reject and block any subsequent requests from that peer: -~~~{objective-c} +```objc #pragma mark - MCNearbyServiceAdvertiserDelegate - (void)advertiser:(MCNearbyServiceAdvertiser *)advertiser @@ -90,7 +90,7 @@ didReceiveInvitationFromPeer:(MCPeerID *)peerID invitationHandler(acceptedInvitation, (acceptedInvitation ? session : nil)); }] showInView:self.view]; } -~~~ +``` > For sake of simplicity, this example contrives a block-based initializer for `UIActionSheet`, which allows for the `invitationHandler` to be passed directly into the action sheet responder in order to avoid the messy business of creating and managing a custom delegate object. This method can be implemented in a category, or adapted from [any of the implementations available on CocoaPods](http://cocoapods.org/?q=uiactionsheet%20blocks) @@ -98,12 +98,12 @@ didReceiveInvitationFromPeer:(MCPeerID *)peerID As in the example above, sessions are created by advertisers, and passed to peers when accepting an invitation to connect. An `MCSession` object is initialized with the local peer identifier, as well as `securityIdentity` and `encryptionPreference` parameters. -~~~{objective-c} +```objc MCSession *session = [[MCSession alloc] initWithPeer:localPeerID securityIdentity:nil encryptionPreference:MCEncryptionNone]; session.delegate = self; -~~~ +``` `securityIdentity` is an optional parameter that allows peers to securely identify peers by X.509 certificates. When specified, the first object should be an `SecIdentityRef` identifying the client, followed by one or more `SecCertificateRef` objects than can be used to verify the local peer’s identity. @@ -121,14 +121,14 @@ The `MCSessionDelegate` protocol will be covered in the section on sending and r Clients can discover advertised services using `MCNearbyServiceBrowser`, which is initialized with the local peer identifier and the service type, much like for `MCNearbyServiceAdvertiser`. -~~~{objective-c} +```objc MCNearbyServiceBrowser *browser = [[MCNearbyServiceBrowser alloc] initWithPeer:localPeerID serviceType:XXServiceType]; browser.delegate = self; -~~~ +``` There may be many peers advertising a particular service, so as a convenience to the user (and the developer), the `MCBrowserViewController` offers a built-in, standard way to present and connect to advertising peers: -~~~{objective-c} +```objc MCBrowserViewController *browserViewController = [[MCBrowserViewController alloc] initWithBrowser:browser session:session]; @@ -139,7 +139,7 @@ browserViewController.delegate = self; ^{ [browser startBrowsingForPeers]; }]; -~~~ +``` When a browser has finished connecting to peers, it calls `-browserViewControllerDidFinish:` on its delegate, to notify the presenting view controller that it should update its UI to accommodate the newly-connected clients. @@ -155,7 +155,7 @@ Once peers are connected to one another, information can be sent between them. T Messages are sent with `-sendData:toPeers:withMode:error:`: -~~~{objective-c} +```objc NSString *message = @"Hello, World!"; NSData *data = [message dataUsingEncoding:NSUTF8StringEncoding]; NSError *error = nil; @@ -165,13 +165,13 @@ if (![self.session sendData:data error:&error]) { NSLog(@"[Error] %@", error); } -~~~ +``` * * * Messages are received through the `MCSessionDelegate` method `-sessionDidReceiveData:fromPeer:`. Here's how one would decode the message sent in the previous code example: -~~~{objective-c} +```objc #pragma mark - MCSessionDelegate - (void)session:(MCSession *)session @@ -183,12 +183,12 @@ Messages are received through the `MCSessionDelegate` method `-sessionDidReceive encoding:NSUTF8StringEncoding]; NSLog(@"%@", message); } -~~~ +``` Another approach would be to send `NSKeyedArchiver`-encoded objects: -~~~{objective-c} -id object = // ...; +```objc +id object = <#...#>; NSData *data = [NSKeyedArchiver archivedDataWithRootObject:object]; NSError *error = nil; if (![self.session sendData:data @@ -197,9 +197,9 @@ if (![self.session sendData:data error:&error]) { NSLog(@"[Error] %@", error); } -~~~ +``` -~~~{objective-c} +```objc #pragma mark - MCSessionDelegate - (void)session:(MCSession *)session @@ -212,15 +212,15 @@ if (![self.session sendData:data [unarchiver finishDecoding]; NSLog(@"%@", object); } -~~~ +``` -> In order to guard against object substitution attacks, it is important to set `requiresSecureCoding` to `YES`, such that an exception is thrown if the root object class does not conform to ``. For more information, see the [NSHipster article on [NSSecureCoding](http://nshipster.com/nssecurecoding/). +> In order to guard against object substitution attacks, it is important to set `requiresSecureCoding` to `YES`, such that an exception is thrown if the root object class does not conform to ``. For more information, see the [NSHipster article on [NSSecureCoding](https://nshipster.com/nssecurecoding/). ### Streams Streams are created with `-startStreamWithName:toPeer:`: -~~~{objective-c} +```objc NSOutputStream *outputStream = [session startStreamWithName:name toPeer:peer]; @@ -230,14 +230,14 @@ stream.delegate = self; forMode:NSDefaultRunLoopMode]; [stream open]; -// ... -~~~ +<#...#> +``` * * * Streams are received by the `MCSessionDelegate` with `-session:didReceiveStream:withName:fromPeer:`: -~~~{objective-c} +```objc #pragma mark - MCSessionDelegate - (void)session:(MCSession *)session @@ -250,7 +250,7 @@ didReceiveStream:(NSInputStream *)stream forMode:NSDefaultRunLoopMode]; [stream open]; } -~~~ +``` Both the input and output streams must be scheduled and opened before they can be used. Once that's done, streams can be read from and written to just like any other bound pair. @@ -258,7 +258,7 @@ Both the input and output streams must be scheduled and opened before they can b Resources are sent with `sendResourceAtURL:withName:toPeer:withCompletionHandler:`: -~~~{objective-c} +```objc NSURL *fileURL = [NSURL fileURLWithPath:@"path/to/resource"]; NSProgress *progress = [self.session sendResourceAtURL:fileURL @@ -268,15 +268,15 @@ NSProgress *progress = { NSLog(@"[Error] %@", error); }]; -~~~ +``` -The returned `NSProgress` object can be [Key-Value Observed](http://nshipster.com/key-value-observing/) to monitor progress of the file transfer, as well as provide a cancellation handler, through the `-cancel` method. +The returned `NSProgress` object can be [Key-Value Observed](https://nshipster.com/key-value-observing/) to monitor progress of the file transfer, as well as provide a cancellation handler, through the `-cancel` method. * * * Receiving resources happens across two methods in `MCSessionDelegate`: `-session:didStartReceivingResourceWithName:fromPeer:withProgress:` & `-session:didFinishReceivingResourceWithName:fromPeer:atURL:withError:`: -~~~{objective-c} +```objc #pragma mark - MCSessionDelegate - (void)session:(MCSession *)session @@ -284,7 +284,7 @@ didStartReceivingResourceWithName:(NSString *)resourceName fromPeer:(MCPeerID *)peerID withProgress:(NSProgress *)progress { - // ... + <#...#> } - (void)session:(MCSession *)session @@ -301,7 +301,7 @@ didFinishReceivingResourceWithName:(NSString *)resourceName NSLog(@"[Error] %@", error); } } -~~~ +``` Again, the `NSProgress` parameter in `-session:didStartReceivingResourceWithName:fromPeer:withProgress:` allows the receiving peer to monitor the file transfer progress. In `-session:didFinishReceivingResourceWithName:fromPeer:atURL:withError:`, it is the responsibility of the delegate to move the file at the temporary `localURL` to a permanent location. diff --git a/2013-12-16-launch-options.md b/2013-12-16-launch-options.md index 549f2b42..50347249 100644 --- a/2013-12-16-launch-options.md +++ b/2013-12-16-launch-options.md @@ -1,6 +1,6 @@ --- title: UIApplicationDelegate launchOptions -author: Mattt Thompson +author: Mattt category: Cocoa excerpt: "AppDelegate is the dumping ground for functionality in iOS." status: @@ -21,7 +21,7 @@ This week, all will be revealed in this NSHipster tell-all about the least under Every app begins with `UIApplicationDelegate -application:didFinishLaunchingWithOptions:` (or more accurately, `-application:willFinishLaunchingWithOptions:`, when implemented). It is called by the application to notify its delegate that the launch process is finishing, and nearly ready to run. -An app launches when its icon is tapped on [Springboard](http://en.wikipedia.org/wiki/SpringBoard), but there are several other occasions in which an app can be launched. For example, an app registered for a custom URL scheme, such as `twitter://`, could be launched as a result of opening a URL. An app could also be launched in response to a push notification, or a significant change in device location. +An app launches when its icon is tapped on [Springboard](https://en.wikipedia.org/wiki/SpringBoard), but there are several other occasions in which an app can be launched. For example, an app registered for a custom URL scheme, such as `twitter://`, could be launched as a result of opening a URL. An app could also be launched in response to a push notification, or a significant change in device location. Determining why and how an app launched is the responsibility of the `launchOptions` parameter. Like a `userInfo` dictionary, `-application:didFinishLaunchingWithOptions:` can get information for particular named keys in `launchOptions`. @@ -33,9 +33,9 @@ Numerous as they are, `launchOptions` keys can be more easily understood when or Apps can launch other apps by passing URLs: -~~~{objective-c} -[[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"app://..."]]; -~~~ +```objc +[[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"app:<#...#>"]]; +``` For example, an `http://` URL would open in Safari, a `mailto://` URL would open in Mail, and a `tel://` URL would open in Phone. @@ -48,7 +48,7 @@ An app can also be launched through URLs with additional system information. Whe > - `UIApplicationLaunchOptionsSourceApplicationKey`: Identifies the app that requested the launch of your app. The value of this key is an `NSString` object that represents the bundle ID of the app that made the request > - `UIApplicationLaunchOptionsAnnotationKey`: Indicates that custom data was provided by the app that requested the opening of the URL. The value of this key is a property-list object containing the custom data. -~~~{objective-c} +```objc NSURL *fileURL = [[NSBundle mainBundle] URLForResource:@"Document" withExtension:@"pdf"]; if (fileURL) { UIDocumentInteractionController *documentInteractionController = [UIDocumentInteractionController interactionControllerWithURL:fileURL]; @@ -56,11 +56,11 @@ if (fileURL) { [documentInteractionController setDelegate:self]; [documentInteractionController presentPreviewAnimated:YES]; } -~~~ +``` ## Responding to Notification -Not to be confused with [`NSNotification`](http://nshipster.com/nsnotification-and-nsnotificationcenter/), apps can be sent remote or local notifications. +Not to be confused with [`NSNotification`](https://nshipster.com/nsnotification-and-nsnotificationcenter/), apps can be sent remote or local notifications. ### Remote Notification @@ -68,12 +68,12 @@ Introduced in iOS 3, remote, or "push" notifications are one of the defining fea To register for remote notifications, `registerForRemoteNotificationTypes:` is called in `application:didFinishLaunchingWithOptions:`. -~~~{objective-c} +```objc [application registerForRemoteNotificationTypes: - UIRemoteNotificationTypeBadge | + UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound | - UIRemoteNotificationTypeAlert]; -~~~ + UIRemoteNotificationTypeAlert]; +``` ...which, if successful, calls `-application:didRegisterForRemoteNotificationsWithDeviceToken:`. Once the device has been successfully registered, it can receive push notifications at any time. @@ -86,17 +86,17 @@ If an app receives a notification while in the foreground, its delegate will cal Since this introduces two separate code paths for notification handling, a common approach is to have `application:didFinishLaunchingWithOptions:` manually call `application:didReceiveRemoteNotification:`: -~~~{objective-c} +```objc - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { - // ... + <#...#> if (launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey]) { [self application:application didReceiveRemoteNotification:launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey]]; } } -~~~ +``` ### Local Notification @@ -112,15 +112,15 @@ A local notification populates the launch options on `UIApplicationLaunchOptions In the case where it is desirable to show an alert for a local notification delivered when the app is active in the foreground, and otherwise wouldn't provide a visual indication, here's how one might use the information from `UILocalNotification` to do it manually: -~~~{objective-c} -// .h +```objc +// AppDelegate.h @import AVFoundation; @interface AppDelegate () @property (readwrite, nonatomic, assign) SystemSoundID localNotificationSound; @end -// .m +// AppDelegate.m - (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification { @@ -148,7 +148,7 @@ didReceiveLocalNotification:(UILocalNotification *)notification AudioServicesDisposeSystemSoundID(self.localNotificationSound); } } -~~~ +``` ## Location Event @@ -160,7 +160,7 @@ But fear not! With iOS region monitoring, your app can be launched on location e Here's an example of how an app might go about monitoring for significant location change to determine launch behavior: -~~~{objective-c} +```objc // .h @import CoreLocation; @@ -172,7 +172,7 @@ Here's an example of how an app might go about monitoring for significant locati - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { - // ... + <#...#> if (![CLLocationManager locationServicesEnabled]) { [[[UIAlertView alloc] initWithTitle:NSLocalizedString(@"Location Services Disabled", nil) @@ -190,7 +190,7 @@ didFinishLaunchingWithOptions:(NSDictionary *)launchOptions [self.locationManager startUpdatingLocation]; } } -~~~ +``` ## Newsstand @@ -204,10 +204,10 @@ Newsstand can launch when newly-downloaded assets are available. This is how you register: -~~~{objective-c} +```objc [application registerForRemoteNotificationTypes: - UIRemoteNotificationTypeNewsstandContentAvailability]; -~~~ + UIRemoteNotificationTypeNewsstandContentAvailability]; +``` And this is the key to look out for in `launchOptions`: @@ -224,7 +224,7 @@ If an app launches, instantiates a `CBCentralManager` or `CBPeripheralManager` w > - `UIApplicationLaunchOptionsBluetoothCentralsKey`: Indicates that the app previously had one or more `CBCentralManager` objects and was relaunched by the Bluetooth system to continue actions associated with those objects. The value of this key is an `NSArray` object containing one or more `NSString` objects. Each string in the array represents the restoration identifier for a central manager object. > - `UIApplicationLaunchOptionsBluetoothPeripheralsKey`: Indicates that the app previously had one or more `CBPeripheralManager` objects and was relaunched by the Bluetooth system to continue actions associated with those objects. The value of this key is an `NSArray` object containing one or more `NSString` objects. Each string in the array represents the restoration identifier for a peripheral manager object. -~~~{objective-c} +```objc // .h @import CoreBluetooth; @@ -241,7 +241,7 @@ if (self.centralManager.state == CBCentralManagerStatePoweredOn) { NSDictionary *scanOptions = @{CBCentralManagerScanOptionAllowDuplicatesKey:@YES}; [self.centralManager scanForPeripheralsWithServices:services options:scanOptions]; } -~~~ +``` * * * diff --git a/2014-01-01-new-years-2014.md b/2014-01-01-new-years-2014.md index ed6413c2..af262f4a 100644 --- a/2014-01-01-new-years-2014.md +++ b/2014-01-01-new-years-2014.md @@ -1,27 +1,27 @@ --- title: "Reader Submissions -
New Year's 2014" -author: Mattt Thompson +author: Mattt category: Reader Submissions excerpt: "As we prepare to increment our NSDateComponents -year by 1, it's time once again for NSHipster end-of-the-year Reader Submissions!" status: - swift: n/a + swift: n/a --- -As we prepare to increment our `NSDateComponents -year` by `1`, it's time once again for NSHipster end-of-the-year Reader Submissions! [Last year](https://gist.github.com/mattt/4148342), we got some [mind-blowing tips and tricks](http://nshipster.com/reader-submissions-new-years-2013/). With the release of iOS 7 & Mavericks, and a year's worth of new developments in the Objective-C ecosystem, there was a ton of new stuff to write about. +As we prepare to increment our `NSDateComponents -year` by `1`, it's time once again for NSHipster end-of-the-year Reader Submissions! [Last year](https://gist.github.com/mattt/4148342), we got some [mind-blowing tips and tricks](https://nshipster.com/reader-submissions-new-years-2013/). With the release of iOS 7 & Mavericks, and a year's worth of new developments in the Objective-C ecosystem, there was a ton of new stuff to write about. Thanks to [Arnaud Coomans](https://github.com/acoomans), [Cédric Luthi](https://github.com/0xced), [David Grandinetti](https://github.com/dbgrandi), [Ell Neal](https://github.com/ellneal), [Eric Allam](https://github.com/rubymaverick), [Erik Kerber](https://github.com/eskerber), [Jim Kubicek](https://github.com/jkubicek), [Joachim Bengtsson](https://github.com/nevyn), [Johannes Lund](https://github.com/Anviking), [Josh Avant](https://github.com/joshavant), [João Prado Maia](https://github.com/jpm), [Justin R. Miller](https://github.com/incanus), [Kamil Pyć](https://github.com/PycKamil), [Matthew Teece](https://github.com/mteece), [Maximilian Tagher](https://github.com/MaxGabriel), [Nigel Timothy Barber](https://github.com/mindbrix), [Nolan O'Brien](https://github.com/NSProgrammer), [Pitiphong Phongpattranont](https://github.com/pitiphong-p), [Steve Moser](https://gist.github.com/stevemoser), [Thomas Visser](https://github.com/Thomvis), [Vadim Shpakovski](https://github.com/shpakovski), & [@jurre](https://github.com/jurre) for [contributing their great tips](https://gist.github.com/7414618). -* * * +--- ## GCC Code Block Evaluation C Extension Let's make this official: **NSHipster's Objective-C trend of 2013 is code block evaluation assignment**. Recommended by both [Jim Kubicek](https://github.com/jkubicek) and [Maximilian Tagher](https://github.com/MaxGabriel) (citing [this blog post](http://cocoa-dom.tumblr.com/post/56517731293/new-thing-i-do-in-code) by [Dominik Wagner](https://github.com/monkeydom)), this trick does wonders to make code cleaner, safer, and more concise. -Behind the magic is a GCC C extension, which causes a code block to return a value if enclosed within brackets and parentheses. +Behind the magic is a GCC C extension, which causes a code block to return a value if enclosed within brackets and parentheses. Watch, as it cuts through this view controller code like butter! -~~~{objective-c} +```objc self.searchBar = ({ UISearchBar *searchBar = [[UISearchBar alloc] initWithFrame:({ CGRect frame = self.tableView.frame; @@ -31,7 +31,7 @@ self.searchBar = ({ searchBar.delegate = self; searchBar; }); -~~~ +``` This not only segregates configuration details into initialization, but the additional scope allows generic variable names like `frame`, `button`, and `view` to be reused in subsequent initializations. No more `loginButtonFrame = ... / signupButtonFrame = ...`! @@ -39,42 +39,41 @@ If code craftsmanship is important to you, strongly consider making this standar ## Default Values with GNU-style Ternary `?:` - The ternary operator, `?`, is shorthand for `if () {...} else {...}`. However, because of how difficult it can be to understand statements with ternary operators at a glance, they are generally dispreferred by veteran coders. Nonetheless, [Maximilian Tagher](https://github.com/MaxGabriel) offers a lesser-known (yet much-loved by those in-the-know) use of the ternary operator: `?:`, which acts as a convenient way to specify a fallback value to return if the left-hand side is `nil`. -~~~{objective-c} +```objc NSLog(@"%@", @"a" ?: @"b"); // @"a" NSLog(@"%@", nil ?: @"b"); // @"b" -~~~ +``` This is especially convenient for providing default behavior when a property may not be set: -~~~{objective-c} +```objc dispatch_async(self.completionQueue ?: dispatch_get_main_queue(), ^{ - // ... + <#...#> }); -~~~ +``` The main downside of this approach is that default Xcode project warning settings will raise a warning. You can get around this by wrapping the relevant code block in `#pragma` declarations, but the added LOC nullifies much of the brevity that this approach provides: -~~~{objective-c} +```objc #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wgnu" self.name = name ?: @"Unknown"; #pragma clang diagnostic pop -~~~ +``` ## `@import` [Vadim Shpakovski](https://github.com/shpakovski) reminds us of one of the more obscure additions to LLVM 5.0, the `@import` keyword. No more are the days of `Xcode ▹ Project ▹ TARGETS ▹ General ▹ Linked Frameworks and Libraries ▹ +`. With `@import`, the Xcode will automatically link `MapKit`, `CoreData`, or any other referenced framework as necessary. Even `Prefix.pch` benefits, sporting a svelte new physique: -~~~{objective-c} +```objc @import UIKit; @import Foundation; @import CoreGraphics; -~~~ +``` ## Customizing MKMapView Tiles @@ -90,7 +89,6 @@ Speaking of iOS maps, [João Prado Maia](https://github.com/jpm) cites an amazin Shifting gears a little bit, [Eric Allam](https://github.com/rubymaverick) remarks that `NSAttributedString` can do HTML now in iOS 7 with the new [`NSHTMLTextDocumentType`](https://developer.apple.com/library/ios/documentation/UIKit/Reference/NSAttributedString_UIKit_Additions/Reference/Reference.html#//apple_ref/doc/uid/TP40011688) document type attribute. Combine with [MMMarkdown](https://github.com/mdiep/MMMarkdown) for ridiculously easy [Markdown rendering in a `UITextView`](http://initwithfunk.com/blog/2013/09/29/easy-markdown-rendering-with-nsattributedstring-on-ios-7/). - --- ## Launch Arguments & User Defaults @@ -113,14 +111,14 @@ The command line argument `-TestFeatureEnabled YES` can be checked in code with [David Grandinetti](https://github.com/dbgrandi) has a tip for apps that want to track outgoing links from within an app: override `AppDelegate -openURL:`: -~~~{objective-c} +```objc -(BOOL)openURL:(NSURL *)url{ if ([[url scheme] hasPrefix:@"http"]) { [[GAI sharedInstance].defaultTracker sendView:[url absoluteString]]; } return [super openURL:url]; } -~~~ +``` In this example, this information is being sent to Google Analytics, but one could easily adapt this approach for any analytics provider. @@ -128,9 +126,9 @@ In this example, this information is being sent to Google Analytics, but one cou Still resisting the aesthetics of this year's iOS makeover? [Kamil Pyć](https://github.com/PycKamil) shows us how to act as if iOS 7 never happened: -~~~{objective-c} +```objc [[NSUserDefaults standardUserDefaults] setObject:@YES forKey:@"UIUseLegacyUI"] -~~~ +``` > And, of course, following up from a previous tip, since `NSUserDefaults` is tied to launch arguments, this can also be specified on launch. Keep this tucked in the back of your mind—this could make for a simple yet effective April Fool's joke. @@ -148,7 +146,7 @@ Fortunately, [Cédric Luthi](https://github.com/0xced) shows us how to tell if a ## A .plist of Emoji, Grouped by Category -In an encore submission, Cédric brings us a [.plist file of Emoji grouped by category](https://gist.github.com/mattt/8185075) (mirrored from CloudApp to Gist in order to be more searchable). 😄👍 +In an encore submission, Cédric brings us a [.plist file of Emoji grouped by category](https://gist.github.com/mattt/8185075) (mirrored from CloudApp to Gist in order to be more searchable). 😄👍 --- @@ -156,7 +154,7 @@ In an encore submission, Cédric brings us a [.plist file of Emoji grouped by c Here's a simple function from [Nolan O'Brien](https://github.com/NSProgrammer) that can be used to determine the type of image data based on the first couple bytes of the header: -~~~{objective-c} +```objc static inline NSPUIImageType NSPUIImageTypeFromData(NSData *imageData) { if (imageData.length > 4) { const unsigned char * bytes = [imageData bytes]; @@ -179,38 +177,38 @@ static inline NSPUIImageType NSPUIImageTypeFromData(NSData *imageData) { return NSPUIImageType_Unknown; } -~~~ +``` --- ## Print KVO Context -Once again, showing off his unmatched knowledge of Objective-C internals, Cédric shares this extremely useful tip for debugging [Key-Value Observing](http://nshipster.com/key-value-observing/). +Once again, showing off his unmatched knowledge of Objective-C internals, Cédric shares this extremely useful tip for debugging [Key-Value Observing](https://nshipster.com/key-value-observing/). > Print which [context](https://gist.github.com/ddribin/5158614#comment-798104) is passed to `observeValueForKeyPath:ofObject:change:context:` in lldb. ->Say you have declared a context like this: +> Say you have declared a context like this: -~~~{objective-c} +```objc static const void *MyFooContext = &MyFooContext; -~~~ +``` > ...and you want to to know what context it is when you are inside -~~~{objective-c} +```objc - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context -~~~ +``` > You can do this: -~~~ +``` (lldb) image lookup -a `context` Address: MyApp[0x00026258] (MyApp.__DATA.__data + 4) Summary: MyFooContext -~~~ +``` --- @@ -218,7 +216,7 @@ static const void *MyFooContext = &MyFooContext; On the subject of Key-Value Coding, [Pitiphong Phongpattranont](https://github.com/pitiphong-p) offers this useful function that builds a keypath from a variable list of selectors: -~~~{objective-c} +```objc inline NSString * PTPKeyPathForSelectors(SEL selector, ...) { if (!selector) { return nil; @@ -235,22 +233,22 @@ inline NSString * PTPKeyPathForSelectors(SEL selector, ...) { return [selectors componentsJoinedByString:@"."]; } -~~~ +``` -~~~{objective-c} +```objc NSString *keyPath = PTPKeyPathForSelectors(@selector(data), @selector(name), nil); // => @"data.name" -~~~ +``` ## Nomad CLI Utilities And finally, [Matthew Teece](https://github.com/mteece) gives a shout-out to [Nomad](http://nomad-cli.com), a world-class collection of command-line utilities—specifically, [Houston](https://github.com/nomad/houston), which can send and manage push notifications from the command line, or within your Ruby application. -~~~{bash} +```terminal $ apn push "" -c /path/to/cert.pem -m "Hello!" -~~~ +``` -* * * +--- Thus concludes this year's reader submissions. Thanks again to everyone for your submissions! diff --git a/2014-01-13-nsrange.md b/2014-01-13-nsrange.md index 1af331bc..8ec3a231 100644 --- a/2014-01-13-nsrange.md +++ b/2014-01-13-nsrange.md @@ -1,44 +1,44 @@ --- title: NSRange -author: Mattt Thompson +author: Mattt category: Cocoa excerpt: "NSRange is one of the essential types of Foundation. Passed around and returned in methods throughout the framework, being well-versed in this struct has a range of benefits." status: - swift: n/a + swift: n/a --- `NSRange` is one of the essential types of Foundation. Passed around and returned in methods throughout the framework, being well-versed in this struct has a range of benefits, which this week's article will help you locate. -* * * +--- Ranges are data types used to describe a contiguous interval of integers. They are most commonly used with strings, arrays, and similarly-ordered collections. For Objective-C programs, the Foundation type `NSRange` is used. In other languages, ranges are often encoded as a two-element array, containing the start and end indexes. In Foundation, `NSRange` instead encodes a range as struct containing the location and length. By command-clicking (`⌘-ʘ`) on the `NSRange` symbol in Xcode, we can jump directly to its declaration in `Foundation/NSRange.h`: -~~~{objective-c} +```objc typedef struct _NSRange { NSUInteger location; NSUInteger length; } NSRange; -~~~ +``` In practice, this approach helps mitigate common off-by-one errors when working with ranges. For example, compare the equivalent Javascript and Objective-C code for creating a range of characters for a given string: #### range.js -~~~{javascript} +```javascript var string = "hello, world"; var range = [0, string.length - 1]; -~~~ +``` Forgetting to subtract `1` for the end index in Javascript would result in an out-of-bounds error later. #### range.m -~~~{objective-c} +```objc NSString *string = @"hello, world"; NSRange range = NSMakeRange(0, [string length]); -~~~ +``` `NSRange`'s approach is clearer and less prone to error—especially when it comes to more complex arithmetic operations on ranges. @@ -46,142 +46,142 @@ NSRange range = NSMakeRange(0, [string length]); ### Strings -~~~{objective-c} +```objc NSString *string = @"lorem ipsum dolor sit amet"; NSRange range = [string rangeOfString:@"ipsum"]; // {.location=6, .length=5} NSString *substring = [string substringWithRange:range]; // @"ipsum" -~~~ +``` `NSString` does not have a method like `containsString:`. Instead, `rangeOfString:` can be used to check for an `NSNotFound` location value: -~~~{objective-c} +```objc NSString *input = ...; if ([input rangeOfString:@"keyword"].location != NSNotFound) { - // ... + <#...#> } -~~~ +``` ### Arrays -~~~{objective-c} +```objc NSArray *array = @[@"a", @"b", @"c", @"d"]; NSArray *subarray = [array subarrayWithRange:NSMakeRange(1, 2)]; // @[@"b", @"c"] -~~~ +``` ### Index Sets -[NSIndexSet](http://nshipster.com/nsindexset/) is a Foundation collection class that is similar to `NSRange`, with the notable exception of being able to support non-contiguous series. An `NSIndexSet` can be created from a range using the `indexSetWithIndexesInRange:` class constructor: +[NSIndexSet](https://nshipster.com/nsindexset/) is a Foundation collection class that is similar to `NSRange`, with the notable exception of being able to support non-contiguous series. An `NSIndexSet` can be created from a range using the `indexSetWithIndexesInRange:` class constructor: -~~~{objective-c} +```objc NSRange range = NSMakeRange(0, 10); NSIndexSet *indexSet = [NSIndexSet indexSetWithIndexesInRange:range]; -~~~ +``` ## Functions Because `NSRange` is not a class, creating and using instances is done through function calls, rather than, say, instance methods. -> Many of the NSRange functions are named counter to the modern conventions of Foundation and CoreFoundation wherein the relevant type of the function immediately follows the two-letter namespace. For example, `NSMakeRange` should instead be named `NSRangeMake`, following the example of `CGRectMake` and `CGSizeMake`, et al. Similarly, a better name for `NSEqualRanges` would be `NSRangeEqualToRange`, just like `CGPointEqualToPoint`. +> Many of the NSRange functions are named counter to the modern conventions of Foundation and CoreFoundation wherein the relevant type of the function immediately follows the two-letter namespace. For example, `NSMakeRange` should instead be named `NSRangeMake`, following the example of `CGRectMake` and `CGSizeMake`, et al. Similarly, a better name for `NSEqualRanges` would be `NSRangeEqualToRange`, just like `CGPointEqualToPoint`. > > Although consistency in itself is likely not sufficient reason to go through the trouble of replacing existing usage, [this gist](https://gist.github.com/mattt/8402537) shows how one could make their own code base a little more OCD-friendly. ### Creating an NSRange -> - `NSMakeRange`: Creates a new NSRange from the specified values. +> - `NSMakeRange`: Creates a new NSRange from the specified values. -~~~{objective-c} +```objc NSArray *array = @[@1, @2, @3]; NSRange range = NSMakeRange(0, [array count]); // {.location=0, .length=3} -~~~ +``` ### Querying Information > - `NSEqualRanges`: Returns a Boolean value that indicates whether two given ranges are equal. -~~~{objective-c} +```objc NSRange range1 = NSMakeRange(0, 6); NSRange range2 = NSMakeRange(2, 7); BOOL equal = NSEqualRanges(range1, range2); // NO -~~~ +``` -> - `NSLocationInRange`: Returns a Boolean value that indicates whether a specified position is in a given range. +> - `NSLocationInRange`: Returns a Boolean value that indicates whether a specified position is in a given range. -~~~{objective-c} +```objc NSRange range = NSMakeRange(3, 4); BOOL contained = NSLocationInRange(5, range); // YES -~~~ +``` > - `NSMaxRange`: Returns the sum of the location and length of the range. -~~~{objective-c} +```objc NSRange range = NSMakeRange(3, 4); NSUInteger max = NSMaxRange(range); // 7 -~~~ +``` ### Set Operations > - `NSIntersectionRange`: Returns the intersection of the specified ranges. If the returned range’s length field is `0`, then the two ranges don’t intersect, and the value of the location field is undefined. -~~~{objective-c} +```objc NSRange range1 = NSMakeRange(0, 6); NSRange range2 = NSMakeRange(2, 7); NSRange intersectionRange = NSIntersectionRange(range1, range2); // {.location=2, .length=4} -~~~ +``` > - `NSUnionRange`: Returns the union of the specified ranges. A range covering all indices in and between range1 and range2. If one range is completely contained in the other, the returned range is equal to the larger range. -~~~{objective-c} +```objc NSRange range1 = NSMakeRange(0, 6); NSRange range2 = NSMakeRange(2, 7); NSRange unionRange = NSUnionRange(range1, range2); // {.location=0, .length=9} -~~~ +``` -### Converting Between NSString * & NSRange +### Converting Between NSString \* & NSRange > - `NSStringFromRange`: Returns a string representation of a range. -~~~{objective-c} +```objc NSRange range = NSMakeRange(3, 4); NSString *string = NSStringFromRange(range); // @"{3,4}" -~~~ +``` > - `NSRangeFromString`: Returns a range from a textual representation. -~~~{objective-c} +```objc NSString *string = @"{1,5}"; NSRange range = NSRangeFromString(string); // {.location=1, .length=5} -~~~ +``` If the string passed into `NSRangeFromString` does not represent a valid range, it will return a range with its location and length set to `0`. -~~~{objective-c} +```objc NSString *string = @"invalid"; NSRange range = NSRangeFromString(string); // {.location=0, .length=0} -~~~ +``` While one might be tempted to use `NSStringFromRange` to box `NSRange` for inclusion within an `NSArray`, `NSValue +valueWithRange:` is the way to go: -~~~{objective-c} +```objc NSRange range = NSMakeRange(0, 3); NSValue *value = [NSValue valueWithRange:range]; -~~~ +``` -* * * +--- `NSRange` is one of the few cases where some of the underlying implementation of its functions are actually exposed and inlined in the public headers: #### Foundation/NSRange.h -~~~{objective-c} +```objc NS_INLINE NSRange NSMakeRange(NSUInteger loc, NSUInteger len) { NSRange r; r.location = loc; @@ -200,7 +200,7 @@ NS_INLINE BOOL NSLocationInRange(NSUInteger loc, NSRange range) { NS_INLINE BOOL NSEqualRanges(NSRange range1, NSRange range2) { return (range1.location == range2.location && range1.length == range2.length); } -~~~ +``` ## NSRangePointer @@ -208,13 +208,13 @@ One oddity worth mentioning with `NSRange` is the existence of `NSRangePointer`. #### Foundation/NSRange.h -~~~{objective-c} +```objc typedef NSRange *NSRangePointer; -~~~ +``` So. Without a definitive origin story, one would have to assume that this type was created by a well-meaning framework engineer who noted the confusion around `NSRange` being a struct and not a class. `NSRange *` is equivalent to `NSRangePointer`, though the latter can be found in out parameters for various methods throughout Foundation. `NSAttributedString`, for instance, has an `NSRangePointer` parameter for returning the effective range of an attribute at a particular index (since the attribute likely starts and ends before outside of the specified index): -~~~{objective-c} +```objc NSMutableAttributedString *mutableAttributedString = ...; NSRange range; if ([mutableAttributedString attribute:NSUnderlineStyleAttributeName @@ -226,17 +226,17 @@ if ([mutableAttributedString attribute:NSUnderlineStyleAttributeName value:[UIColor blueColor] range:range]; } -~~~ +``` ## CFRange One final caveat: Core Foundation also defines a `CFRange` type, which differs from `NSRange` in using `CFIndex` types for its members, and having only the function `CFRangeMake`: -~~~{objective-c} +```objc typedef struct { CFIndex location; CFIndex length; } CFRange; -~~~ +``` Anyone working with CoreText or another low-level C API is likely to encounter `CFRange` in place of `NSRange`. diff --git a/2014-01-20-extended-file-attributes.md b/2014-01-20-extended-file-attributes.md index 5aa09ab8..45805ddd 100644 --- a/2014-01-20-extended-file-attributes.md +++ b/2014-01-20-extended-file-attributes.md @@ -1,6 +1,6 @@ --- title: Extended File Attributes -author: Mattt Thompson +author: Mattt category: Objective-C excerpt: "Amidst revelations of widespread spying by the NSA, the concept of metadata has taken an unexpected role in the national conversation about government surveillance. What is it? And how much does it reveal about us and our daily habits? These are questions that the American people are asking, and they deserve an answer." status: @@ -15,11 +15,11 @@ Acting with a sense of civic and patriotic duty rivaled only by Uncle Sam wallop For every file on a UNIX filesystem, there is associated metadata. Indeed, having a path, permissions, and timestamp attributes is what makes a file a file, rather than just a blob of data. -However, on OS X and iOS, additional metadata can be stored in [**extended file attributes**](http://en.wikipedia.org/wiki/Extended_file_attributes). Introduced in OS X Tiger, they are perfect for associating small, application-specific data with a file. EAs are stored in the attributes B*-Tree of the HFS+ filesystem, and have a maximum size of 128KB as of OS X Lion & iOS 5. +However, on OS X and iOS, additional metadata can be stored in [**extended file attributes**](https://en.wikipedia.org/wiki/Extended_file_attributes). Introduced in OS X Tiger, they are perfect for associating small, application-specific data with a file. EAs are stored in the attributes B*-Tree of the HFS+ filesystem, and have a maximum size of 128KB as of OS X Lion & iOS 5. What kind of information, you ask? Invoke the `ls` command in the terminal and pass the `@` option to see what information hides in plain sight. -~~~ +``` $ ls -l@ -rw-r--r--@ 1 mattt staff 12292 Oct 19 05:59 .DS_Store com.apple.FinderInfo 32 @@ -29,7 +29,7 @@ $ ls -l@ -rw-r--r--@ 1 mattt staff 1438 Dec 18 14:31 Podfile com.macromates.selectionRange 4 com.macromates.visibleIndex 1 -~~~ +``` - Finder stores 32 bytes of information in `.DS_Store`, though for reasons that aren't entirely clear. - Xcode takes 15 bytes to denote the TextEncoding to use for a particular file. @@ -37,16 +37,16 @@ $ ls -l@ The extended attributes API, declared in ``, has functions for getting, setting, listing, and removing attributes: -~~~{objective-c} +```objc ssize_t getxattr(const char *path, const char *name, void *value, size_t size, u_int32_t position, int options); int setxattr(const char *path, const char *name, void *value, size_t size, u_int32_t position, int options); ssize_t listxattr(const char *path, char *namebuf, size_t size, int options); int removexattr(const char *path, const char *name, int options); -~~~ +``` -To show these in action, consider the use of extended attributes to associate an [HTTP Etag](http://en.wikipedia.org/wiki/HTTP_ETag) with a file: +To show these in action, consider the use of extended attributes to associate an [HTTP Etag](https://en.wikipedia.org/wiki/HTTP_ETag) with a file: -~~~{objective-c} +```objc NSHTTPURLResponse *response = ...; NSURL *fileURL = ...; @@ -54,11 +54,11 @@ const char *filePath = [fileURL fileSystemRepresentation]; const char *name = "com.Example.Etag"; const char *value = [[response allHeaderFields][@"Etag"] UTF8String]; int result = setxattr(filePath, name, value, strlen(value), 0, 0); -~~~ +``` As another example, previous to iOS 5.0.1, EAs were the designated way to denote that a particular file should not be synchronized with iCloud (as of iOS 5.1, `NSURL -setResourceValue:forKey:error:` is used, which sets the `com.apple.metadata:com_apple_backup_excludeItem` EA instead): -~~~{objective-c} +```objc #include if (!&NSURLIsExcludedFromBackupKey) { @@ -74,7 +74,7 @@ if (!&NSURLIsExcludedFromBackupKey) { forKey:NSURLIsExcludedFromBackupKey error:&error]; } -~~~ +``` Lest extended attributes veer dangerously close to "being a hammer that makes everything look like a nail", let it be made clear: **extended attributes should not be used for critical data**. Not all volume formats support extended attributes, so copying between, say, HFS+ and FAT32 may result in a loss of information. Also consider that nothing is stopping any application from deleting or overwriting extended attributes at any time. diff --git a/2014-01-27-stewardship.md b/2014-01-27-stewardship.md index 06c24e83..88a98be6 100644 --- a/2014-01-27-stewardship.md +++ b/2014-01-27-stewardship.md @@ -1,14 +1,14 @@ --- title: Stewardship -author: Mattt Thompson -category: "" +author: Mattt +category: Miscellaneous tags: nshipster excerpt: "Stewardship is an old word. It evokes the ethic of public service and duty. To be a steward is to embody the responsibilities that come with ownership. It is an act that justifies authority through continued accountability; both the greatest challenge and reward of creating and maintaining a project." status: - swift: n/a + swift: n/a --- -Open Source communities function within what economists describe as a [Gift Economy](http://en.wikipedia.org/wiki/Gift_economy). Rather than paying one another for goods or services through barter or currency, everyone shares freely with one another, and gains [social currency](http://en.wikipedia.org/wiki/Whuffie) based on their generosity. It's similar to how friends tend to take turns inviting one another over for dinner or a party. +Open Source communities function within what economists describe as a [Gift Economy](https://en.wikipedia.org/wiki/Gift_economy). Rather than paying one another for goods or services through barter or currency, everyone shares freely with one another, and gains [social currency](https://en.wikipedia.org/wiki/Whuffie) based on their generosity. It's similar to how friends tend to take turns inviting one another over for dinner or a party. With the negligible cost of distributing software over the Internet, developers are able to participate with millions of others around the world. And as a result, we have been able to collaboratively build amazing software. @@ -60,30 +60,30 @@ CocoaPods is the de facto dependency manager for integrating third party code in #### NSHipsterKit.podspec -~~~{ruby} +```ruby 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' => + s.homepage = 'https://nshipster.com' + s.authors = { 'Mattt' => '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 -~~~ +``` Once the `.podspec` has been submitted to the CocoaPods specs repository, a consumer would be able to add it to their own project with a Podfile: #### Podfile -~~~{ruby} +```ruby platform :ios, '7.0' pod 'NSHipsterKit', '~> 1.0' -~~~ +``` ## Maintaining @@ -107,7 +107,7 @@ Deviating from these conventions as an author is disrespectful to anyone using t ### Answering Questions -One of our greatest flaws as humans is our relative inability to comprehend not knowing or understanding something that we ourselves do. This makes it extremely difficult to diagnose (and at times empathize with) misunderstandings that someone else might be having. +One of our greatest flaws as humans is our relative inability to comprehend not knowing or understanding something that we ourselves do. This makes it extremely difficult to diagnose (and at times empathize with) misunderstandings that someone else might be having. There's also a slight sadistic tendency for developers to lord knowledge over anyone who doesn't know as much as they do. We had to figure it out for ourselves (uphill both ways, in the snow) so why shouldn't they have to as well? @@ -138,7 +138,7 @@ In any case, there will come a time when the lights need to be turned off, and i The alternative is to become a liability, an attractive nuisance... a mockery of what once was a respectable code base. -* * * +--- Creating is one of the most fulfilling experiences in life, and it's something that's only improved by sharing with others. As software developers, we have a unique opportunity to be unbounded by physical limitations to help one another. diff --git a/2014-02-03-mktileoverlay-mkmapsnapshotter-mkdirections.md b/2014-02-03-mktileoverlay-mkmapsnapshotter-mkdirections.md index 31312a98..599b2f03 100644 --- a/2014-02-03-mktileoverlay-mkmapsnapshotter-mkdirections.md +++ b/2014-02-03-mktileoverlay-mkmapsnapshotter-mkdirections.md @@ -1,6 +1,6 @@ --- title: "MKTileOverlay,
MKMapSnapshotter &
MKDirections" -author: Mattt Thompson +author: Mattt category: Cocoa excerpt: "Unless you work with MKMapView. on a regular basis, the last you may have heard about the current state of cartography on iOS may not have been under the cheeriest of circumstances. Therefore, it may come as a surprise maps on iOS have gotten quite a bit better in the intervening releases. Quite good, in fact." status: @@ -20,19 +20,19 @@ This week on NSHipster, we'll introduce `MKTileOverlay`, `MKMapSnapshotter`, and Don't like the default Apple Maps tiles? [`MKTileOverlay`](https://developer.apple.com/library/ios/documentation/MapKit/Reference/MKTileOverlay_class/Reference/Reference.html) allows you to seamlessly swap out to another tile set in just a few lines of code. -> Just like [OpenStreetMap](http://www.openstreetmap.org) and [Google Maps](https://maps.google.com), MKTileOverlay uses [spherical mercator projection (EPSG:3857)](http://en.wikipedia.org/wiki/Mercator_projection#The_spherical_model). +> Just like [OpenStreetMap](http://www.openstreetmap.org) and [Google Maps](https://maps.google.com), MKTileOverlay uses [spherical mercator projection (EPSG:3857)](https://en.wikipedia.org/wiki/Mercator_projection#The_spherical_model). ### Setting Custom Map View Tile Overlay -~~~{swift} +```swift let template = "http://tile.openstreetmap.org/{z}/{x}/{y}.png" let overlay = MKTileOverlay(URLTemplate: template) overlay.canReplaceMapContent = true mapView.addOverlay(overlay, level: .AboveLabels) -~~~ -~~~{objective-c} +``` +```objc static NSString * const template = @"http://tile.openstreetmap.org/{z}/{x}/{y}.png"; MKTileOverlay *overlay = [[MKTileOverlay alloc] initWithURLTemplate:template]; @@ -40,7 +40,7 @@ overlay.canReplaceMapContent = YES; [self.mapView addOverlay:overlay level:MKOverlayLevelAboveLabels]; -~~~ +``` MKTileOverlay is initialized with a URL template string, with the `x` & `y` tile coordinates within the specified zoom level. [MapBox has a great explanation for this scheme is used to generate tiles](https://www.mapbox.com/developers/guide/): @@ -71,7 +71,7 @@ After setting `canReplaceMapContent` to `YES`, the overlay is added to the `MKMa In the map view's delegate, `mapView:rendererForOverlay:` is implemented simply to return a new `MKTileOverlayRenderer` instance when called for the `MKTileOverlay` overlay. -~~~{swift} +```swift // MARK: MKMapViewDelegate func mapView(mapView: MKMapView, rendererForOverlay overlay: MKOverlay) -> MKOverlayRenderer { @@ -81,8 +81,8 @@ func mapView(mapView: MKMapView, rendererForOverlay overlay: MKOverlay) -> MKOve return MKTileOverlayRenderer(tileOverlay: tileOverlay) } -~~~ -~~~{objective-c} +``` +```objc #pragma mark - MKMapViewDelegate - (MKOverlayRenderer *)mapView:(MKMapView *)mapView @@ -94,7 +94,7 @@ func mapView(mapView: MKMapView, rendererForOverlay overlay: MKOverlay) -> MKOve return nil; } -~~~ +``` > Speaking of [MapBox](https://www.mapbox.com), [Justin R. Miller](https://github.com/incanus) maintains [MBXMapKit](https://www.mapbox.com/mbxmapkit/), a MapBox-enabled drop-in replacement for `MKMapView`. It's the easiest way to get up-and-running with this world-class mapping service, and highly recommended for anyone looking to make an impact with maps in their next release. @@ -102,7 +102,7 @@ func mapView(mapView: MKMapView, rendererForOverlay overlay: MKOverlay) -> MKOve If you need to accommodate a different tile coordinate scheme with your server, or want to add in-memory or offline caching, this can be done by subclassing `MKTileOverlay` and overriding `-URLForTilePath:` and `-loadTileAtPath:result:`: -~~~{swift} +```swift class MKHipsterTileOverlay : MKTileOverlay { let cache = NSCache() let operationQueue = NSOperationQueue() @@ -128,8 +128,8 @@ class MKHipsterTileOverlay : MKTileOverlay { } } } -~~~ -~~~{objective-c} +``` +```objc @interface XXTileOverlay : MKTileOverlay @property NSCache *cache; @property NSOperationQueue *operationQueue; @@ -160,7 +160,7 @@ class MKHipsterTileOverlay : MKTileOverlay { } @end -~~~ +``` ## MKMapSnapshotter @@ -170,7 +170,7 @@ Another addition to iOS 7 was [`MKMapSnapshotter`](https://developer.apple.com/l ### Creating a Map View Snapshot -~~~{swift} +```swift let options = MKMapSnapshotOptions() options.region = mapView.region options.size = mapView.frame.size @@ -188,8 +188,8 @@ snapshotter.startWithCompletionHandler { snapshot, error in let data = UIImagePNGRepresentation(snapshot.image) data?.writeToURL(fileURL, atomically: true) } -~~~ -~~~{objective-c} +``` +```objc MKMapSnapshotOptions *options = [[MKMapSnapshotOptions alloc] init]; options.region = self.mapView.region; options.size = self.mapView.frame.size; @@ -208,7 +208,7 @@ MKMapSnapshotter *snapshotter = [[MKMapSnapshotter alloc] initWithOptions:option NSData *data = UIImagePNGRepresentation(image); [data writeToURL:fileURL atomically:YES]; }]; -~~~ +``` First, an `MKMapSnapshotOptions` object is created, which is used to specify the region, size, scale, and [camera](https://developer.apple.com/library/mac/documentation/MapKit/Reference/MKMapCamera_class/Reference/Reference.html) used to render the map image. @@ -220,7 +220,7 @@ However, this only draws the map for the specified region; annotations are rende Including annotations—or indeed, any additional information to the map snapshot—can be done by dropping down into Core Graphics: -~~~{swift} +```swift snapshotter.startWithQueue(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) { snapshot, error in guard let snapshot = snapshot else { print("Snapshot error: \(error)") @@ -249,8 +249,8 @@ snapshotter.startWithQueue(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEF let data = UIImagePNGRepresentation(compositeImage) data?.writeToURL(fileURL, atomically: true) } -~~~ -~~~{objective-c} +``` +```objc [snapshotter startWithQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) completionHandler:^(MKMapSnapshot *snapshot, NSError *error) { if (error) { @@ -283,13 +283,13 @@ snapshotter.startWithQueue(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEF } UIGraphicsEndImageContext(); }]; -~~~ +``` ## MKDirections The final iOS 7 addition to MapKit that we'll discuss is [`MKDirections`](https://developer.apple.com/library/mac/documentation/MapKit/Reference/MKDirections_class/Reference/Reference.html). -> `MKDirections`' spiritual predecessor (of sorts), [`MKLocalSearch`](https://developer.apple.com/library/ios/documentation/MapKit/Reference/MKLocalSearch/Reference/Reference.html) was discussed in [a previous NSHipster article](http://nshipster.com/mklocalsearch/) +> `MKDirections`' spiritual predecessor (of sorts), [`MKLocalSearch`](https://developer.apple.com/library/ios/documentation/MapKit/Reference/MKLocalSearch/Reference/Reference.html) was discussed in [a previous NSHipster article](https://nshipster.com/mklocalsearch/) As its name implies, `MKDirections` fetches routes between two waypoints. A `MKDirectionsRequest` object is initialized with a `source` and `destination`, and is then passed into an `MKDirections` object, which can calculate several possible routes and estimated travel times. @@ -299,7 +299,7 @@ Building on the previous example, here is how `MKDirections` might be used to cr ### Getting Snapshots for each Step of Directions on a Map View -~~~{swift} +```swift let request = MKDirectionsRequest() request.source = MKMapItem.mapItemForCurrentLocation() request.destination = MKMapItem(...) @@ -390,8 +390,8 @@ func stepImagesFromDirectionsResponse(response: MKDirectionsResponse, completion } } } -~~~ -~~~{objective-c} +``` +```objc NSMutableArray *mutableStepImages = [NSMutableArray array]; MKDirectionsRequest *request = [[MKDirectionsRequest alloc] init]; @@ -448,7 +448,7 @@ MKDirections *directions = [[MKDirections alloc] initWithRequest:request]; }]; } }]; -~~~ +``` * * * diff --git a/2014-02-10-associated-objects.md b/2014-02-10-associated-objects.md index 9ed63c90..06fb7afa 100644 --- a/2014-02-10-associated-objects.md +++ b/2014-02-10-associated-objects.md @@ -1,23 +1,23 @@ --- title: Associated Objects -author: Mattt Thompson +author: Mattt category: Objective-C excerpt: "Associated Objects is a feature of the Objective-C 2.0 runtime, which allows objects to associate arbitrary values for keys at runtime. It's dark juju, to be handled with as much caution as any other function from objc/runtime.h" status: swift: n/a --- -~~~{objective-c} +```objc #import -~~~ +``` Objective-C developers are conditioned to be wary of whatever follows this ominous incantation. And for good reason: messing with the Objective-C runtime changes the very fabric of reality for all of the code that runs on it. -In the right hands, the functions of `` have the potential to add powerful new behavior to an application or framework, in ways that would otherwise not be possible. In the wrong hands, it drains the proverbial [sanity meter](http://en.wikipedia.org/wiki/Eternal_Darkness:_Sanity's_Requiem#Sanity_effects) of the code, and everything it may interact with (with [terrifying side-effects](http://www.youtube.com/watch?v=RSXcajQnasc#t=0m30s)). +In the right hands, the functions of `` have the potential to add powerful new behavior to an application or framework, in ways that would otherwise not be possible. In the wrong hands, it drains the proverbial [sanity meter](https://en.wikipedia.org/wiki/Eternal_Darkness:_Sanity's_Requiem#Sanity_effects) of the code, and everything it may interact with (with [terrifying side-effects](https://www.youtube.com/watch?v=RSXcajQnasc#t=0m30s)). -Therefore, it is with great trepidation that we consider this [Faustian bargain](http://en.wikipedia.org/wiki/Deal_with_the_Devil), and look at one of the subjects most-often requested by NSHipster readers: associated objects. +Therefore, it is with great trepidation that we consider this [Faustian bargain](https://en.wikipedia.org/wiki/Deal_with_the_Devil), and look at one of the subjects most-often requested by NSHipster readers: associated objects. -* * * +--- Associated Objects—or Associative References, as they were originally known—are a feature of the Objective-C 2.0 runtime, introduced in OS X Snow Leopard (available in iOS 4). The term refers to the following three C functions declared in ``, which allow objects to associate arbitrary values for keys at runtime: @@ -27,17 +27,11 @@ Associated Objects—or Associative References, as they were originally known— Why is this useful? It allows developers to **add custom properties to existing classes in categories**, which [is an otherwise notable shortcoming for Objective-C](https://developer.apple.com/library/ios/documentation/cocoa/conceptual/ProgrammingWithObjectiveC/CustomizingExistingClasses/CustomizingExistingClasses.html). -#### NSObject+AssociatedObject.h - -~~~{objective-c} +```objc @interface NSObject (AssociatedObject) @property (nonatomic, strong) id associatedObject; @end -~~~ -#### NSObject+AssociatedObject.m - -~~~{objective-c} @implementation NSObject (AssociatedObject) @dynamic associatedObject; @@ -48,20 +42,20 @@ Why is this useful? It allows developers to **add custom properties to existing - (id)associatedObject { return objc_getAssociatedObject(self, @selector(associatedObject)); } -~~~ +``` It is often recommended that they key be a `static char`—or better yet, the pointer to one. Basically, an arbitrary value that is guaranteed to be constant, unique, and scoped for use within getters and setters: -~~~{objective-c} +```objc static char kAssociatedObjectKey; objc_getAssociatedObject(self, &kAssociatedObjectKey); -~~~ +``` However, a much simpler solution exists: just use a selector. - - + ## Associative Object Behaviors @@ -71,17 +65,17 @@ Values can be associated onto objects according to the behaviors defined by the Behavior - @property Equivalent + @property Equivalent Description - OBJC_ASSOCIATION_ASSIGN + OBJC_ASSOCIATION_ASSIGN - @property (assign) or @property (unsafe_unretained) + @property (assign) or @property (unsafe_unretained) Specifies a weak reference to the associated object. @@ -89,10 +83,10 @@ Values can be associated onto objects according to the behaviors defined by the - OBJC_ASSOCIATION_RETAIN_NONATOMIC + OBJC_ASSOCIATION_RETAIN_NONATOMIC - @property (nonatomic, strong) + @property (nonatomic, strong) Specifies a strong reference to the associated object, and that the association is not made atomically. @@ -100,10 +94,10 @@ Values can be associated onto objects according to the behaviors defined by the - OBJC_ASSOCIATION_COPY_NONATOMIC + OBJC_ASSOCIATION_COPY_NONATOMIC - @property (nonatomic, copy) + @property (nonatomic, copy) Specifies that the associated object is copied, and that the association is not made atomically. @@ -111,10 +105,10 @@ Values can be associated onto objects according to the behaviors defined by the - OBJC_ASSOCIATION_RETAIN + OBJC_ASSOCIATION_RETAIN - @property (atomic, strong) + @property (atomic, strong) Specifies a strong reference to the associated object, and that the association is made atomically. @@ -122,10 +116,10 @@ Values can be associated onto objects according to the behaviors defined by the - OBJC_ASSOCIATION_COPY + OBJC_ASSOCIATION_COPY - @property (atomic, copy) + @property (atomic, copy) Specifies that the associated object is copied, and that the association is made atomically. @@ -136,32 +130,55 @@ Values can be associated onto objects according to the behaviors defined by the Weak associations to objects made with `OBJC_ASSOCIATION_ASSIGN` are not zero `weak` references, but rather follow a behavior similar to `unsafe_unretained`, which means that one should be cautious when accessing weakly associated objects within an implementation. -> According to the Deallocation Timeline described in [WWDC 2011, Session 322](https://developer.apple.com/videos/wwdc/2011/#322-video) (~36:00), associated objects are erased surprisingly late in the object lifecycle, in `object_dispose()`, which is invoked by `NSObject -dealloc`. +{% info %} +According to the deallocation timeline described in +[WWDC 2011, Session 322](https://asciiwwdc.com/2011/sessions/322) (~36:00), +associated objects are erased surprisingly late in the object lifecycle --- +`object_dispose()`, +which is invoked by `NSObject -dealloc`. +{% endinfo %} ## Removing Values One may be tempted to call `objc_removeAssociatedObjects()` at some point in their foray into associated objects. However, [as described in the documentation](https://developer.apple.com/library/mac/documentation/Cocoa/Reference/ObjCRuntimeRef/Reference/reference.html#//apple_ref/c/func/objc_removeAssociatedObjects), it's unlikely that you would have an occasion to invoke it yourself: ->The main purpose of this function is to make it easy to return an object to a "pristine state”. You should not use this function for general removal of associations from objects, since it also removes associations that other clients may have added to the object. Typically you should use objc_setAssociatedObject with a nil value to clear an association. +> The main purpose of this function is to make it easy to return an object to a "pristine state”. You should not use this function for general removal of associations from objects, since it also removes associations that other clients may have added to the object. Typically you should use objc_setAssociatedObject with a nil value to clear an association. ## Patterns -- **Adding private variables to facilitate implementation details**. When extending the behavior of a built-in class, it may be necessary to keep track of additional state. This is the _textbook_ use case for associated objects. For example, AFNetworking uses associated objects on its `UIImageView` category to [store a request operation object](https://github.com/AFNetworking/AFNetworking/blob/2.1.0/UIKit%2BAFNetworking/UIImageView%2BAFNetworking.m#L57-L63), used to asynchronously fetch a remote image at a particular URL. -- **Adding public properties to configure category behavior.** Sometimes, it makes more sense to make category behavior more flexible with a property, than in a method parameter. In these situations, a public-facing property is an acceptable situation to use associated objects. To go back to the previous example of AFNetworking, its category on `UIImageView`, [its `imageResponseSerializer`](https://github.com/AFNetworking/AFNetworking/blob/2.1.0/UIKit%2BAFNetworking/UIImageView%2BAFNetworking.h#L60-L65) allows image views to optionally apply a filter, or otherwise change the rendering of a remote image before it is set and cached to disk. -- **Creating an associated observer for KVO**. When using [KVO](http://nshipster.com/key-value-observing/) in a category implementation, it is recommended that a custom associated-object be used as an observer, rather than the object observing itself. +### Adding private variables to facilitate implementation details + +When extending the behavior of a built-in class, it may be necessary to keep track of additional state. This is the _textbook_ use case for associated objects. + +### Adding public properties to configure category behavior. + +Sometimes, it makes more sense to make category behavior more flexible with a property, than in a method parameter. In these situations, a public-facing property is an acceptable situation to use associated objects. + +### Creating an associated observer for KVO + +When using [KVO](https://nshipster.com/key-value-observing/) in a category implementation, it is recommended that a custom associated-object be used as an observer, rather than the object observing itself. ## Anti-Patterns -- **Storing an associated object, when the value is not needed**. A common pattern for views is to create a convenience method that populates fields and attributes based on a model object or compound value. If that value does not need to be recalled later, it is acceptable, and indeed preferable, not to associate with that object. -- **Storing an associated object, when the value can be inferred.** For example, one might be tempted to store a reference to a custom accessory view's containing `UITableViewCell`, for use in `tableView:accessoryButtonTappedForRowWithIndexPath:`, when this can retrieved by calling `cellForRowAtIndexPath:`. -- **Using associated objects instead of X**, where X is any one the following: - - [Subclassing](https://developer.apple.com/library/ios/documentation/cocoa/conceptual/ProgrammingWithObjectiveC/CustomizingExistingClasses/CustomizingExistingClasses.html) for when inheritance is a more reasonable fit than composition. - - [Target-Action](https://developer.apple.com/library/ios/documentation/general/conceptual/Devpedia-CocoaApp/TargetAction.html) for adding interaction events to responders. - - [Gesture Recognizers](https://developer.apple.com/library/ios/documentation/EventHandling/Conceptual/EventHandlingiPhoneOS/GestureRecognizer_basics/GestureRecognizer_basics.html) for any situations when target-action doesn't suffice. - - [Delegation](https://developer.apple.com/library/ios/documentation/general/conceptual/DevPedia-CocoaCore/Delegation.html) when behavior can be delegated to another object. - - [NSNotification & NSNotificationCenter](http://nshipster.com/nsnotification-and-nsnotificationcenter/) for communicating events across a system in a loosely-coupled way. +### Storing an associated object, when the value is not needed -* * * +A common pattern for views is to create a convenience method that populates fields and attributes based on a model object or compound value. If that value does not need to be recalled later, it is acceptable, and indeed preferable, not to associate with that object. + +### Storing an associated object, when the value can be inferred + +For example, one might be tempted to store a reference to a custom accessory view's containing `UITableViewCell`, for use in `tableView:accessoryButtonTappedForRowWithIndexPath:`, when this can retrieved by calling `cellForRowAtIndexPath:`. + +### Using associated objects instead of _X_ + +...where X is any one the following: + + - [Subclassing](https://developer.apple.com/library/ios/documentation/cocoa/conceptual/ProgrammingWithObjectiveC/CustomizingExistingClasses/CustomizingExistingClasses.html) for when inheritance is a more reasonable fit than composition. + - [Target-Action](https://developer.apple.com/library/ios/documentation/general/conceptual/Devpedia-CocoaApp/TargetAction.html) for adding interaction events to responders. + - [Gesture Recognizers](https://developer.apple.com/library/ios/documentation/EventHandling/Conceptual/EventHandlingiPhoneOS/GestureRecognizer_basics/GestureRecognizer_basics.html) for any situations when target-action doesn't suffice. + - [Delegation](https://developer.apple.com/library/ios/documentation/general/conceptual/DevPedia-CocoaCore/Delegation.html) when behavior can be delegated to another object. + - [NSNotification & NSNotificationCenter](https://nshipster.com/nsnotification-and-nsnotificationcenter/) for communicating events across a system in a loosely-coupled way. + +--- Associated objects should be seen as a method of last resort, rather than a solution in search of a problem (and really, categories themselves really shouldn't be at the top of the toolchain to begin with). diff --git a/2014-02-17-method-swizzling.md b/2014-02-17-method-swizzling.md index c4caca87..b3c49093 100644 --- a/2014-02-17-method-swizzling.md +++ b/2014-02-17-method-swizzling.md @@ -1,6 +1,6 @@ --- title: Method Swizzling -author: Mattt Thompson +author: Mattt category: Objective-C excerpt: "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 changed at runtime, by changing how selectors are mapped to underlying functions in a class's dispatch table." status: @@ -18,9 +18,9 @@ status: > Would you do it?
> And so we cannot know ourselves or what we'd really do...
> With all your power ... What would you do?
-> The Flaming Lips, "The Yeah Yeah Yeah Song (With All Your Power)" +> The Flaming Lips, "The Yeah Yeah Yeah Song (With All Your Power)" -In last week's article about [associated objects](http://nshipster.com/associated-objects/), we began to explore the dark arts of the Objective-C runtime. This week, we venture further, to discuss what is perhaps the most contentious of runtime hackery techniques: method swizzling. +In last week's article about [associated objects](https://nshipster.com/associated-objects/), we began to explore the dark arts of the Objective-C runtime. This week, we venture further, to discuss what is perhaps the most contentious of runtime hackery techniques: method swizzling. * * * @@ -30,9 +30,10 @@ For example, let's say we wanted to track how many times each view controller is Each view controller could add tracking code to its own implementation of `viewDidAppear:`, but that would make for a ton of duplicated boilerplate code. Subclassing would be another possibility, but it would require subclassing `UIViewController`, `UITableViewController`, `UINavigationController`, and every other view controller class—an approach that would also suffer from code duplication. -Fortunately, there is another way: **method swizzling** from a category. Here's how to do it: +Fortunately, there is another way: **method swizzling** from a category. +Here's how to do it: -~~~{objective-c} +```objc #import @implementation UIViewController (Tracking) @@ -49,25 +50,21 @@ Fortunately, there is another way: **method swizzling** from a category. Here's Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector); // When swizzling a class method, use the following: - // Class class = object_getClass((id)self); - // ... // Method originalMethod = class_getClassMethod(class, originalSelector); // Method swizzledMethod = class_getClassMethod(class, swizzledSelector); - BOOL didAddMethod = - class_addMethod(class, - originalSelector, - method_getImplementation(swizzledMethod), - method_getTypeEncoding(swizzledMethod)); + IMP originalImp = method_getImplementation(originalMethod); + IMP swizzledImp = method_getImplementation(swizzledMethod); - if (didAddMethod) { - class_replaceMethod(class, + class_replaceMethod(class, swizzledSelector, - method_getImplementation(originalMethod), + originalImp, method_getTypeEncoding(originalMethod)); - } else { - method_exchangeImplementations(originalMethod, swizzledMethod); - } + class_replaceMethod(class, + originalSelector, + swizzledImp, + method_getTypeEncoding(swizzledMethod)); + }); } @@ -79,9 +76,7 @@ Fortunately, there is another way: **method swizzling** from a category. Here's } @end -~~~ - -> In computer science, [pointer swizzling](http://en.wikipedia.org/wiki/Pointer_swizzling) is the conversion of references based on name or position to direct pointer references. While the origins of Objective-C's usage of the term are not entirely known, it's understandable why it was co-opted, since method swizzling involves changing the reference of a function pointer by its selector. +``` Now, when any instance of `UIViewController`, or one of its subclasses invokes `viewWillAppear:`, a log statement will print out. @@ -89,6 +84,19 @@ Injecting behavior into the view controller lifecycle, responder events, view dr Regardless of _why_ or _where_ one chooses to use swizzling, the _how_ remains absolute: +{% info %} + +In computer science, +[pointer swizzling](https://en.wikipedia.org/wiki/Pointer_swizzling) +is the conversion of references based on name or position +to direct pointer references. +While the origins of Objective-C's usage of the term are not entirely known, +it's understandable why it was co-opted, +since method swizzling involves changing the reference of a function pointer +by its selector. + +{% endinfo %} + ## +load vs. +initialize **Swizzling should always be done in `+load`.** @@ -101,7 +109,7 @@ Because method swizzling affects global state, it is important to minimize the p **Swizzling should always be done in a `dispatch_once`.** -Again, because swizzling changes global state, we need to take every precaution available to us in the runtime. Atomicity is one such precaution, as is a guarantee that code will be executed exactly once, even across different threads. Grand Central Dispatch's `dispatch_once` provides both of these desirable behaviors, and should be considered as much a standard practice for swizzling as they are for [initializing singletons](http://nshipster.com/c-storage-classes/). +Again, because swizzling changes global state, we need to take every precaution available to us in the runtime. Atomicity is one such precaution, as is a guarantee that code will be executed exactly once, even across different threads. Grand Central Dispatch's `dispatch_once` provides both of these desirable behaviors, and should be considered as much a standard practice for swizzling as they are for [initializing singletons](https://nshipster.com/c-storage-classes/). ## Selectors, Methods, & Implementations @@ -121,28 +129,42 @@ To swizzle a method is to change a class's dispatch table in order to resolve me It may appear that the following code will result in an infinite loop: -~~~{objective-c} +```objc - (void)xxx_viewWillAppear:(BOOL)animated { [self xxx_viewWillAppear:animated]; NSLog(@"viewWillAppear: %@", NSStringFromClass([self class])); } -~~~ +``` Surprisingly, it won't. In the process of swizzling, `xxx_viewWillAppear:` has been reassigned to the original implementation of `UIViewController -viewWillAppear:`. It's good programmer instinct for calling a method on `self` in its own implementation to raise a red flag, but in this case, it makes sense if we remember what's _really_ going on. However, if we were to call `viewWillAppear:` in this method, it _would_ cause an infinite loop, since the implementation of this method will be swizzled to the `viewWillAppear:` selector at runtime. -> Remember to prefix your swizzled method name, the same way you might any other contentious category method. +{% warning %} + +Remember to prefix your swizzled method name +as you would any other contentious category method. + +{% endwarning %} ## Considerations Swizzling is widely considered a voodoo technique, prone to unpredictable behavior and unforeseen consequences. While it is not the safest thing to do, method swizzling is reasonably safe, when the following precautions are taken: -- **Always invoke the original implementation of a method (unless you have a good reason not to)**: APIs provide a contract for input and output, but the implementation in-between is a black box. Swizzling a method and not calling the original implementation may cause underlying assumptions about private state to break, along with the rest of your application. -- **Avoid collisions**: Prefix category methods, and make damn well sure that nothing else in your code base (or any of your dependencies) are monkeying around with the same piece of functionality as you are. -- **Understand what's going on**: Simply copy-pasting swizzling code without understanding how it works is not only dangerous, but is a wasted opportunity to learn a lot about the Objective-C runtime. Read through the [Objective-C Runtime Reference](https://developer.apple.com/library/mac/documentation/Cocoa/Reference/ObjCRuntimeRef/Reference/reference.html#//apple_ref/c/func/method_getImplementation) and browse `` to get a good sense of how and why things happen. _Always endeavor to replace magical thinking with understanding._ -- **Proceed with caution**: No matter how confident you are about swizzling Foundation, UIKit, or any other built-in framework, know that everything could break in the next release. Be ready for that, and go the extra mile to ensure that in playing with fire, you don't get `NSBurned`. +### Always invoke the original implementation of a method (unless you have a good reason not to) + +APIs provide a contract for input and output, but the implementation in-between is a black box. Swizzling a method and not calling the original implementation may cause underlying assumptions about private state to break, along with the rest of your application. + +### Avoid collisions + +Prefix category methods, and make damn well sure that nothing else in your code base (or any of your dependencies) are monkeying around with the same piece of functionality as you are. + +### Understand what's going on + +Simply copy-pasting swizzling code without understanding how it works is not only dangerous, but is a wasted opportunity to learn a lot about the Objective-C runtime. Read through the [Objective-C Runtime Reference](https://developer.apple.com/library/mac/documentation/Cocoa/Reference/ObjCRuntimeRef/Reference/reference.html#//apple_ref/c/func/method_getImplementation) and browse `` to get a good sense of how and why things happen. _Always endeavor to replace magical thinking with understanding._ + +### Proceed with caution -> Feeling gun shy about invoking the Objective-C runtime directly? [Jonathan ‘Wolf’ Rentzsch](https://twitter.com/rentzsch) provides a battle-tested, CocoaPods-ready library called [JRSwizzle](https://github.com/rentzsch/jrswizzle) that will take care of everything for you. +No matter how confident you are about swizzling Foundation, UIKit, or any other built-in framework, know that everything could break in the next release. Be ready for that, and go the extra mile to ensure that in playing with fire, you don't get `NSBurned`. * * * -Like [associated objects](http://nshipster.com/associated-objects/), method swizzling is a powerful technique when you need it, but should be used sparingly. +Like [associated objects](/associated-objects/), method swizzling is a powerful technique when you need it, but should be used sparingly. diff --git a/2014-02-24-namespacing.md b/2014-02-24-namespacing.md index af5bb98a..510f3fcf 100644 --- a/2014-02-24-namespacing.md +++ b/2014-02-24-namespacing.md @@ -1,6 +1,6 @@ --- title: Namespacing -author: Mattt Thompson +author: Mattt category: Objective-C excerpt: "Namespacing is the preeminent bugbear of Objective-C. A cosmetic quirk with global implications, the language's lack of identifier containers remains a source of prodigious quantities of caremad for armchair language critics." status: @@ -15,9 +15,9 @@ Like a parent faced with the task of explaining the concept of death or the non- > Why, Jimmy, `NS` stands for `NeXTSTEP` (well, actually, `NeXTSTEP/Sun`, but we'll cover that with "the birds & the bees" talk), and it's used to... -...but by the time the words have left your mouth, you can already sense the disappointment in their face. Their innocence has been lost, and with an audible *sigh* of resignation, they start to ask uncomfortable questions about [@](http://nshipster.com/at-compiler-directives/) +...but by the time the words have left your mouth, you can already sense the disappointment in their face. Their innocence has been lost, and with an audible _sigh_ of resignation, they start to ask uncomfortable questions about [@](https://nshipster.com/at-compiler-directives/) -* * * +--- Namespacing is the preeminent bugbear of Objective-C. A cosmetic quirk with global implications, the language's lack of identifier containers remains a source of prodigious quantities of caremad for armchair language critics. @@ -33,16 +33,16 @@ As noted many times in this publication, Objective-C is built directly on top of You can see this for yourself—try defining a new static variable with the same name as an existing `@interface`, and the compiler will generate an error: -~~~{objective-c} +```objc @interface XXObject : NSObject @end static char * XXObject; // Redefinition of "XXObject" as different kind of symbol -~~~ +``` That said, the Objective-C runtime creates a layer of abstraction on top of the C type system, allowing the following code to compile without even a snicker: -~~~{objective-c} +```objc @protocol Malkovich @end @@ -66,7 +66,7 @@ That said, the Objective-C runtime creates a layer of abstraction on top of the return Malkovich; } @end -~~~ +``` Within the context of the Objective-C runtime, a program is able to differentiate between a class, a protocol, a category, an instance variable, an instance method, and a class method all having the same name. @@ -90,43 +90,43 @@ A veteran Mac or iOS developer will have likely memorized most if not all of the - ABAddressBook / AddressBookUI - ACAccounts - ADiAd - ALAssetsLibrary - AUAudioUnit - AVAVFoundation - CACoreAnimation - CBCoreBluetooth - CFCoreFoundation / CFNetwork - CGCoreGraphics / QuartzCore / ImageIO - CICoreImage - CLCoreLocation - CMCoreMedia / CoreMotion - CVCoreVideo - EAExternalAccessory - EKEventKit / EventKitUI - GCGameController - GLK*GLKit - JSJavaScriptCore - MAMediaAccessibility - MCMultipeerConnectivity - MFMessageUI* - MIDI*CoreMIDI - MKMapKit - MPMediaPlayer - NKNewsstandKit - NSFoundation, AppKit, CoreData - PKPassKit - QLQuickLook - SCSystemConfiguration - Sec*Security* - SKStoreKit / SpriteKit - SLSocial - SSSafari Services - TWTwitter - UIUIKit - UTMobileCoreServices + ABAddressBook / AddressBookUI + ACAccounts + ADiAd + ALAssetsLibrary + AUAudioUnit + AVAVFoundation + CACoreAnimation + CBCoreBluetooth + CFCoreFoundation / CFNetwork + CGCoreGraphics / QuartzCore / ImageIO + CICoreImage + CLCoreLocation + CMCoreMedia / CoreMotion + CVCoreVideo + EAExternalAccessory + EKEventKit / EventKitUI + GCGameController + GLK*GLKit + JSJavaScriptCore + MAMediaAccessibility + MCMultipeerConnectivity + MFMessageUI* + MIDI*CoreMIDI + MKMapKit + MPMediaPlayer + NKNewsstandKit + NSFoundation, AppKit, CoreData + PKPassKit + QLQuickLook + SCSystemConfiguration + Sec*Security* + SKStoreKit / SpriteKit + SLSocial + SSSafari Services + TWTwitter + UIUIKit + UTMobileCoreServices @@ -144,23 +144,23 @@ Because of this, many established libraries still use 2-letter prefixes. Conside - AFAFNetworking ("Alamofire") - RKRestKit - GPUGPUImage - SDSDWebImage - MBMBProgressHUD - FBFacebook SDK - FMFMDB ("Flying Meat") - JKJSONKit - FUIFlatUI - NINimbus - RACReactive Cocoa + AFAFNetworking ("Alamofire") + RKRestKit + GPUGPUImage + SDSDWebImage + MBMBProgressHUD + FBFacebook SDK + FMFMDB ("Flying Meat") + JKJSONKit + FUIFlatUI + NINimbus + RACReactive Cocoa Seeing as how [we're already seeing prefix overlap among 3rd-party libraries](https://github.com/AshFurrow/AFTabledCollectionView), make sure that you follow a 3+-letter convention in your own code. -> For especially future-focused library authors, consider using [`@compatibility_alias`](http://nshipster.com/at-compiler-directives/) to provide a seamless migration path for existing users in your next major upgrade. +> For especially future-focused library authors, consider using [`@compatibility_alias`](https://nshipster.com/at-compiler-directives/) to provide a seamless migration path for existing users in your next major upgrade. ### Method Prefixes @@ -168,21 +168,21 @@ It's not just classes that are prone to naming collisions: selectors suffer from Consider the category: -~~~{objective-c} +```objc @interface NSString (PigLatin) - (NSString *)pigLatinString; @end -~~~ +``` If `-pigLatinString` were implemented by another category (or added to the `NSString` class in a future version of iOS or OS X), any calls to that method would result in undefined behavior, since no guarantee is made as to the order in which methods are defined by the runtime. This can be guarded against by prefixing the method name, just like the class name (prefixing the category name isn't a bad idea, either): -~~~{objective-c} +```objc @interface NSString (XXXPigLatin) - (NSString *)xxx_pigLatinString; @end -~~~ +``` Apple's recommendation that [all category methods use prefixes](https://developer.apple.com/library/ios/documentation/cocoa/conceptual/ProgrammingWithObjectiveC/CustomizingExistingClasses/CustomizingExistingClasses.html#//apple_ref/doc/uid/TP40011210-CH6-SW4) is even less widely known or accepted than its policy on class prefixing. @@ -196,9 +196,9 @@ Just as with constitutional scholarship, there will be strict and loose interpre #### Swizzling -The one case where method prefixing (or suffixing) is absolutely necessary is when doing method replacement, as discussed in last week's article on [swizzling](http://nshipster.com/method-swizzling/). +The one case where method prefixing (or suffixing) is absolutely necessary is when doing method replacement, as discussed in last week's article on [swizzling](https://nshipster.com/method-swizzling/). -~~~{objective-c} +```objc @implementation UIViewController (Swizzling) - (void)xxx_viewDidLoad { @@ -206,19 +206,19 @@ The one case where method prefixing (or suffixing) is absolutely necessary is wh // Swizzled implementation } -~~~ +``` ## Do We _Really_ Need Namespaces? With all of the recent talk about replacing / reinventing / reimagining Objective-C, it's almost taken as a given that namespacing would be an obvious feature. But what does that actually get us? -**Aesthetics?** Aside from IETF members and military personnel, nobody likes the visual aesthetic of CLAs. But would `::`, `/`, or an extra `.` really make matters better? Do we _really_ want to start calling `NSArray` "Foundation Array"? (And what would I do with NSHipster.com ?!) +**Aesthetics?** Aside from IETF members and military personnel, nobody likes the visual aesthetic of CLAs. But would `::`, `/`, or an extra `.` really make matters better? Do we _really_ want to start calling `NSArray` "Foundation Array"? (And what would I do with NSHipster.com ?!) **Semantics?** Start to look closely at any other language, and how they actually use namespaces, and you'll realize that namespaces don't magically solve all matters of ambiguity. If anything, the additional context makes things worse. Not to create a straw man, but an imagined implementation of Objective-C namespaces probably look a lot like this: -~~~{objective-c} +```objc @namespace XX @implementation Object @@ -227,12 +227,12 @@ Not to create a straw man, but an imagined implementation of Objective-C namespa - (void)foo { F:Array *array = @[@1,@2, @3]; - // ... + <#...#> } @end @end -~~~ +``` What we have currently—warts and all—has the notable advantage of non-ambiguity. There is no mistaking `NSString` for anything other than what it is, either by the compiler or when we talk about it as developers. There are no special contextual considerations to consider when reading through code to understand what actors are at play. And best of all: class names are [_exceedingly_ easy to search for](http://lmgtfy.com/?q=NSString). diff --git a/2014-03-03-nstemporarydirectory.md b/2014-03-03-nstemporarydirectory.md deleted file mode 100644 index 429b8e02..00000000 --- a/2014-03-03-nstemporarydirectory.md +++ /dev/null @@ -1,178 +0,0 @@ ---- -title: "NSTemporaryDirectory /
NSItemReplacementDirectory /
mktemp(3)" -author: Mattt Thompson -category: Cocoa -excerpt: "Volumes have been written about persisting data, but when it comes to short-lived, temporary files, there is very little to go on for Objective-C. (Or if there has, perhaps it was poetically ephemeral itself)." -revisions: - "2016-03-10": Translated sample code into Swift. -status: - swift: 2.1.1 - reviewed: March 10, 2016 ---- - -Volumes have been written about persisting data, but when it comes to short-lived, temporary files, there is very little to go on for Objective-C. (Or if there has, perhaps it was poetically ephemeral itself). - -* * * - -Temporary files are used to write a buffer to disk, to either be atomically moved to a permanent location, or processed in some manner and then discarded. Creating a temporary file involves finding the appropriate part of the filesystem, generating a unique name, and moving or deleting the file after you're finished using it. - -## Finding an Enclosing Directory - -The first step to creating temporary files or directories is to find a reasonable, out-of-the-way place to write to—somewhere that won't be backed up by Time Machine or synced to iCloud or the like. - -On Unix systems, the `/tmp` directory was the de facto scratch space, but with the sandboxed containers of iOS and OS X apps today, a hard-coded path just won't cut it. - -`NSTemporaryDirectory` is a Foundation function that returns the directory designated for writing short-lived files on the targeted platform. - -### A Wild Goose Chase - -In recent years, Apple has pushed to extricate filesystem path operations from `NSString` APIs, recommending that users switch to using `NSURL` and `NSURL`-based APIs for classes like `NSFileManager`. Unfortunately, the migration has not been entirely smooth. - -Consider the documentation for `NSTemporaryDirectory`: - -> See the `NSFileManager` method `URLForDirectory:inDomain:appropriateForURL:create:error:` for the preferred means of finding the correct temporary directory. - -Alright, fair enough. Let's see what's going on with `NSFileManager -URLForDirectory:inDomain:appropriateForURL:create:error:`: - -> You can also use this method to create a new temporary directory for storing things like autosave files; to do so, specify `NSItemReplacementDirectory` for the directory parameter, `NSUserDomainMask` for the `domain` parameter, and a valid parent directory for the `url` parameter. After locating (or creating) the desired directory, this method returns the URL for that directory. - -Huh? Even after reading through that a few times, it's still unclear how to use this, or what the expected behavior is. A quick search through the mailing lists [reaffirms](http://lists.apple.com/archives/cocoa-dev/2012/Apr/msg00117.html) this [confusion](http://lists.apple.com/archives/cocoa-dev/2012/Feb/msg00186.html). - -_Actually_, this method appears to be intended for moving _existing_ temporary files to a permanent location on disk with `-replaceItemAtURL:withItemAtURL:backupItemName:options:resultingItemURL:error:`. Not exactly what we're looking for. - -So much for the `NSString` filesystem API migration. Let's stick to something that works: - -~~~{swift} -NSURL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true) -~~~ -~~~{objective-c} -[NSURL fileURLWithPath:NSTemporaryDirectory() isDirectory:YES]; -~~~ - -## Generating a Unique Directory or File Name - -With a place to call home (temporarily), the next step is to figure out what to name our temporary file. We don't really care what temporary files are named—the only real concern is that they're unique, so as to not interfere with, or be interfered by, any other temporary files. - -The best way to generate a unique identifier is to use the `globallyUniqueString` method on `NSProcessInfo` - -~~~{swift} -let identifier = NSProcessInfo.processInfo().globallyUniqueString -~~~ -~~~{objective-c} -NSString *identifier = [[NSProcessInfo processInfo] globallyUniqueString]; -~~~ - -This will return a string in the format: `5BD255F4-CA55-4B82-A555-0F4BC5CA2AD6-479-0000018E14D059CC` - -> Other sources advise the direct invocation of the `mktemp(3)` system command in order to mitigate potential conflicts. However, using `NSProcessInfo -globallyUniqueString` to generate unique names is extremely unlikely to result in a collision. - -Alternatively, `NSUUID` ([discussed previously](http://nshipster.com/uuid-udid-unique-identifier)) also produces workable results, assuming that you're not doing anything _too_ crazy. - -~~~{swift} -NSUUID().UUIDString -~~~ -~~~{objective-c} -[[NSUUID UUID] UUIDString] -~~~ - -This produces a string in the format: `22361D15-E17B-4C48-AEA6-C73BBEA17011` - -## Creating a Temporary File Path - -Using the aforementioned technique for generating unique identifiers, we can create unique temporary file paths: - -~~~{swift} -let fileName = String(format: "%@_%@", NSProcessInfo.processInfo().globallyUniqueString, "file.txt") -let fileURL = NSURL(fileURLWithPath: NSTemporaryDirectory()).URLByAppendingPathComponent(fileName) -~~~ -~~~{objective-c} -NSString *fileName = [NSString stringWithFormat:@"%@_%@", [[NSProcessInfo processInfo] globallyUniqueString], @"file.txt"]; -NSURL *fileURL = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:fileName]]; -~~~ - -## Creating a Temporary Directory - -In situations where many temporary files might be created by a process, it may be a good idea to create a temporary sub-directory, which could then be removed for easy cleanup. - -Creating a temporary directory is no different than any other invocation of `NSFileManager -createDirectoryAtURL:withIntermediateDirectories:attributes:error:`: - -~~~{swift} -let directoryURL = NSURL(fileURLWithPath: NSTemporaryDirectory()).URLByAppendingPathComponent(NSProcessInfo.processInfo().globallyUniqueString, isDirectory: true) -do { - try NSFileManager.defaultManager().createDirectoryAtURL(directoryURL, withIntermediateDirectories: true, attributes: nil) -} catch { - // ... -} -~~~ -~~~{objective-c} -NSURL *directoryURL = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]] isDirectory:YES]; -[[NSFileManager defaultManager] createDirectoryAtURL:directoryURL withIntermediateDirectories:YES attributes:nil error:&error]; -~~~ - -And, of course, temporary file paths relative to this directory can be created with `URLByAppendingPathComponent:`: - -~~~{swift} -let fileURL = directoryURL.URLByAppendingPathComponent(fileName) -~~~ -~~~{objective-c} -NSURL *fileURL = [directoryURL URLByAppendingPathComponent:fileName]; -~~~ - -## Writing to a Temporary File - -Files don't exist on the file system until a particular file path is either touched or written to. - -### NSData -writeToURL:options:error - -There are several ways in which data is written to disk in Foundation. The most straightforward of which is `NSData -writeToURL:options:error`: - -~~~{swift} -let data: NSData = ... -do { - try data.writeToURL(fileURL, options: .AtomicWrite) -} catch { - // ... -} -~~~ -~~~{objective-c} -NSData *data = ...; -NSError *error = nil; -[data writeToURL:fileURL options:NSDataWritingAtomic error:&error]; -~~~ - -### NSOutputStream - -For more advanced APIs, it is not uncommon to pass an `NSOutputStream` instance to direct the flow of data. Again, creating an output stream to a temporary file path is no different than any other kind of file path: - -~~~{swift} -let outputStream = NSOutputStream(toFileAtPath: fileURL.absoluteString, append: false) -~~~ -~~~{objective-c} -NSOutputStream *outputStream = [NSOutputStream outputStreamToFileAtPath:[fileURL absoluteString] append:NO]; -~~~ - -### Cleaning Up - -The final step is what makes a temporary file _actually temporary_: clean up. - -Although files in a system-designated temporary directory make no guarantees about how long they'll exist before being deleted automatically by the operating system (up to a few days, according to scattered reports), it's still good practice to take care of it yourself once you're finished. - -Do that with `NSFileManager -removeItemAtURL:`, which works for both a temporary file and a temporary directory: - -~~~{swift} -do { - try NSFileManager.defaultManager().removeItemAtURL(fileURL) -} catch { - // ... -} -~~~ -~~~{objective-c} -NSError *error = nil; -[[NSFileManager defaultManager] removeItemAtURL:fileURL error:&error]; -~~~ - -* * * - -"This too shall pass" is a mantra that acknowledges that all things are indeed temporary. Within the context of the application lifecycle, some things are more temporary than others, and it is in that knowledge that we act appropriately, seeking to find the right place, make a unique impact, and leave without a trace. - -Perhaps we can learn something from this cycle in our own, brief and glorious lifecycle. diff --git a/2014-03-03-temporary-files.md b/2014-03-03-temporary-files.md new file mode 100644 index 00000000..e82eba37 --- /dev/null +++ b/2014-03-03-temporary-files.md @@ -0,0 +1,383 @@ +--- +title: "Temporary Files" +author: Mattt +category: Cocoa +excerpt: >- + Volumes have been written about persisting data, + but when it comes to short-lived, temporary files, + there is very little to go on for Cocoa. + (Or if there has, perhaps it was poetically ephemeral itself). +revisions: + "2014-03-03": Original publication + "2018-10-24": Updated for Swift 4.2 + "2018-11-21": Corrected use of `url(for:in:appropriateFor:create:)` + "2018-11-21": Corrected use of `url(for:in:appropriateFor:create:)` + "2019-03-09": Corrected use of deprecated `NSData.WritingOptions.atomicWrite` +status: + swift: 4.2 +--- + +Volumes have been written about persisting data, +but when it comes to short-lived, temporary files, +there is very little to go on for Cocoa. +(Or if there has, perhaps it was poetically ephemeral itself). + +--- + +Temporary files are used to write data to disk +before either moving it to a permanent location +or discarding it. +For example, when a movie editor app exports a project, +it may write each frame to a temporary file until it reaches the end +and moves the completed file to the `~/Movies` directory. +Using a temporary file for these kinds of situations +ensures that tasks are completed atomically +(either you get a finished product or nothing at all; nothing half-way), +and without creating excessive memory pressure on the system +(on most computers, disk space is plentiful whereas memory is limited). + +There are four distinct steps to working with a temporary file: + +1. Creating a temporary directory in the filesystem +2. Creating a temporary file in that directory with a unique filename +3. Writing data to the temporary file +4. Moving or deleting the temporary file once you're finished with it + +## Creating a Temporary Directory + +The first step to creating a temporary file +is to find a reasonable, out-of-the-way location to which you can write --- +somewhere inconspicuous that doesn't +get in the way of the user +or get picked up by a system process like +Spotlight indexing, Time Machine backups, or iCloud sync. + +On Unix systems, the `/tmp` directory is the de facto scratch space. +However, today's macOS and iOS apps run in a container +and don't have access to system directories; +a hard-coded path like that isn't going to cut it. + +If you don't intend to keep the temporary file around, +you can use the `NSTemporaryDirectory()` function +to get a path to a temporary directory for the current user. + +```swift +let temporaryDirectoryURL = URL(fileURLWithPath: NSTemporaryDirectory(), + isDirectory: true) +``` + +```objc +NSURL *temporaryDirectoryURL = [NSURL fileURLWithPath: NSTemporaryDirectory() + isDirectory: YES]; +``` + +Alternatively, +if you intend to move your temporary file to a destination URL, +the preferred (albeit more complicated) approach +is to call the `FileManager` method `uri(for:in:appropriateFor:create:)`. + +```swift +let destinationURL: URL = <#/path/to/destination#> +let temporaryDirectoryURL = + try FileManager.default.url(for: .itemReplacementDirectory, + in: .userDomainMask, + appropriateFor: destinationURL, + create: true) +``` + +```objc +NSURL *destinationURL = <#/path/to/destination#>; + +NSFileManager *fileManager = [NSFileManager defaultManager]; +NSError *error = nil; +NSURL *temporaryDirectoryURL = + [fileManager URLForDirectory:NSItemReplacementDirectory + inDomain:NSUserDomainMask + appropriateForURL:destinationURL + create:YES + error:&error]; +``` + +The parameters of this method are frequently misunderstood, +so let's go through each to understand what this method actually does: + +- We pass the item replacement search path (`.itemReplacementDirectory`) + to say that we're interested in a temporary directory. +- We pass the user domain mask (`.userDomainMask`) + to get a directory that's accessible to the user. +- For the `appropriateForURL` parameter, + we specify our `destinationURL`, + so that the system returns a temporary directory + from which a file can be quickly moved to the destination + (and not, say across different volumes). +- Finally, we pass `true` to the `create` parameter + to save us the additional step of creating it ourselves. + +The resulting directory will have a path that looks something like this: +file:///var/folders/l3/kyksr35977d8nfl1mhw6l_c00000gn/T/TemporaryItems/(A%20Document%20Being%20Saved%20By%20NSHipster%208)/ + +## Creating a Temporary File + +With a place to call home (at least temporarily), +the next step is to figure out what to call our temporary file. +We're not picky about what it's named --- +just so long as it's unique, +and doesn't interfere with any other temporary files in the directory. + +The best way to generate a unique identifier +is the `ProcessInfo` property `globallyUniqueString`: + +```swift +ProcessInfo().globallyUniqueString +``` + +```objc +[[NSProcessInfo processInfo] globallyUniqueString]; +``` + +The resulting filename will look something like this: +42BC63F7-E79E-4E41-8E0D-B72B049E9254-25121-000144AB9F08C9C1 + +Alternatively, +[`UUID`](https://nshipster.com/uuid-udid-unique-identifier) +also produces workably unique identifiers: + +```swift +UUID().uuidString +``` + +```objc +[[NSUUID UUID] UUIDString] +``` + +A generated UUID string has the following format: +B49C292E-573D-4F5B-A362-3F2291A786E7 + +Now that we have an appropriate directory and a unique filename, +let's put them together to create our temporary file: + +```swift +let destinationURL: URL = <#/path/to/destination#> + +let temporaryDirectoryURL = + try FileManager.default.url(for: .itemReplacementDirectory, + in: .userDomainMask, + appropriateFor: destinationURL, + create: true) + +let temporaryFilename = ProcessInfo().globallyUniqueString + +let temporaryFileURL = + temporaryDirectoryURL.appendingPathComponent(temporaryFilename) +``` + +```objc +NSURL *destinationURL = <#/path/to/destination#>; + +NSFileManager *fileManager = [NSFileManager defaultManager]; +NSError *error = nil; +NSURL *temporaryDirectoryURL = + [fileManager URLForDirectory:NSItemReplacementDirectory + inDomain:NSUserDomainMask + appropriateForURL:destinationURL + create:YES + error:&error]; + +NSString *temporaryFilename = + [[NSProcessInfo processInfo] globallyUniqueString]; +NSURL *temporaryFileURL = + [temporaryDirectoryURL + URLByAppendingPathComponent:temporaryFilename]; +``` + +## Writing to a Temporary File + +The sole act of creating a file URL is of no consequence to the file system; +a file is created only when the file path is written to. +So let's talk about our options for doing that: + +### Writing Data to a URL + +The simplest way to write data to a file +is to call the `Data` method `write(to:options)`: + +```swift +let data: Data = <#some data#> +try data.write(to: temporaryFileURL, + options: .atomic) +``` + +```objc +NSData *data = <#some data#>; +NSError *error = nil; +[data writeToURL:temporaryFileURL + options:NSDataWritingAtomic + error:&error]; +``` + +By passing the `atomic` option, +we ensure that either all of the data is written +or the method returns an error. + +### Writing Data to a File Handle + +If you're doing anything more complicated +than writing a single `Data` object to a file, +you might instead create an empty file +and use a `FileHandle` to write data incrementally. + +```swift +fileManager.createFile(atPath: temporaryFileURL.path, contents: Data()) + +let fileHandle = try FileHandle(forWritingTo: temporaryFileURL) +defer { fileHandle.closeFile() } + +fileHandle.write(data) + +// ... +``` + +```objc +[fileManager createFileAtPath: [temporaryFileURL path] + contents: [NSData data] + attributes: @{}]; + +NSError *error = nil; +NSFileHandle *fileHandle = + [NSFileHandle fileHandleForWritingToURL:temporaryFileURL + error:&error]; +[fileHandle writeData:data]; + +// ... + +[fileHandle closeFile]; +``` + +### Writing Data to an Output Stream + +For more advanced APIs, +it's not uncommon to use `OutputStream` +to direct the flow of data. +Creating an output stream to a temporary file +is no different than any other kind of file: + +```swift +let outputStream = + OutputStream(url: temporaryFileURL, append: true)! +defer { outputStream.close() } + +data.withUnsafeBytes { bytes in + outputStream.write(bytes, maxLength: bytes.count) +} +``` + +```objc +NSOutputStream *outputStream = + [NSOutputStream outputStreamWithURL:temporaryFileURL + append:YES]; + +[outputStream write:data.bytes + maxLength:data.length]; + +[outputStream close]; +``` + +{% info %} +In Swift, +calling `fileHandle.closeFile()` or +`outputStream.close()` +within a [`defer`](https://nshipster.com/guard-and-defer/) statement +is a convenient way to fulfill the API contract +of closing a file when we're done with it. +(Of course, don't do this if you want to keep the file handle open +longer than the enclosing scope). +{% endinfo %} + +## Moving or Deleting the Temporary File + +Files in system-designated temporary directories +are periodically deleted by the operating system. +So if you intend to hold onto the file that you've been writing to, +you need to move it somewhere outside the line of fire. + +If you already know where the file's going to live, +you can use `FileManager` to move it to its permanent home: + +```swift +let fileURL: URL = <#/path/to/file#> +try FileManager.default.moveItem(at: temporaryFileURL, + to: fileURL) +``` + +```objc +NSFileManager *fileManager = [NSFileManager defaultManager]; + +NSURL *fileURL = <#/path/to/file#>; +NSError *error = nil; +[fileManager moveItemAtURL:temporaryFileURL + toURL:fileURL + error:&error]; +``` + +{% info %} + +Or, if you're not entirely settled on that, +you can use the same approach +to locate a cache directory where the file can lie low for a while: + +```swift +let cacheDirectoryURL = + try FileManager.default.url(for: .cachesDirectory, + in: .userDomainMask, + appropriateFor: nil, + create: false) +``` + +```objc +NSFileManager *fileManager = [NSFileManager defaultManager]; + +NSError *error = nil; +NSURL *cacheDirectoryURL = + [fileManager URLForDirectory:NSCachesDirectory + inDomain:NSUserDomainMask + appropriateForURL:nil + create:NO + error:&error]; +``` + +{% endinfo %} + +Although the system eventually takes care of files in temporary directories, +it's not a bad idea to be a responsible citizen +and follow the guidance of +_"take only pictures; leave only footprints."_ + +`FileManager` can help us out here as well, +with the `removeItem(at:)` method: + +```swift +try FileManager.default.removeItem(at: temporaryFileURL) +``` + +```objc +NSFileManager *fileManager = [NSFileManager defaultManager]; + +NSError *error = nil; +[fileManager removeItemAtURL:temporaryFileURL + error:&error]; +``` + +--- + +_"This too shall pass"_ +is a mantra that acknowledges that all things are indeed temporary. + +Within the context of the application lifecycle, +some things are more temporary than others, +and it's with that knowledge that we choose to act appropriately: +seeking to find the right place, +make a unique impact, +and leave without a trace. + +Perhaps we can learn something from this cycle in our own, +brief and glorious lifecycle. diff --git a/2014-03-10-dictionary-services.md b/2014-03-10-dictionary-services.md index ad4ebebf..3e880256 100644 --- a/2014-03-10-dictionary-services.md +++ b/2014-03-10-dictionary-services.md @@ -1,28 +1,52 @@ --- -title: "UIReferenceLibraryViewController /
DCSDictionaryRef/
/usr/share/dict/words" -author: Mattt Thompson +title: Dictionary Services +author: Mattt category: Cocoa tags: cfhipsterref -excerpt: "Though widely usurped of their 'go-to reference' status by the Internet, dictionaries and word lists serve an important role behind the scenes of functionality ranging from spell check, grammar check, and auto-correct to auto-summarization and semantic analysis." +excerpt: >- + Though widely usurped of their 'go-to reference' status by the Internet, + dictionaries and word lists serve an important role + behind the scenes for features ranging from + spell check, grammar check, and auto-correct to + auto-summarization and semantic analysis. +revisions: + "2014-03-10": Original publication + "2019-01-07": Updated for Swift 4.2 status: - swift: t.b.c. + swift: 4.2 --- -Librarian, illustrated by Conor Heelan - -This week's article is about dictionaries. Not the `NSDictionary` / `CFDictionaryRef` we encounter everyday, but those distant lexicographic vestiges of school days past. - -> But seriously, why are dictionaries called that, anyway? Why can't we just settle on `Hash`, like those nice Ruby folks? What's that? Semantic overlap with hashing functions and cryptographic digests? Well, dictionary isn't _that_ bad. Anything other than "associative arrays", I reckon. - -Though widely usurped of their "go-to reference" status by the Internet, dictionaries and word lists serve an important role behind the scenes of functionality ranging from spell check, grammar check, and auto-correct to auto-summarization and semantic analysis. So, for your reference, here's a look at the ways and means by which computers give meaning to the world through words, in Unix, OS X, and iOS. - -* * * +This week's article is about dictionaries. +No, not the `Dictionary` / `NSDictionary` / `CFDictionaryRef` +we encounter every day, +but rather those distant lexicographic vestiges of school days past. + +{% info %} +But seriously, why are dictionaries called that, anyway? +Why can't we just settle on `Hash`, like those nice Ruby folks? +_What's that? +Semantic overlap with hashing functions and cryptographic digests?_ +Well, dictionary isn't _that_ bad. +Anything's better than "associative arrays", I suppose... +{% endinfo %} + +Though widely usurped of their 'go-to reference' status by the Internet, +dictionaries and word lists serve an important role +behind the scenes for features ranging from +spell check, grammar check, and auto-correct to +auto-summarization and semantic analysis. +So, for your reference, +here's a look at the ways and means by which +computers give meaning to the world through words, +in Unix, macOS, and iOS. ## Unix -Nearly all Unix distributions include a small collection newline-delimited list of words. On OS X, these can be found at `/usr/share/dict`: +Nearly all Unix distributions include +a small collection newline-delimited list of words. +On macOS, these can be found at `/usr/share/dict`: -~~~bash +```terminal $ ls /usr/share/dict README connectives @@ -30,18 +54,22 @@ $ ls /usr/share/dict web2 web2a words@ -> web2 -~~~ +``` -Symlinked to `words` is the `web2` word list, which, though not exhaustive, is still a sizable corpus: +Symlinked to `words` is the `web2` word list, +which --- +though not exhaustive --- +is still a sizable corpus: -~~~bash +```terminal $ 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": +Skimming with `head` shows what fun lies herein. +Such excitement is rarely so palpable as it is among words beginning with "a": -~~~bash +```terminal $ head /usr/share/dict/words A a @@ -53,22 +81,32 @@ $ head /usr/share/dict/words aardvark aardwolf Aaron -~~~ - -These giant, system-provided text files make it easy to `grep` crossword puzzle clues, generate mnemonic pass phrases, and seed databases, but from a user perspective, `/usr/share/dict`'s monolingualism and lack of associated meaning make it less than useful for everyday use. - -OS X builds upon this with its own system dictionaries. Never one to disappoint, the operating system's penchant for extending Unix functionality through strategically placed bundles and plist files is in full force with how dictionaries are distributed. - -* * * - -## OS X - -The OS X analog to `/usr/share/dict` can be found in `/Library/Dictionaries`. -A quick peek into the shared system dictionaries demonstrates one immediate improvement over Unix, by acknowledging the existence of languages other than English: - -~~~bash +``` + +These giant, system-provided text files make it easy to +`grep` crossword puzzle clues, +generate mnemonic passphrases, and +seed databases. +But from a user perspective, +`/usr/share/dict`'s monolingualism +and lack of associated meaning +make it less than helpful for everyday use. + +macOS builds upon this with its own system dictionaries. +Never one to disappoint, +the operating system's penchant for extending Unix functionality +by way of strategically placed bundles and plist files +is in full force here with how dictionaries are distributed. + +## macOS + +The macOS analog to `/usr/share/dict` can be found in `/Library/Dictionaries`. +A quick peek into the shared system dictionaries +demonstrates one immediate improvement over Unix: +acknowledging the existence of languages other than English: + +```terminal $ ls /Library/Dictionaries/ - Apple Dictionary.dictionary/ Diccionario General de la Lengua Española Vox.dictionary/ Duden Dictionary Data Set I.dictionary/ @@ -85,15 +123,18 @@ $ ls /Library/Dictionaries/ Sanseido The WISDOM English-Japanese Japanese-English Dictionary.dictionary/ Simplified Chinese - English.dictionary/ The Standard Dictionary of Contemporary Chinese.dictionary/ -~~~ +``` -OS X ships with dictionaries in Chinese, English, French, Dutch, Italian, Japanese, Korean, as well as an English thesaurus and a special dictionary for Apple-specific terminology. +macOS 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. -Diving deeper into the rabbit hole, we peruse the `.dictionary` bundles to see them for what they really are: +Diving deeper into the rabbit hole, +we peruse the `.dictionary` bundles to see them for what they really are: -~~~bash +```terminal $ ls "/Library/Dictionaries/New Oxford American Dictionary.dictionary/Contents" - Body.data DefaultStyle.css EntryID.data @@ -105,43 +146,86 @@ $ ls "/Library/Dictionaries/New Oxford American Dictionary.dictionary/Contents" Resources/ _CodeSignature/ version.plist -~~~ +``` -A filesystem autopsy reveals some interesting implementation details. In the case of the New Oxford American Dictionary in particular, contents include: +{% comment %} +🧛🏼‍ +(What is a dictionary? +A miserable little pile of definitions. +But enough talk... +Have at you!) +{% endcomment %} -- Binary-encoded `KeyText.data`, `KeyText.index`, & `Content.data` +A filesystem autopsy reveals some interesting implementation details. +For New Oxford American Dictionary, in particular, +its contents include: + +- Binary-encoded `KeyText.data`, `KeyText.index`, and `Content.data` - CSS for styling entries - 1207 images, from A-Frame to Zither. -- Preference to switch between [US English Diacritical Pronunciation](http://en.wikipedia.org/wiki/Pronunciation_respelling_for_English) and [IPA](http://en.wikipedia.org/wiki/International_Phonetic_Alphabet) (International Phonetic Alphabet) -- Manifest & signature for dictionary contents +- Preference to switch between + [US English Diacritical Pronunciation](https://en.wikipedia.org/wiki/Pronunciation_respelling_for_English) and + [International Phonetic Alphabet (IPA)](https://en.wikipedia.org/wiki/International_Phonetic_Alphabet) + +Proprietary binary encoding like this would usually be +the end of the road in terms of what one could reasonably do with data. +Luckily, Core Services provides APIs to read this information. + +### Getting the Definition of Words + +To get the definition of a word on macOS, +one can use the `DCSCopyTextDefinition` function +found in the Core Services framework: + +```swift +import Foundation +import CoreServices.DictionaryServices + +func define(_ word: String) -> String? { + let nsstring = word as NSString + let cfrange = CFRange(location: 0, length: nsstring.length) -Normally, proprietary binary encoding would be the end of the road in terms of what one could reasonably do with data, but luckily, Core Services provides APIs to read this information. + guard let definition = DCSCopyTextDefinition(nil, nsstring, cfrange) else { + return nil + } -#### Getting Definition of Word + return String(definition.takeUnretainedValue()) +} -To get the definition of a word on OS X, one can use the `DCSCopyTextDefinition` function, found in the Core Services framework: +define("apple") // "apple | ˈapəl | noun 1 the round fruit of a tree..." +``` -~~~{objective-c} +```objc #import NSString *word = @"apple"; NSString *definition = (__bridge_transfer NSString *)DCSCopyTextDefinition(NULL, (__bridge CFStringRef)word, CFRangeMake(0, [word length])); NSLog(@"%@", definition); -~~~ +``` -Wait, where did all of those great dictionaries go? +_Wait, where did all of those great dictionaries go?_ -Well, they all disappeared into that first `NULL` argument. One might expect to provide a `DCSCopyTextDefinition` type here, as prescribed by the function definition. However, there are no public functions to construct or copy such a type, making `NULL` the only available option. The documentation is as clear as it is stern: +Well, they all disappeared into that first `NULL` argument. +One might expect to provide a `DCSCopyTextDefinition` type here --- +as prescribed by the function definition. +However, there are no public functions to construct or copy such a type, +making `nil` the only available option. +The documentation is as clear as it is stern: -> This parameter is reserved for future use, so pass `NULL`. Dictionary Services searches in all active dictionaries. +> This parameter is reserved for future use, so pass `NULL`. +> Dictionary Services searches in all active dictionaries. -"Dictionary Services searches in **all active dictionaries**", you say? Sounds like a loophole! +"Dictionary Services searches in **all active dictionaries**", you say? +Sounds like a loophole! #### Setting Active Dictionaries -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: +Now, there's nothing programmers love to hate to love more than +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: -~~~{objective-c} +```objc NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; NSMutableDictionary *dictionaryPreferences = [[userDefaults persistentDomainForName:@"com.apple.DictionaryServices"] mutableCopy]; NSArray *activeDictionaries = [dictionaryPreferences objectForKey:@"DCSActiveDictionaries"]; @@ -154,17 +238,24 @@ dictionaryPreferences[@"DCSActiveDictionaries"] = @[@"/Library/Dictionaries/Oxfo } dictionaryPreferences[@"DCSActiveDictionaries"] = activeDictionaries; [userDefaults setPersistentDomain:dictionaryPreferences forName:@"com.apple.DictionaryServices"]; -~~~ +``` -"But this is OS X, a platform whose manifest destiny cannot be contained by meager sandboxing attempts from Cupertino!", you cry. "Isn't there a more civilized approach? Like, say, private APIs?" +_"But this is macOS, +a platform whose manifest destiny +cannot be contained by meager sandboxing attempts from Cupertino!"_, +you cry. +_"Isn't there a more civilized approach? Like, say, private APIs?"_ -Why yes, yes there are. +Why yes. +Yes there are. -### Private APIs +#### Exploring Private APIs -Not publicly exposed, but still available through Core Services are a number of functions that cut closer to the dictionary services functionality that we crave: +Not publicly exposed but still available through Core Services +are a number of functions that cut closer to +the dictionary services functionality we crave: -~~~{objective-c} +```objc extern CFArrayRef DCSCopyAvailableDictionaries(); extern CFStringRef DCSDictionaryGetName(DCSDictionaryRef dictionary); extern CFStringRef DCSDictionaryGetShortName(DCSDictionaryRef dictionary); @@ -182,13 +273,15 @@ extern CFStringRef DCSRecordGetRawHeadword(CFTypeRef record); extern CFStringRef DCSRecordGetString(CFTypeRef record); extern CFStringRef DCSRecordGetTitle(CFTypeRef record); extern DCSDictionaryRef DCSRecordGetSubDictionary(CFTypeRef record); -~~~ +``` -Private as they are, these functions aren't about to start documenting themselves, so let's take a look at how they're used: +Private as they are, +these functions aren't about to start documenting themselves. +So let's take a look at how they're used: #### Getting Available Dictionaries -~~~{objective-c} +```objc NSMapTable *availableDictionariesKeyedByName = [NSMapTable mapTableWithKeyOptions:NSPointerFunctionsCopyIn valueOptions:NSPointerFunctionsObjectPointerPersonality]; @@ -197,13 +290,15 @@ for (id dictionary in (__bridge_transfer NSArray *)DCSCopyAvailableDictionaries( NSString *name = (__bridge NSString *)DCSDictionaryGetName((__bridge DCSDictionaryRef)dictionary); [availableDictionariesKeyedByName setObject:dictionary forKey:name]; } -~~~ +``` #### Getting Definition for Word -With instances of the elusive `DCSDictionaryRef` type available at our disposal, we can now see what all of the fuss is about with that first argument in `DCSCopyTextDefinition`: +With instances of the elusive `DCSDictionaryRef` type available at our disposal, +we can now see what all of the fuss is about with +that first argument in `DCSCopyTextDefinition`: -~~~{objective-c} +```objc NSString *word = @"apple"; for (NSString *name in availableDictionariesKeyedByName) { @@ -230,50 +325,74 @@ for (NSString *name in availableDictionariesKeyedByName) { } } } -~~~ - -Most surprising from this experimentation is the ability to access the raw HTML for entries, which combined with a dictionary's bundled CSS, produces the result seen in Dictionary.app. +``` -![Entry for "apple" in Dictionary.app]({{ site.asseturl }}/dictionary.png) +Most surprising from this experimentation +is the ability to access the raw HTML for entries, +which --- combined with a dictionary's bundled CSS --- +produces the result seen in Dictionary.app. -> For any fellow linguistics nerds or markup curious folks out there, here's [the HTML of the entry for the word "apple"](https://gist.github.com/mattt/9453538). +{% asset dictionary.png alt="Entry for "apple" in Dictionary.app" %} -In the process of writing this article, I _accidentally_ created [an Objective-C wrapper](https://github.com/mattt/DictionaryKit) around this forbidden fruit (so forbidden by our favorite fruit company, so don't try submitting this to the App Store). - -* * * +{% info %} +In the process of writing this article, +I _accidentally_ created a +[wrapper library](https://github.com/mattt/DictionaryKit) +around this forbidden fruit +(that is, forbidden by our favorite fruit company; +don't try submitting this to the App Store). +{% endinfo %} ## 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 available (as of iOS 5) through the obscure UIKit class `UIReferenceLibraryViewController`. +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 available +through an 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` is similar to an +`MFMessageComposeViewController` in that provides +a minimally-configurable view controller around system functionality +that's intended to present modally. -Simply initialize with the desired term: +You initialize it with the desired term: -~~~{objective-c} +```objc UIReferenceLibraryViewController *referenceLibraryViewController = [[UIReferenceLibraryViewController alloc] initWithTerm:@"apple"]; [viewController presentViewController:referenceLibraryViewController animated:YES completion:nil]; -~~~ - -![Presenting a UIReferenceLibraryViewController]({{ site.asseturl }}/uireferencelibraryviewcontroller-1.png) +``` -This is the same behavior that one might encounter by tapping the "Define" `UIMenuItem` on a highlighted word in a `UITextView`. +{% asset uireferencelibraryviewcontroller-1.png alt="Presenting a UIReferenceLibraryViewController" %} -> Tapping on "Manage" brings up a view to download additional dictionaries. +This is the same behavior that one might encounter +when tapping the "Define" menu item on a highlighted word in a text view. -![Presenting a UIReferenceLibraryViewController]({{ site.asseturl }}/uireferencelibraryviewcontroller-2.png) +{% asset uireferencelibraryviewcontroller-2.png alt="Presenting a UIReferenceLibraryViewController" %} -`UIReferenceLibraryViewController` also provides the class method `dictionaryHasDefinitionForTerm:`. A developer would do well to call this before presenting a dictionary view controller that will inevitably have nothing to display. +`UIReferenceLibraryViewController` also provides +the class method `dictionaryHasDefinitionForTerm:`. +A developer would do well to call this +before presenting a dictionary view controller +that will inevitably have nothing to display. -~~~{objective-c} +```objc [UIReferenceLibraryViewController dictionaryHasDefinitionForTerm:@"apple"]; -~~~ +``` -> In both cases, it appears that `UIReferenceLibraryViewController` will do its best to normalize the search term, so stripping whitespace or changing to lowercase should not be necessary. +{% info %} +In both cases, it appears that `UIReferenceLibraryViewController` will do its best to normalize the search term, so stripping whitespace or changing to lowercase should not be necessary. +{% endinfo %} -* * * +--- -From Unix word lists to their evolved `.dictionary` bundles on OS X (and presumably iOS), words are as essential to application programming as mathematical constants and the "Sosumi" alert noise. Consider how the aforementioned APIs can be integrated into your own app, or used to create a kind of app you hadn't previously considered. There are a [wealth](http://nshipster.com/nslocalizedstring/) [of](http://nshipster.com/nslinguistictagger/) [linguistic](http://nshipster.com/search-kit/) [technologies](http://nshipster.com/uilocalizedindexedcollation/) baked into Apple's platforms, so take advantage of them. +From Unix word lists to their evolved `.dictionary` bundles on macOS +(and presumably iOS, too), +words are as essential to application programming +as mathematical constants and the +["Sosumi"](https://en.wikipedia.org/wiki/Sosumi) alert noise. +Consider how the aforementioned APIs can be integrated into your own app, +or used to create a kind of app you hadn't previously considered. diff --git a/2014-03-17-empathy.md b/2014-03-17-empathy.md index fc26c133..bfcfa38c 100644 --- a/2014-03-17-empathy.md +++ b/2014-03-17-empathy.md @@ -1,7 +1,7 @@ --- title: Empathy -author: Mattt Thompson -category: "" +author: Mattt +category: Miscellaneous tags: nshipster excerpt: "We naturally want to help one another, to explain ideas, to be generous and patient. However, on the Internet, human nature seems to drop a few packets." status: diff --git a/2014-03-24-nsurl.md b/2014-03-24-nsurl.md index 810dc765..71955acf 100644 --- a/2014-03-24-nsurl.md +++ b/2014-03-24-nsurl.md @@ -1,35 +1,35 @@ --- title: "NSURL /
NSURLComponents" -author: Mattt Thompson +author: Mattt category: Cocoa excerpt: "Of all the one-dimensional data types out there, URIs reign supreme. Here, in a single, human-parsable string, is every conceivable piece of information necessary to encode the location of any piece of information that has, does, and will ever exist on a computer." revisions: - "2014-03-24": Original publication. - "2015-09-08": Added Swift translation & section on `stringByAddingPercentEncodingWithAllowedCharacters`. + "2014-03-24": Original publication + "2015-09-08": Added Swift translation & section on `stringByAddingPercentEncodingWithAllowedCharacters` status: - swift: 2.0 - reviewed: September 9, 2015 + swift: 2.0 + reviewed: September 9, 2015 --- -There is a simple beauty to—let's call them "one-dimensional data types": numbers or strings formatted to contain multiple values, retrievable through a mathematical operator or parsing routine. Like how the hexadecimal color #EE8262 can have [red, green, and blue components](http://en.wikipedia.org/wiki/Web_colors) extracted by masking and shifting its bits, or how [regular expressions](http://en.wikipedia.org/wiki/Regular_expression) can match and capture complex patterns in just a few characters. +There is a simple beauty to—let's call them "one-dimensional data types": numbers or strings formatted to contain multiple values, retrievable through a mathematical operator or parsing routine. Like how the hexadecimal color #EE8262 can have [red, green, and blue components](https://en.wikipedia.org/wiki/Web_colors) extracted by masking and shifting its bits, or how [regular expressions](https://en.wikipedia.org/wiki/Regular_expression) can match and capture complex patterns in just a few characters. -Of all the one-dimensional data types out there, [URIs](http://en.wikipedia.org/wiki/URI_scheme) reign supreme. Here, in a single, human-parsable string, is every conceivable piece of information necessary to encode the location of any piece of information that has, does, and will ever exist on a computer. +Of all the one-dimensional data types out there, [URIs](https://en.wikipedia.org/wiki/URI_scheme) reign supreme. Here, in a single, human-parsable string, is every conceivable piece of information necessary to encode the location of any piece of information that has, does, and will ever exist on a computer. In its most basic form, a URI is comprised of a scheme name and a hierarchical part, with an optional query and fragment: -~~~ +``` : [ ? ] [ # ] -~~~ +``` Many protocols, including HTTP, specify a regular structure for information like the username, password, port, and path in the hierarchical part: -![URL Structure]({{ site.asseturl }}/nsurl.png) +![URL Structure]({% asset nsurl.png @path %}) A solid grasp of network programming is rooted in an unshakeable familiarity with URL components. As a software developer, this means having a command over the URI functionality in your programming language's standard library. > If a programming language does not have a URI module in its standard library, run, don't walk, to a real language that does. -* * * +--- In Foundation, URLs are represented by `NSURL`. @@ -38,13 +38,14 @@ In Foundation, URLs are represented by `NSURL`. ```swift let url = NSURL(string: "http://example.com/") ``` -```objective-c + +```objc NSURL *url = [NSURL URLWithString:@"http://example.com"]; ``` If the passed string is not a valid URL, this initializer will return `nil`. -`NSString` also has some vestigial functionality for path manipulation, [as described a few weeks back](http://nshipster.com/nstemporarydirectory/), but that's being slowly migrated over to `NSURL`. While the extra conversion from `NSString` to `NSURL` is not the most convenient step, it's always worthwhile. If a value is a URL, it should be stored and passed as an `NSURL`; conflating the two types is reckless and lazy API design. +`NSString` also has some vestigial functionality for path manipulation, [as described a few weeks back](https://nshipster.com/nstemporarydirectory/), but that's being slowly migrated over to `NSURL`. While the extra conversion from `NSString` to `NSURL` is not the most convenient step, it's always worthwhile. If a value is a URL, it should be stored and passed as an `NSURL`; conflating the two types is reckless and lazy API design. `NSURL` also has the initializer `NSURL(string:relativeToURL:)`, which can be used to construct a URL from a string relative to a base URL. The behavior of this method can be a source of confusion, because of how it treats leading `/`'s in relative paths. @@ -71,7 +72,8 @@ NSURL(string:"/foo/", relativeToURL: baseURL) NSURL(string:"http://example2.com/", relativeToURL: baseURL) // http://example2.com/ ``` -```objective-c + +```objc NSURL *baseURL = [NSURL URLWithString:@"http://example.com/v1/"]; [NSURL URLWithString:@"foo" relativeToURL:baseURL]; @@ -126,7 +128,7 @@ The documentation and examples found in [`NSURL`'s documentation](https://develo Quietly added in iOS 7 and OS X Mavericks was `NSURLComponents`, which can best be described by what it could have been named instead: `NSMutableURL`. -`NSURLComponents` instances are constructed much in the same way as `NSURL`, with a provided `NSString` and optional base URL to resolve against (`NSURLComponents(string:)` & `NSURLComponents(URL:resolvingAgainstBaseURL:)`). It can also be initialized without any arguments to create an empty storage container, similar to [`NSDateComponents`](http://nshipster.com/nsdatecomponents/). +`NSURLComponents` instances are constructed much in the same way as `NSURL`, with a provided `NSString` and optional base URL to resolve against (`NSURLComponents(string:)` & `NSURLComponents(URL:resolvingAgainstBaseURL:)`). It can also be initialized without any arguments to create an empty storage container, similar to [`NSDateComponents`](https://nshipster.com/nsdatecomponents/). The difference here, between `NSURL` and `NSURLComponents`, is that component properties are read-write. This provides a safe and direct way to modify individual components of a URL: @@ -141,7 +143,7 @@ The difference here, between `NSURL` and `NSURLComponents`, is that component pr > Attempting to set an invalid scheme string or negative port number will throw an exception. -In addition, `NSURLComponents` also has read-write properties for [percent-encoded](http://en.wikipedia.org/wiki/Percent-encoding) versions of each component. +In addition, `NSURLComponents` also has read-write properties for [percent-encoded](https://en.wikipedia.org/wiki/Percent-encoding) versions of each component. - `percentEncodedUser` - `percentEncodedPassword` @@ -166,7 +168,8 @@ if let escapedTitle = title.stringByAddingPercentEncodingWithAllowedCharacters(N components.query = "title=\(escapedTitle)" } ``` -```objective-c + +```objc NSString *title = @"NSURL / NSURLComponents"; NSString *escapedTitle = [title stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]]; components.query = [NSString stringWithFormat:@"title=%@", escapedTitle]; @@ -185,7 +188,8 @@ extension NSCharacterSet { public class func URLFragmentAllowedCharacterSet() -> NSCharacterSet } ``` -```{objective-c} + +```objective-c // Predefined character sets for the six URL components and subcomponents which allow percent encoding. These character sets are passed to -stringByAddingPercentEncodingWithAllowedCharacters:. @interface NSCharacterSet (NSURLUtilities) + (NSCharacterSet *)URLUserAllowedCharacterSet; @@ -197,22 +201,20 @@ extension NSCharacterSet { @end ``` - ## Bookmark URLs -One final topic worth mentioning are bookmark URLs, which can be used to safely reference files between application launches. Think of them as a persistent [file descriptor](http://en.wikipedia.org/wiki/File_descriptor). +One final topic worth mentioning are bookmark URLs, which can be used to safely reference files between application launches. Think of them as a persistent [file descriptor](https://en.wikipedia.org/wiki/File_descriptor). > A bookmark is an opaque data structure, enclosed in an `NSData` object, that describes the location of a file. Whereas path- and file reference URLs are potentially fragile between launches of your app, a bookmark can usually be used to re-create a URL to a file even in cases where the file was moved or renamed. You can read more about bookmark URLs in ["Locating Files Using Bookmarks" from Apple's File System Programming Guide](https://developer.apple.com/library/ios/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/AccessingFilesandDirectories/AccessingFilesandDirectories.html) - -* * * +--- Forget jet packs and flying cars—my idea of an exciting future is one where everything has a URL, is encoded in Markdown, and stored in Git. If your mind isn't blown by the implications of a _universal_ resource locator, then I would invite you to reconsider. -Like [hypertext](http://en.wikipedia.org/wiki/Hypertext), universal identification is a philosophical concept that both pre-dates and transcends computers. Together, they form the fabric of our information age: a framework for encoding our collective understanding of the universe as a network of individual facts, in a fashion that is hauntingly similar to how the neurons in our brains do much the same. +Like [hypertext](https://en.wikipedia.org/wiki/Hypertext), universal identification is a philosophical concept that both pre-dates and transcends computers. Together, they form the fabric of our information age: a framework for encoding our collective understanding of the universe as a network of individual facts, in a fashion that is hauntingly similar to how the neurons in our brains do much the same. -We are just now crossing the precipice of a [Cambrian Explosion](http://en.wikipedia.org/wiki/Cambrian_explosion) in physical computing. In an internet of things, where [every object of our lives has a URL](http://en.wikipedia.org/wiki/IPv6#Larger_address_space) and embeds an electronic soul, a digital consciousness will emerge. Not to say, necessarily, that [the singularity is near](http://en.wikipedia.org/wiki/The_Singularity_Is_Near), but we're on the verge of something incredible. +We are just now crossing the precipice of a [Cambrian Explosion](https://en.wikipedia.org/wiki/Cambrian_explosion) in physical computing. In an internet of things, where [every object of our lives has a URL](https://en.wikipedia.org/wiki/IPv6#Larger_address_space) and embeds an electronic soul, a digital consciousness will emerge. Not to say, necessarily, that [the singularity is near](https://en.wikipedia.org/wiki/The_Singularity_Is_Near), but we're on the verge of something incredible. Quite lofty implications for a technology used most often to exchange pictures of cats. diff --git a/2014-03-31-avspeechsynthesizer.md b/2014-03-31-avspeechsynthesizer.md index 0b8e7856..4b4d7f8b 100644 --- a/2014-03-31-avspeechsynthesizer.md +++ b/2014-03-31-avspeechsynthesizer.md @@ -1,122 +1,229 @@ --- title: AVSpeechSynthesizer -author: Mattt Thompson +author: Mattt category: Cocoa -excerpt: "Though we're a long way off from Hal or Her, we should never forget about the billions of other people out there for us to talk to." +excerpt: Though we're a long way off from Hal or Her, + we shouldn't forget about the billions of people out there for us to talk to. +revisions: + "2014-03-31": Original publication + "2018-08-08": Updated for iOS 12 and macOS Mojave + "2019-12-09": Updated for iOS 13 status: - swift: 2.0 - reviewed: September 10, 2015 + swift: 4.2 + reviewed: August 8, 2018 --- -Though we're a long way off from [_Hal_](https://www.youtube.com/watch?v=ARJ8cAGm6JE) or [_Her_](https://www.youtube.com/watch?v=WzV6mXIOVl4), we should never forget about the billions of other people out there for us to talk to. +Though we're a long way off from +[_Hal_](https://www.youtube.com/watch?v=ARJ8cAGm6JE) or +[_Her_](https://www.youtube.com/watch?v=WzV6mXIOVl4), +we shouldn't forget about the billions of people out there for us to talk to. + +Of the thousands of languages in existence, +an individual is fortunate to gain a command of just a few within their lifetime. +And yet, +over several millennia of human co-existence, +civilization has managed to make things work (more or less) +through an ad-hoc network of +interpreters, translators, scholars, +and children raised in the mixed linguistic traditions of their parents. +We've seen that mutual understanding fosters peace +and that conversely, +mutual unintelligibility destabilizes human relations. + +It's fitting that the development of computational linguistics +should coincide with the emergence of the international community we have today. +Working towards mutual understanding, +intergovernmental organizations like the United Nations and European Union +have produced a substantial corpus of +[parallel texts](http://en.wikipedia.org/wiki/Parallel_text), +which form the foundation of modern language translation technologies. + +Computer-assisted communication +between speakers of different languages consists of three tasks: +**transcribing** the spoken words into text, +**translating** the text into the target language, +and **synthesizing** speech for the translated text. + +This article focuses on how iOS handles the last of these: speech synthesis. -Of the thousands of languages in existence, an individual is fortunate to gain a command of just two within their lifetime. And yet, over several millennia of human co-existence, civilization has managed to make things work, more or less, through an ad-hoc network of interpreters, translators, scholars, and children raised in the mixed linguistic traditions of their parents. We've seen that mutual understanding fosters peace, and that conversely, mutual unintelligibility destabilizes human relations. - -It is fitting that the development of computational linguistics should coincide with the emergence of the international community we have today. Working towards mutual understanding, intergovernmental organizations like the United Nations and European Union have produced a substantial corpora of [parallel texts](http://en.wikipedia.org/wiki/Parallel_text), which form the foundation of modern language translation technologies. - -> Another related linguistic development is the [Esperanto](http://en.wikipedia.org/wiki/Esperanto) language, created by L. L. Zamenhof in an effort to promote harmony between people of different countries. - -And while automatic text translation has reached an acceptable level for everyday communication, there is still a divide when we venture out into unfamiliar places. There is still much work to be done in order to augment our ability to communicate with one another in person. +--- -* * * +Introduced in iOS 7 and available in macOS 10.14 Mojave, +`AVSpeechSynthesizer` produces speech from text. -Introduced in iOS 7, `AVSpeechSynthesizer` produces synthesized speech from a given `AVSpeechUtterance`. Each utterance can adjust its rate of speech and pitch, and be configured to use any one of the available `AVSpeechSynthesisVoice`s: +To use it, +create an `AVSpeechUtterance` object with the text to be spoken +and pass it to the `speakUtterance(_:)` method: ```swift import AVFoundation let string = "Hello, World!" let utterance = AVSpeechUtterance(string: string) -utterance.voice = AVSpeechSynthesisVoice(language: "en-US") let synthesizer = AVSpeechSynthesizer() synthesizer.speakUtterance(utterance) ``` -~~~{objective-c} +```objc NSString *string = @"Hello, World!"; AVSpeechUtterance *utterance = [[AVSpeechUtterance alloc] initWithString:string]; utterance.voice = [AVSpeechSynthesisVoice voiceWithLanguage:@"en-US"]; AVSpeechSynthesizer *synthesizer = [[AVSpeechSynthesizer alloc] init]; [synthesizer speakUtterance:utterance]; -~~~ +``` + +You can use the adjust the volume, pitch, and rate of speech +by configuring the corresponding properties on the `AVSpeechUtterance` object. -When speaking, a synthesizer can either be paused immediately or on the next word boundary, which makes for a less jarring user experience. +When speaking, +a synthesizer can be paused on the next word boundary, +which makes for a less jarring user experience than stopping mid-vowel. ```swift -synthesizer.pauseSpeakingAtBoundary(.Word) +synthesizer.pauseSpeakingAtBoundary(.word) ``` -~~~{objective-c} +```objc [synthesizer pauseSpeakingAtBoundary:AVSpeechBoundaryWord]; -~~~ +``` ## Supported Languages -Mac OS 9 users will no doubt have fond memories of the old system voices: Bubbles, Cellos, Pipe Organ, and Bad News. +Mac OS 9 users will no doubt have fond memories of the old system voices --- +especially the novelty ones, like +Bubbles, Cellos, Pipe Organ, and Bad News. + +In the spirit of quality over quantity, +each language is provided a voice for each major locale region. +So instead of asking for "Fred" or "Markus", +`AVSpeechSynthesisVoice` asks for `en-US` or `de-DE`. + +VoiceOver supports over 30 different languages. +For an up-to-date list of what's available, +call `AVSpeechSynthesisVoice` class method `speechVoices()` +or check [this support article](https://support.apple.com/en-us/HT206175). + +By default, +`AVSpeechSynthesizer` will speak using a voice +based on the user's current language preferences. +To avoid sounding like a +[stereotypical American in Paris](https://www.youtube.com/watch?v=v-3RZl3YyJw), +set an explicit language by selecting a `AVSpeechSynthesisVoice`. + +```swift +let string = "Bonjour!" +let utterance = AVSpeechUtterance(string: string) +utterance.voice = AVSpeechSynthesisVoice(language: "fr") +``` + +```objc +NSString *string = @"Bonjour!"; +AVSpeechUtterance *utterance = [[AVSpeechUtterance alloc] initWithString:string]; +utterance.voice = [AVSpeechSynthesisVoice voiceWithLanguage:@"fr-FR"]; +``` + +Many APIs in foundation and other system frameworks +use ISO 681 codes to identify languages. +`AVSpeechSynthesisVoice`, however, takes an +[IETF Language Tag](http://en.wikipedia.org/wiki/IETF_language_tag), +as specified [BCP 47 Document Series](http://tools.ietf.org/html/bcp47). +If an utterance string and voice aren't in the same language, +speech synthesis fails. + +> Not all languages are preloaded on the device, +> and may have to be downloaded in the background +> before speech can be synthesized. + +{% comment %} + +> [This gist](https://gist.github.com/mattt/9892187) +> shows how to detect an ISO 681 language code from an arbitrary string, +> and convert that to an IETF language tag. +> {% endcomment %} + +## Customizing Pronunciation -> These can still be installed on OS X. Just look under the "English (United States) - Novelty" voices in the "Dictation & Speech" preference pane. +A few years after it first debuted on iOS, +`AVUtterance` added functionality to control +the pronunciation of particular words, +which is especially helpful for proper names. -In the name of quality over quantity, each language is provided a voice for each major locale region. So instead of asking for "Fred" and "Markus", `AVSpeechSynthesisVoice` asks for `en-US` and `de-DE`. +To take advantage of it, +construct an utterance using `init(attributedString:)` +instead of `init(string:)`. +The initializer scans through the attributed string +for any values associated with the `AVSpeechSynthesisIPANotationAttribute`, +and adjusts pronunciation accordingly. -As of iOS 8.1, `[AVSpeechSynthesisVoice speechVoices]` the following languages and locales are supported: +```swift +import AVFoundation + +let text = "It's pronounced 'tomato'" + +let mutableAttributedString = NSMutableAttributedString(string: text) +let range = NSString(string: text).range(of: "tomato") +let pronunciationKey = NSAttributedString.Key(rawValue: AVSpeechSynthesisIPANotationAttribute) + +// en-US pronunciation is /tə.ˈme͡ɪ.do͡ʊ/ +mutableAttributedString.setAttributes([pronunciationKey: "tə.ˈme͡ɪ.do͡ʊ"], range: range) + +let utterance = AVSpeechUtterance(attributedString: mutableAttributedString) + +// en-GB pronunciation is /tə.ˈmɑ.to͡ʊ/... but too bad! +utterance.voice = AVSpeechSynthesisVoice(language: "en-GB") + +let synthesizer = AVSpeechSynthesizer() +synthesizer.speak(utterance) +``` + +Beautiful. 🍅 -- Arabic (`ar-SA`) -- Chinese (`zh-CN`, `zh-HK`, `zh-TW`) -- Czech (`cs-CZ`) -- Danish (`da-DK`) -- Dutch (`nl-BE`, `nl-NL`) -- English (`en-AU`, `en-GB`, `en-IE`, `en-US`, `en-ZA`) -- Finnish (`fi-FI`) -- French (`fr-CA`, `fr-FR`) -- German (`de-DE`) -- Greek (`el-GR`) -- Hebrew (`he-IL`) -- Hindi (`hi-IN`) -- Hungarian (`hu-HU`) -- Indonesian (`id-ID`) -- Italian (`it-IT`) -- Japanese (`ja-JP`) -- Korean (`ko-KR`) -- Norwegian (`no-NO`) -- Polish (`pl-PL`) -- Portuguese (`pt-BR`, `pt-PT`) -- Romanian (`ro-RO`) -- Russian (`ru-RU`) -- Slovak (`sk-SK`) -- Spanish (`es-ES`, `es-MX`) -- Swedish (`sv-SE`) -- Thai (`th-TH`) -- Turkish (`tr-TR`) +Of course, [this property is undocumented](https://developer.apple.com/documentation/avfoundation/avspeechsynthesisipanotationattribute) +at the time of writing, +so you wouldn't know that the IPA you get from Wikipedia +won't work correctly unless you watched +[this session from WWDC 2018](https://developer.apple.com/videos/play/wwdc2018/236/). -`NSLocale` and `NSLinguisticTagger` both use ISO 681 codes to identify languages. `AVSpeechSynthesisVoice`, however, takes an [IETF Language Tag](http://en.wikipedia.org/wiki/IETF_language_tag), as specified [BCP 47 Document Series](http://tools.ietf.org/html/bcp47). If an utterance string and voice aren't in the same language, speech synthesis will fail. +To get IPA notation that `AVSpeechUtterance` can understand, +you can open the Settings app, +navigate to Accessibility > VoiceOver > Speech > Pronunciations, +and... say it yourself! -> [This gist](https://gist.github.com/mattt/9892187) shows how to detect an ISO 681 language code from an arbitrary string, and convert that to an IETF language tag. +{% asset speech-pronunciation-replacement alt="Speech Pronunciation Replacement" %} -## Delegate Methods +## Hooking Into Speech Events -What makes `AVSpeechSynthesizer` really amazing for developers is the ability to hook into speech events. An object conforming to `AVSpeechSynthesizerDelegate` can be called when its speech synthesizer either starts or finishes, pauses or continues, and as each range of the utterance is spoken. +One of the coolest features of `AVSpeechSynthesizer` +is how it lets developers hook into speech events. +An object conforming to `AVSpeechSynthesizerDelegate` can be called +when a speech synthesizer +starts or finishes, +pauses or continues, +and as each range of the utterance is spoken. -For example, an app, in addition to synthesizing a voice utterance, could show that utterance in a label, and highlight the word currently being spoken: +For example, an app --- +in addition to synthesizing a voice utterance --- +could show that utterance in a label, +and highlight the word currently being spoken: ```swift var utteranceLabel: UILabel! // MARK: AVSpeechSynthesizerDelegate -func speechSynthesizer(synthesizer: AVSpeechSynthesizer, willSpeakRangeOfSpeechString characterRange: NSRange, utterance: AVSpeechUtterance) { - let mutableAttributedString = NSMutableAttributedString(string: utterance.speechString) - mutableAttributedString.addAttribute(NSForegroundColorAttributeName, value: UIColor.redColor(), range: characterRange) - utteranceLabel.attributedText = mutableAttributedString -} - -func speechSynthesizer(synthesizer: AVSpeechSynthesizer, didFinishSpeechUtterance utterance: AVSpeechUtterance) { - utteranceLabel.attributedText = NSAttributedString(string: utterance.speechString) +override func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, + willSpeakRangeOfSpeechString characterRange: NSRange, + utterance: AVSpeechUtterance) +{ + self.utterranceLabel.attributedText = + attributedString(from: utterance.speechString, + highlighting: characterRange) } ``` -~~~{objective-c} +```objc #pragma mark - AVSpeechSynthesizerDelegate - (void)speechSynthesizer:(AVSpeechSynthesizer *)synthesizer @@ -128,22 +235,32 @@ willSpeakRangeOfSpeechString:(NSRange)characterRange value:[UIColor redColor] range:characterRange]; self.utteranceLabel.attributedText = mutableAttributedString; } +``` -- (void)speechSynthesizer:(AVSpeechSynthesizer *)synthesizer - didFinishSpeechUtterance:(AVSpeechUtterance *)utterance -{ - self.utteranceLabel.attributedText = [[NSAttributedString alloc] initWithString:self.utteranceString]; -} -~~~ - -![AVSpeechSynthesizer Example]({{ site.asseturl }}/avspeechsynthesizer-example.gif) - -See [this example app](https://github.com/mattt/AVSpeechSynthesizer-Example) for a demonstration of live text-highlighting for all of the supported languages. - -* * * +{% asset avspeechsynthesizer-example.gif alt="AVSpeechSynthesizer Example" width=320 %} -Anyone who travels to an unfamiliar place returns with a profound understanding of what it means to communicate. It's totally different from how one is taught a language in High School: instead of genders and cases, it's about emotions and patience and clinging onto every shred of understanding. One is astounded by the extent to which two humans can communicate with hand gestures and facial expressions. One is also humbled by how frustrating it can be when pantomiming breaks down. +Check out [this Playground](https://github.com/NSHipster/AVSpeechSynthesizer-Example) +for an example of live text-highlighting for all of the supported languages. -In our modern age, we have the opportunity to go out in a world augmented by a collective computational infrastructure. Armed with `AVSpeechSynthesizer` and the myriad other linguistic technologies on iOS and elsewhere, we have never been more capable of breaking down the forces that most divide our species. +--- -If that isn't universe-denting, then I don't know what is. +Anyone who travels to an unfamiliar place +returns with a profound understanding of what it means to communicate. +It's totally different from how one is taught a language in High School: +instead of genders and cases, +it's about emotions +and patience +and clinging onto every shred of understanding. +One is astounded by the extent to which two humans +can communicate with hand gestures and facial expressions. +One is also humbled by how frustrating it can be when pantomiming breaks down. + +In our modern age, we have the opportunity to go out in a world +augmented by a collective computational infrastructure. +Armed with `AVSpeechSynthesizer` +and myriad other linguistic technologies on our devices, +we've never been more capable of breaking down the forces +that most divide our species. + +If that isn't universe-denting, +then I don't know what is. diff --git a/2014-04-07-configuration-profiles.md b/2014-04-07-configuration-profiles.md index 753bccec..84eaceab 100644 --- a/2014-04-07-configuration-profiles.md +++ b/2014-04-07-configuration-profiles.md @@ -1,7 +1,7 @@ --- title: Configuration Profiles -author: Mattt Thompson -category: "" +author: Mattt +category: Miscellaneous excerpt: "One of the major criticisms of iOS as a platform is how locked down it is. iOS Configuration Profiles offer an interesting mechanism to work around these restrictions." status: swift: n/a @@ -43,7 +43,7 @@ There are several ways to deploy configuration profiles: > In addition to deploying configuration profiles, the [Apple Configurator](https://itunes.apple.com/us/app/apple-configurator/id434433123?mt=12) can generate profiles, as an alternative to hand-writing XML yourself. -![iOS Configurator - Generate]({{ site.asseturl }}/ios-configurator-generate.png) +![iOS Configurator - Generate]({% asset ios-configurator-generate.png @path %}) ## Use Cases @@ -69,7 +69,7 @@ Just as EOF / WOFF / SVG fonts allow typefaces to be distributed over the web, t ### Enhancing Security -Security has quickly become a killer feature for apps, as discussed in our article about [Multipeer Connectivity](http://nshipster.com/multipeer-connectivity/). +Security has quickly become a killer feature for apps, as discussed in our article about [Multipeer Connectivity](https://nshipster.com/multipeer-connectivity/). Perhaps configuration profiles, with the ability to embed certificates and single sign-on credentials, could add another level of security to communication apps. diff --git a/2014-04-14-xcode-plugins.md b/2014-04-14-xcode-plugins.md index 759ed1f9..e7c4b1ce 100644 --- a/2014-04-14-xcode-plugins.md +++ b/2014-04-14-xcode-plugins.md @@ -1,18 +1,29 @@ --- title: Xcode Plugins -author: Mattt Thompson +author: Mattt category: Xcode -tags: popular excerpt: "This week on NSHipster: a roundup of some of the most useful and exciting plugins for Xcode—ready for you to try out yourself today!" +revisions: + "2014-04-14": Original publication + "2019-03-25": Added deprecation notice status: - swift: n/a + swift: n/a --- -Apple is nothing if not consistent. From [Pentalobular screws](http://en.wikipedia.org/wiki/Pentalobe_screw) to [Sandboxing](https://developer.apple.com/app-sandboxing/), customers are simply expected to relinquish a fair amount of control when they choose to buy a Mac or iPhone. Whether these design decisions are made to ensure a good user experience, or this control is exercised as an end in itself is debatable, but the reality is that in both hardware and software, Apple prefers an ivory tower to a bazaar. +{% error do %} +As of 2016, +Xcode Plugins are no longer supported. +Please see our +[follow-up article](https://nshipster.com/xcode-source-extensions/) +for more information about their successors: +Xcode Source Extensions. +{% enderror %} + +Apple is nothing if not consistent. From [Pentalobular screws](https://en.wikipedia.org/wiki/Pentalobe_screw) to [Sandboxing](https://developer.apple.com/app-sandboxing/), customers are simply expected to relinquish a fair amount of control when they choose to buy a Mac or iPhone. Whether these design decisions are made to ensure a good user experience, or this control is exercised as an end in itself is debatable, but the reality is that in both hardware and software, Apple prefers an ivory tower to a bazaar. No better example of this can be found with Xcode: the very software that software developers use to build software for the walled ecosystems of iOS & OS X software, _is itself a closed ecosystem_. -Indeed, significant progress has been made in recent years to break open the developer workflow, from alternative IDEs like [AppCode](http://www.jetbrains.com/objc/?utm_source=nshipster) to build tools like [CocoaPods](http://cocoapods.org), [xctool](http://nshipster.com/xctool/) and [nomad](http://nomad-cli.com). However, the notion that Xcode itself could be customized and extended by mere mortals is extremely recent, and just now starting to pick up steam. +Indeed, significant progress has been made in recent years to break open the developer workflow, from alternative IDEs like [AppCode](http://www.jetbrains.com/objc/?utm_source=nshipster) to build tools like [CocoaPods](http://cocoapods.org), [xctool](https://nshipster.com/xctool/) and [nomad](http://nomad-cli.com). However, the notion that Xcode itself could be customized and extended by mere mortals is extremely recent, and just now starting to pick up steam. Xcode has had a plugin architecture going back to when Interface Builder was its own separate app. However, this system was relatively obscure, undocumented, and not widely used by third parties. Despite this, developers like [Delisa Mason](https://twitter.com/kattrali) and [Marin Usalj](https://twitter.com/_supermarin) have done incredible work creating a stable and vibrant ecosystem of third-party Xcode extensions. @@ -21,15 +32,16 @@ Xcode has had a plugin architecture going back to when Interface Builder was its This week on NSHipster: a roundup of some of the most useful and exciting plugins for Xcode—ready for you to try out yourself today! > And since these question come up every time there's an article with pictures: +> > 1. The color scheme is [Tomorrow Night](https://github.com/ChrisKempson/Tomorrow-Theme) > 2. The app used to make animated GIFs is [LICEcap](http://www.cockos.com/licecap/) -* * * +--- ## Making Xcode More Like `X` Just as New York became a melting pot of cultures from immigrants arriving at -[Ellis Island](http://en.wikipedia.org/wiki/Ellis_Island), Xcode has welcomed the tired, poor, huddled masses of developers from every platform and language imaginable. Like those first wave Americans, who settled into their respective ethnic neighborhoods to re-establish their traditions in a new land, so too have new iOS developers brought over their preferred workflows and keybindings. +[Ellis Island](https://en.wikipedia.org/wiki/Ellis_Island), Xcode has welcomed the tired, poor, huddled masses of developers from every platform and language imaginable. Like those first wave Americans, who settled into their respective ethnic neighborhoods to re-establish their traditions in a new land, so too have new iOS developers brought over their preferred workflows and keybindings. Perhaps you would appreciate a taste of home in the land of Cupertino. @@ -39,13 +51,13 @@ Finding it _too easy_ to quit Xcode? Try [XVim](https://github.com/JugglerShu/XV ### SublimeText -![SCXcodeMiniMap]({{ site.asseturl }}/scxcodeminimap.png) +![SCXcodeMiniMap]({% asset scxcodeminimap.png @path %}) -Do you miss having a code minimap along the right gutter of your editor to put things into perspective? Install [SCXcodeMiniMap](https://github.com/stefanceriu/SCXcodeMiniMap) and never again miss the tree nodes for the forest. +Do you miss having a code minimap along the right gutter of your editor to put things into perspective? Install [SCXcodeMiniMap](https://github.com/stefanceriu/SCXcodeMiniMap) and never again miss the tree nodes for the forest. ### Atom -![Show in GitHub]({{ site.asseturl }}/showingithub.png) +![Show in GitHub]({% asset showingithub.png @path %}) Looking to be more in tune with GitHub? Add the [Show in GitHub / BitBucket](https://github.com/larsxschneider/ShowInGitHub) plugin to open to the selected lines of a file online. @@ -55,19 +67,19 @@ Rather than waiting with crossed fingers and clenched teeth each June, as Apple ### Add Line Breaks to Issue Navigator -![BBUFullIssueNavigator]({{ site.asseturl }}/bbufullissuenavigator.png) +![BBUFullIssueNavigator]({% asset bbufullissuenavigator.png @path %}) -An annoyance going back to Xcode 4 has been the truncation of items in the Issues Navigator. Never again be frustrated by surprise ellipses when compiler warnings were just starting to get interesting, with [BBUFullIssueNavigator](https://github.com/neonichu/BBUFullIssueNavigator). +An annoyance going back to Xcode 4 has been the truncation of items in the Issues Navigator. Never again be frustrated by surprise ellipses when compiler warnings were just starting to get interesting, with [BBUFullIssueNavigator](https://github.com/neonichu/BBUFullIssueNavigator). ### Dismiss Debugging Console When Typing -![BBUDebuggerTuckAway]({{ site.asseturl }}/bbudebuggertuckaway.gif) +![BBUDebuggerTuckAway]({% asset bbudebuggertuckaway.gif @path %}) Another annoyance going back to Xcode 4 is how the debugging console seems to always get in the way. No more, with [BBUDebuggerTuckAway](https://github.com/neonichu/BBUDebuggerTuckAway). As soon as you start typing in the editor, the debugging window will get out of your way. ### Add ANSI Color Support to Debugging Console -![XcodeColors]({{ site.asseturl }}/xcodecolors.png) +![XcodeColors]({% asset xcodecolors.png @path %}) `ncurses` enthusiasts will no doubt be excited by the [XcodeColors](https://github.com/robbiehanson/XcodeColors) plugin, which adds support for ANSI colors to appear in the debugging console. @@ -85,19 +97,19 @@ Not being the most verbose language in existence, Objective-C can use all the he ### Autocomplete `switch` Statements -![SCXcodeSwitchExpander]({{ site.asseturl }}/scxcodeswitchexpander.gif) +![SCXcodeSwitchExpander]({% asset scxcodeswitchexpander.gif @path %}) -Fact: `switch` statements and [`NS_ENUM`](http://nshipster.com/ns_enum-ns_options/) go together like mango and sweet sticky rice. The only way it could be improved would be with [SCXcodeSwitchExpander](https://github.com/stefanceriu/SCXcodeSwitchExpander) with automagically fills out a `case` statement for each value in the enumeration. +Fact: `switch` statements and [`NS_ENUM`](https://nshipster.com/ns_enum-ns_options/) go together like mango and sweet sticky rice. The only way it could be improved would be with [SCXcodeSwitchExpander](https://github.com/stefanceriu/SCXcodeSwitchExpander) with automagically fills out a `case` statement for each value in the enumeration. ### Autocomplete Documentation -![VVDocumenter]({{ site.asseturl }}/vvdocumenter.gif) +![VVDocumenter]({% asset vvdocumenter.gif @path %}) -[Documentation](http://nshipster.com/documentation/) adds a great deal of value to a code base, but it's a tough habit to cultivate. The [VVDocumenter-Xcode](https://github.com/onevcat/VVDocumenter-Xcode) plugin does a great deal to reduce the amount of work necessary to add [appledoc](http://gentlebytes.com/appledoc/)-compatible header documentation. Install it and wrap your code in a loving lexical embrace. +[Documentation](https://nshipster.com/documentation/) adds a great deal of value to a code base, but it's a tough habit to cultivate. The [VVDocumenter-Xcode](https://github.com/onevcat/VVDocumenter-Xcode) plugin does a great deal to reduce the amount of work necessary to add [appledoc](http://gentlebytes.com/appledoc/)-compatible header documentation. Install it and wrap your code in a loving lexical embrace. ## Formatting Xcode -["Code organization is a matter of hygiene"](http://nshipster.com/pragma/), so you owe it to yourself and your team to keep whitespace consistent in your code base. Make it easier on yourself by automating the process with these plugins. +["Code organization is a matter of hygiene"](https://nshipster.com/pragma/), so you owe it to yourself and your team to keep whitespace consistent in your code base. Make it easier on yourself by automating the process with these plugins. ### Code Formatting with ClangFormat @@ -105,7 +117,7 @@ Fact: `switch` statements and [`NS_ENUM`](http://nshipster.com/ns_enum-ns_option ### Statement Alignment -![XAlign]({{ site.asseturl }}/xalign.gif) +![XAlign]({% asset xalign.gif @path %}) Fancy yourself a code designer, automated formatters be damned? [XAlign](https://github.com/qfish/XAlign) automatically aligns assignments _just so_, to appease your most egregious OCD tendencies. @@ -115,7 +127,7 @@ In a similar vein to what [Bret Victor writes about Learnable Programming](http: ### Inspect `NSColor` / `UIColor` Instances -![ColorSense]({{ site.asseturl }}/colorsense.png) +![ColorSense]({% asset colorsense.png @path %}) Telling what a color is from its RGB values alone is a hard-won skill, so faced with an `NSColor` or `UIColor` value, we have little recourse to know what it'll look like until the code is built and run. Enter [ColorSense for Xcode](https://github.com/omz/ColorSense-for-Xcode) @@ -125,13 +137,13 @@ Quoth the README: ### Autocomplete Images from Project Bundle -![KSImageNamed]({{ site.asseturl }}/ksimagenamed.gif) +![KSImageNamed]({% asset ksimagenamed.gif @path %}) Similar to the ColorSense plugin, [KSImageNamed](https://github.com/ksuther/KSImageNamed-Xcode) will preview and autocomplete images in `[UIImage imageNamed:]` declarations. ### Semantics Highlighting -![Polychromatic]({{ site.asseturl }}/polychromatic.png) +![Polychromatic]({% asset polychromatic.png @path %}) Any editor worth its salt is expected to have some form of syntax highlighting. But [this recent post by Evan Brooks](https://medium.com/p/3a6db2743a1e) presents the idea of _semantic_ highlighting in editors. The idea is that each variable within a scope would be assigned a particular color, which would be consistent across references. This way, one could easily tell the difference between two instance variables in the same method. @@ -139,14 +151,13 @@ Any editor worth its salt is expected to have some form of syntax highlighting. ### Localization -![Lin]({{ site.asseturl }}/lin-1.png) +![Lin]({% asset lin-1.png @path %}) -![Lin]({{ site.asseturl }}/lin-2.png) +![Lin]({% asset lin-2.png @path %}) -It's no secret that NSHipster has [a soft spot for localization](http://nshipster.com/nslocalizedstring/). For this reason, this publication is emphatic in its recommendation of [Lin](https://github.com/questbeat/Lin-Xcode5), a clever Xcode plugin that brings the localization editor to your code. +It's no secret that NSHipster has [a soft spot for localization](https://nshipster.com/nslocalizedstring/). For this reason, this publication is emphatic in its recommendation of [Lin](https://github.com/questbeat/Lin-Xcode5), a clever Xcode plugin that brings the localization editor to your code. - -* * * +--- Xcode's plugin architecture is based on a number of private frameworks specific to Xcode, including DVTKit & IDEKit. A [complete list](https://github.com/luisobo/Xcode5-RuntimeHeaders) can be derived by running [`class-dump`](http://stevenygard.com/projects/class-dump/) on the Xcode app bundle. diff --git a/2014-04-21-uiactivityviewcontroller.md b/2014-04-21-uiactivityviewcontroller.md index bf236034..93c84ff8 100644 --- a/2014-04-21-uiactivityviewcontroller.md +++ b/2014-04-21-uiactivityviewcontroller.md @@ -1,309 +1,268 @@ --- title: UIActivityViewController -author: Mattt Thompson +author: Mattt category: Cocoa -excerpt: "The relationship between code and data has long been a curious one." +excerpt: >- + iOS provides a unified interface for users to + share and perform actions on strings, images, URLs, + and other items within an app. +revisions: + "2014-03-31": Original publication + "2018-12-05": Updated for iOS 12 and Swift 4.2 status: - swift: 2.0 - reviewed: September 7, 2015 + swift: 4.2 + reviewed: December 5, 2018 --- -The relationship between code and data has long been a curious one. - -Certain programming languages, such as [Lisp](http://en.wikipedia.org/wiki/Lisp_programming_language), [Io](http://en.wikipedia.org/wiki/Io_%28programming_language%29), and [Mathematica](http://en.wikipedia.org/wiki/Mathematica) are [homoiconic](http://en.wikipedia.org/wiki/Homoiconicity), meaning that their code is represented as a data primitive, which itself can be manipulated in code. Most other languages, including Objective-C, however, create a strict boundary between the two, shying away from `eval()` and other potentially dangerous methods of dynamic instructing loading. - -This tension between code and data is brought to a whole new level when the data in question is too large or unwieldy to represent as anything but a byte stream. The question of how to encode, decode, and interpret the binary representation of images, documents, and media has been ongoing since the very first operating systems. - -The Core Services framework on OS X and Mobile Core Services framework on iOS provide functions that identify and categorize data types by file extension and [MIME type](http://en.wikipedia.org/wiki/Internet_media_type), according to [Universal Type Identifiers](http://en.wikipedia.org/wiki/Uniform_Type_Identifier). UTIs provide an extensible, hierarchical categorization system, which affords the developer great flexibility in handling even the most exotic 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. - -UTIs have worked reasonably well within the filesystem abstraction of the desktop. However, in a mobile paradigm, where files and directories are hidden from the user, this breaks down quickly. And, what's more, the rise of cloud services and social media has placed greater importance on remote entities over local files. Thus, a tension between UTIs and URLs. - -It's clear that we need something else. Could `UIActivityViewController` be the solution we so desperately seek? - -* * * - -`UIActivityViewController`, introduced in iOS 6, provides a unified services interface for sharing and performing actions on data within an application. - -Given a collection of actionable data, a `UIActivityViewController` instance is created as follows: - -~~~{swift} -let string: String = ... -let URL: NSURL = ... - -let activityViewController = UIActivityViewController(activityItems: [string, URL], applicationActivities: nil) -navigationController?.presentViewController(activityViewController, animated: true) { - // ... +On iOS, +`UIActivityViewController` provides a unified interface for users to +share and perform actions on strings, images, URLs, +and other items within an app. + +You create a `UIActivityViewController` +by passing in the items you want to share +and any custom activities you want to support +(we'll show how to do that later on). +You then present that view controller as you would any other modal or popover. + +```swift +let string = "Hello, world!" +let url = URL(string: "https://nshipster.com")! +let image = UIImage(named: "mustache.jpg") +let pdf = Bundle.main.url(forResource: "Q4 Projections", + withExtension: "pdf") + +let activityViewController = + UIActivityViewController(activityItems: [string, url, image, pdf], + applicationActivities: nil) + +present(activityViewController, animated: true) { + <#...#> } -~~~ +``` -~~~{objective-c} -NSString *string = ...; -NSURL *URL = ...; +When you run this code +the following is presented on the screen: -UIActivityViewController *activityViewController = - [[UIActivityViewController alloc] initWithActivityItems:@[string, URL] - applicationActivities:nil]; -[navigationController presentViewController:activityViewController - animated:YES - completion:^{ - // ... -}]; -~~~ +{% asset uiactivityviewcontroller.png alt="UIActivityViewController" %} -This would present the following at the bottom of the screen: +By default, +`UIActivityViewController` shows all the activities available +for the items provided, +but you can exclude certain activity types +via the `excludedActivityTypes` property. -![UIActivityViewController]({{ site.asseturl }}/uiactivityviewcontroller.png) - -By default, `UIActivityViewController` will show all available services supporting the provided items, but certain activity types can be excluded: - -~~~{swift} -activityViewController.excludedActivityTypes = [UIActivityTypePostToFacebook] -~~~ - -~~~{objective-c} -activityViewController.excludedActivityTypes = @[UIActivityTypePostToFacebook]; -~~~ +```swift +activityViewController.excludedActivityTypes = [.postToFacebook] +``` Activity types are divided up into "action" and "share" types: -### UIActivityCategoryAction +- **Action** (`UIActivityCategoryAction`) activity items + take an action on selected content, + such as copying text to the pasteboard + or printing an image. +- **Share** (`UIActivityCategoryShare`) activity items + share the selected content, + such as composing a message containing a URL + or posting an image to Twitter. -- `UIActivityTypePrint` -- `UIActivityTypeCopyToPasteboard` -- `UIActivityTypeAssignToContact` -- `UIActivityTypeSaveToCameraRoll` -- `UIActivityTypeAddToReadingList` -- `UIActivityTypeAirDrop` +Each activity type supports certain kinds of items. +For example, +you can post a String, URL, and / or image to Twitter, +but you can't assign a string to be the photo for a contact. -### UIActivityCategoryShare +The following tables show the available activity types for each category +and their supported items: -- `UIActivityTypeMessage` -- `UIActivityTypeMail` -- `UIActivityTypePostToFacebook` -- `UIActivityTypePostToTwitter` -- `UIActivityTypePostToFlickr` -- `UIActivityTypePostToVimeo` -- `UIActivityTypePostToTencentWeibo` -- `UIActivityTypePostToWeibo` - -Each activity type supports a number of different data types. For example, a Tweet might be composed of an `NSString`, along with an attached image and/or URL. - -### Supported Data Types by Activity Type +### UIActivityCategoryAction - +
- + + + + + + + - - - - + - + + + + + + + - - - - + + - + - + + + + + + + + - - + + + - - - - + +
Activity Type{% asset uiactivity-icon-string.svg %}{% asset uiactivity-icon-url.svg %}{% asset uiactivity-icon-image.svg %}{% asset uiactivity-icon-file.svg %}
StringAttributed String URLData ImageAssetOtherFiles
Post To Facebook{% asset uiactivity-airDrop.png width=32 height=32 %}airDrop
{% asset uiactivity-addToReadingList.png width=32 height=32 %}addToReadingList
Post To Twitter{% asset uiactivity-assignToContact.png width=32 height=32 %}assignToContact
Post To Weibo{% asset uiactivity-copyToPasteboard.png width=32 height=32 %}copyToPasteboard
{% asset uiactivity-print.png width=32 height=32 %}print
Message{% asset uiactivity-saveToCameraRoll.png width=32 height=32 %}saveToCameraRoll ✓*✓* ✓*sms:// NSURL
+ +### UIActivityCategoryShare + + + - - - - - - - - + + + + + - - - - - - - - + + + + + + - + + + - - - - - - - - - + + + + + - - - - - + + + - - - - + + - - - + - - - - + + - - - + + - - - - - + + - + + - - -
Mail✓+✓+✓+{% asset uiactivity-icon-string.svg %}{% asset uiactivity-icon-url.svg %}{% asset uiactivity-icon-image.svg %}{% asset uiactivity-icon-file.svg %}
Print✓+✓+UIPrintPageRenderer, UIPrintFormatter, & UIPrintInfoStringURLImageFiles
Copy To Pasteboard{% asset uiactivity-mail.png width=32 height=32 %}mail UIColor, NSDictionary
Assign To Contact{% asset uiactivity-message.png width=32 height=32 %}message
Save To Camera Roll{% asset uiactivity-postToFacebook.png width=32 height=32 %}postToFacebook
Add To Reading List{% asset uiactivity-postToFlickr.png width=32 height=32 %}postToFlickr
Post To Flickr{% asset uiactivity-postToTencentWeibo.png width=32 height=32 %}postToTencentWeibo
Post To Vimeo{% asset uiactivity-postToTwitter.png width=32 height=32 %}postToTwitter
Post To Tencent Weibo{% asset uiactivity-postToVimeo.png width=32 height=32 %}postToVimeo
AirDrop{% asset uiactivity-postToWeibo.png width=32 height=32 %}postToWeibo
-## `` & `UIActivityItemProvider` - -Similar to how a [pasteboard item](https://developer.apple.com/library/mac/documentation/cocoa/reference/NSPasteboardItem_Class/Reference/Reference.html) can be used to provide data only when necessary, in order to avoid excessive memory allocation or processing time, activity items can be of a custom type. - -Any object conforming to ``, including the built-in `UIActivityItemProvider` class, can be used to dynamically provide different kinds of data depending on the activity type. - -### `` - -#### Getting the Data Items - -- `activityViewControllerPlaceholderItem:` -- `activityViewController:itemForActivityType:` - -#### Providing Information About the Data Items - -- `activityViewController:subjectForActivityType:` -- `activityViewController:dataTypeIdentifierForActivityType:` -- `activityViewController:thumbnailImageForActivityType:suggestedSize:` - -One example of how this could be used is to customize a message, depending on whether it's to be shared on Facebook or Twitter. - -~~~{swift} -func activityViewController(activityViewController: UIActivityViewController, itemForActivityType activityType: String) -> AnyObject? { - if activityType == UIActivityTypePostToFacebook { - return NSLocalizedString("Like this!", comment: "comment") - } else if activityType == UIActivityTypePostToTwitter { - return NSLocalizedString("Retweet this!", comment: "comment") - } else { - return nil - } -} -~~~ - -~~~{objective-c} -- (id)activityViewController:(UIActivityViewController *)activityViewController - itemForActivityType:(NSString *)activityType -{ - if ([activityType isEqualToString:UIActivityTypePostToFacebook]) { - return NSLocalizedString(@"Like this!"); - } else if ([activityType isEqualToString:UIActivityTypePostToTwitter]) { - return NSLocalizedString(@"Retweet this!"); - } else { - return nil; - } -} -~~~ +{% info %} +`UIActivityViewController` allows users to choose how they share content. +However, as a developer, +you can access this functionality directly. +Here are the corresponding APIs for each of the system-provided activity types: + +| Activity Type | Corresponding API | +| ------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------- | +| `addToReadingList` | [`SSReadingList`](https://developer.apple.com/documentation/safariservices/ssreadinglist/1621226-additem) | +| `assignToContact` | [`CNContact`](https://developer.apple.com/documentation/contacts/cncontact) | +| `copyToPasteboard` | [`UIPasteboard`](https://developer.apple.com/documentation/uikit/uipasteboard/1829417-setitems) | +| `print` | [`UIPrintInteractionController`](https://developer.apple.com/documentation/uikit/uiprintinteractioncontroller/1618174-printtoprinter) | +| `saveToCameraRoll` | [`UIImageWriteToSavedPhotosAlbum`](https://developer.apple.com/documentation/uikit/1619125-uiimagewritetosavedphotosalbum) | +| `mail` | [`MFMailComposeViewController`](https://developer.apple.com/documentation/messageui/mfmailcomposeviewcontroller) | +| `message` | [`MFMessageComposeViewController`](https://developer.apple.com/documentation/messageui/mfmessagecomposeviewcontroller) | +| `postToFacebook`
`postToFlickr`
`postToTencentWeibo`
`postToTwitter`
`postToVimeo`
`postToWeibo` | [`SLComposeViewController`](https://developer.apple.com/documentation/social/slcomposeviewcontroller) | + +{% endinfo %} ## Creating a Custom UIActivity -In addition to the aforementioned system-provided activities, its possible to create your own activity. +In addition to the system-provided activities, +you can create your own activities. -As an example, let's create a custom activity type that takes an image URL and applies a mustache to it using [mustache.me](http://mustache.me). +As an example, +let's create a custom activity +that takes an image and applies a mustache to it via a web application. - +
- - + + @@ -311,355 +270,195 @@ As an example, let's create a custom activity type that takes an image URL and a
Jony Ive BeforeJony Ive AfterJony Ive BeforeJony Ive After
Before
-First, we define a [reverse-DNS identifier](http://en.wikipedia.org/wiki/Reverse_domain_name_notation) for the activity type: - -~~~{swift} -let HIPMustachifyActivityType = "com.nshipster.activity.Mustachify" -~~~ - -~~~{objective-c} -static NSString * const HIPMustachifyActivityType = @"com.nshipster.activity.Mustachify"; -~~~ +### Defining a Custom Activity Type -Then specify the category as `UIActivityCategoryAction` and provide a localized title & iOS version appropriate image: +First, +define a new activity type constant +in an extension to `UIActivity.ActivityType`, +initialized with a +[reverse-DNS identifier](https://en.wikipedia.org/wiki/Reverse_domain_name_notation). -~~~{swift} -// MARK: - UIActivity - -override class func activityCategory() -> UIActivityCategory { - return .Action +```swift +extension UIActivity.ActivityType { + static let mustachify = + UIActivity.ActivityType("com.nshipster.mustachify") } +``` -override func activityType() -> String? { - return HIPMustachifyActivityType -} +### Creating a UIActivity Subclass -override func activityTitle() -> String? { - return NSLocalizedString("Mustachify", comment: "comment") -} +Next, +create a subclass of `UIActivity` +and override the default implementations of the +`activityCategory` type property +and `activityType`, `activityTitle`, and `activityImage` instance properties. -override func activityImage() -> UIImage? { - if #available(iOS 7.0, *) { - return UIImage(named: "MustachifyUIActivity7") - } else { - return UIImage(named: "MustachifyUIActivity") +```swift +class MustachifyActivity: UIActivity { + override class var activityCategory: UIActivity.Category { + return .action } -} -~~~ - -~~~{objective-c} -#pragma mark - UIActivity - -+ (UIActivityCategory)activityCategory { - return UIActivityCategoryAction; -} - -- (NSString *)activityType { - return HIPMustachifyActivityType; -} - -- (NSString *)activityTitle { - return NSLocalizedString(@"Mustachify", nil); -} -- (UIImage *)activityImage { - if (NSFoundationVersionNumber > NSFoundationVersionNumber_iOS_6_1) { - return [UIImage imageNamed:@"MustachifyUIActivity7"]; - } else { - return [UIImage imageNamed:@"MustachifyUIActivity"]; + override var activityType: UIActivity.ActivityType? { + return .mustachify } -} -~~~ - -Next, we create a helper function, `HIPMatchingURLsInActivityItems`, which returns an array of any image URLs of the supported type. -~~~{swift} -func HIPMatchingURLsInActivityItems(activityItems: [AnyObject]) -> [AnyObject] { - return activityItems.filter { - if let url = $0 as? NSURL where !url.fileURL { - return url.pathExtension?.caseInsensitiveCompare("jpg") == .OrderedSame - || url.pathExtension?.caseInsensitiveCompare("png") == .OrderedSame - } + override var activityTitle: String? { + return NSLocalizedString("Mustachify", comment: "activity title") + } - return false + override var activityImage: UIImage? { + return UIImage(named: "mustachify-icon") } -} -~~~ - -~~~{objective-c} -static NSArray * HIPMatchingURLsInActivityItems(NSArray *activityItems) { - return [activityItems filteredArrayUsingPredicate:[NSPredicate predicateWithBlock: - ^BOOL(id item, __unused NSDictionary *bindings) { - if ([item isKindOfClass:[NSURL class]] && - ![(NSURL *)item isFileURL]) { - return [[(NSURL *)item pathExtension] caseInsensitiveCompare:@"jpg"] == NSOrderedSame || - [[(NSURL *)item pathExtension] caseInsensitiveCompare:@"png"] == NSOrderedSame; - } - return NO; - }]]; + <#...#> } -~~~ +``` -This function is then used in `-canPerformWithActivityItems:` and `prepareWithActivityItems:` to get the mustachio'd image URL of the first PNG or JPEG, if any. +### Determining Which Items are Actionable -~~~{swift} -override func canPerformWithActivityItems(activityItems: [AnyObject]) -> Bool { - return HIPMatchingURLsInActivityItems(activityItems).count > 0 -} +Activities are responsible for determining +whether they can act on a given array of items +by overriding the `canPerform(withActivityItems:)` method. -override func prepareWithActivityItems(activityItems: [AnyObject]) { - let HIPMustachifyMeURLFormatString = "http://mustachify.me/%d?src=%@" +Our custom activity can work if any of the items is an image, +which we identify with some fancy pattern matching on a for-in loop: - if let firstMatch = HIPMatchingURLsInActivityItems(activityItems).first, mustacheType = self.mustacheType { - imageURL = NSURL(string: String(format: HIPMustachifyMeURLFormatString, [mustacheType, firstMatch])) +```swift +override func canPerform(withActivityItems activityItems: [Any]) -> Bool { + for case is UIImage in activityItems { + return true } - // ... + return false } -~~~ +``` -~~~{objective-c} -- (BOOL)canPerformWithActivityItems:(NSArray *)activityItems { - return [HIPMatchingURLsInActivityItems(activityItems) count] > 0; -} +### Preparing for Action -- (void)prepareWithActivityItems:(NSArray *)activityItems { - static NSString * const HIPMustachifyMeURLFormatString = @"http://mustachify.me/%d?src=%@"; +Once an activity has determined that it can work with the specified items, +it uses the `prepare(withActivityItems:)` +to get ready to perform the activity. - self.imageURL = [NSURL URLWithString:[NSString stringWithFormat:HIPMustachifyMeURLFormatString, self.mustacheType, [HIPMatchingURLsInActivityItems(activityItems) firstObject]]]; -} -~~~ +In the case of our custom activity, +we take the PNG representation of the first image in the array of items +and stores that in an instance variable: -Our webservice provides a variety of mustache options, which are defined in an enumeration: +```swift +var sourceImageData: Data? -~~~{swift} -enum HIPMustacheType: Int { - case English, Horseshoe, Imperial, Chevron, Natural, Handlebar -} -~~~ - -~~~{objective-c} -typedef NS_ENUM(NSInteger, HIPMustacheType) { - HIPMustacheTypeEnglish, - HIPMustacheTypeHorseshoe, - HIPMustacheTypeImperial, - HIPMustacheTypeChevron, - HIPMustacheTypeNatural, - HIPMustacheTypeHandlebar, -}; -~~~ - -Finally, we provide a `UIViewController` to display the image. For this example, a simple `UIWebView` controller suffices. - -~~~{swift} -class HIPMustachifyWebViewController: UIViewController, UIWebViewDelegate { - var webView: UIWebView { get } +override func prepare(withActivityItems activityItems: [Any]) { + for case let image as UIImage in activityItems { + self.sourceImageData = image.pngData() + return + } } -~~~ - -~~~{objective-c} -@interface HIPMustachifyWebViewController : UIViewController -@property (readonly, nonatomic, strong) UIWebView *webView; -@end -~~~ - -~~~{swift} -func activityViewController() -> UIViewController { - let webViewController = HIPMustachifyWebViewController() - - let request = NSURLRequest(URL: imageURL) - webViewController.webView.loadRequest(request) +``` + +### Performing the Activity + +The `perform()` method is the most important part of your activity. +Because processing can take some time, +this is an asynchronous method. +However, for lack of a completion handler, +you signal that work is done by calling the `activityDidFinish(_:)` method. + +Our custom activity delegates the mustachification process to a web app +using a data task sent from the shared `URLSession`. +If all goes well, the `mustachioedImage` property is set +and `activityDidFinish(_:)` is called with `true` +to indicate that the activity finished successfully. +If an error occurred in the request +or we can't create an image from the provided data, +we call `activityDidFinish(_:)` with `false` to indicate failure. + +```swift +var mustachioedImage: UIImage? + +override func perform() { + let url = URL(string: "https://mustachify.app/")! + var request = URLRequest(url: url) + request.httpMethod = "POST" + request.httpBody = self.sourceImageData + + URLSession.shared.dataTask(with: request) { (data, _, error) in + guard error == nil else { + self.activityDidFinish(false) + return + } - return webViewController + if let data = data, + let image = UIImage(data: data) + { + self.mustachioedImage = image + self.activityDidFinish(true) + } else { + self.activityDidFinish(false) + } + } } -~~~ - -~~~{objective-c} -- (UIViewController *)activityViewController { - HIPMustachifyWebViewController *webViewController = [[HIPMustachifyWebViewController alloc] init]; - - NSURLRequest *request = [NSURLRequest requestWithURL:self.imageURL]; - [webViewController.webView loadRequest:request]; +``` - return webViewController; -} -~~~ +### Showing the Results -To use our brand new mustache activity, we simply pass it in the `UIActivityViewController initializer`: +The final step is to provide a view controller +to be presented with the result of our activity. -~~~{swift} -let mustacheActivity = HIPMustachifyActivity() -let activityViewController = UIActivityViewController(activityItems: [imageURL], applicationActivities: [mustacheActivity]) -~~~ +The QuickLook framework provides a simple, built-in way to display images. +We'll extend our activity to adopt `QLPreviewControllerDataSource` +and return an instance of `QLPreviewController`, +with `self` set as the `dataSource` +for our override of the`activityViewController` method. -~~~{objective-c} -HIPMustachifyActivity *mustacheActivity = [[HIPMustachifyActivity alloc] init]; -UIActivityViewController *activityViewController = - [[UIActivityViewController alloc] initWithActivityItems:@[imageURL] - applicationActivities:@[mustacheActivity]]; -~~~ +```swift +import QuickLook -## Invoking Actions Manually +extension MustachifyActivity: QLPreviewControllerDataSource { + override var activityViewController: UIViewController? { + guard let image = self.mustachioedImage else { + return nil + } -Now is a good time to be reminded that while `UIActivityViewController` allows users to perform actions of their choosing, sharing can still be invoked manually, when the occasion arises. + let viewController = QLPreviewController() + viewController.dataSource = self + return viewController + } -So for completeness, here's how one might go about performing some of these actions manually: + // MARK: QLPreviewControllerDataSource -### Open URL + func numberOfPreviewItems(in controller: QLPreviewController) -> Int { + return self.mustachioedImage != nil ? 1 : 0 + } -~~~{swift} -if let URL = NSURL(string: "http://nshipster.com") { - UIApplication.sharedApplication().openURL(URL) + func previewController(_ controller: QLPreviewController, previewItemAt index: Int) -> QLPreviewItem { + return self.mustachioedImage! + } } -~~~ +``` -~~~{objective-c} -NSURL *URL = [NSURL URLWithString:@"http://nshipster.com"]; -[[UIApplication sharedApplication] openURL:URL]; -~~~ +### Providing a Custom Activity to Users -System-supported URL schemes include: `mailto:`, `tel:`, `sms:`, and `maps:`. +We can use our brand new mustache activity +by passing it to the `applicationActivities` parameter +in the `UIActivityViewController initializer`: -### Add to Safari Reading List +```swift +let activityViewController = + UIActivityViewController(activityItems: [image], + applicationActivities: [Mustachify()]) -~~~{swift} -import SafariServices - -if let URL = NSURL(string: "http://nshipster.com/uiactivityviewcontroller") { - let _ = try? SSReadingList.defaultReadingList()?.addReadingListItemWithURL(URL, - title: "NSHipster", - previewText: "..." - ) -} -~~~ - -~~~{objective-c} -@import SafariServices; - -NSURL *URL = [NSURL URLWithString:@"http://nshipster.com/uiactivityviewcontroller"]; -[[SSReadingList defaultReadingList] addReadingListItemWithURL:URL - title:@"NSHipster" - previewText:@"..." - error:nil]; -~~~ - -### Add to Saved Photos - -~~~{swift} -let image: UIImage = ... -let completionTarget: AnyObject = self -let completionSelector: Selector = "didWriteToSavedPhotosAlbum" -let contextInfo: UnsafeMutablePointer = nil - -UIImageWriteToSavedPhotosAlbum(image, completionTarget, completionSelector, contextInfo) -~~~ - -~~~{objective-c} -UIImage *image = ...; -id completionTarget = self; -SEL completionSelector = @selector(didWriteToSavedPhotosAlbum); -void *contextInfo = NULL; -UIImageWriteToSavedPhotosAlbum(image, completionTarget, completionSelector, contextInfo); -~~~ - -### Send SMS - -~~~{swift} -import MessageUI - -let messageComposeViewController = MFMessageComposeViewController() -messageComposeViewController.messageComposeDelegate = self -messageComposeViewController.recipients = ["mattt@nshipster•com"] -messageComposeViewController.body = "Lorem ipsum dolor sit amet" -navigationController?.presentViewController(messageComposeViewController, animated: true) { - // ... -} -~~~ - -~~~{objective-c} -@import MessageUI; - -MFMessageComposeViewController *messageComposeViewController = [[MFMessageComposeViewController alloc] init]; -messageComposeViewController.messageComposeDelegate = self; -messageComposeViewController.recipients = @[@"mattt@nshipster•com"]; -messageComposeViewController.body = @"Lorem ipsum dolor sit amet"; -[navigationController presentViewController:messageComposeViewController animated:YES completion:^{ - // ... -}]; -~~~ - -### Send Email - -~~~{swift} -import MessageUI - -let mailComposeViewController = MFMailComposeViewController() -mailComposeViewController.mailComposeDelegate = self -mailComposeViewController.setToRecipients(["mattt@nshipster•com"]) -mailComposeViewController.setSubject("Hello") -mailComposeViewController.setMessageBody("Lorem ipsum dolor sit amet", isHTML: false) -navigationController?.presentViewController(mailComposeViewController, animated: true) { - // ... +present(activityViewController, animated: true) { + <#...#> } -~~~ - -~~~{objective-c} -@import MessageUI; - -MFMailComposeViewController *mailComposeViewController = [[MFMailComposeViewController alloc] init]; -mailComposeViewController.mailComposeDelegate = self; -[mailComposeViewController setToRecipients:@[@"mattt@nshipster•com"]]; -[mailComposeViewController setSubject:@"Hello"]; -[mailComposeViewController setMessageBody:@"Lorem ipsum dolor sit amet" - isHTML:NO]; -[navigationController presentViewController:mailComposeViewController animated:YES completion:^{ - // ... -}]; -~~~ - -### Post Tweet - -~~~{swift} -import Social - -let tweetComposeViewController = SLComposeViewController(forServiceType: SLServiceTypeTwitter) -tweetComposeViewController.setInitialText("Lorem ipsum dolor sit amet.") -navigationController?.presentViewController(tweetComposeViewController, animated: true) { - // ... -} -~~~ - -~~~{objective-c} -@import Social; +``` -SLComposeViewController *tweetComposeViewController = [SLComposeViewController composeViewControllerForServiceType:SLServiceTypeTwitter]; -[tweetComposeViewController setInitialText:@"Lorem ipsum dolor sit amet."]; -[self.navigationController presentViewController:tweetComposeViewController - animated:YES - completion:^{ - // ... - }]; -~~~ +{% asset uiactivityviewcontroller-custom-action.png %} -## IntentKit - -While all of this is impressive and useful, there is a particular lacking in the activities paradigm in iOS, when compared to the rich [Intents Model](http://developer.android.com/guide/components/intents-filters.html) found on Android. - -On Android, apps can register for different intents, to indicate that they can be used for Maps, or as a Browser, and be selected as the default app for related activities, like getting directions, or bookmarking a URL. - -While iOS lacks the extensible infrastructure to support this, a 3rd-party library called [IntentKit](https://github.com/intentkit/IntentKit), by [@lazerwalker](https://github.com/lazerwalker) (of [f*ingblocksyntax.com](http://goshdarnblocksyntax.com) fame), is an interesting example of how we might narrow the gap ourselves. - -![IntentKit](https://raw.github.com/intentkit/IntentKit/master/example.gif) - -Normally, a developer would have to do a lot of work to first, determine whether a particular app is installed, and how to construct a URL to support a particular activity. - -IntentKit consolidates the logic of connecting to the most popular Web, Maps, Mail, Twitter, Facebook, and Google+ clients, in a UI similar to `UIActivityViewController`. - -Anyone looking to take their sharing experience to the next level should definitely give this a look. - -* * * +--- -There is a strong argument to be made that the longterm viability of iOS as a platform depends on sharing mechanisms like `UIActivityViewController`. As the saying goes, "Information wants to be free". And anything that stands in the way of federation will ultimately lose to something that does not. +There is a strong argument to be made that +the long-term viability of iOS as a platform +depends on sharing mechanisms like `UIActivityViewController`. -The future prospects of public [remote view controller](http://oleb.net/blog/2012/10/remote-view-controllers-in-ios-6/) APIs gives me hope for the future of sharing on iOS. For now, though, we could certainly do much worse than `UIActivityViewController`. +As the saying goes, _"Information wants to be free."_ +Anything that stands in the way of federation is doomed to fail. diff --git a/2014-04-28-mkgeodesicpolyline.md b/2014-04-28-mkgeodesicpolyline.md index ce070f8f..77f3e456 100644 --- a/2014-04-28-mkgeodesicpolyline.md +++ b/2014-04-28-mkgeodesicpolyline.md @@ -1,6 +1,6 @@ --- title: MKGeodesicPolyline -author: Mattt Thompson +author: Mattt category: Cocoa excerpt: "We knew that the Earth was not flat long before 1492. Early navigators observed the way ships would dip out of view over the horizon many centuries before the Age of Discovery. For many iOS developers, though, a flat MKMapView was a necessary conceit until recently." status: @@ -16,15 +16,15 @@ What changed? The discovery of `MKGeodesicPolyline`, which is the subject of thi * * * -[`MKGeodesicPolyline`](https://developer.apple.com/library/ios/documentation/MapKit/Reference/MKGeodesicPolyline_class/Reference/Reference.html) was introduced to the Map Kit framework in iOS 7. As its name implies, it creates a [geodesic](http://en.wikipedia.org/wiki/Geodesic)—essentially a straight line over a curved surface. +[`MKGeodesicPolyline`](https://developer.apple.com/library/ios/documentation/MapKit/Reference/MKGeodesicPolyline_class/Reference/Reference.html) was introduced to the Map Kit framework in iOS 7. As its name implies, it creates a [geodesic](https://en.wikipedia.org/wiki/Geodesic)—essentially a straight line over a curved surface. -On the surface of a sphere oblate spheroid geoid, the shortest distance between two points appears as an arc on a flat projection. Over large distances, this takes a [pronounced, circular shape](http://en.wikipedia.org/wiki/Great-circle_distance). +On the surface of a sphere oblate spheroid geoid, the shortest distance between two points appears as an arc on a flat projection. Over large distances, this takes a [pronounced, circular shape](https://en.wikipedia.org/wiki/Great-circle_distance). An `MKGeodesicPolyline` is created with an array of 2 `MKMapPoint`s or `CLLocationCoordinate2D`s: ### Creating an `MKGeodesicPolyline` -~~~{swift} +```swift let LAX = CLLocation(latitude: 33.9424955, longitude: -118.4080684) let JFK = CLLocation(latitude: 40.6397511, longitude: -73.7789256) @@ -32,8 +32,8 @@ var coordinates = [LAX.coordinate, JFK.coordinate] let geodesicPolyline = MKGeodesicPolyline(coordinates: &coordinates, count: 2) mapView.addOverlay(geodesicPolyline) -~~~ -~~~{objective-c} +``` +```objc CLLocation *LAX = [[CLLocation alloc] initWithLatitude:33.9424955 longitude:-118.4080684]; CLLocation *JFK = [[CLLocation alloc] initWithLatitude:40.6397511 @@ -47,22 +47,22 @@ MKGeodesicPolyline *geodesicPolyline = count:2]; [mapView addOverlay:geodesicPolyline]; -~~~ +``` Although the overlay looks like a smooth curve, it is actually comprised of thousands of tiny line segments (true to its `MKPolyline` lineage): -~~~{swift} +```swift print(geodesicPolyline.pointCount) // 3984 -~~~ -~~~{objective-c} +``` +```objc NSLog(@"%d", geodesicPolyline.pointCount) // 3984 -~~~ +``` Like any object conforming to the `MKOverlay` protocol, an `MKGeodesicPolyline` instance is displayed by adding it to an `MKMapView` with `addOverlay()` and implementing `mapView(_:rendererForOverlay:)`: ### Rendering `MKGeodesicPolyline` on an `MKMapView` -~~~{swift} +```swift // MARK: MKMapViewDelegate func mapView(mapView: MKMapView, rendererForOverlay overlay: MKOverlay) -> MKOverlayRenderer { @@ -77,8 +77,8 @@ func mapView(mapView: MKMapView, rendererForOverlay overlay: MKOverlay) -> MKOve return renderer } -~~~ -~~~{objective-c} +``` +```objc #pragma mark - MKMapViewDelegate - (MKOverlayRenderer *)mapView:(MKMapView *)mapView @@ -96,15 +96,15 @@ func mapView(mapView: MKMapView, rendererForOverlay overlay: MKOverlay) -> MKOve return renderer; } -~~~ +``` -![MKGeodesicPolyline on an MKMapView]({{ site.asseturl }}/mkgeodesicpolyline.jpg) +![MKGeodesicPolyline on an MKMapView]({% asset mkgeodesicpolyline.jpg @path %}) -> For comparison, here's the same geodesic overlaid with a route created from [`MKDirections`](http://nshipster.com/mktileoverlay-mkmapsnapshotter-mkdirections/): +> For comparison, here's the same geodesic overlaid with a route created from [`MKDirections`](https://nshipster.com/mktileoverlay-mkmapsnapshotter-mkdirections/): -![MKGeodesicPolyline on an MKMapView compared to MKDirections Polyline]({{ site.asseturl }}/mkgeodesicpolyline-with-directions.jpg) +![MKGeodesicPolyline on an MKMapView compared to MKDirections Polyline]({% asset mkgeodesicpolyline-with-directions.jpg @path %}) -[As the crow flies](http://en.wikipedia.org/wiki/As_the_crow_flies), it's 3,983 km.
+[As the crow flies](https://en.wikipedia.org/wiki/As_the_crow_flies), it's 3,983 km.
As the wolf runs, it's 4,559 km—nearly 15% longer.
…and that's just distance; taking into account average travel speed, the total time is ~5 hours by air and 40+ hours by land. @@ -114,43 +114,43 @@ Since geodesics make reasonable approximations for flight paths, a common use ca To do this, we'll make properties for our map view and geodesic polyline between LAX and JFK, and add new properties for the `planeAnnotation` and `planeAnnotationPosition` (the index of the current map point for the polyline): -~~~{swift} +```swift // MARK: Flight Path Properties var mapView: MKMapView! var flightpathPolyline: MKGeodesicPolyline! var planeAnnotation: MKPointAnnotation! var planeAnnotationPosition = 0 -~~~ -~~~{objective-c} +``` +```objc @interface MapViewController () @property MKMapView *mapView; @property MKGeodesicPolyline *flightpathPolyline; @property MKPointAnnotation *planeAnnotation; @property NSUInteger planeAnnotationPosition; @end -~~~ +``` Next, right below the initialization of our map view and polyline, we create an `MKPointAnnotation` for our plane: -~~~{swift} +```swift let annotation = MKPointAnnotation() annotation.title = NSLocalizedString("Plane", comment: "Plane marker") mapView.addAnnotation(annotation) self.planeAnnotation = annotation self.updatePlanePosition() -~~~ -~~~{objective-c} +``` +```objc self.planeAnnotation = [[MKPointAnnotation alloc] init]; self.planeAnnotation.title = NSLocalizedString(@"Plane", nil); [self.mapView addAnnotation:self.planeAnnotation]; [self updatePlanePosition]; -~~~ +``` That call to `updatePlanePosition` in the last line ticks the animation and updates the position of the plane: -~~~{swift} +```swift func updatePlanePosition() { let step = 5 guard planeAnnotationPosition + step < flightpathPolyline.pointCount @@ -164,8 +164,8 @@ func updatePlanePosition() { performSelector("updatePlanePosition", withObject: nil, afterDelay: 0.03) } -~~~ -~~~{objective-c} +``` +```objc - (void)updatePlanePosition { static NSUInteger const step = 5; @@ -180,13 +180,13 @@ func updatePlanePosition() { [self performSelector:@selector(updatePlanePosition) withObject:nil afterDelay:0.03]; } -~~~ +``` We'll perform this method roughly 30 times a second, until the plane has arrived at its final destination. Finally, we implement `mapView(_:viewForAnnotation:)` to have the annotation render on the map view: -~~~{swift} +```swift func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? { let planeIdentifier = "Plane" @@ -197,8 +197,8 @@ func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> return annotationView } -~~~ -~~~{objective-c} +``` +```objc - (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id )annotation { @@ -213,9 +213,9 @@ func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> return annotationView; } -~~~ +``` -![MKAnnotationView without Rotation]({{ site.asseturl }}/mkgeodesicpolyline-airplane-animate.gif) +![MKAnnotationView without Rotation]({% asset mkgeodesicpolyline-airplane-animate.gif @path %}) Hmm… close but no [SkyMall Personalized Cigar Case Flask](http://www.skymall.com/personalized-cigar-case-flask/GC900.html). @@ -225,47 +225,47 @@ Let's update the rotation of the plane as it moves across its flightpath. To calculate the plane's direction, we'll take the slope from the previous and next points: -~~~{swift} +```swift let previousMapPoint = points[planeAnnotationPosition] planeAnnotationPosition += step let nextMapPoint = points[planeAnnotationPosition] self.planeDirection = directionBetweenPoints(previousMapPoint, nextMapPoint) self.planeAnnotation.coordinate = MKCoordinateForMapPoint(nextMapPoint) -~~~ -~~~{objective-c} +``` +```objc MKMapPoint previousMapPoint = self.flightpathPolyline.points[self.planeAnnotationPosition]; self.planeAnnotationPosition += step; MKMapPoint nextMapPoint = self.flightpathPolyline.points[self.planeAnnotationPosition]; self.planeDirection = XXDirectionBetweenPoints(previousMapPoint, nextMapPoint); self.planeAnnotation.coordinate = MKCoordinateForMapPoint(nextMapPoint); -~~~ +``` `directionBetweenPoints` is a function that returns a `CLLocationDirection` (0 – 360 degrees, where North = 0) given two `MKMapPoint`s. > We calculate from `MKMapPoint`s rather than converted coordinates, because we're interested in the slope of the line on the flat projection. -~~~{swift} +```swift private func directionBetweenPoints(sourcePoint: MKMapPoint, _ destinationPoint: MKMapPoint) -> CLLocationDirection { let x = destinationPoint.x - sourcePoint.x let y = destinationPoint.y - sourcePoint.y return radiansToDegrees(atan2(y, x)) % 360 + 90 } -~~~ -~~~{objective-c} +``` +```objc static CLLocationDirection XXDirectionBetweenPoints(MKMapPoint sourcePoint, MKMapPoint destinationPoint) { double x = destinationPoint.x - sourcePoint.x; double y = destinationPoint.y - sourcePoint.y; return fmod(XXRadiansToDegrees(atan2(y, x)), 360.0f) + 90.0f; } -~~~ +``` That convenience function `radiansToDegrees` (and its partner, `degreesToRadians`) are simply: -~~~{swift} +```swift private func radiansToDegrees(radians: Double) -> Double { return radians * 180 / M_PI } @@ -273,8 +273,8 @@ private func radiansToDegrees(radians: Double) -> Double { private func degreesToRadians(degrees: Double) -> Double { return degrees * M_PI / 180 } -~~~ -~~~{objective-c} +``` +```objc static inline double XXRadiansToDegrees(double radians) { return radians * 180.0f / M_PI; } @@ -282,26 +282,26 @@ static inline double XXRadiansToDegrees(double radians) { static inline double XXDegreesToRadians(double degrees) { return degrees * M_PI / 180.0f; } -~~~ +``` That direction is stored in a new property, `var planeDirection: CLLocationDirection`, calculated from `self.planeDirection = directionBetweenPoints(currentMapPoint, nextMapPoint)` in `updatePlanePosition` (ideally renamed to `updatePlanePositionAndDirection` with this addition). To make the annotation rotate, we apply a `transform` on `annotationView`: -~~~{swift} +```swift annotationView.transform = CGAffineTransformRotate(mapView.transform, degreesToRadians(planeDirection)) -~~~ -~~~{objective-c} +``` +```objc self.annotationView.transform = CGAffineTransformRotate(self.mapView.transform, XXDegreesToRadians(self.planeDirection)); -~~~ +``` -![MKAnnotationView with Rotation]({{ site.asseturl }}/mkgeodesicpolyline-airplane-animate-rotate.gif) +![MKAnnotationView with Rotation]({% asset mkgeodesicpolyline-airplane-animate-rotate.gif @path %}) Ah much better! At last, we have mastered the skies with a fancy visualization, worthy of any travel-related app. * * * -Perhaps more than any other system framework, MapKit has managed to get incrementally better, little by little with every iOS release [[1]](http://nshipster.com/mktileoverlay-mkmapsnapshotter-mkdirections/) [[2]](http://nshipster.com/mklocalsearch/). For anyone with a touch-and-go relationship to the framework, returning after a few releases is a delightful experience of discovery and rediscovery. +Perhaps more than any other system framework, MapKit has managed to get incrementally better, little by little with every iOS release [[1]](https://nshipster.com/mktileoverlay-mkmapsnapshotter-mkdirections/) [[2]](https://nshipster.com/mklocalsearch/). For anyone with a touch-and-go relationship to the framework, returning after a few releases is a delightful experience of discovery and rediscovery. I look forward to seeing what lies on the horizon with iOS 8 and beyond. diff --git a/2014-05-05-ibaction-iboutlet-iboutletcollection.md b/2014-05-05-ibaction-iboutlet-iboutletcollection.md index c7b59e14..8801f248 100644 --- a/2014-05-05-ibaction-iboutlet-iboutletcollection.md +++ b/2014-05-05-ibaction-iboutlet-iboutletcollection.md @@ -1,13 +1,13 @@ --- title: "IBAction / IBOutlet / IBOutletCollection" -author: Mattt Thompson +author: Mattt category: Cocoa excerpt: "In programming, what often begins as a necessary instruction eventually becomes a vestigial cue for humans. For developers just starting with Cocoa & Cocoa Touch, the IBAction, IBOutlet, and IBOutletCollection macros are particularly bewildering examples of this phenomenon" status: swift: t.b.c. --- -In programming, what often begins as a necessary instruction eventually becomes a vestigial cue for humans. In the case of Objective-C, [`#pragma` directives](http://nshipster.com/pragma/), [method type encodings](http://nshipster.com/type-encodings/), and all but the most essential [storage classes](http://nshipster.com/c-storage-classes/) have been rendered essentially meaningless, as the compiler becomes increasingly sophisticated. Discarded and disregarded during the compilation phase, they nonetheless remain useful to the development process as a whole, insofar as what they can tell other developers about the code itself. +In programming, what often begins as a necessary instruction eventually becomes a vestigial cue for humans. In the case of Objective-C, [`#pragma` directives](https://nshipster.com/pragma/), [method type encodings](https://nshipster.com/type-encodings/), and all but the most essential [storage classes](https://nshipster.com/c-storage-classes/) have been rendered essentially meaningless, as the compiler becomes increasingly sophisticated. Discarded and disregarded during the compilation phase, they nonetheless remain useful to the development process as a whole, insofar as what they can tell other developers about the code itself. For developers just starting with Cocoa & Cocoa Touch, the `IBAction`, `IBOutlet`, and `IBOutletCollection` macros are particularly bewildering examples of this phenomenon. With their raison d'être obscured by decades of change, confusion by anyone without sufficient context is completely understandable. @@ -15,25 +15,25 @@ As we'll learn in this week's article, though having outgrown their technical ne * * * -Unlike other [two-letter prefixes](http://nshipster.com/namespacing/), `IB` does not refer to a system framework, but rather Interface Builder. +Unlike other [two-letter prefixes](https://nshipster.com/namespacing/), `IB` does not refer to a system framework, but rather Interface Builder. -[Interface Builder](http://en.wikipedia.org/wiki/Interface_Builder) can trace its roots to the halcyon days of Objective-C, when it and Project Builder comprised the NeXTSTEP developer tools (circa 1988). Before it was subsumed into Xcode 4, Interface Builder remained remarkably unchanged from its 1.0 release. An iOS developer today would feel right at home on a NeXTSTEP workstation, control-dragging views into outlets. +[Interface Builder](https://en.wikipedia.org/wiki/Interface_Builder) can trace its roots to the halcyon days of Objective-C, when it and Project Builder comprised the NeXTSTEP developer tools (circa 1988). Before it was subsumed into Xcode 4, Interface Builder remained remarkably unchanged from its 1.0 release. An iOS developer today would feel right at home on a NeXTSTEP workstation, control-dragging views into outlets. -Back when they were separate applications, it was a challenge to keep the object graph represented in a `.nib` document in Interface Builder synchronized with its corresponding `.h` & `.m` files in [Project Builder](http://en.wikipedia.org/wiki/Project_Builder) (what would eventually become Xcode). `IBOutlet` and `IBAction` were used as keywords, to denote what parts of the code should be visible to Interface Builder. +Back when they were separate applications, it was a challenge to keep the object graph represented in a `.nib` document in Interface Builder synchronized with its corresponding `.h` & `.m` files in [Project Builder](https://en.wikipedia.org/wiki/Project_Builder) (what would eventually become Xcode). `IBOutlet` and `IBAction` were used as keywords, to denote what parts of the code should be visible to Interface Builder. `IBAction` and `IBOutlet` are, themselves, computationally meaningless, as their macro definitions (in `UINibDeclarations.h`) demonstrate: -~~~{objective-c} +```objc #define IBAction void #define IBOutlet -~~~ +``` -> Well actually, there's more than meets the eye. Scrying the [Clang source code](https://llvm.org/svn/llvm-project/cfe/trunk/test/SemaObjC/iboutlet.m), we see that they're actually defined by [__attribute__](http://nshipster.com/__attribute__/)-backed attributes: +> Well actually, there's more than meets the eye. Scrying the [Clang source code](https://llvm.org/svn/llvm-project/cfe/trunk/test/SemaObjC/iboutlet.m), we see that they're actually defined by [__attribute__](https://nshipster.com/__attribute__/)-backed attributes: -~~~{objective-c} +```objc #define IBOutlet __attribute__((iboutlet)) #define IBAction __attribute__((ibaction)) -~~~ +``` ## IBAction @@ -52,7 +52,7 @@ Thanks to strong, and often compiler-enforced conventions, naming is especially For example: -~~~{objective-c} +```objc // YES - (IBAction)refresh:(id)sender; @@ -63,7 +63,7 @@ For example: - (IBAction)peformSomeAction; - (IBAction)didTapButton:(id)sender; -~~~ +``` ## IBOutlet @@ -75,7 +75,7 @@ An `IBOutlet` connection is usually established between a view or control and it As with anything in modern Objective-C, **properties are preferred to direct ivar access**. The same is true of `IBOutlet`s: -~~~{objective-c} +```objc // YES @interface GallantViewController : UIViewController @property (nonatomic, weak) IBOutlet UISwitch *switch; @@ -86,7 +86,7 @@ As with anything in modern Objective-C, **properties are preferred to direct iva IBOutlet UISwitch *_switch } @end -~~~ +``` Since properties are the conventional way to expose and access members of a class, both externally and internally, they are preferred in this case as well, if only for consistency. @@ -111,36 +111,36 @@ The reason why most `IBOutlet` views can get away with `weak` ownership is that `IBOutletCollection` is `#define`'d in `UINibDeclarations.h` as: -~~~{objective-c} +```objc #define IBOutletCollection(ClassName) -~~~ +``` …which is defined in a much more satisfying way, again, [in the Clang source code](http://opensource.apple.com/source/clang/clang-318.0.45/src/tools/clang/test/SemaObjC/iboutletcollection-attr.m): -~~~{objective-c} +```objc #define IBOutletCollection(ClassName) __attribute__((iboutletcollection(ClassName))) -~~~ +``` -Unlike `IBAction` or `IBOutlet`, `IBOutletCollection` takes a class name as an argument, which is, incidentally, as close to Apple-sanctioned [generics](http://en.wikipedia.org/wiki/Generic_programming) as one gets in Objective-C. +Unlike `IBAction` or `IBOutlet`, `IBOutletCollection` takes a class name as an argument, which is, incidentally, as close to Apple-sanctioned [generics](https://en.wikipedia.org/wiki/Generic_programming) as one gets in Objective-C. As a top-level object, an `IBOutletCollection` `@property` should be declared `strong`, with an `NSArray *` type: -~~~{objective-c} +```objc @property (nonatomic, strong) IBOutletCollection(UIButton) NSArray *buttons; -~~~ +``` There are two rather curious things to note about an `IBOutletCollection` array: - **Its order is not necessarily guaranteed**. The order of an outlet collection appears to be roughly the order in which their connections are established in Interface Builder. However, there are numerous reports of that order changing across versions of Xcode, or as a natural consequence of version control. Nonetheless, having code rely on a fixed order is strongly discouraged. - **No matter what type is declared for the property, an `IBOutletCollection` is always an `NSArray`**. In fact, any type can be declared: `NSSet *`, `id `—heck, even `UIColor *` (depending on your error flags)! No matter what you put, an `IBOutletCollection` will always be stored as an `NSArray`, so you might as well have that type match up in your declaration to avoid compiler warnings. -With the advent of Objective-C [object literals](http://nshipster.com/at-compiler-directives/), `IBOutletCollection` has fallen slightly out of favor—at least for the common use case of convenience accessors, as in: +With the advent of Objective-C [object literals](https://nshipster.com/at-compiler-directives/), `IBOutletCollection` has fallen slightly out of favor—at least for the common use case of convenience accessors, as in: -~~~{objective-c} +```objc for (UILabel *label in labels) { label.font = [UIFont systemFontOfSize:14]; } -~~~ +``` Since declaring a collection of outlets is now as easy as comma-delimiting them within `@[]`, it may make just as much sense to do that as create a distinct collection. diff --git a/2014-05-12-nshipster-quiz-5.md b/2014-05-12-nshipster-quiz-5.md index 34e07c37..7f1aa133 100644 --- a/2014-05-12-nshipster-quiz-5.md +++ b/2014-05-12-nshipster-quiz-5.md @@ -1,6 +1,6 @@ --- title: "NSHipster Quiz #5" -author: Mattt Thompson +author: Mattt category: Trivia excerpt: "This fifth incarnation of the NSHipster Quiz took on a distinct North-of-the-Border flavor, as part of the NSNorth conference in Ottawa, Ontario. Think you're up to the challenge, eh?" status: @@ -21,104 +21,98 @@ As always, you can play along at home or at work with your colleagues. Here are - Play with up to 5 friends for maximum enjoyment - Don't be lame and look things up on the Internet or in Xcode -* * * +--- -Round 1: General Knowledge --------------------------- +## Round 1: General Knowledge Current events, miscellaneous tidbits, and random trivia. Following a time-honored traditions for NSHipster quizzes, the first round is always a mis-mash of people, places, and pop culture. -1. In 2011, Apple deprecated OS X's Common Data Security Architecture, leaving them unaffected by what recent vulnerability. -2. According to rumors, Apple will be partnering with which company to add song recognition functionality to Siri in iOS 8? -3. The White House expressed disappointment over a "selfie" of Boston Red Sox player David Ortiz and President Obama, due to allegations that it was a promotional stunt for which company? -4. In Sony's forthcoming Steve Jobs biopic, which actor was recently rumored to being approached by director Danny Boyle to play the lead role? For a bonus point: which actor was previously confirmed for this role, before director David Fincher backed out of the project? -5. In Apple's Q2 Earnings call, Tim Cook announced the company had acquired 24 companies so far in 2014, including Burstly, which is better known for what service for iOS developers? -6. After a rumored $3.2 billion acquisition bid by Apple, which American record producer, rapper and entrepreneur has described himself as "the first billionaire in hip hop"? For a bonus point: what is his _legal_ (i.e. non-stage) name? -7. A widespread (and recently debunked) rumor of Apple announcing Lightning-cable-powered biometric ear buds was originally disclosed on which social network for Silicon Valley insiders? -8. Oracle won an important victory in the U.S. Court of Appeals against Google in their suit regarding copyright claims of what? -9. What hot new social networking app allows you to anonymously chat with patrons of its eponymous, popular American chain restaurant? +1. In 2011, Apple deprecated OS X's Common Data Security Architecture, leaving them unaffected by what recent vulnerability. +2. According to rumors, Apple will be partnering with which company to add song recognition functionality to Siri in iOS 8? +3. The White House expressed disappointment over a "selfie" of Boston Red Sox player David Ortiz and President Obama, due to allegations that it was a promotional stunt for which company? +4. In Sony's forthcoming Steve Jobs biopic, which actor was recently rumored to being approached by director Danny Boyle to play the lead role? For a bonus point: which actor was previously confirmed for this role, before director David Fincher backed out of the project? +5. In Apple's Q2 Earnings call, Tim Cook announced the company had acquired 24 companies so far in 2014, including Burstly, which is better known for what service for iOS developers? +6. After a rumored $3.2 billion acquisition bid by Apple, which American record producer, rapper and entrepreneur has described himself as "the first billionaire in hip hop"? For a bonus point: what is his _legal_ (i.e. non-stage) name? +7. A widespread (and recently debunked) rumor of Apple announcing Lightning-cable-powered biometric ear buds was originally disclosed on which social network for Silicon Valley insiders? +8. Oracle won an important victory in the U.S. Court of Appeals against Google in their suit regarding copyright claims of what? +9. What hot new social networking app allows you to anonymously chat with patrons of its eponymous, popular American chain restaurant? 10. If one were to sit down at a NeXTstation and open "/NextLibrary/Frameworks/AppKit.framework/Resources/", they would find the file "NSShowMe.tiff". Who is pictured in this photo? -![NSShowMe.tiff]({{ site.asseturl }}/NSShowMe.tiff) +![NSShowMe.tiff]({% asset NSShowMe.tiff %}) -Round 2: Core Potpourri ------------------------ +## Round 2: Core Potpourri With the fluff out of the way, it's now time to dive into some hardcore Cocoa fundamentals. How well do _you_ know the standard library? -1. What unit does a Bluetooth peripheral measure RSSI, or received signal strength intensity in? -2. What is the return value of the following code: `UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType, @"image/jpeg", NULL)`? -3. What function must be called before calling `SecTrustGetCertificateCount` on a `SecTrustRef`? -4. What UIKit class can be used to show the definition of a word? -5. An `SCNetworkReachabilityRef` can be created from three different sets of arguments. Fill in the blank `SCNetworkReachabilityCreateWith_______`. (1 pt. each) -6. `mach_absolute_time()` returns a count of Mach absolute time units. What function can be used to convert this into something more useful, like nanoseconds? -7. How many arguments does `CGRectDivide` take? -8. What function would you call to generate a random integer between `1` and `N` -9. What CoreFoundation function can, among other things, transliterate between different writing systems? +1. What unit does a Bluetooth peripheral measure RSSI, or received signal strength intensity in? +2. What is the return value of the following code: `UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType, @"image/jpeg", NULL)`? +3. What function must be called before calling `SecTrustGetCertificateCount` on a `SecTrustRef`? +4. What UIKit class can be used to show the definition of a word? +5. An `SCNetworkReachabilityRef` can be created from three different sets of arguments. Fill in the blank `SCNetworkReachabilityCreateWith_______`. (1 pt. each) +6. `mach_absolute_time()` returns a count of Mach absolute time units. What function can be used to convert this into something more useful, like nanoseconds? +7. How many arguments does `CGRectDivide` take? +8. What function would you call to generate a random integer between `1` and `N` +9. What CoreFoundation function can, among other things, transliterate between different writing systems? 10. What is LLVM's logo? And, for a bonus point: What is GCC's logo? -Activity Sheet: NSAnagram -------------------------- +## Activity Sheet: NSAnagram -First introduced in [NSHipster Quiz #4](http://nshipster.com/nshipster-quiz-4/), NSAnagram has become loved and hated, in equal parts, by those who have dared to take the challenge. Each question is an anagram, whose letters can be rearranged to form the name of a class or type in a well-known system framework (hint: Foundation, CoreFoundation, CoreLocation, StoreKit, and UIKit are represented here). Good luck! +First introduced in [NSHipster Quiz #4](https://nshipster.com/nshipster-quiz-4/), NSAnagram has become loved and hated, in equal parts, by those who have dared to take the challenge. Each question is an anagram, whose letters can be rearranged to form the name of a class or type in a well-known system framework (hint: Foundation, CoreFoundation, CoreLocation, StoreKit, and UIKit are represented here). Good luck! -1. Farms To Rent -2. Zest On Mine! -3. A Stressful Nude -4. Non-payment Attacks, Sir! -5. Allegiant Ace, Conglomerated -6. Mental Burlesque Ruts -7. Ulcer Porn: OFF -8. Forgive Traded Crap -9. Cautionary Mini Dam +1. Farms To Rent +2. Zest On Mine! +3. A Stressful Nude +4. Non-payment Attacks, Sir! +5. Allegiant Ace, Conglomerated +6. Mental Burlesque Ruts +7. Ulcer Porn: OFF +8. Forgive Traded Crap +9. Cautionary Mini Dam 10. Coil Infatuation... Coil -* * * +--- # Answers -Round 1: General Knowledge --------------------------- - -1. Heartbleed -2. Shazam -3. Samsung -4. Leonardo DiCaprio, previously Christian Bale) -5. TestFlight -6. Dr. Dre, a.k.a Andre Romelle Young -7. Secret -8. Java APIs -9. [WhatsApplebees](http://whatsapplebees.com) +## Round 1: General Knowledge + +1. Heartbleed +2. Shazam +3. Samsung +4. Leonardo DiCaprio, previously Christian Bale) +5. TestFlight +6. Dr. Dre, a.k.a Andre Romelle Young +7. Secret +8. Java APIs +9. [WhatsApplebees](http://whatsapplebees.com) 10. A young Scott Forstall -Round 2: Core Potpourri ------------------------ - -1. [decibels (dB)](http://en.wikipedia.org/wiki/Received_signal_strength_indication) -2. [`public.jpeg`](https://developer.apple.com/library/ios/documentation/miscellaneous/Reference/UTIRef/Articles/System-DeclaredUniformTypeIdentifiers.html) -3. [`SecTrustEvaluate`](https://developer.apple.com/library/mac/documentation/security/Reference/certifkeytrustservices/Reference/reference.html) -4. [`UIReferenceLibraryViewController`](http://nshipster.com/dictionary-services/) -5. [`Address`, `AddressPair`, `Name`](https://developer.apple.com/library/mac/documentation/SystemConfiguration/Reference/SCNetworkReachabilityRef/Reference/reference.html) -6. [`mach_timebase_info()`](https://developer.apple.com/library/ios/qa/qa1643/_index.html) -7. [5](https://developer.apple.com/library/mac/documentation/graphicsimaging/reference/CGGeometry/Reference/reference.html#//apple_ref/c/func/CGRectDivide) -8. [`arc4random_uniform`](https://developer.apple.com/library/mac/documentation/Darwin/Reference/Manpages/man3/arc4random_uniform.3.html) -9. [`CFStringTransform`](https://developer.apple.com/library/mac/documentation/corefoundation/Reference/CFMutableStringRef/Reference/reference.html#//apple_ref/doc/uid/20001504-CH201-BCIGCACA) +## Round 2: Core Potpourri + +1. [decibels (dB)](https://en.wikipedia.org/wiki/Received_signal_strength_indication) +2. [`public.jpeg`](https://developer.apple.com/library/ios/documentation/miscellaneous/Reference/UTIRef/Articles/System-DeclaredUniformTypeIdentifiers.html) +3. [`SecTrustEvaluate`](https://developer.apple.com/library/mac/documentation/security/Reference/certifkeytrustservices/Reference/reference.html) +4. [`UIReferenceLibraryViewController`](https://nshipster.com/dictionary-services/) +5. [`Address`, `AddressPair`, `Name`](https://developer.apple.com/library/mac/documentation/SystemConfiguration/Reference/SCNetworkReachabilityRef/Reference/reference.html) +6. [`mach_timebase_info()`](https://developer.apple.com/library/ios/qa/qa1643/_index.html) +7. [5](https://developer.apple.com/library/mac/documentation/graphicsimaging/reference/CGGeometry/Reference/reference.html#//apple_ref/c/func/CGRectDivide) +8. [`arc4random_uniform`](https://developer.apple.com/library/mac/documentation/Darwin/Reference/Manpages/man3/arc4random_uniform.3.html) +9. [`CFStringTransform`](https://developer.apple.com/library/mac/documentation/corefoundation/Reference/CFMutableStringRef/Reference/reference.html#//apple_ref/doc/uid/20001504-CH201-BCIGCACA) 10. [LLVM's Logo is a **wyvern**, or **dragon**](http://llvm.org/Logo.html). [GCC's Logo is an **egg** (with a **gnu** bursting out of it)](http://gcc.gnu.org) -Round 3: NSAnagram ------------------- - -1. `NSFormatter` -2. `NSTimeZone` -3. `NSUserDefaults` -4. `SKPaymentTransaction` -5. `CLLocationManagerDelegate` -6. `NSMutableURLRequest` -7. `CFRunLoopRef` -8. `CGDataProviderRef` -9. `UIDynamicAnimator` +## Round 3: NSAnagram + +1. `NSFormatter` +2. `NSTimeZone` +3. `NSUserDefaults` +4. `SKPaymentTransaction` +5. `CLLocationManagerDelegate` +6. `NSMutableURLRequest` +7. `CFRunLoopRef` +8. `CGDataProviderRef` +9. `UIDynamicAnimator` 10. `UILocalNotification` -* * * +--- How did you do this time? Tweet out your score to see how you stack up to your peers! diff --git a/2014-05-19-benchmarking.md b/2014-05-19-benchmarking.md index aa9b54bc..63ea80de 100644 --- a/2014-05-19-benchmarking.md +++ b/2014-05-19-benchmarking.md @@ -1,6 +1,6 @@ --- title: Benchmarking -author: Mattt Thompson +author: Mattt category: Objective-C excerpt: "Abstractions are necessary for doing meaningful work, but they come at a cost. By benchmarking, a programmer can uncover the hidden performance characteristics of their code, and use this information to optimize accordingly." status: @@ -33,7 +33,7 @@ The Scientific Method outlines a series of steps to logically deduce answers for In the case of programming, there are generally two kinds of questions to be asked: -- **What are the _absolute_ performance characteristics of this code?** Is the procedure bound by _computation_ or _memory_? What is the [limiting behavior](http://en.wikipedia.org/wiki/Big_O_notation) across different sample sizes? +- **What are the _absolute_ performance characteristics of this code?** Is the procedure bound by _computation_ or _memory_? What is the [limiting behavior](https://en.wikipedia.org/wiki/Big_O_notation) across different sample sizes? - **What are the _relative_ performance characteristics of this code, as compared to its alternatives?** Which is faster, methodA or methodB? Because the underlying factors of everything from the operating system down to the metal itself are extremely variable, performance should be measured across a large number of trials. For most applications, something on the order of 105 to 108 samples should be acceptable. @@ -44,26 +44,26 @@ For this example, let's take a look at the performance characteristics of adding To establish a benchmark, we specify a `count` of objects to add, and the number of `iterations` to run this process. -```objective-c +```objc static size_t const count = 1000; static size_t const iterations = 10000; ``` Since we're not testing the stack allocation of objects, we declare the object to be added to the array once, outside of the benchmark. -```objective-c +```objc id object = @"🐷"; ``` Benchmarking is as simple as taking the time before running, and comparing it against the time after. `CACurrentMediaTime()` is a convenient way to measure time in seconds derived from `mach_absolute_time`. -> Unlike `NSDate` or `CFAbsoluteTimeGetCurrent()` offsets, `mach_absolute_time()` and `CACurrentMediaTime()` are based on the internal host clock, a precise, monatomic measure, and not subject to changes in the external time reference, such as those caused by time zones, daylight savings, or leap seconds +> Unlike `NSDate` or `CFAbsoluteTimeGetCurrent()` offsets, `mach_absolute_time()` and `CACurrentMediaTime()` are based on the internal host clock, a precise, monotonic measure, and not subject to changes in the external time reference, such as those caused by time zones, daylight savings, or leap seconds `for` loops are used to increment `count` and `iterations`. Each iteration is enclosed by an `@autoreleasepool`, to keep the memory footprint low. Putting it all together, here's a simple way to benchmark code in Objective-C: -```objective-c +```objc CFTimeInterval startTime = CACurrentMediaTime(); { for (size_t i = 0; i < iterations; i++) { @@ -91,7 +91,7 @@ Allow me to introduce you to `dispatch_benchmark`. `dispatch_benchmark` is part of [`libdispatch`](http://libdispatch.macosforge.org), a.k.a Grand Central Dispatch. Curiously, though, this function is not publicly declared, so you'll have to do that yourself: -```objective-c +```objc extern uint64_t dispatch_benchmark(size_t count, void (^block)(void)); ``` @@ -116,7 +116,7 @@ If you happened to skim all of that, be encouraged to read through that again— Here's what the previous example looks like if we were to use `dispatch_benchmark` instead: -```objective-c +```objc uint64_t t = dispatch_benchmark(iterations, ^{ @autoreleasepool { NSMutableArray *mutableArray = [NSMutableArray array]; @@ -138,7 +138,7 @@ For this example, let's consider the age-old question of "What difference does p Let's find out: -```objective-c +```objc uint64_t t_0 = dispatch_benchmark(iterations, ^{ @autoreleasepool { NSMutableArray *mutableArray = [NSMutableArray array]; diff --git a/2014-05-26-cocoapods.md b/2014-05-26-cocoapods.md index 43c29a4f..a960228c 100644 --- a/2014-05-26-cocoapods.md +++ b/2014-05-26-cocoapods.md @@ -1,15 +1,13 @@ --- title: CocoaPods -author: Mattt Thompson +author: Mattt category: Open Source tags: cfhipsterref excerpt: "When well thought-out and implemented, infrastructure is a multiplying force that drives growth and development. In the case of Objective-C, CocoaPods has provided a much-needed tool for channeling and organizing open source participation." status: - swift: n/a + swift: n/a --- -Egg Merchant, illustrated by Conor Heelan - 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. It all has to do with solving the problem of scale. @@ -20,10 +18,6 @@ In the case of Objective-C, [CocoaPods](http://cocoapods.org) provided a much-ne This week on NSHipster, we'll celebrate the launch of CocoaPods 0.33, [an important milestone for the project](http://blog.cocoapods.org/CocoaPods-0.33/), by taking a look back at where we came from, discussing where we are now, and thinking about what's to come. -> The following historical look at the origins of CocoaPods is, admittedly, a bit wordy for this publication. So if you're looking for technical details, feel free to [skip directly to that](#using-cocoapods). - ---- - ## 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 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. @@ -38,13 +32,15 @@ In those early years of iPhone OS, we started to see the first massively adopted Of this new wave of developers, those coming from a Ruby background had a significant influence on the code and culture of Objective-C. Ruby, a spiritual successor to Perl, had its own package manager similar to CPAN: [RubyGems](https://rubygems.org). -> Why so much influence from Ruby? Here's my pet theory: Ruby started gaining popular traction because of [Rails](http://rubyonrails.org), which hit 1.0 at the end of 2005. Given that the average duration of a startup gig seems to be about 1½ – 2½ years, the timing works out such that those first and second waves of bored Rails developers itching to jump ship would find a place in the emerging app space. +{% info %} +Why so much influence from Ruby? Here's my pet theory: Ruby started gaining popular traction because of [Rails](http://rubyonrails.org), which hit 1.0 at the end of 2005. Given that the average duration of a startup gig seems to be about 1½ – 2½ years, the timing works out such that those first and second waves of bored Rails developers itching to jump ship would find a place in the emerging app space. +{% endinfo %} As open source contributions in Objective-C began to get some traction, the pain points of code distribution were starting to become pretty obvious: Lacking frameworks, code for iOS could be packaged as a static library, but getting that set up and keeping code and static distributions in sync was an arduous process. -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 either—especially at a time when the body of code was split between [ARC and non-ARC](http://en.wikipedia.org/wiki/Automatic_Reference_Counting). +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 either—especially at a time when the body of code was split between [ARC and non-ARC](https://en.wikipedia.org/wiki/Automatic_Reference_Counting). ### Enter CocoaPods @@ -58,15 +54,14 @@ Since its initial proof-of-concept, the project has grown to include [14 core te 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. -> To break the 4th wall for a moment: Seriously, _thank you_, ladies and gentlemen of CocoaPods. You've done an amazing job. Keep up the good work! - ---- - ## 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. -> For the most up-to-date information on how to use CocoaPods, check out the [official guides](http://guides.cocoapods.org). +{% info %} +For the most up-to-date information on how to use CocoaPods, +check out the [official guides](http://guides.cocoapods.org). +{% endinfo %} ### Installing CocoaPods @@ -74,13 +69,18 @@ CocoaPods is installed through RubyGems, the Ruby package manager, which comes w To install, open Terminal.app and enter the following command: -~~~{bash} +```terminal $ sudo gem install cocoapods -~~~ +``` Now you should have the `pod` command available in the terminal. -> If you're using a Ruby versioning manager, like [rbenv](https://github.com/sstephenson/rbenv), you may need to run a command to re-link a binary shim to the library (e.g. `$ rbenv rehash`). +{% warning %} +If you're using a Ruby versioning manager like +[rbenv](https://github.com/sstephenson/rbenv), +you may need to run a command to re-link a binary shim to the library +(e.g. `$ rbenv rehash`). +{% endwarning %} ### Managing Dependencies @@ -94,51 +94,53 @@ A `Podfile` is where the dependencies of a project are listed. It is equivalent To create a Podfile, `cd` into the directory of your `.xcodeproj` file and enter the command: -~~~{bash} +```terminal $ pod init -~~~ +``` #### Podfile -~~~{ruby} +```ruby platform :ios, '7.0' target "AppName" do end -~~~ +``` Dependencies can have varying levels of specificity. For most libraries, pegging to a minor or patch version is the safest and easiest way to include them in your project. -~~~{ruby} +```ruby pod 'X', '~> 1.1' -~~~ +``` -> CocoaPods follows [Semantic Versioning](http://semver.org) conventions. +{% info %} +CocoaPods follows [Semantic Versioning](http://semver.org) conventions. +{% endinfo %} To include a library not included in the public specs database, a Git, Mercurial, or SVN repository can be used instead, for which a `commit`, `branch`, or `tag` can be specified. -~~~{ruby} +```ruby pod 'Y', :git => 'https://github.com/NSHipster/Y.git', :commit => 'b4dc0ffee' -~~~ +``` Once all of the dependencies have been specified, they can be installed with: -~~~{bash} +```terminal $ pod install -~~~ +``` When this is run, CocoaPods will recursively analyze the dependencies of each project, resolving them into a dependency graph, and serializing into a `Podfile.lock` file. -> For example, if two libraries require [AFNetworking](http://afnetworking.com), CocoaPods will determine a version that satisfies both requirements, and links them with a common installation of it. +For example, if two libraries require [AFNetworking](http://afnetworking.com), CocoaPods will determine a version that satisfies both requirements, and links them with a common installation of it. 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. This static library becomes a dependency for your original application target. An `xcworkspace` file is created, and should be used from that point onward. This allows the original `xcodeproj` file to remain unchanged. Subsequent invocations of `pod install` will add new pods or remove old pods according to the locked dependency graph. To update the individual dependencies of a project to the latest version, do the following: -~~~{bash} +```terminal $ pod update -~~~ +``` ### Trying Out a CocoaPod @@ -146,11 +148,11 @@ One great, but lesser-known, feature of CocoaPods is the `try` command, which al Invoking `$ pod try` with the name of a project in the public specs database opens up any example projects for the library: -~~~{bash} +```terminal $ pod try Ono -~~~ +``` -![Ono.xcworkspace]({{ site.asseturl }}/cocoapods-try-ono.png) +![Ono.xcworkspace]({% asset cocoapods-try-ono.png @path %}) ## Creating a CocoaPod @@ -164,39 +166,42 @@ Remember: **_raising_ the bar for contribution within a software ecosystem _lowe A `.podspec` file is the atomic unit of a CocoaPods dependency. It specifies the name, version, license, and source files for a library, along with other metadata. -> The [official guide to the Podfile](http://guides.cocoapods.org/using/the-podfile) has some great information and examples. +{% info %} +The [official guide to the Podfile](http://guides.cocoapods.org/using/the-podfile) +has some great information and examples. +{% endinfo %} #### NSHipsterKit.podspec -~~~{ruby} +```ruby 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' => + s.homepage = 'https://nshipster.com' + s.authors = { 'Mattt' => '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 -~~~ +``` Once published to the public specs database, anyone could add it to their project, specifying their Podfile thusly: #### Podfile -~~~{ruby} +```ruby pod 'NSHipsterKit', '~> 1.0' -~~~ +``` A `.podspec` file can be useful for organizing internal or private dependencies as well: -~~~{ruby} +```ruby pod 'Z', :path => 'path/to/directory/with/podspec' -~~~ +``` ### Publishing a CocoaPod @@ -208,26 +213,26 @@ The CocoaPods Trunk service solves a lot of this, making the process nicer for e To get started, you must first register your machine with the Trunk service. This is easy enough, just specify your email address (the one you use for committing library code) along with your name. -~~~{bash} +```terminal $ pod trunk register mattt@nshipster.com "Mattt Thompson" -~~~ +``` Now, all it takes to publish your code to CocoaPods is a single command. The same command works for creating a new library or adding a new version to an existing one: -~~~{bash} +```terminal $ pod trunk push NAME.podspec -~~~ +``` -> Authors of existing CocoaPods can claim their libraries [with a few simple steps](http://blog.cocoapods.org/Claim-Your-Pods/). +{% info %} +Authors of existing CocoaPods can claim their libraries [with a few simple steps](http://blog.cocoapods.org/Claim-Your-Pods/). +{% endinfo %} -*** - -## 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 just one example of the great work being done on Objective-C infrastructure. Other community tools, like [Travis CI](http://blog.travis-ci.com/introducing-mac-ios-rubymotion-testing/), [CocoaDocs](http://cocoadocs.org), and [Nomad](http://nomad-cli.com) have dramatically improved the everyday experience iOS and OS X development for the community. -It can be tempting to be snarky, contrarian, or grumpy about the direction of a community. No matter what, though, let us all try our best to enter into dialogue in good faith, offering constructive criticism where we can. We should help each other to be good [stewards](http://nshipster.com/stewardship/) of what we share, and strive towards [empathy](http://nshipster.com/empathy/) in all our interactions. +It can be tempting to be snarky, contrarian, or grumpy about the direction of a community. No matter what, though, let us all try our best to enter into dialogue in good faith, offering constructive criticism where we can. We should help each other to be good [stewards](https://nshipster.com/stewardship/) of what we share, and strive towards [empathy](https://nshipster.com/empathy/) in all our interactions. CocoaPods is a good thing for Objective-C. And it's only getting better. diff --git a/2014-06-03-nshipster-quiz-6.md b/2014-06-03-nshipster-quiz-6.md index 6704fb34..3a88cc9b 100644 --- a/2014-06-03-nshipster-quiz-6.md +++ b/2014-06-03-nshipster-quiz-6.md @@ -1,6 +1,6 @@ --- title: "NSHipster Quiz #6" -author: Mattt Thompson +author: Mattt category: Trivia excerpt: "Our second annual WWDC Pub Quiz! With dozens of teams, comprised of developers from all around the world, the competition was fierce. How will you stack up?" status: @@ -19,44 +19,41 @@ For everyone that couldn't make it to the event, here's an opportunity to play a - Play with up to 5 friends for maximum enjoyment - Don't be lame and look things up on the Internet or in Xcode -* * * +--- -Round 1: General Knowledge --------------------------- +## Round 1: General Knowledge Current events, miscellaneous tidbits, and random trivia. Following a time-honored traditions for NSHipster quizzes, the first round is always a mis-mash of people, places, and pop culture. -1. On iOS 8, what magic phrase can be used to activate Siri, when the device is plugged in? -2. What game, crestfallen by the runaway success of its clone, 2048, was at least slightly vindicated last night with an ADA win? -3. Which alternative search engine was added to the latest release of Safari? -4. Weeks after its announcement, Apple finally confirmed its $3B acquisition of Beats Electronics. What is the name of Dre’s Co-founder? -5. Yosemite is, of course, the code name of OS X Yosemite, but this code name was used before. What was the product? _(Hint: It was released in 1999 and had a top clock speed of 450MHz)_ -6. What is the name of the valley in Yosemite that was flooded after construction of the O'Shaughnessy Dam in 1927, which provides drinking water to San Francisco? -7. Much of the reason why Yosemite exists today is thanks to the Sierra Club and a Scottish-born naturalist. What is this gentleman's name? -8. 20 years ago, Apple launched a new experimental language. It had a syntax like this: `let x :: = 2;`. What was this language’s name? -9. What does a Swift eat? +1. On iOS 8, what magic phrase can be used to activate Siri, when the device is plugged in? +2. What game, crestfallen by the runaway success of its clone, 2048, was at least slightly vindicated last night with an ADA win? +3. Which alternative search engine was added to the latest release of Safari? +4. Weeks after its announcement, Apple finally confirmed its $3B acquisition of Beats Electronics. What is the name of Dre’s Co-founder? +5. Yosemite is, of course, the code name of OS X Yosemite, but this code name was used before. What was the product? _(Hint: It was released in 1999 and had a top clock speed of 450MHz)_ +6. What is the name of the valley in Yosemite that was flooded after construction of the O'Shaughnessy Dam in 1927, which provides drinking water to San Francisco? +7. Much of the reason why Yosemite exists today is thanks to the Sierra Club and a Scottish-born naturalist. What is this gentleman's name? +8. 20 years ago, Apple launched a new experimental language. It had a syntax like this: `let x :: = 2;`. What was this language’s name? +9. What does a Swift eat? 10. What is the birdwatching term for the overall impression or appearance of a bird based on its shape, posture, & flying style? -Round 2: So You Think You Can Swift? ------------------------------------- +## Round 2: So You Think You Can Swift? Having only been introduced the day before, Swift was fresh on everyone's minds, and the hot topic of conversation. To put everyone's knowledge to the test, the following 10 exercises were posed to attendees. For anyone revisiting this quiz months or years after the fact, this should be incredibly easy. But just imagine coming into these questions having only skimmed a few hundred pages of the Swift iBook (if at all). And now imagine that you're a beer and a half into a late night during the week of WWDC. Now you can start to appreciate how grumpy this round made a lot of people. -1. Declare a constant `d` equal to `3.0`. -2. Declare a variable `s` of type `String`. -3. Interpolate the value of `d` into a String literal. -4. Set a var `b` to `28` using an octal literal. -5. Declare an optional property of type `Int` named `x`. -6. Declare a Highlander `enum` of type `Int`, with an element named "One". -7. Override viewDidLoad in a UIViewController Subclass. -8. Declare a class C that adopting the NSCoding protocol. -9. Alias `String` as `Rope`. +1. Declare a constant `d` equal to `3.0`. +2. Declare a variable `s` of type `String`. +3. Interpolate the value of `d` into a String literal. +4. Set a var `b` to `28` using an octal literal. +5. Declare an optional property of type `Int` named `x`. +6. Declare a Highlander `enum` of type `Int`, with an element named "One". +7. Override viewDidLoad in a UIViewController Subclass. +8. Declare a class C that adopting the NSCoding protocol. +9. Alias `String` as `Rope`. 10. Declare a protocol method `m`, which returns both an `Int`, and a `Dictionary`, with `String` keys and any value. -Round 3: Music Round --------------------- +## Round 3: Music Round Rounding out the competition was the venerable staple of pub trivia everywhere: the music round! @@ -65,64 +62,61 @@ The theme of this music round is—you guessed it—songs used in Apple commerci > Due to the peculiarities of embedded video, you may have to click through in order to hear the song. Don't worry—the link is to the song itself, not the Apple ad, so you'll at least have something to puzzle over.
    -
  1. -
  2. -
  3. -
  4. -
  5. -
  6. -
  7. -
  8. -
  9. -
  10. +
  11. +
  12. +
  13. +
  14. +
  15. +
  16. +
  17. +
  18. +
  19. +
-* * * +--- # Answers -Round 1: General Knowledge --------------------------- - -1. "Hey Siri" -2. [Threes](http://asherv.com/threes/) -3. [DuckDuckGo](https://duckduckgo.com) -4. [Jimmy Iovine](http://en.wikipedia.org/wiki/Jimmy_Iovine) -5. [Power Macintosh G3 (Blue & White)](http://en.wikipedia.org/wiki/Power_Macintosh_G3_%28Blue_%26_White%29) -6. [Hetch Hetchy](http://en.wikipedia.org/wiki/Hetch_Hetchy) -7. [John Muir](http://en.wikipedia.org/wiki/John_Muir) -8. [Dylan](http://en.wikipedia.org/wiki/Dylan_(programming_language)) -9. [Insects](http://en.wikipedia.org/wiki/Swift#Feeding) -10. [Jizz](http://en.wikipedia.org/wiki/Jizz_%28birding%29) - -Round 2: So You Think You Can Swift? ------------------------------------- - -1. `let d = 3.0` -2. `var s: String` -3. `"\(d)"` -4. `var b = 0o34` -5. `var x: Int?` -6. `enum Highlander: Int { case One = 1}` -7. `override func viewDidLoad() { ... }` -8. `class C: NSCoding` -9. `typealias Rope = String` +## Round 1: General Knowledge + +1. "Hey Siri" +2. [Threes](http://asherv.com/threes/) +3. [DuckDuckGo](https://duckduckgo.com) +4. [Jimmy Iovine](https://en.wikipedia.org/wiki/Jimmy_Iovine) +5. [Power Macintosh G3 (Blue & White)](https://en.wikipedia.org/wiki/Power_Macintosh_G3_%28Blue_%26_White%29) +6. [Hetch Hetchy](https://en.wikipedia.org/wiki/Hetch_Hetchy) +7. [John Muir](https://en.wikipedia.org/wiki/John_Muir) +8. [Dylan]() +9. [Insects](https://en.wikipedia.org/wiki/Swift#Feeding) +10. [Jizz](https://en.wikipedia.org/wiki/Jizz_%28birding%29) + +## Round 2: So You Think You Can Swift? + +1. `let d = 3.0` +2. `var s: String` +3. `"\(d)"` +4. `var b = 0o34` +5. `var x: Int?` +6. `enum Highlander: Int { case One = 1}` +7. `override func viewDidLoad() { ... }` +8. `class C: NSCoding` +9. `typealias Rope = String` 10. `func m() -> (Int, Dictionary)` -Round 3: Music Round --------------------- - -1. [**iPod 3G**: Jet — "Are You Gonna Be My Girl?"](https://www.youtube.com/watch?v=TaVFCdwT0hk) -2. [**G4 Cube**: The Jimi Hendrix Experience — "Purple Haze"](https://www.youtube.com/watch?v=rzn0dhNm0aE) -3. [**iPod Shuffle**: Caesars — "Jerk It Out"](https://www.youtube.com/watch?v=nuLx1Uk1ceg) -4. [**iTunes**: Green Day — "I Fought The Law"](https://www.youtube.com/watch?v=rcToyN2_cSs) -5. [**iPod 4G (U2 Special Edition)**: U2 — "Vertigo"](https://www.youtube.com/watch?v=t1ENjxFMXkw) -6. [**OS X (First-Run Experience)**: Honeycut — "Exodus Honey"](https://www.youtube.com/watch?v=hmb1t8RMsu4) -7. [**iPod 1G**: The Propellerheads — "Take California"](https://www.youtube.com/watch?v=eb1bPg4NlwY) -8. [**iPhone 5s**: Pixies — "Gigantic"](https://www.youtube.com/watch?v=ODmfmUWqlSA) -9. [**iBook**: Miles Davis — "Flamenco Sketches" (with voiceover by Jeff Goldblum)](https://www.youtube.com/watch?v=T3Gvy-8gtOU) +## Round 3: Music Round + +1. [**iPod 3G**: Jet — "Are You Gonna Be My Girl?"](https://www.youtube.com/watch?v=TaVFCdwT0hk) +2. [**G4 Cube**: The Jimi Hendrix Experience — "Purple Haze"](https://www.youtube.com/watch?v=rzn0dhNm0aE) +3. [**iPod Shuffle**: Caesars — "Jerk It Out"](https://www.youtube.com/watch?v=nuLx1Uk1ceg) +4. [**iTunes**: Green Day — "I Fought The Law"](https://www.youtube.com/watch?v=rcToyN2_cSs) +5. [**iPod 4G (U2 Special Edition)**: U2 — "Vertigo"](https://www.youtube.com/watch?v=t1ENjxFMXkw) +6. [**OS X (First-Run Experience)**: Honeycut — "Exodus Honey"](https://www.youtube.com/watch?v=hmb1t8RMsu4) +7. [**iPod 1G**: The Propellerheads — "Take California"](https://www.youtube.com/watch?v=eb1bPg4NlwY) +8. [**iPhone 5s**: Pixies — "Gigantic"](https://www.youtube.com/watch?v=ODmfmUWqlSA) +9. [**iBook**: Miles Davis — "Flamenco Sketches" (with voiceover by Jeff Goldblum)](https://www.youtube.com/watch?v=T3Gvy-8gtOU) 10. [**iMac "Sage"**: Kermit The Frog — "(It’s Not Easy) Bein' Green"](https://www.youtube.com/watch?v=0awG75V2OQw) -* * * +--- How did you do this time? Tweet out your score to see how you stack up to your peers! diff --git a/2014-06-09-ios8.md b/2014-06-09-ios8.md index 1b670635..44046079 100644 --- a/2014-06-09-ios8.md +++ b/2014-06-09-ios8.md @@ -1,7 +1,7 @@ --- title: iOS 8 -author: Mattt Thompson -category: "" +author: Mattt +category: Miscellaneous excerpt: "Ask anyone, and they'll tell you: WWDC 2014 was the one of the most exciting in recent memory. This week, we'll take a look beneath the headline features, and share some of the more obscure APIs that everyone should know about." status: swift: 2.0 @@ -26,12 +26,12 @@ This week, we'll take a look beneath the headline features, and share some of th Forget `[[UIDevice currentDevice] systemVersion]` and `NSFoundationVersionNumber`, there's a new way to determine the current operating system in code: `NSProcessInfo -isOperatingSystemAtLeastVersion` -~~~{swift} +```swift import Foundation let yosemite = NSOperatingSystemVersion(majorVersion: 10, minorVersion: 10, patchVersion: 0) NSProcessInfo().isOperatingSystemAtLeastVersion(yosemite) // false -~~~ +``` Keep in mind, however, that a test for capability, such as with `SomeClass.class` or `respondsToSelector:`, is preferable to checking the OS version. Compiler macros in C or Swift can be used to conditionally compile source based on the build configuration of the target. @@ -39,7 +39,7 @@ Keep in mind, however, that a test for capability, such as with `SomeClass.class One of the features most sorely lacking in Foundation was the ability to work with units for quantities like mass or length. In iOS 8 and OS X Yosemite, three new classes were introduced that fills the gap: `NSEnergyFormatter`, `NSMassFormatter`, & `NSLengthFormatter`. -> This effectively doubles the number of [`NSFormatter`](http://nshipster.com/nsformatter/) subclasses in Foundation, which was previously limited to `NSNumberFormatter`, `NSDateFormatter`, & `NSByteCountFormatter`. +> This effectively doubles the number of [`NSFormatter`](https://nshipster.com/nsformatter/) subclasses in Foundation, which was previously limited to `NSNumberFormatter`, `NSDateFormatter`, & `NSByteCountFormatter`. Although these new formatter classes are part of Foundation, they were added primarily for use in HealthKit. @@ -47,33 +47,33 @@ Although these new formatter classes are part of Foundation, they were added pri `NSEnergyFormatter` formats energy in Joules, the raw unit of work for exercises, and Calories, which is used when working with nutrition information. -~~~{swift} +```swift let energyFormatter = NSEnergyFormatter() energyFormatter.forFoodEnergyUse = true let joules = 10_000.0 print(energyFormatter.stringFromJoules(joules)) // "2.39 Cal" -~~~ +``` ### NSMassFormatter Although the fundamental unit of physical existence, mass is pretty much relegated to tracking the weight of users in HealthKit. Yes, mass and weight are different, but this is programming, not science class, so stop being pedantic. -~~~{swift} +```swift let massFormatter = NSMassFormatter() let kilograms = 60.0 print(massFormatter.stringFromKilograms(kilograms)) // "132 lb" -~~~ +``` ### NSLengthFormatter Rounding out the new `NSFormatter` subclasses is `NSLengthFormatter`. Think of it as a more useful version of `MKDistanceFormatter`, with more unit options and formatting options. -~~~{swift} +```swift let lengthFormatter = NSLengthFormatter() let meters = 5_000.0 print(lengthFormatter.stringFromMeters(meters)) // "3.107 mi" -~~~ +``` ## CMPedometer @@ -81,7 +81,7 @@ Continuing on iOS 8's health kick, `CMStepCounter` is revamped in the latest rel It's amazing what that M7 chip is capable of. -~~~{swift} +```swift import CoreMotion let lengthFormatter = NSLengthFormatter() @@ -99,13 +99,13 @@ pedometer.startPedometerUpdatesFromDate(NSDate()) { data, error in } } } -~~~ +``` ## CMAltimeter On supported devices, a `CMPedometer`'s stats on `floorsAscended` / `floorsDescended` can be augmented with `CMAltimeter` to get a more granular look at vertical distance traveled: -~~~{swift} +```swift import CoreMotion let altimeter = CMAltimeter() @@ -116,13 +116,13 @@ if CMAltimeter.isRelativeAltitudeAvailable() { } } } -~~~ +``` ## CLFloor `CLFloor` is a new API in iOS 8 that ties the new features in CoreMotion with Apple's ambitious plan to map the interiors of the largest buildings in the world. Look for this information to play a significant role in future hyperlocal mapping applications. -~~~{swift} +```swift import CoreLocation class LocationManagerDelegate: NSObject, CLLocationManagerDelegate { @@ -136,7 +136,7 @@ class LocationManagerDelegate: NSObject, CLLocationManagerDelegate { let manager = CLLocationManager() manager.delegate = LocationManagerDelegate() manager.startUpdatingLocation() -~~~ +``` ## HKStatistics @@ -146,7 +146,7 @@ HealthKit manages your biometrics from all of your devices in a single unified A The following example shows how statistics summed over the duration of the day can be grouped and interpreted individually: -~~~{swift} +```swift import HealthKit let collection: HKStatisticsCollection? = ... @@ -174,7 +174,7 @@ for source in sources { } } } -~~~ +``` NSHipster will be covering a lot more about HealthKit in future editions, so stay tuned! @@ -182,7 +182,7 @@ NSHipster will be covering a lot more about HealthKit in future editions, so sta In many ways, WWDC 2014 was the year that Apple _fixed their shit_. Small things, like adding the missing `NSStream` initializer for creating a bound stream pair (without resorting to awkwardly-bridged `CFStreamCreatePairWithSocketToHost` call). Behold: `+[NSStream getStreamsToHostWithName:port:inputStream:outputStream:]` -~~~{swift} +```swift var inputStream: NSInputStream? var outputStream: NSOutputStream? @@ -190,18 +190,18 @@ NSStream.getStreamsToHostWithName("nshipster.com", port: 5432, inputStream: &inputStream, outputStream: &outputStream) -~~~ +``` ## NSString -localizedCaseInsensitiveContainsString Also filed under: "small but solid fixes", is this convenience method for `String`/`NSString`: -~~~{swift} +```swift let string = "Café" let substring = "É" string.localizedCaseInsensitiveContainsString(substring) // true -~~~ +``` ## CTRubyAnnotationRef @@ -209,7 +209,7 @@ If you're a linguistics and typography nerd, this new addition to the CoreText f ...oh right. Ruby. No, not [Ruby](https://www.ruby-lang.org/en/). [Ruby](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ruby). It's used to display the pronunciation of characters in certain Asian scripts. -~~~{objective-c} +```objc @import CoreText; NSString *kanji = @"猫"; @@ -220,7 +220,7 @@ CFStringRef furigana[kCTRubyPositionCount] = CTRubyAnnotationRef ruby = CTRubyAnnotationCreate(kCTRubyAlignmentAuto, kCTRubyOverhangAuto, 0.5, furigana); -~~~ +``` Admittedly, the documentation isn't entirely clear on how exactly to incorporate this into the rest of your `CoreText` drawing calls, but the result would look something like this: @@ -242,30 +242,30 @@ What's even nerdier than Ruby annotations? The new calendar identifiers added to The Foundation URL Loading System has remained relatively unchanged since last year's `NSURLSession` blowout. However, `NSURLCredentialStorage` has been given some TLC, with new functions that get and set credentials for tasks in asynchronous, non-blocking fashion. -~~~{swift} +```swift import Foundation let session = NSURLSession() -let task = session.dataTaskWithURL(NSURL(string: "http://nshipster.com")!) { data, response, error in - // ... +let task = session.dataTaskWithURL(NSURL(string: "https://nshipster.com")!) { data, response, error in + <#...#> } let protectionSpace = NSURLProtectionSpace() let credentialStorage = NSURLCredentialStorage() credentialStorage.getCredentialsForProtectionSpace(protectionSpace, task: task) { credentials in - // ... + <#...#> } -~~~ +``` ## kUTTypeToDoItem -Looking through the latest API diffs, one might notice the large number of new [UTIs](http://en.wikipedia.org/wiki/Uniform_Type_Identifier) constants. One that caught my eye was `kUTTypeToDoItem`: +Looking through the latest API diffs, one might notice the large number of new [UTIs](https://en.wikipedia.org/wiki/Uniform_Type_Identifier) constants. One that caught my eye was `kUTTypeToDoItem`: -~~~{swift} +```swift import MobileCoreServices kUTTypeToDoItem // "public.to-do-item" -~~~ +``` As a public type, iOS & OS X now provide a unified way to share tasks between applications. If you happen to work on a task management tool (and, let's be honest, the chances are extremely good, considering how damn many of them there are in the App Store), proper integration with this system type should be put at the top of your list. @@ -275,7 +275,7 @@ Most users are completely unaware that most pictures taken with phones these day New to the Image I/O framework is a convenient new option for `CGImageDestination`: `kCGImageMetadataShouldExcludeGPS`, which does what you'd expect. -~~~{swift} +```swift import UIKit import ImageIO import MobileCoreServices @@ -291,8 +291,8 @@ if let imageDestination = CGImageDestinationCreateWithURL(fileURL, kUTTypeJPEG, CGImageDestinationAddImage(imageDestination, cgImage, options) CGImageDestinationFinalize(imageDestination) } -~~~ -~~~{objective-c} +``` +```objc @import UIKit; @import ImageIO; @import MobileCoreServices; @@ -314,7 +314,7 @@ CGImageDestinationCreateWithURL((__bridge CFURLRef)fileURL, CGImageDestinationAddImage(imageDestinationRef, [image CGImage], (__bridge CFDictionaryRef)options); CGImageDestinationFinalize(imageDestinationRef); CFRelease(imageDestinationRef); -~~~ +``` ## WTF_PLATFORM_IOS @@ -326,7 +326,7 @@ CFRelease(imageDestinationRef); `WKWebView` offers Safari-level performance to your own app, and further improves on `UIWebView` with preferences and configurations: -~~~{swift} +```swift import WebKit let preferences = WKPreferences() @@ -336,9 +336,9 @@ let configuration = WKWebViewConfiguration() configuration.preferences = preferences let webView = WKWebView(frame: self.view.bounds, configuration: configuration) -let request = NSURLRequest(URL: NSURL(string: "http://nshipster.com")!) +let request = NSURLRequest(URL: NSURL(string: "https://nshipster.com")!) webView.loadRequest(request) -~~~ +``` ## NSQualityOfService @@ -363,14 +363,14 @@ Imagine: with CloudKit and LocalAuthentication, nearly all of the friction to cr LocalAuthentication works in terms of an `LAContext` class, which evaluates a specified policy, and gives a thumbs up or thumbs down on user authentication. At no point is any biometric information made available to the application—everything is kept safe on the hardware itself. -~~~{swift} +```swift let context = LAContext() var error: NSError? if context.canEvaluatePolicy(.DeviceOwnerAuthenticationWithBiometrics, error: &error) { context.evaluatePolicy(.DeviceOwnerAuthenticationWithBiometrics, localizedReason: "...") { success, error in if success { - // ... + <#...#> } else { print("Error: \(error)") } @@ -378,8 +378,8 @@ if context.canEvaluatePolicy(.DeviceOwnerAuthenticationWithBiometrics, error: &e } else { print("Error: \(error)") } -~~~ -~~~{objective-c} +``` +```objc LAContext *context = [[LAContext alloc] init]; NSError *error = nil; @@ -390,7 +390,7 @@ if ([context canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics localizedReason:NSLocalizedString(@"...", nil) reply:^(BOOL success, NSError *error) { if (success) { - // ... + <#...#> } else { NSLog(@"%@", error); } @@ -398,7 +398,7 @@ if ([context canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics } else { NSLog(@"%@", error); } -~~~ +``` * * * diff --git a/2014-06-16-clang-diagnostics.md b/2014-06-16-clang-diagnostics.md index 6533ac52..b049953b 100644 --- a/2014-06-16-clang-diagnostics.md +++ b/2014-06-16-clang-diagnostics.md @@ -1,10 +1,11 @@ --- title: Clang Diagnostics -author: Mattt Thompson +author: Mattt category: Objective-C excerpt: "Diagnostics combine logic with analytics to arrive at a conclusion. It's science and engineering at their purest. It's human reasoning at its most potent. For us developers, our medium of code informs the production of subsequent code, creating a positive feedback loop that has catapulted the development of technology exponentially over the last half century. For us Objective-C developers specifically, the most effective diagnostics come from Clang." status: swift: n/a +retired: true --- Diagnostics combine logic with analytics to arrive at a conclusion. It's science and engineering at their purest. It's human reasoning at its most potent. @@ -17,29 +18,29 @@ Clang is the C / Objective-C front-end to the LLVM compiler. It has a deep under That amazing readout you get when you "Build & Analyze" (`⌘⇧B`) is a function of the softer, more contemplative side of Clang: its code diagnostics. -In our article about [`#pragma`](http://nshipster.com/pragma/), we quipped: +In our article about [`#pragma`](https://nshipster.com/pragma/), we quipped: > Pro tip: Try setting the `-Weverything` flag and checking the "Treat Warnings as Errors" box your build settings. This turns on Hard Mode in Xcode. Now, we stand by this advice, and encourage other developers to step up their game and treat build warnings more seriously. However, there are some situations in which you and Clang reach an impasse. For example, consider the following `switch` statement: -~~~{objective-c} +```objc switch (style) { case UITableViewCellStyleDefault: case UITableViewCellStyleValue1: case UITableViewCellStyleValue2: case UITableViewCellStyleSubtitle: - // ... + <#...#> default: return; } -~~~ +``` When certain flags are enabled, Clang will complain that the "default label in switch which covers all enumeration values". However, if we _know_ that, zooming out into a larger context, `style` is (for better or worse) derived from an external representation (e.g. JSON resource) that allows for unconstrained `NSInteger` values, the `default` case is a necessary safeguard. The only way to insist on this inevitability is to use `#pragma` to ignore a warning flag temporarily: > `push` & `pop` are used to save and restore the compiler state, similar to Core Graphics or OpenGL contexts. -~~~{objective-c} +```objc #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wcovered-switch-default" switch (style) { @@ -47,21 +48,17 @@ switch (style) { case UITableViewCellStyleValue1: case UITableViewCellStyleValue2: case UITableViewCellStyleSubtitle: - // ... + <#...#> default: return; } #pragma clang diagnostic pop -~~~ +``` > Again, and this cannot be stressed enough, Clang is right at least 99% of the time. Actually fixing an analyzer warning is _strongly_ preferred to ignoring it. Use `#pragma clang diagnostic ignored` as a method of last resort. This week, as a public service, we've compiled a (mostly) comprehensive list of Clang warning strings and their associated flags, which can be found here: -**[F\*\*\*ingClangWarnings.com](http://fuckingclangwarnings.com)** +**[ClangWarnings.com](https://clangwarnings.com)** You can also find the compiler and analyzer flags for any warning you might encounter by `^`-Clicking the corresponding entry in the Xcode Issue Navigator and selecting "Reveal in Log". (If this option is disabled, try building the project again). - -* * * - -Corrections? Additions? Open a [Pull Request](https://github.com/mattt/fuckingclangwarnings.com/pulls) to submit your change. Any help would be greatly appreciated. diff --git a/2014-07-07-nscalendarunityear.md b/2014-07-07-nscalendarunityear.md index 4d07c3ca..74786f33 100644 --- a/2014-07-07-nscalendarunityear.md +++ b/2014-07-07-nscalendarunityear.md @@ -1,33 +1,34 @@ --- title: NSCalendarUnitYear -author: Mattt Thompson +author: Mattt category: Swift excerpt: "NSHipster.com was launched 2 years ago to the day. Each week since has featured a new article on some obscure topic in Objective-C or Cocoa (with only a couple gaps). Let's celebrate with some cake." +retired: true status: - swift: 2.0 - reviewed: September 9, 2015 + swift: 2.0 + reviewed: September 9, 2015 --- -NSHipster.com was launched 2 years ago to the day, with [a little article about NSIndexSet](http://nshipster.com/nsindexset/). Each week since has featured a new article on some obscure topic in Objective-C or Cocoa (with only a couple gaps), which have been read by millions of visitors in over 180 different countries. +NSHipster.com was launched 2 years ago to the day, with [a little article about NSIndexSet](https://nshipster.com/nsindexset/). Each week since has featured a new article on some obscure topic in Objective-C or Cocoa (with only a couple gaps), which have been read by millions of visitors in over 180 different countries. -> This is actually the 101st article, which means that [by television industry standards](http://en.wikipedia.org/wiki/100_episodes), this site is now suitable for broadcast syndication. (Coming soon to TBS!) +> This is actually the 101st article, which means that [by television industry standards](https://en.wikipedia.org/wiki/100_episodes), this site is now suitable for broadcast syndication. (Coming soon to TBS!) Let's celebrate with some cake: - - - + Cute, right? Let's see what this looks like in code: -~~~{swift} +```swift var cakePath = UIBezierPath() cakePath.moveToPoint(CGPointMake(31.5, 32.5)) cakePath.addCurveToPoint(CGPointMake(6.5, 66.1), controlPoint1: CGPointMake(31.5, 32.5), controlPoint2: CGPointMake(6.9, 46.3)) @@ -35,13 +36,13 @@ cakePath.addCurveToPoint(CGPointMake(6.5, 66.5), controlPoint1: CGPointMake(6.5, cakePath.addLineToPoint(CGPointMake(6.5, 95)) ... -~~~ +``` Wait, hold up. What is this, Objective-C? Manipulating `UIBezierPath`s isn't exactly ground-breaking stuff, but with a few dozen more lines to go, we can make this a bit easier for ourselves. How about we put some syntactic icing on this cake with some [custom operators](https://developer.apple.com/library/prerelease/ios/documentation/swift/conceptual/swift_programming_language/AdvancedOperators.html#//apple_ref/doc/uid/TP40014097-CH27-XID_28)? -~~~{swift} +```swift infix operator ---> { associativity left } func ---> (left: UIBezierPath, right: (CGFloat, CGFloat)) -> UIBezierPath { let (x, y) = right @@ -65,7 +66,7 @@ func +~ (left: UIBezierPath, right: ((CGFloat, CGFloat), (CGFloat, CGFloat), (CG return left } -~~~ +``` > Get it? `--->` replaces `moveToPoint`, while `+-` replaces `addLineToPoint`, and `+~` replaces `addCurveToPoint`. This declaration also does away with all of the redundant calls to `CGPointMake`, opting instead for simple coordinate tuples. @@ -81,7 +82,6 @@ After full Emoji support (`let 🐶🐮`), custom operators are perhaps the shin ### A Dramatization of the Perils of Shiny Swift Features - > `SCENE: SAN FRANCISCO, THE YEAR IS 2017` > > `GREYBEARD:` So I inherited an old Swift codebase today, and I found this line of code—I swear to `$DEITY`—it just reads `😾 |--~~> 💩`. @@ -102,7 +102,7 @@ The moral of that cautionary tale: **use custom operators and emoji sparingly**. (Or whatever, the very next code sample totally ignores that advice) -~~~{swift} +```swift // Happy 2nd Birthday, NSHipster // 😗💨🎂✨2️⃣ @@ -168,13 +168,13 @@ UIColor.whiteColor().setFill() UIColor.blackColor().setStroke() 📍.fill() 📍.stroke() -~~~ +``` I'm as amazed as anyone that this actually compiles. Everything is terrible. -* * * +--- Anyway, Happy 2nd Birthday, NSHipster! diff --git a/2014-07-14-nsoperation.md b/2014-07-14-nsoperation.md index bb758e8d..8a590a0e 100644 --- a/2014-07-14-nsoperation.md +++ b/2014-07-14-nsoperation.md @@ -1,6 +1,6 @@ --- title: NSOperation -author: Mattt Thompson +author: Mattt category: Cocoa tags: nshipster excerpt: "In life, there's always work to be done. Every day brings with it a steady stream of tasks and chores to fill the working hours of our existence. Productivity is, as in life as it is in programming, a matter of scheduling and prioritizing and multi-tasking work in order to keep up appearances." @@ -15,7 +15,7 @@ Yet, no matter how burdened one's personal ToDo list becomes, it pales in compar Productivity is, as in life as it is in programming, a matter of scheduling and prioritizing and multi-tasking work in order to keep up appearances. -The secret to making apps snappy is to offload as much unnecessary work to the background as possible, and in this respect, the modern Cocoa developer has two options: [Grand Central Dispatch](http://en.wikipedia.org/wiki/Grand_Central_Dispatch) and [`NSOperation`](https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/NSOperation_class/Reference/Reference.html). This article will primarily focus on the latter, though it's important to note that the two are quite complementary (more on that later). +The secret to making apps snappy is to offload as much unnecessary work to the background as possible, and in this respect, the modern Cocoa developer has two options: [Grand Central Dispatch](https://en.wikipedia.org/wiki/Grand_Central_Dispatch) and [`NSOperation`](https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/NSOperation_class/Reference/Reference.html). This article will primarily focus on the latter, though it's important to note that the two are quite complementary (more on that later). * * * @@ -29,7 +29,7 @@ But simply wrapping computation into an object doesn't do much without a little ## NSOperationQueue -`NSOperationQueue` regulates the concurrent execution of operations. It acts as a priority queue, such that operations are executed in a roughly [First-In-First-Out](http://en.wikipedia.org/wiki/FIFO) manner, with higher-priority (`NSOperation.queuePriority`) ones getting to jump ahead of lower-priority ones. `NSOperationQueue` can also limit the maximum number of concurrent operations to be executed at any given moment, using the `maxConcurrentOperationCount` property. +`NSOperationQueue` regulates the concurrent execution of operations. It acts as a priority queue, such that operations are executed in a roughly [First-In-First-Out](https://en.wikipedia.org/wiki/FIFO) manner, with higher-priority (`NSOperation.queuePriority`) ones getting to jump ahead of lower-priority ones. `NSOperationQueue` can also limit the maximum number of concurrent operations to be executed at any given moment, using the `maxConcurrentOperationCount` property. > NSOperationQueue itself is backed by a Grand Central Dispatch queue, though that's a private implementation detail. @@ -66,7 +66,7 @@ All operations may not be equally important. Setting the `queuePriority` propert ### NSOperationQueuePriority -~~~{swift} +```swift public enum NSOperationQueuePriority : Int { case VeryLow case Low @@ -74,8 +74,8 @@ public enum NSOperationQueuePriority : Int { case High case VeryHigh } -~~~ -~~~{objective-c} +``` +```objc typedef NS_ENUM(NSInteger, NSOperationQueuePriority) { NSOperationQueuePriorityVeryLow = -8L, NSOperationQueuePriorityLow = -4L, @@ -83,7 +83,7 @@ typedef NS_ENUM(NSInteger, NSOperationQueuePriority) { NSOperationQueuePriorityHigh = 4, NSOperationQueuePriorityVeryHigh = 8 }; -~~~ +``` ## Quality of Service @@ -99,7 +99,7 @@ The following enumerated values are used to denote the nature and urgency of an ### NSQualityOfService -~~~{swift} +```swift @available(iOS 8.0, OSX 10.10, *) public enum NSQualityOfService : Int { case UserInteractive @@ -108,8 +108,8 @@ public enum NSQualityOfService : Int { case Background case Default } -~~~ -~~~{objective-c} +``` +```objc typedef NS_ENUM(NSInteger, NSQualityOfService) { NSQualityOfServiceUserInteractive = 0x21, NSQualityOfServiceUserInitiated = 0x19, @@ -117,7 +117,7 @@ typedef NS_ENUM(NSInteger, NSQualityOfService) { NSQualityOfServiceBackground = 0x09, NSQualityOfServiceDefault = -1 } NS_ENUM_AVAILABLE(10_10, 8_0); -~~~ +``` - `.UserInteractive`:UserInteractive QoS is used for work directly involved in providing an interactive UI such as processing events or drawing to the screen. - `.UserInitiated`: UserInitiated QoS is used for performing work that has been explicitly requested by the user and for which results must be immediately presented in order to allow for further user interaction. For example, loading an email after a user has selected it in a message list. @@ -125,21 +125,21 @@ typedef NS_ENUM(NSInteger, NSQualityOfService) { - `.Background`: Background QoS is used for work that is not user initiated or visible. In general, a user is unaware that this work is even happening and it will run in the most efficient manner while giving the most deference to higher QoS work. For example, pre-fetching content, search indexing, backups, and syncing of data with external systems. - `.Default`: Default QoS indicates the absence of QoS information. Whenever possible QoS information will be inferred from other sources. If such inference is not possible, a QoS between UserInitiated and Utility will be used. -~~~{swift} +```swift let backgroundOperation = NSOperation() backgroundOperation.queuePriority = .Low backgroundOperation.qualityOfService = .Background let operationQueue = NSOperationQueue.mainQueue() operationQueue.addOperation(backgroundOperation) -~~~ -~~~{objective-c} +``` +```objc NSOperation *backgroundOperation = [[NSOperation alloc] init]; backgroundOperation.queuePriority = NSOperationQueuePriorityLow; backgroundOperation.qualityOfService = NSOperationQualityOfServiceBackground; [[NSOperationQueue mainQueue] addOperation:backgroundOperation]; -~~~ +``` ## Asynchronous Operations @@ -155,16 +155,16 @@ For example, to describe the process of downloading and resizing an image from a Expressed in code: -~~~{swift} +```swift let networkingOperation: NSOperation = ... let resizingOperation: NSOperation = ... resizingOperation.addDependency(networkingOperation) let operationQueue = NSOperationQueue.mainQueue() operationQueue.addOperations([networkingOperation, resizingOperation], waitUntilFinished: false) -~~~ +``` -~~~{objective-c} +```objc NSOperation *networkingOperation = ... NSOperation *resizingOperation = ... [resizingOperation addDependency:networkingOperation]; @@ -172,7 +172,7 @@ NSOperation *resizingOperation = ... NSOperationQueue *operationQueue = [NSOperationQueue mainQueue]; [operationQueue addOperation:networkingOperation]; [operationQueue addOperation:resizingOperation]; -~~~ +``` An operation will not be started until all of its dependencies return `true` for `finished`. @@ -182,23 +182,23 @@ Make sure not to accidentally create a dependency cycle, such that A depends on When an `NSOperation` finishes, it will execute its `completionBlock` exactly once. This provides a really nice way to customize the behavior of an operation when used in a model or view controller. -~~~{swift} +```swift let operation = NSOperation() operation.completionBlock = { print("Completed") } NSOperationQueue.mainQueue().addOperation(operation) -~~~ +``` -~~~{objective-c} +```objc NSOperation *operation = ...; operation.completionBlock = ^{ NSLog("Completed"); }; [[NSOperationQueue mainQueue] addOperation:operation]; -~~~ +``` For example, you could set a completion block on a network operation to do something with the response data from the server once it's finished loading. diff --git a/2014-07-21-xctestcase.md b/2014-07-21-xctestcase.md index 3e33d6e7..23905196 100644 --- a/2014-07-21-xctestcase.md +++ b/2014-07-21-xctestcase.md @@ -1,21 +1,20 @@ --- title: "XCTestCase /
XCTestExpectation /
measureBlock()" -author: Mattt Thompson +author: Mattt category: Xcode excerpt: "This week, we'll take a look at `XCTest`, the testing framework built into Xcode, as well as the exciting new additions in Xcode 6: `XCTestExpectation` and performance tests." revisions: - "2015-04-07": Added note about location of call to `fulfill()`; new Objective-C examples -hiddenlang: "" + "2015-04-07": Added note about location of call to `fulfill()`; new Objective-C examples status: - swift: 1.2 - reviewed: June 25, 2015 + swift: 1.2 + reviewed: June 25, 2015 --- Although iOS 8 and Swift has garnered the lion's share of attention of the WWDC 2014 announcements, the additions and improvements to testing in Xcode 6 may end up making some of the most profound impact in the long-term. This week, we'll take a look at `XCTest`, the testing framework built into Xcode, as well as the exciting new additions in Xcode 6: `XCTestExpectation` and performance tests. -* * * +--- Most Xcode project templates now support testing out-of-the-box. For example, when a new iOS app is created in Xcode with `⇧⌘N`, the resulting project file will be configured with two top-level groups (in addition to the "Products" group): "AppName" & "AppNameTests". The project's auto-generated scheme enables the shortcut `⌘R` to build and run the executable target, and `⌘U` to build and run the test target. @@ -44,7 +43,8 @@ class Tests: XCTestCase { } } ``` -```objective-c + +```objc @interface Tests : XCTestCase @property NSCalendar *calendar; @@ -80,10 +80,11 @@ override func setUp() { locale = NSLocale(localeIdentifier: "en_US") } ``` -```objective-c + +```objc - (void)setUp { [super setUp]; - + self.calendar = [NSCalendar calendarWithIdentifier:NSCalendarIdentifierGregorian]; self.locale = [NSLocale localeWithLocaleIdentifier:@"en_US"]; } @@ -107,7 +108,8 @@ func testOnePlusOneEqualsTwo() { XCTAssertEqual(1 + 1, 2, "one plus one should equal two") } ``` -```objective-c + +```objc - (void)testOnePlusOneEqualsTwo { XCTAssertEqual(1 + 1, 2, "one plus one should equal two"); } @@ -124,7 +126,8 @@ To be entirely reductionist, all of the `XCTest` assertions come down to a singl ```swift XCTAssert(expression, format...) ``` -```objective-c + +```objc XCTAssert(expression, format...); ``` @@ -140,7 +143,8 @@ For `Bool` values, or simple boolean expressions, use `XCTAssertTrue` & `XCTAsse XCTAssertTrue(expression, format...) XCTAssertFalse(expression, format...) ``` -```objective-c + +```objc XCTAssertTrue(expression, format...); XCTAssertFalse(expression, format...); ``` @@ -155,7 +159,8 @@ When testing whether two values are equal, use `XCTAssert[Not]Equal` for scalar XCTAssertEqual(expression1, expression2, format...) XCTAssertNotEqual(expression1, expression2, format...) ``` -```objective-c + +```objc XCTAssertEqual(expression1, expression2, format...); XCTAssertNotEqual(expression1, expression2, format...); @@ -165,13 +170,14 @@ XCTAssertNotEqualObjects(expression1, expression2, format...); > `XCTAssert[Not]EqualObjects` is not necessary in Swift, since there is no distinction between scalars and objects. -When specifically testing whether two `Double`, `Float`, or other floating-point values are equal, use `XCTAssert[Not]EqualWithAccuracy`, to account for any issues with [floating point accuracy](http://en.wikipedia.org/wiki/Floating_point#Representable_numbers.2C_conversion_and_rounding): +When specifically testing whether two `Double`, `Float`, or other floating-point values are equal, use `XCTAssert[Not]EqualWithAccuracy`, to account for any issues with [floating point accuracy](https://en.wikipedia.org/wiki/Floating_point#Representable_numbers.2C_conversion_and_rounding): ```swift XCTAssertEqualWithAccuracy(expression1, expression2, accuracy, format...) XCTAssertNotEqualWithAccuracy(expression1, expression2, accuracy, format...) ``` -```objective-c + +```objc XCTAssertEqualWithAccuracy(expression1, expression2, accuracy, format...); XCTAssertNotEqualWithAccuracy(expression1, expression2, accuracy, format...); ``` @@ -186,7 +192,8 @@ Use `XCTAssert[Not]Nil` to assert the existence (or non-existence) of a given va XCTAssertNil(expression, format...) XCTAssertNotNil(expression, format...) ``` -```objective-c + +```objc XCTAssertNil(expression, format...); XCTAssertNotNil(expression, format...); ``` @@ -198,7 +205,8 @@ Finally, the `XCTFail` assertion will always fail: ```swift XCTFail(format...) ``` -```objective-c + +```objc XCTFail(format...); ``` @@ -206,7 +214,7 @@ XCTFail(format...); ### Performance Testing -New in Xcode 6 is the ability to [benchmark the performance of code](http://nshipster.com/benchmarking/): +New in Xcode 6 is the ability to [benchmark the performance of code](https://nshipster.com/benchmarking/): ```swift func testDateFormatterPerformance() { @@ -221,7 +229,8 @@ func testDateFormatterPerformance() { } } ``` -```objective-c + +```objc - (void)testDateFormatterPerformance { NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; dateFormatter.dateStyle = NSDateFormatterLongStyle; @@ -235,7 +244,7 @@ func testDateFormatterPerformance() { } ``` -The measured block is executed ten times and the test output shows the average execution time as well as individual run times and standard deviation: +The test output shows the average execution time for the measured block as well as individual run times and standard deviation: ``` Test Case '-[_Tests testDateFormatterPerformance]' started. @@ -243,7 +252,7 @@ Test Case '-[_Tests testDateFormatterPerformance]' started. Test Case '-[_Tests testDateFormatterPerformance]' passed (0.274 seconds). ``` -Performance tests help establish a per-device baseline of performance for hot code paths and will fail if execution time becomes significantly slower. Sprinkle them into your test cases to ensure that significant algorithms and procedures remain performant as time goes on. +Performance tests help establish a baseline of performance for hot code paths. Sprinkle them into your test cases to ensure that significant algorithms and procedures remain performant as time goes on. ## XCTestExpectation @@ -254,7 +263,8 @@ To make a test asynchronous, first create an expectation with `expectationWithDe ```swift let expectation = expectationWithDescription("...") ``` -```objective-c + +```objc XCTestExpectation *expectation = [self expectationWithDescription:@"..."]; ``` @@ -262,12 +272,13 @@ Then, at the bottom of the method, add the `waitForExpectationsWithTimeout` meth ```swift waitForExpectationsWithTimeout(10) { error in - // ... + <#...#> } ``` -```objective-c + +```objc [self waitForExpectationsWithTimeout:10 handler:^(NSError *error) { - // ... + <#...#> }]; ``` @@ -276,7 +287,8 @@ Now, the only remaining step is to `fulfill` that `expecation` in the relevant c ```swift expectation.fulfill() ``` -```objective-c + +```objc [expectation fulfill]; ``` @@ -286,14 +298,14 @@ Here's an example of how the response of an asynchronous networking request can ```swift func testAsynchronousURLConnection() { - let URL = NSURL(string: "http://nshipster.com/")! + let URL = NSURL(string: "https://nshipster.com/")! let expectation = expectationWithDescription("GET \(URL)") let session = NSURLSession.sharedSession() let task = session.dataTaskWithURL(URL) { data, response, error in XCTAssertNotNil(data, "data should not be nil") XCTAssertNil(error, "error should be nil") - + if let HTTPResponse = response as? NSHTTPURLResponse, responseURL = HTTPResponse.URL, MIMEType = HTTPResponse.MIMEType @@ -318,19 +330,20 @@ func testAsynchronousURLConnection() { } } ``` -```objective-c + +```objc - (void)testAsynchronousURLConnection { - NSURL *URL = [NSURL URLWithString:@"http://nshipster.com/"]; + NSURL *URL = [NSURL URLWithString:@"https://nshipster.com/"]; NSString *description = [NSString stringWithFormat:@"GET %@", URL]; XCTestExpectation *expectation = [self expectationWithDescription:description]; - + NSURLSession *session = [NSURLSession sharedSession]; NSURLSessionDataTask *task = [session dataTaskWithURL:URL completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { XCTAssertNotNil(data, "data should not be nil"); XCTAssertNil(error, "error should be nil"); - + if ([response isKindOfClass:[NSHTTPURLResponse class]]) { NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response; XCTAssertEqual(httpResponse.statusCode, 200, @"HTTP response status code should be 200"); @@ -339,15 +352,15 @@ func testAsynchronousURLConnection() { } else { XCTFail(@"Response was not NSHTTPURLResponse"); } - + [expectation fulfill]; }]; - + [task resume]; - + [self waitForExpectationsWithTimeout:task.originalRequest.timeoutInterval handler:^(NSError *error) { if (error != nil) { - NSLog(@"Error: %@", error.localizedDescription); + NSLog(@"Error: %@", error.localizedDescription); } [task cancel]; }]; @@ -356,11 +369,11 @@ func testAsynchronousURLConnection() { ## Mocking in Swift -With first-class support for asynchronous testing, Xcode 6 seems to have fulfilled all of the needs of a modern test-driven developer. Well, perhaps save for one: [mocking](http://en.wikipedia.org/wiki/Mock_object). +With first-class support for asynchronous testing, Xcode 6 seems to have fulfilled all of the needs of a modern test-driven developer. Well, perhaps save for one: [mocking](https://en.wikipedia.org/wiki/Mock_object). Mocking can be a useful technique for isolating and controlling behavior in systems that, for reasons of complexity, non-determinism, or performance constraints, do not usually lend themselves to testing. Examples include simulating specific networking interactions, intensive database queries, or inducing states that might emerge under a particular race condition. -There are a couple of [open source libraries](http://nshipster.com/unit-testing/#open-source-libraries) for creating mock objects and [stubbing](http://en.wikipedia.org/wiki/Test_stub) method calls, but these libraries largely rely on Objective-C runtime manipulation, something that is not currently possible with Swift. +There are a couple of [open source libraries](https://nshipster.com/unit-testing/#open-source-libraries) for creating mock objects and [stubbing](https://en.wikipedia.org/wiki/Test_stub) method calls, but these libraries largely rely on Objective-C runtime manipulation, something that is not currently possible with Swift. However, this may not actually be necessary in Swift, due to its less-constrained syntax. @@ -391,7 +404,7 @@ func testFetchRequestWithMockedManagedObjectContext() { } ``` -* * * +--- With Xcode 6, we've finally arrived: **the built-in testing tools are now good enough to use on their own**. That is to say, there are no particularly compelling reasons to use any additional abstractions in order to provide acceptable test coverage for the vast majority apps and libraries. Except in extreme cases that require extensive stubbing, mocking, or other exotic test constructs, XCTest assertions, expectations, and performance measurements should be sufficient. diff --git a/2014-08-04-alamofire.md b/2014-08-04-alamofire.md index 935c441c..9df08dda 100644 --- a/2014-08-04-alamofire.md +++ b/2014-08-04-alamofire.md @@ -1,15 +1,16 @@ --- title: Alamofire -author: Mattt Thompson +author: Mattt category: Open Source excerpt: "Although we still have a few months to wait before we can ship apps in Swift, there is already a proliferation of open source projects built with this new language. One such project is Alamofire." +retired: true status: - swift: 1.1 + swift: 1.1 --- Swift has hit a reset button on the iOS developer community. It's really been something to behold for seasoned Objective-C developers. -Literally overnight, conversations shifted from [namespacing](http://nshipster.com/namespacing/) and [swizzling](http://nshipster.com/method-swizzling/) to [generics](https://developer.apple.com/library/prerelease/ios/documentation/swift/conceptual/swift_programming_language/Generics.html#//apple_ref/doc/uid/TP40014097-CH26-XID_272) and [type inference](https://developer.apple.com/library/prerelease/ios/documentation/swift/conceptual/swift_programming_language/TypeCasting.html#//apple_ref/doc/uid/TP40014097-CH22-XID_487). At a time of the year when Twitter would normally be abuzz with discussion about the latest APIs or speculation about upcoming hardware announcements, the discourse has been dominated by all things Swift. +Literally overnight, conversations shifted from [namespacing](https://nshipster.com/namespacing/) and [swizzling](https://nshipster.com/method-swizzling/) to [generics](https://developer.apple.com/library/prerelease/ios/documentation/swift/conceptual/swift_programming_language/Generics.html#//apple_ref/doc/uid/TP40014097-CH26-XID_272) and [type inference](https://developer.apple.com/library/prerelease/ios/documentation/swift/conceptual/swift_programming_language/TypeCasting.html#//apple_ref/doc/uid/TP40014097-CH22-XID_487). At a time of the year when Twitter would normally be abuzz with discussion about the latest APIs or speculation about upcoming hardware announcements, the discourse has been dominated by all things Swift. A new language with new syntax and evolving conventions, Swift has captured the attention and imagination of iOS & OS X developers both old and new. @@ -19,7 +20,7 @@ One such project is [Alamofire](https://github.com/Alamofire/Alamofire). This we > Full Disclosure: this article, as with the rest of NSHipster, is written by the creator of AFNetworking and Alamofire. While this makes me qualified to write about the technical details and direction of these projects, it certainly doesn't allow for an objective take on their relative merits. So take all of this with a grain of salt. -* * * +--- [Alamofire](https://github.com/Alamofire/Alamofire) is an HTTP networking library written in Swift. It leverages NSURLSession and the Foundation URL Loading System to provide first-class networking capabilities in a convenient Swift interface. @@ -58,7 +59,7 @@ Alamofire.request(.GET, "http://httpbin.org/get") Even minor syntactic differences can have wide-reaching implications for language conventions. -Chief among the complaints lodged against Objective-C was its use of square brackets for denoting [message passing](http://en.wikipedia.org/wiki/Message_passing). One of the practical implications of `[ ]` syntax is the difficulty in chaining methods together. Even with Xcode autocomplete, `@property` dot syntax, and [key-value coding key-paths](http://nshipster.com/kvc-collection-operators/), it is still rare to see deeply nested invocations. +Chief among the complaints lodged against Objective-C was its use of square brackets for denoting [message passing](https://en.wikipedia.org/wiki/Message_passing). One of the practical implications of `[ ]` syntax is the difficulty in chaining methods together. Even with Xcode autocomplete, `@property` dot syntax, and [key-value coding key-paths](https://nshipster.com/kvc-collection-operators/), it is still rare to see deeply nested invocations. > In many ways, the concession of introducing dot syntax for properties in Objective-C 2.0 only served to escalate tensions further, although conventions have started to solidify in recent years. @@ -88,7 +89,7 @@ Closures are deeply integrated into Swift, so much so that if a methods's last a The chained methods in the previous example show this in action. For typical usage, it's really convenient to omit the syntactic cruft. -### Optional Arguments & Flexible Method Signatures +### Optional Arguments & Flexible Method Signatures When communicating with web APIs, it's common to send parameters with URL queries or HTTP body data: @@ -125,7 +126,6 @@ The `encode` method on each `ParameterEncoding` case transforms a request and se Given a complex, nested set of parameters, encoding and sending as JSON is recommended: - > There are no standards defining the encoding of data structures into URL-encoded query parameters, meaning that parsing behavior can vary between web application implementations. Even worse, there are certain structures that cannot be unambiguously represented by a query string. This is why JSON (or XML or plist) encoding is recommended for anything more complex than key-value, if the web API supports it. ```swift @@ -188,12 +188,12 @@ lazy var defaultHeaders: [String: String] = { // Accept-Language HTTP Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4 let acceptLanguage: String = { - // ... + <#...#> }() // User-Agent Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.43 let userAgent: String = { - // ... + <#...#> }() return ["Accept-Encoding": acceptEncoding, @@ -219,12 +219,12 @@ override var data: NSData! { There's a lot baked into the < 1000 LOC comprising Alamofire. Any aspiring Swift developer or API author would be advised to [peruse through the source code](https://github.com/Alamofire/Alamofire) to get a better sense of what's going on. -* * * +--- > For anyone wondering where this leaves AFNetworking, don't worry: **AFNetworking is stable and reliable, and isn't going anywhere.** In fact, over the coming months, a great deal of work is going to be put into improving test coverage and documentation for AFNetworking 2 and its first-party extensions. > It's also important to note that AFNetworking can be easily used from Swift code, just like any other Objective-C code. Alamofire is a separate project investigating the implications of new language features and conventions on the problem of making HTTP requests. -Alamofire 1.0 is scheduled to coincide with the 1.0 release of Swift... whenever that is. Part of that milestone is [complete documentation](http://nshipster.com/swift-documentation/), and 100% Unit Test Coverage, making use of the new [Xcode 6 testing infrastructure](http://nshipster.com/xctestcase/) & [httpbin](http://httpbin.org) by [Kenneth Reitz](http://www.kennethreitz.org). +Alamofire 1.0 is scheduled to coincide with the 1.0 release of Swift... whenever that is. Part of that milestone is [complete documentation](https://nshipster.com/swift-documentation/), and 100% Unit Test Coverage, making use of the new [Xcode 6 testing infrastructure](https://nshipster.com/xctestcase/) & [httpbin](http://httpbin.org) by [Kenneth Reitz](http://www.kennethreitz.org). We're all doing our best to understand how to design, implement, and distribute code in Swift. Alamofire is just one of many exciting new libraries that will guide the development of the language and community in the coming months and years. For anyone interested in being part of this, I welcome your contributions. As the mantra goes: [pull requests welcome](https://github.com/Alamofire/Alamofire/compare/). diff --git a/2014-08-11-swift-operators.md b/2014-08-11-swift-operators.md index a38f9a10..01e11d9e 100644 --- a/2014-08-11-swift-operators.md +++ b/2014-08-11-swift-operators.md @@ -1,429 +1,559 @@ --- title: Swift Operators -author: Mattt Thompson +author: Mattt category: Swift tags: swift -excerpt: "Operators in Swift are among the most interesting and indeed controversial features of this new language." +excerpt: >- + Operators are what do the work of a program. + They are the very execution of an executable; + the teleological driver of every process. +revisions: + "2014-08-11": Original publication + "2018-10-03": Updated for Swift 4.2 status: - swift: 2.0 - reviewed: September 8, 2015 + swift: 4.2 + reviewed: October 3, 2018 --- -What would a program be without statements? A mish-mash of classes, namespaces, conditionals, loops, and namespaces signifying nothing. +What would a program be without operators? +A mishmash of classes, namespaces, conditionals, loops, and namespaces +signifying nothing. -Statements are what do the work of a program. They are the very execution of an executable. +Operators are what do the work of a program. +They are the very execution of an executable; +the teleological driver of every process. +Operators are a topic of great importance for developers +and the focus of this week's NSHipster article. -If we were to take apart a statement—say `1 + 2`—decomposing it into its constituent parts, we would find an operator and operands: +## Operator Precedence and Associativity -| 1 | + | 2 | -|:-------------------:|:-------------------:|:-------------------:| -| left operand | operator | right operand | +If we were to dissect an expression --- +say `1 + 2` --- +and decompose it into constituent parts, +we would find one operator and two operands: -Although expressions are flat, the compiler will construct a tree representation, or AST: +| 1 | + | 2 | +| :----------: | :------: | :-----------: | +| left operand | operator | right operand | -![1 + 2 AST]({{ site.asseturl }}/swift-operators-one-plus-two.svg) +Expressions are expressed in a single, flat line of code, +from which the compiler constructs an +AST, +or abstract syntax tree: +{% asset swift-operators-one-plus-two.svg alt="1 + 2 AST" %} -Compound statements, like `1 + 2 + 3` +For compound expressions, +like `1 + 2 * 3` +or `5 - 2 + 3`, +the compiler uses rules for +operator precedence and associativity +to resolve the expression into a single value. -| (1 + 2) | + | 3 | -|:-------------------:|:-------------------:|:-------------------:| -| left operand | operator | right operand | +Operator precedence rules, +[similar to the ones you learned in primary school](https://en.wikipedia.org/wiki/Order_of_operations), +determine the order in which different kinds of operators are evaluated. +In this case, multiplication has a higher precedence than addition, +so `2 * 3` evaluates first. +| 1 | + | (2 \* 3) | +| :----------: | :------: | :-----------: | +| left operand | operator | right operand | -![1 + 2 + 3 AST]({{ site.asseturl }}/swift-operators-one-plus-two-plus-three.svg) +{% asset swift-operators-one-plus-two-times-three.svg alt="1 + 2 * 3 AST" %} -Or, to take an even more complex statement, `1 + 2 * 3 % 4`, the compiler would use operator precedence to resolve the expression into a single statement: +Associativity determines the order in which +operators with the same precedence are resolved. +If an operator is left-associative, +then the operand on the left-hand side is evaluated first: (`(5 - 2) + 3`); +if right-associative, +then the right-hand side operator is evaluated first: `5 - (2 + 3)`. +Arithmetic operators are left-associative, +so `5 - 2 + 3` evaluates to `6`. -| 1 | + | ((2 * 3) % 4) | -|:-------------------:|:-------------------:|:-------------------------:| -| left operand | operator | right operand | +| (5 - 2) | + | 3 | +| :----------: | :------: | :-----------: | +| left operand | operator | right operand | - -![1 + 2 * 3 % 4 AST]({{ site.asseturl }}/swift-operators-one-plus-two-times-three-mod-four.svg) - -Operator precedence rules, similar to the ones [you learned in primary school](http://en.wikipedia.org/wiki/Order_of_operations), provide a canonical ordering for any compound statement: - -``` -1 + 2 * 3 % 4 -1 + ((2 * 3) % 4) -1 + (6 % 4) -1 + 2 -``` - -However, consider the statement `5 - 2 + 3`. Addition and subtraction have the same operator precedence, but evaluating the subtraction first `(5 - 2) + 3` yields 6, whereas evaluating subtraction after addition, `5 - (2 + 3)`, yields `0`. In code, arithmetic operators are left-associative, meaning that the left hand side will evaluate first (`(5 - 2) + 3`). - -Operators can be unary and ternary as well. The `!` prefix operator negates a logical value of the operand. The `?:` ternary operator collapses an `if-else` expression, by evaluating the statement to the left of the `?` in order to either execute the statement left of the `:` (statement is `true`) or right of `:` (statement is `false`). +{% asset swift-operators-one-plus-two-plus-three.svg alt="5 - 2 + 3 AST" %} ## Swift Operators -Swift includes a set of operators that should be familiar to C or Objective-C developers, with a few additions (notably, the range and nil coalescing operators): - -### Prefix +The Swift Standard Library includes most of the operators +that a programmer might expect coming from another language in the C family, +as well as a few convenient additions like +the nil-coalescing operator (`??`) +and pattern match operator (`~=`), +as well as operators for +type checking (`is`), +type casting (`as`, `as?`, `as!`) +and forming open or closed ranges (`...`, `..<`). + +### Infix Operators + +Swift uses infix notation for binary operators +_(as opposed to, say [Reverse Polish Notation](https://en.wikipedia.org/wiki/Reverse_Polish_notation))_. +The Infix operators are grouped below +according to their associativity +and precedence level, in descending order: + +
+ +#### BitwiseShiftPrecedence + +{::nomarkdown} +
+
<<
+
Bitwise left shift
+
>>
+
Bitwise right shift
+
+{:/} + +#### MultiplicationPrecedence + +{::nomarkdown} +
+
*
+
Multiply
+
/
+
Divide
+
%
+
Remainder
+
&*
+
Multiply, ignoring overflow
+
&/
+
Divide, ignoring overflow
+
&%
+
Remainder, ignoring overflow
+
&
+
Bitwise AND
+
+{:/} + +#### AdditionPrecedence + +{::nomarkdown} +
+
+
+
Add
+
-
+
Subtract
+
&+
+
Add with overflow
+
&-
+
Subtract with overflow
+
|
+
Bitwise OR
+
^
+
Bitwise XOR
+
+{:/} + +#### RangeFormationPrecedence + +{::nomarkdown} +
+
..<
+
Half-open range
+
...
+
Closed range
+
+{:/} + +#### CastingPrecedence + +{::nomarkdown} +
+
is
+
Type check
+
as
+
Type cast
+
+{:/} + +#### NilCoalescingPrecedence + +{::nomarkdown} +
+
??
+
nil Coalescing
+
+{:/} + +#### ComparisonPrecedence + +{::nomarkdown} +
+
<
+
Less than
+
<=
+
Less than or equal
+
>
+
Greater than
+
>=
+
Greater than or equal
+
==
+
Equal
+
!=
+
Not equal
+
===
+
Identical
+
!==
+
Not identical
+
~=
+
Pattern match
+
+{:/} + +#### LogicalConjunctionPrecedence + +{::nomarkdown} +
+
&&
+
Logical AND
+
+{:/} + +#### LogicalDisjunctionPrecedence + +{::nomarkdown} +
+
||
+
Logical OR
+
+{:/} + +#### DefaultPrecedence + +_(None)_ + +#### AssignmentPrecedence + +{::nomarkdown} +
+
=
+
Assign
+
*=
+
Multiply and assign
+
/=
+
Divide and assign
+
%=
+
Remainder and assign
+
+=
+
Add and assign
+
-=
+
Subtract and assign
+
<<=
+
Left bit shift and assign
+
>>=
+
Right bit shift and assign
+
&=
+
Bitwise AND and assign
+
^=
+
Bitwise XOR and assign
+
|=
+
Bitwise OR and assign
+
+{:/} + +
+ +{% info %} + +Operator precedence groups were originally defined with numerical precedence. +For example, multiplicative operators defined a precedence value of 150, +so they were evaluated before additive operators, +which defined a precedence value of 140. + +In Swift 3, +operators changed to define precedence by partial ordering +to form a DAG +or [directed acyclic graph](https://en.wikipedia.org/wiki/Directed_acyclic_graph). +For detailed information about this change, +read Swift Evolution proposal +[SE-0077 "Improved operator declarations"](https://github.com/apple/swift-evolution/blob/master/proposals/0077-operator-precedence.md). + +{% endinfo %} + +### Unary Operators + +In addition to binary operators that take two operands, +there are also unary operators, +which take a single operand. + +#### Prefix Operators + +Prefix operators come before the expression they operate on. +Swift defines a handful of these by default: - `+`: Unary plus - `-`: Unary minus - `!`: Logical NOT - `~`: Bitwise NOT +- `...`: Open-ended partial range +- `..<`: Closed partial range -### Infix - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Exponentiative {precedence 160}
<<Bitwise left shift
>>Bitwise right shift
Multiplicative { associativity left precedence 150 }
*Multiply
/Divide
%Remainder
&*Multiply, ignoring overflow
&/Divide, ignoring overflow
&%Remainder, ignoring overflow
&Bitwise AND
Additive { associativity left precedence 140 }
+Add
-Subtract
&+Add with overflow
&-Subtract with overflow
|Bitwise OR
^Bitwise XOR
Range { precedence 135 }
..<Half-open range
...Closed range
Cast { precedence 132 }
isType check
asType cast
Comparative { precedence 130 }
<Less than
<=Less than or equal
>Greater than
>=Greater than or equal
==Equal
!=Not equal
===Identical
!==Not identical
~=Pattern match
Conjunctive { associativity left precedence 120 }
&&Logical AND
Disjunctive { associativity left precedence 110 }
||Logical OR
Nil Coalescing { associativity right precedence 110 }
??Nil coalescing
Ternary Conditional { associativity right precedence 100 }
?:Ternary conditional
Assignment { associativity right precedence 90 }
=Assign
*=Multiply and assign
/=Divide and assign
%=Remainder and assign
+=Add and assign
-=Subtract and assign
<<=Left bit shift and assign
>>=Right bit shift and assign
&=Bitwise AND and assign
^=Bitwise XOR and assign
|=Bitwise OR and assign
&&=Logical AND and assign
||=Logical OR and assign
- -### Member Functions - -In addition to the aforementioned standard operators, there are some _de facto_ operators defined by the language: - -- `.`: Member Access -- `?`: Optional -- `!`: Forced-Value -- `[]`: Subscript -- `[]=`: Subscript Assignment - -## Overloading - -Swift has the ability to overload operators such that existing operators, like `+`, can be made to work with additional types. - -To overload an operator, simply define a new function for the operator symbol, taking the appropriate number and type of arguments. - -For example, to overload `*` to repeat a string a specified number of times: +For example, +the `!` prefix operator +negates a logical value of its operand +and the `-` prefix operator +negates the numeric value of its operand. ```swift -func * (left: String, right: Int) -> String { - if right <= 0 { - return "" - } - - var result = left - for _ in 1.. [Double] { - var sum = [Double](count: left.count, repeatedValue: 0.0) - for (i, _) in enumerate(left) { - sum[i] = left[i] + right[i] - } - - return sum -} +true ? "Yes" : "No" // "Yes" ``` -The result is now an array with the pairwise sums of each element, expressed as `Double`: +In Swift, +`TernaryPrecedence` is defined lower than `DefaultPrecedence` +and higher than `AssignmentPrecedence`. +But, in general, it's better to keep ternary operator usage simple +(or avoid them altogether). -```swift -[1, 2] + [3, 4] // [4.0, 6.0] -``` +## Operator Overloading -And if the operator were also overloaded to work with `Int` types, with: +Once an operator is declared, +it can be associated with a type method or top-level function. +When an operator can resolve different functions +depending on the types of operands, +then we say that the operator is overloaded. -```swift -func +(left: [Int], right: [Int]) -> [Int] { - var sum = [Int](count: left.count, repeatedValue: 0) - for (i, _) in enumerate(left) { - sum[i] = left[i] + right[i] - } +The most prominent examples of overloading can be found with the `+` operator. +In many languages, `+` can be used to perform +arithmetic addition (`1 + 2 => 3`) +or concatenation for arrays and other collections (`[1] + [2] => [1, 2]` ). - return sum -} -``` +Developers have the ability to overload standard operators +by declaring a new function for the operator symbol with +the appropriate number and type of arguments. -The result would then be an array of pairwise sums, expressed as `Int`. +For example, to overload the `*` operator +to repeat a `String` a specified number of times, +you'd declare the following top-level function: ```swift -[1, 2] + [3, 4] // [4, 6] -``` +func * (lhs: String, rhs: Int) -> String { + guard rhs > 0 else { + return "" + } -Herein lies the original sin of operator overloading: **ambiguous semantics**. + return String(repeating: lhs, count: rhs) +} -Having been limited to basic arithmetic operators across many years and programming languages, overloading of operators has become commonplace: +"hello" * 3 // hellohellohello +``` -- Computing Sum of Integers: `1 + 2 // 3` -- Computing Sum of Floats: `1.0 + 2.0 // 3.0` -- Appending to String: `"a" + "b" // "ab"` -- Appending to Array: `["foo"] + ["bar"] // ["foo", "bar"]` +This kind of language use is, however, controversial. +(Any C++ developer would be all too eager to regale you with horror stories of the non-deterministic havoc this can wreak) -It makes sense that `+` would work on numbers—that's just math. But think about it: _why should adding two strings together concatenate them_? `1 + 2` isn't `12` ([except in Javascript](https://www.destroyallsoftware.com/talks/wat)). Is this really intuitive, or is it just familiar. +Consider the following statement: -> PHP uses `.` for string concatenation (which is objectively a terrible idea). Objective-C allows consecutive string literals to be appended with whitespace. +```swift +[1, 2] + [3, 4] // [1, 2, 3, 4] +``` -In the run-up to its initial stable release, Swift still has some work to do in resolving ambiguities in operator semantics. Recent changes, such as the addition of the `nil` coalescing operator (`??`), and the decision for optionals not to conform to `BooleanType` (confusing in the case of `Bool?`) are encouraging, and demonstrate the need for us to collectively ask ourselves _"does this really make sense?"_, and file radars appropriately. +By default, the `+` operator concatenates the elements of both arrays, +and is implemented using a generic function definition. -> I'm specifically concerned about the semantics of array operators, as demonstrated in the previous example. My 2 cents: arrays should forego the `+` and `-` operators in lieu of `<<`: +If you were to declare a specialized function +that overloads the `+` for arrays of `Double` values +to perform member-wise addition, +it would override the previous concatenating behavior: ```swift -func << (inout left: [T], right: [T]) -> [T] { - left.extend(right) - return left +// 👿 +func + (lhs: [Double], rhs: [Double]) -> [Double] { + return zip(lhs, rhs).map(+) } -func << (inout left: [T], right: T) -> [T] { - left.append(right) - return left -} +[1.0, 3.0, 5.0] + [2.0, 4.0, 6.0] // [3.0, 7.0, 11.0] ``` -## Custom Operators +Herein lies the original sin of operator overloading: +**ambiguous semantics**. -An even more controversial and exciting feature is the ability to define custom operators. +It makes sense that `+` would work on numbers --- that's maths. +But if you really think about it, +_why should adding two strings together concatenate them_? +`1 + 2` isn't `12` +([except in Javascript](https://www.destroyallsoftware.com/talks/wat)). +Is this really intuitive? ...or is it just _familiar_. -Consider the arithmetic operator found in many programming languages, but missing in Swift is `**`, which raises the left hand number to the power of the right hand number (the `^` symbol, commonly used for superscripts, is already used to perform a [bitwise XOR](http://en.wikipedia.org/wiki/Bitwise_operation#XOR)). +Something to keep in mind when deciding whether to overload an existing operator. -To add this operator in Swift, first declare the operator: +{% info %} -```swift -infix operator ** { associativity left precedence 160 } -``` +By comparison, +PHP uses `.` for string concatenation, +whereas SQL uses `||`; +Objective-C doesn't have an operator, per se, +but will append consecutive string literals with whitespace. -- `infix` specifies that it is a binary operator, taking a left and right hand argument -- `operator` is a reserved word that must be preceded with either `prefix`, `infix`, or `postfix` -- `**` is the operator itself -- `associativity left` means that operations are grouped from the left -- `precedence 160` means that it will evaluate with the same precedence as the exponential operators `<<` and `>>` (left and right bitshift). +{% endinfo %} -```swift -func ** (left: Double, right: Double) -> Double { - return pow(left, right) -} +## Defining Custom Operators -2 ** 3 -// 8 -``` +One of the most exciting features of Swift +(though also controversial) +is the ability to define custom operators. + +Consider the exponentiation operator, `**`, +found in many programming languages, +but missing from Swift. +It raises the left-hand number to the power of the right-hand number. +(The `^` symbol, commonly used for superscripts, +is already used by the +[bitwise XOR](https://en.wikipedia.org/wiki/Bitwise_operation#XOR) operator). -When creating custom operators, make sure to also create the corresponding assignment operator, if appropriate: +Exponentiation has a higher operator precedence than multiplication, +and since Swift doesn't have a built-in precedence group that we can use, +we first need to declare one ourselves: ```swift -infix operator **= { associativity right precedence 90 } -func **= (inout left: Double, right: Double) { - left = left ** right +precedencegroup ExponentiationPrecedence { + associativity: right + higherThan: MultiplicationPrecedence } ``` -> Note that `left` is `inout`, which makes sense, since assignment mutates the original value. +Now we can declare the operator itself: -### Custom Operators with Protocol and Method - -Function definitions for the operators themselves should be extremely simple—a single LOC, really. For anything more complex, some additional setup is warranted. +```swift +infix operator ** : ExponentiationPrecedence +``` -Take, for example, a custom operator, `=~`, which returns whether the left hand side matches a regular expression on the right hand side: +Finally, +we implement a top-level function using our new operator: ```swift -protocol RegularExpressionMatchable { - func match(pattern: String, options: NSRegularExpressionOptions) throws -> Bool -} +import Darwin -extension String: RegularExpressionMatchable { - func match(pattern: String, options: NSRegularExpressionOptions = []) throws -> Bool { - let regex = try NSRegularExpression(pattern: pattern, options: options) - return regex.numberOfMatchesInString(self, options: [], range: NSRange(location: 0, length: 0.distanceTo(utf16.count))) != 0 - } +func ** (lhs: Double, rhs: Double) -> Double { + return pow(lhs, rhs) } -infix operator =~ { associativity left precedence 130 } -func =~ (left: T, right: String) -> Bool { - return try! left.match(right, options: []) -} +2 ** 3 // 8 ``` -- First, a `RegularExpressionMatchable` `protocol` is declared, with a single method for matching regular expressions. -- Next, an `extension` adding conformance to this `protocol` to `String` is declared, with a provided implementation of `match`, using `NSRegularExpression`. -- Finally, the `=~` operator is declared and implemented on a generic type conforming to `RegularExpressionMatchable`. +{% info %} + +We need to import the Darwin module +to access the standard math function, `pow(_:_:)`. +(Alternatively, we could import Foundation instead to the same effect.) -By doing this, a user has the option to use the `match` function instead of the operator. It also has the added benefit of greater flexibility in what options are passed into the method. +{% endinfo %} + +When you create a custom operator, +consider providing a mutating variant as well: ```swift -let cocoaClassPattern = "^[A-Z]{2,}[A-Za-z0-9]+$" +infix operator **= : AssignmentPrecedence +func **= (lhs: inout Double, rhs: Double) { + lhs = pow(lhs, rhs) +} -try? "NSHipster".match(cocoaClassPattern) // true -"NSHipster" =~ cocoaClassPattern // true +var n: Double = 10 +n **= 1 + 2 // n = 1000 ``` -This is all to say: **a custom operator should only ever be provided as a convenience for an existing function.** - ### Use of Mathematical Symbols -Custom operators can contain any of the following ASCII characters /, =, -, +, !, *, %, <, >, &, |, ^, or ~, or any of the Unicode characters in the "Math Symbols" character set. +A custom operator can use combinations of the characters +`/`, `=`, `-`, `+`, `!`, `*`, `%`, `<`, `>`, `&`, `|`, `^`, or `~`, +and any characters found in the +[Mathematical Operators](https://en.wikipedia.org/wiki/Mathematical_Operators) +Unicode block, among others. -This makes it possible to take the square root of a number with a single `√` prefix operator (`⌥v`): +This makes it possible to take the square root of a number +with a single `√` prefix operator: ```swift -prefix operator √ {} -prefix func √ (number: Double) -> Double { - return sqrt(number) +import Darwin + +prefix operator √ +prefix func √ (_ value: Double) -> Double { + return sqrt(value) } -√4 -// 2 +√4 // 2 ``` -Or consider the `±` operator, which can be used either as an `infix` or `prefix` to return a tuple with the sum and difference: +Or consider the `±` operator, +which can be used either as an infix or prefix operator +to return a tuple with the sum and difference: ```swift -infix operator ± { associativity left precedence 140 } -func ± (left: Double, right: Double) -> (Double, Double) { - return (left + right, left - right) +infix operator ± : AdditionPrecedence +func ± (lhs: T, rhs: T) -> (T, T) { + return (lhs + rhs, lhs - rhs) } -prefix operator ± {} -prefix func ± (value: Double) -> (Double, Double) { +prefix operator ± +prefix func ± (_ value: T) -> (T, T) { return 0 ± value } -2 ± 3 -// (5, -1) +2 ± 3 // (5, -1) -±4 -// (4, -4) +±4 // (4, -4) ``` -> For more examples of functions using mathematical notation in Swift, check out [Euler](https://github.com/mattt/Euler). - -Custom operators are hard to type, and therefore hard to use. **Exercise restraint when using custom operators with exotic symbols**. After all, code should not be copy-pasted. - -* * * +{% info %} -Operators in Swift are among the most interesting and indeed controversial features of this new language. +For more examples of functions using mathematical notation in Swift, +check out [Euler](https://github.com/mattt/Euler). -When overriding or defining new operators in your own code, make sure to follow these guidelines: +{% endinfo %} -## Guidelines for Swift Operators +Custom operators are hard to type, and therefore hard to use, +so exercise restraint with exotic symbols. +Code should be typed, not be copy-pasted. -1. Don't create an operator unless its meaning is obvious and undisputed. Seek out any potential conflicts to ensure semantic consistency. -2. Custom operators should only be provided as a convenience. Complex functionality should always be implemented in a function, preferably one specified as a generic using a custom protocol. -3. Pay attention to the precedence and associativity of custom operators. Find the closest existing class of operators and use the appropriate precedence value. -4. If it makes sense, be sure to implement assignment shorthand for a custom operator (e.g. `+=` for `+`). +When overriding or defining new operators in your own code, +make sure to follow these guidelines: +1. Don't create an operator unless its meaning is obvious and undisputed. + Seek out any potential conflicts to ensure semantic consistency. +2. Pay attention to the precedence and associativity of custom operators, + and only define new operator groups as necessary. +3. If it makes sense, consider implementing assigning variants + for your custom operator (e.g. `+=` for `+`). diff --git a/2014-08-18-swift-literal-convertible.md b/2014-08-18-swift-literal-convertible.md deleted file mode 100644 index 2c97e1a0..00000000 --- a/2014-08-18-swift-literal-convertible.md +++ /dev/null @@ -1,272 +0,0 @@ ---- -title: Swift Literal Convertibles -author: Mattt Thompson -category: Swift -tags: swift -excerpt: "Last week, we wrote about overloading and creating custom operators in Swift, a language feature that is as powerful as it is controversial. By all accounts, this week's issue threatens to be equally polarizing, as it covers a feature of Swift that is pervasive, yet invisible: literal convertibles." -status: - swift: 1.2 ---- - -Last week, we wrote about [overloading and creating custom operators](http://nshipster.com/swift-operators/) in Swift, a language feature that is as powerful as it is controversial. - -By all accounts, this week's issue threatens to be equally polarizing, as it covers a feature of Swift that is pervasive, yet invisible: literal convertibles. - -* * * - -In code, a _literal_ is notation representing a fixed value. Most languages define literals for logical values, numbers, strings, and often arrays and dictionaries. - -```swift -let int = 57 -let float = 6.02 -let string = "Hello" -``` - -Literals are so ingrained in a developer's mental model of programming that most of us don't actively consider what the compiler is actually doing (thereby remaining blissfully unaware of neat tricks like [string interning](http://en.wikipedia.org/wiki/String_interning)). - -Having a shorthand for these essential building blocks makes code easier to both read and write. - -In Swift, developers are provided a hook into how values are constructed from literals, called _literal convertible protocols_. - -The standard library defines 10 such protocols: - -- `ArrayLiteralConvertible` -- `BooleanLiteralConvertible` -- `DictionaryLiteralConvertible` -- `ExtendedGraphemeClusterLiteralConvertible` -- `FloatLiteralConvertible` -- `NilLiteralConvertible` -- `IntegerLiteralConvertible` -- `StringLiteralConvertible` -- `StringInterpolationConvertible` -- `UnicodeScalarLiteralConvertible` - -Any `class` or `struct` conforming to one of these protocols will be eligible to have an instance of itself statically initialized from the corresponding literal. - -It's what allows literal values to "just work" across the language. - -Take optionals, for example. - -## NilLiteralConvertible and Optionals - -One of the best parts of optionals in Swift is that the underlying mechanism is actually defined in the language itself: - -```swift -enum Optional : Reflectable, NilLiteralConvertible { - case None - case Some(T) - init() - init(_ some: T) - init(nilLiteral: ()) - - func map(f: (T) -> U) -> U? - func getMirror() -> MirrorType -} -``` - -Notice that `Optional` conforms to the `NilLiteralConvertible` protocol: - -```swift -protocol NilLiteralConvertible { - init(nilLiteral: ()) -} -``` - -Now consider the two statements: - -```swift -var a: AnyObject = nil // ! -var b: AnyObject? = nil -``` - -The declaration of `var a` generates the compiler warning `Type 'AnyObject' does not conform to the protocol 'NilLiteralConvertible`, while the declaration `var b` works as expected. - -Under the hood, when a literal value is assigned, the Swift compiler consults the corresponding `protocol` (in this case `NilLiteralConvertible`), and calls the associated initializer (`init(nilLiteral: ())`). - -Although the implementation of `init(nilLiteral: ())` is private, the end result is that an `Optional` set to `nil` becomes `.None`. - -## StringLiteralConvertible and Regular Expressions - -Swift literal convertibles can be used to provide convenient shorthand initializers for custom objects. - -Recall our [`Regex`](http://nshipster.com/swift-operators/) example from last week: - -```swift -struct Regex { - let pattern: String - let options: NSRegularExpressionOptions! - - private var matcher: NSRegularExpression { - return NSRegularExpression(pattern: self.pattern, options: self.options, error: nil) - } - - init(pattern: String, options: NSRegularExpressionOptions = nil) { - self.pattern = pattern - self.options = options - } - - func match(string: String, options: NSMatchingOptions = nil) -> Bool { - return self.matcher.numberOfMatchesInString(string, options: options, range: NSMakeRange(0, string.utf16Count)) != 0 - } -} -``` - -Developers coming from a Ruby or Perl background may be disappointed by Swift's lack of support for regular expression literals, but this can be retcon'd in using the `StringLiteralConvertible` protocol: - -```swift -extension Regex: StringLiteralConvertible { - typealias ExtendedGraphemeClusterLiteralType = StringLiteralType - - init(unicodeScalarLiteral value: UnicodeScalarLiteralType) { - self.pattern = "\(value)" - } - - init(extendedGraphemeClusterLiteral value: ExtendedGraphemeClusterLiteralType) { - self.pattern = value - } - - init(stringLiteral value: StringLiteralType) { - self.pattern = value - } -} -``` - -> `StringLiteralConvertible` itself inherits from the `ExtendedGraphemeClusterLiteralConvertible` protocol, which in turn inherits from `UnicodeScalarLiteralConvertible`. `ExtendedGraphemeClusterLiteralType` is an internal type representing a `String` of length 1, while `UnicodeScalarLiteralType` is an internal type representing a `Character`. In order to implement the required `init`s, `ExtendedGraphemeClusterLiteralType` and `UnicodeScalarLiteralType` can be `typealias`'d to `StringLiteralType` and `Character`, respectively. - -Now, we can do this: - -```swift -let string: String = "foo bar baz" -let regex: Regex = "foo" - -regex.match(string) // true -``` - -...or more simply: - -```swift -"foo".match(string) // true -``` - -Combined with the [custom operator `=~`](http://nshipster.com/swift-operators), this can be made even more idiomatic: - -```swift -"foo bar baz" =~ "foo" // true -``` - ---- - -Some might bemoan this as the end of comprehensibility, while others will see this merely as filling in one of the missing parts of this new language. - -It's all just a matter of what you're used to, and whether you think a developer is entitled to add features to a language in order for it to better suit their purposes. - -> Either way, I hope we can all agree that this language feature is _interesting_, and worthy of further investigation. So in that spirit, let's venture forth and illustrate a few more use cases. - ---- - -## ArrayLiteralConvertible and Sets - -For a language with such a deep regard for immutability and safety, it's somewhat odd that there is no built-in support for sets in the standard library. - -Arrays are nice and all, but the `O(1)` lookup and idempotence of sets... _\*whistful sigh\*_ - -So here's a simple example of how `Set` might be implemented in Swift, using the built-in `Dictionary` type: - -```swift -struct Set { - typealias Index = T - private var dictionary: [T: Bool] = [:] - - var count: Int { - return self.dictionary.count - } - - var isEmpty: Bool { - return self.dictionary.isEmpty - } - - func contains(element: T) -> Bool { - return self.dictionary[element] ?? false - } - - mutating func put(element: T) { - self.dictionary[element] = true - } - - mutating func remove(element: T) -> Bool { - if self.contains(element) { - self.dictionary.removeValueForKey(element) - return true - } else { - return false - } - } -} -``` - -> A real, standard library-calibre implementation of `Set` would involve a _lot_ more Swift-isms, like generators, sequences, and all manner of miscellaneous protocols. It's enough to write an entirely separate article about. - -Of course, a standard collection class is only as useful as it is convenient to use. `NSSet` wasn't so lucky to receive the first-class treatment when array and dictionary literal syntax was introduced with the [Apple LLVM Compiler 4.0](http://clang.llvm.org/docs/ObjectiveCLiterals.html), but we can right the wrongs of the past with the `ArrayLiteralConvertible` protocol: - -```swift -protocol ArrayLiteralConvertible { - typealias Element - init(arrayLiteral elements: Element...) -} -``` - -Extending `Set` to adopt this protocol is relatively straightforward: - -```swift -extension Set: ArrayLiteralConvertible { - public init(arrayLiteral elements: T...) { - for element in elements { - put(element) - } - } -} -``` - -But that's all it takes to achieve our desired results: - -```swift -let set: Set = [1,2,3] -set.contains(1) // true -set.count // 3 -``` - -> This example does, however, highlight a legitimate concern for literal convertibles: **type inference ambiguity**. Because of the significant API overlap between collection classes like `Array` and `Set`, one could ostensibly write code that would behave differently depending on how the type was resolved (e.g. set addition is idempotent, whereas arrays accumulate, so the count after adding two equivalent elements would differ) - -## StringLiteralConvertible and URLs - -Alright, one last example creative use of literal convertibles: URL literals. - -`NSURL` is the fiat currency of the URL Loading System, with the nice feature of introspection of its component parts according to [RFC 2396](https://www.ietf.org/rfc/rfc2396.txt). Unfortunately, it's so inconvenient to instantiate, that third-party framework authors often decide to ditch them in favor of worse-but-more-convenient strings for method parameters. - -With a simple extension on `NSURL`, one can get the best of both worlds: - -```swift -extension NSURL: StringLiteralConvertible { - public class func convertFromExtendedGraphemeClusterLiteral(value: String) -> Self { - return self(string: value) - } - - public class func convertFromStringLiteral(value: String) -> Self { - return self(string: value) - } -} -``` - -One neat feature of literal convertibles is that the type inference works even without a variable declaration: - -```swift -"http://nshipster.com/".host // nshipster.com -``` - -* * * - -As a community, it's up to us to decide what capabilities of Swift are features and what are bugs. We'll be the ones to distinguish pattern from anti-pattern; convention from red flag. - -So it's unclear, at the present moment, how things like literal convertibles, custom operators, and all of the other capabilities of Swift will be reconciled. This publication has, at times, been more or less prescriptive on how things should be, but in this case, that's not the case here. - -All there is to be done is to experiment and learn. diff --git a/2014-08-18-swift-literals.md b/2014-08-18-swift-literals.md new file mode 100644 index 00000000..d862425d --- /dev/null +++ b/2014-08-18-swift-literals.md @@ -0,0 +1,412 @@ +--- +title: Swift Literals +author: Mattt +category: Swift +tags: swift +excerpt: >- + Literals are representations of values in source code. + The different kinds of literals that Swift provides — + and how it makes them available — + has a profound impact on how we write and think about code. +revisions: + "2014-08-18": Original publication + "2018-08-22": Updated for Swift 4.2 +status: + swift: 4.2 +--- + +In 1911, +linguist [Franz Boas](https://en.wikipedia.org/wiki/Franz_Boas) +observed that speakers of +[Eskimo–Aleut languages](https://en.wikipedia.org/wiki/Eskimo–Aleut_languages) +used different words to distinguish falling snowflakes from snow on the ground. +By comparison, English speakers typically refer to both as "snow," +but create a similar distinction between raindrops and puddles. + +Over time, +this simple empirical observation +has warped into an awful cliché that +"Eskimos _\[sic\]_ have 50 different words for snow" --- +which is unfortunate, +because Boas' original observation was empirical, +and the resulting weak claim of linguistic relativity is uncontroversial: +languages divide semantic concepts into separate words +in ways that may (and often do) differ from one another. +Whether that's more an accident of history +or reflective of some deeper truth about a culture is unclear, +and subject for further debate. + +It's in this framing that you're invited to consider +how the different kinds of literals in Swift +shape the way we reason about code. + +## Standard Literals + +A literal is a representation of a value in source code, +such as a number or a string. + +Swift provides the following kinds of literals: + +| Name | Default Inferred Type | Examples | +| ------------------------- | --------------------- | --------------------------------- | +| Integer | `Int` | `123`, `0b1010`, `0o644`, `0xFF`, | +| Floating-Point | `Double` | `3.14`, `6.02e23`, `0xAp-2` | +| String | `String` | `"Hello"`, `""" . . . """` | +| Extended Grapheme Cluster | `Character` | `"A"`, `"é"`, `"🇺🇸"` | +| Unicode Scalar | `Unicode.Scalar` | `"A"`, `"´"`, `"\u{1F1FA}"` | +| Boolean | `Bool` | `true`, `false` | +| Nil | `Optional` | `nil` | +| Array | `Array` | `[1, 2, 3]` | +| Dictionary | `Dictionary` | `["a": 1, "b": 2]` | + +The most important thing to understand about literals in Swift +is that they specify a value, but not a definite type. + +When the compiler encounters a literal, +it attempts to infer the type automatically. +It does this by looking for each type +that could be initialized by that kind of literal, +and narrowing it down based on any other constraints. + +If no type can be inferred, +Swift initializes the default type for that kind of literal --- +`Int` for an integer literal, +`String` for a string literal, +and so on. + +```swift +57 // Integer literal +"Hello" // String literal +``` + +In the case of `nil` literals, +the type can never be inferred automatically +and therefore must be declared. + +```swift +nil // ! cannot infer type +nil as String? // Optional.none +``` + +For array and dictionary literals, +the associated types for the collection +are inferred based on its contents. +However, inferring types for large or nested collections +is a complex operation and +may significantly increase the amount of time it takes to compile your code. +You can keep things snappy by adding an explicit type in your declaration. + +```swift +// Explicit type in the declaration +// prevents expensive type inference during compilation +let dictionary: [String: [Int]] = [ + "a": [1, 2], + "b": [3, 4], + "c": [5, 6], + <#...#> +] +``` + +## Playground Literals + +In addition to the standard literals listed above, +there are a few additional literal types for code in Playgrounds: + +| Name | Default Inferred Type | Examples | +| ----- | --------------------- | ---------------------------------------------------- | +| Color | `NSColor` / `UIColor` | `#colorLiteral(red: 1, green: 0, blue: 1, alpha: 1)` | +| Image | `NSImage` / `UIImage` | `#imageLiteral(resourceName: "icon")` | +| File | `URL` | `#fileLiteral(resourceName: "articles.json")` | + +In Xcode or Swift Playgrounds on the iPad, +these octothorpe-prefixed literal expressions +are automatically replaced by an interactive control +that provides a visual representation of the referenced color, image, or file. + +```swift +// Code +#colorLiteral(red: 0.7477839589, green: 0.5598286986, blue: 0.4095913172, alpha: 1) + +// Rendering +🏽 +``` + +{% asset color-literal-picker.png alt="Swift Playgrounds Color Literal Picker"%} + +This control also makes it easy for new values to be chosen: +instead of entering RGBA values or file paths, +you're presented with a color picker or file selector. + +--- + +Most programming languages have literals for +Boolean values, numbers, and strings, +and many have literals for arrays, dictionaries, and regular expressions. + +Literals are so ingrained in a developer's mental model of programming +that most of us don't actively consider what the compiler is actually doing. + +Having a shorthand for these essential building blocks +makes code easier to both read and write. + +## How Literals Work + +Literals are like words: +their meaning can change depending on the surrounding context. + +```swift +["h", "e", "l", "l", "o"] // Array +["h" as Character, "e", "l", "l", "o"] // Array +["h", "e", "l", "l", "o"] as Set +``` + +In the example above, +we see that an array literal containing string literals +is initialized to an array of strings by default. +However, if we explicitly cast the first array element as `Character`, +the literal is initialized as an array of characters. +Alternatively, we could cast the entire expression as `Set` +to initialize a set of characters. + +_How does this work?_ + +In Swift, +the compiler decides how to initialize literals +by looking at all the visible types that implement the corresponding +literal expression protocol. + +| Literal | Protocol | +| ------------------------- | --------------------------------------------- | +| Integer | `ExpressibleByIntegerLiteral` | +| Floating-Point | `ExpressibleByFloatLiteral` | +| String | `ExpressibleByStringLiteral` | +| Extended Grapheme Cluster | `ExpressibleByExtendedGraphemeClusterLiteral` | +| Unicode Scalar | `ExpressibleByUnicodeScalarLiteral` | +| Boolean | `ExpressibleByBooleanLiteral` | +| Nil | `ExpressibleByNilLiteral` | +| Array | `ExpressibleByArrayLiteral` | +| Dictionary | `ExpressibleByDictionaryLiteral` | + +To conform to a protocol, +a type must implement its required initializer. +For example, +the `ExpressibleByIntegerLiteral` protocol +requires `init(integerLiteral:)`. + +What's really great about this approach +is that it lets you add literal initialization +for your own custom types. + +## Supporting Literal Initialization for Custom Types + +Supporting initialization by literals when appropriate +can significantly improve the ergonomics of custom types, +making them feel like they're built-in. + +For example, +if you wanted to support +[fuzzy logic](https://en.wikipedia.org/wiki/Fuzzy_logic), +in addition to standard Boolean fare, +you might implement a `Fuzzy` type like the following: + +```swift +struct Fuzzy: Equatable { + var value: Double + + init(_ value: Double) { + precondition(value >= 0.0 && value <= 1.0) + self.value = value + } +} +``` + +A `Fuzzy` value represents a truth value that ranges between +completely true and completely false +over the numeric range 0 to 1 (inclusive). +That is, a value of 1 means completely true, +0.8 means mostly true, +and 0.1 means mostly false. + +In order to work more conveniently with standard Boolean logic, +we can extend `Fuzzy` to adopt the `ExpressibleByBooleanLiteral` protocol. + +```swift +extension Fuzzy: ExpressibleByBooleanLiteral { + init(booleanLiteral value: Bool) { + self.init(value ? 1.0 : 0.0) + } +} +``` + +> In practice, +> there aren't many situations in which it'd be appropriate +> for a type to be initialized using Boolean literals. +> Support for string, integer, and floating-point literals are much more common. + +Doing so doesn't change the default meaning of `true` or `false`. +We don't have to worry about existing code breaking +just because we introduced the concept of half-truths to our code base +("_view did appear animated... maybe?_"). +The only situations in which `true` or `false` initialize a `Fuzzy` value +would be when the compiler could infer the type to be `Fuzzy`: + +```swift +true is Bool // true +true is Fuzzy // false + +(true as Fuzzy) is Fuzzy // true +(false as Fuzzy).value // 0.0 +``` + +Because `Fuzzy` is initialized with a single `Double` value, +it's reasonable to allow values to be initialized with +floating-point literals as well. +It's hard to think of any situations in which +a type would support floating-point literals but not integer literals, +so we should do that too +(however, the converse isn't true; +there are plenty of types that work with integer but not floating point numbers). + +```swift +extension Fuzzy: ExpressibleByIntegerLiteral { + init(integerLiteral value: Int) { + self.init(Double(value)) + } +} + +extension Fuzzy: ExpressibleByFloatLiteral { + init(floatLiteral value: Double) { + self.init(value) + } +} +``` + +With these protocols adopted, +the `Fuzzy` type now looks and feels like +a _bona fide_ member of Swift standard library. + +```swift +let completelyTrue: Fuzzy = true +let mostlyTrue: Fuzzy = 0.8 +let mostlyFalse: Fuzzy = 0.1 +``` + +(Now the only thing left to do is implement the standard logical operators!) + +If convenience and developer productivity is something you want to optimize for, +you should consider implementing whichever literal protocols +are appropriate for your custom types. + +## Future Developments + +Literals are an active topic of discussion +for the future of the language. +Looking forward to Swift 5, +there are a number of current proposals +that could have terrific implications for how we write code. + +### Raw String Literals + +At the time of writing, +[Swift Evolution proposal 0200](https://github.com/apple/swift-evolution/blob/master/proposals/0200-raw-string-escaping.md) +is in active review. +If it's accepted, +future versions of Swift will support "raw" strings, +or string literals that ignores escape sequences. + +From the proposal: + +> Our design adds customizable string delimiters. +> You may pad a string literal with one or more +> `#` (pound, Number Sign, U+0023) characters [...] +> The number of pound signs at the start of the string +> (in these examples, zero, one, and four) +> must match the number of pound signs at the end of the string. + +```swift +"This is a Swift string literal" + +#"This is also a Swift string literal"# + +####"So is this"#### +``` + +This proposal comes as a natural extension of the new multi-line string literals +added in Swift 4 +([SE-0165](https://github.com/apple/swift-evolution/blob/master/proposals/0168-multi-line-string-literals.md)), +and would make it even easier to do work with data formats like JSON and XML. + +If nothing else, +adoption of this proposal +could remove the largest obstacle to using Swift on Windows: +dealing with file paths like `C:\Windows\All Users\Application Data`. + +### Literal Initialization Via Coercion + +Another recent proposal, +[SE-0213: Literal initialization via coercion](https://github.com/apple/swift-evolution/blob/master/proposals/0213-literal-init-via-coercion.md) +is already implemented for Swift 5. + +From the proposal: + +> `T(literal)` should construct `T` +> using the appropriate literal protocol if possible. + +> Currently types conforming to literal protocols +> are type-checked using regular initializer rules, +> which means that for expressions like `UInt32(42)` +> the type-checker is going to look up a set of available initializer choices +> and attempt them one-by-one trying to deduce the best solution. + +In Swift 4.2, +initializing a `UInt64` with its maximum value +results in a compile-time overflow +because the compiler first tries to initialize an `Int` with the literal value. + +```swift +UInt64(0xffff_ffff_ffff_ffff) // overflows in Swift 4.2 +``` + +Starting in Swift 5, +not only will this expression compile successfully, +but it'll do so a little bit faster, too. + +--- + +The words available to a language speaker +influence not only what they say, +but how they think as well. +In the same way, +the individual parts of a programming language +hold considerable influence over how a developer works. + +The way Swift carves up the semantic space of values +makes it different from languages that don't, +for example, +distinguish between integers and floating points +or have separate concepts for strings, characters, and Unicode scalars. +So it's no coincidence that when we write Swift code, +we often think about numbers and strings at a lower level +than if we were hacking away in, say, JavaScript. + +Along the same lines, +Swift's current lack of distinction +between string literals and regular expressions +contributes to the relative lack of regex usage compared to other +scripting languages. + +That's not to say that having or lacking certain words +makes it impossible to express certain ideas --- +just a bit fuzzier. +We can understand "untranslatable" words like +["Saudade"](https://en.wikipedia.org/wiki/Saudade) in Portuguese, +["Han"](https://en.wikipedia.org/wiki/Han_%28cultural%29) in Korean, or +["Weltschmerz"](https://en.wikipedia.org/wiki/Weltschmerz) in German. + +We're all human. +We all understand pain. + +By allowing any type to support literal initialization, +Swift invites us to be part of the greater conversation. +Take advantage of this +and make your own code feel like a natural extension of the standard library. diff --git a/2014-08-24-wkwebkit.md b/2014-08-24-wkwebkit.md deleted file mode 100644 index fe35e820..00000000 --- a/2014-08-24-wkwebkit.md +++ /dev/null @@ -1,227 +0,0 @@ ---- -title: WKWebView -author: Mattt Thompson -category: Cocoa -excerpt: "iOS has a complicated relationship with the web. And it goes back to the very inception of the platform nearly a decade ago." -status: - swift: 2.0 - reviewed: September 8, 2015 ---- - -iOS has a complicated relationship with the web. And it goes back to the very inception of the platform nearly a decade ago. - -It's difficult to appreciate just how differently the first iPhone could have turned out. The iconic touchscreen device we know and love today was just one option on the table. Early prototypes explored the use of a physical keyboard and a touch screen + stylus combo, with screen dimensions going up to 5" x 7". Even the iPod click wheel was a serious contender for a time. - -But perhaps the most significant early decision to be made involved software, not hardware. - -How should the iPhone run software? Apps, like on OSX, or as web pages, using Safari? That choice to fork OS X and build iPhoneOS had widespread implications, and remains a contentious decision to this day. - -Consider this infamous line from Steve Jobs' WWDC 2007 keynote: - -> The full Safari engine is inside of iPhone. And so, you can write amazing Web 2.0 and Ajax apps that look exactly and behave exactly like apps on the iPhone. And these apps can integrate perfectly with iPhone services. They can make a call, they can send an email, they can look up a location on Google Maps. - -The web has always been a second-class citizen on iOS _(which is ironic, since the iPhone is largely responsible for the mobile web existing as it does today)_. `UIWebView` is massive and clunky and leaks memory. It lags behind Mobile Safari, which has the benefit of the Nitro JavaScript engine. - -However, all of this has changed with the introduction of `WKWebView` and the rest of the `WebKit` framework. - -* * * - -`WKWebView` is the centerpiece of the modern WebKit API introduced in iOS 8 & OS X Yosemite. It replaces `UIWebView` in UIKit and `WebView` in AppKit, offering a consistent API across the two platforms. - -Boasting responsive 60fps scrolling, built-in gestures, streamlined communication between app and webpage, and the same JavaScript engine as Safari, `WKWebView` is one of the most significant announcements to come out of WWDC 2014. - -What was a single class and protocol with `UIWebView` & `UIWebViewDelegate` has been factored out into 14 classes and 3 protocols in WKWebKit. Don't be alarmed by the huge jump in complexity, though—this new architecture allows for a ton of new features: - -## WKWebKit Framework - -### Classes - -> - `WKBackForwardList`: A list of webpages previously visited in a web view that can be reached by going back or forward. -> - `WKBackForwardListItem`: Represents a webpage in the back-forward list of a web view. -> - `WKFrameInfo`: Contains information about a frame on a webpage. -> - `WKNavigation`: Contains information for tracking the loading progress of a webpage. -> - `WKNavigationAction`: Contains information about an action that may cause a navigation, used for making policy decisions. -> - `WKNavigationResponse`: Contains information about a navigation response, used for making policy decisions. -> - `WKPreferences`: Encapsulates the preference settings for a web view. -> - `WKProcessPool`: Represents a pool of Web Content processes. -> - `WKUserContentController`: Provides a way for JavaScript to post messages and inject user scripts to a web view. -> - `WKScriptMessage`: Contains information about a message sent from a webpage. -> - `WKUserScript`: Represents a script that can be injected into a webpage. -> - `WKWebViewConfiguration`: A collection of properties with which to initialize a web view. -> - `WKWindowFeatures`: Specifies optional attributes for the containing window when a new web view is requested. - -### Protocols - -> - `WKNavigationDelegate`: Provides methods for tracking the progress of main frame navigations and for deciding load policy for main frame and subframe navigations. -> - `WKScriptMessageHandler`: Provides a method for receiving messages from JavaScript running in a webpage. -> - `WKUIDelegate`: Provides methods for presenting native user interface elements on behalf of a webpage. - -## API Diff Between `UIWebView` & `WKWebView` - -`WKWebView` inherits much of the same programming interface as `UIWebView`, making it convenient for apps to migrate to WKWebKit (and conditionally compile as necessary while iOS 8 gains widespread adoption). - -For anyone interested in the specifics, here's a comparison of the APIs of each class: - -| UIWebView | WKWebView | -|-----------|-----------| -| `var scrollView: UIScrollView { get }` | `var scrollView: UIScrollView { get }` | -| | `var configuration: WKWebViewConfiguration { get }` | -| `var delegate: UIWebViewDelegate?` | `var UIDelegate: WKUIDelegate?` | -| | `var navigationDelegate: WKNavigationDelegate?` | -| | `var backForwardList: WKBackForwardList { get }` | - -### Loading - -| UIWebView | WKWebView | -|-----------|-----------| -| `func loadRequest(request: NSURLRequest)` | `func loadRequest(request: NSURLRequest) -> WKNavigation?` | -| `func loadHTMLString(string: String, baseURL: NSURL?)` | `func loadHTMLString(string: String, baseURL: NSURL) -> WKNavigation?` | -| `func loadData(data: NSData, MIMEType: String, textEncodingName: String, baseURL: NSURL)` | | -| | `var estimatedProgress: Double { get }` | -| | `var hasOnlySecureContent: Bool { get }` | -| `func reload()` | `func reload() -> WKNavigation?` | -| | `func reloadFromOrigin() -> WKNavigation?` | -| `func stopLoading()` | `func stopLoading()` | -| `var request: NSURLRequest? { get }` | | -| | `var URL: NSURL? { get }` | -| | `var title: String? { get }` | - -### History - -| UIWebView | WKWebView | -|-----------|-----------| -| | `func goToBackForwardListItem(item: WKBackForwardListItem) -> WKNavigation?` | -| `func goBack()` | `func goBack() -> WKNavigation?` | -| `func goForward()` | `func goForward() -> WKNavigation?` | -| `var canGoBack: Bool { get }` | `var canGoBack: Bool { get }` | -| `var canGoForward: Bool { get }` | `var canGoForward: Bool { get }` | -| `var loading: Bool { get }` | `var loading: Bool { get }` | - -### Javascript Evaluation - -| UIWebView | WKWebView | -|-----------|-----------| -| `func stringByEvaluatingJavaScriptFromString(script: String) -> String` | | -| | `func evaluateJavaScript(javaScriptString: String, completionHandler: ((AnyObject?, NSError?) -> Void)?)` | - -### Miscellaneous - -| UIWebView | WKWebView | -|-----------|-----------| -| `var keyboardDisplayRequiresUserAction: Bool` | | -| `var scalesPageToFit: Bool` | | -| | `var allowsBackForwardNavigationGestures: Bool` | - -### Pagination - -`WKWebView` currently lacks equivalent APIs for paginating content. - -- `var paginationMode: UIWebPaginationMode` -- `var paginationBreakingMode: UIWebPaginationBreakingMode` -- `var pageLength: CGFloat` -- `var gapBetweenPages: CGFloat` -- `var pageCount: Int { get }` - -### Refactored into `WKWebViewConfiguration` - -The following properties on `UIWebView` have been factored into a separate configuration object, which is passed into the initializer for `WKWebView`: - -- `var allowsInlineMediaPlayback: Bool` -- `var mediaPlaybackRequiresUserAction: Bool` -- `var mediaPlaybackAllowsAirPlay: Bool` -- `var suppressesIncrementalRendering: Bool` - ---- - -## JavaScript ↔︎ Swift Communication - -One of the major improvements over `UIWebView` is how interaction and data can be passed back and forth between an app and its web content. - -### Injecting Behavior with User Scripts - -`WKUserScript` allows JavaScript behavior to be injected at the start or end of document load. This powerful feature allows for web content to be manipulated in a safe and consistent way across page requests. - -As a simple example, here's how a user script can be injected to change the background color of a web page: - -```swift -let source = "document.body.style.background = \"#777\";" -let userScript = WKUserScript(source: source, injectionTime: .AtDocumentEnd, forMainFrameOnly: true) - -let userContentController = WKUserContentController() -userContentController.addUserScript(userScript) - -let configuration = WKWebViewConfiguration() -configuration.userContentController = userContentController -self.webView = WKWebView(frame: self.view.bounds, configuration: configuration) -``` - -`WKUserScript` objects are initialized with JavaScript source, as well as whether it should be injected at the start or end of loading the page, and whether this behavior should be used for all frames, or just the main frame. The user script is then added to a `WKUserContentController`, which is set on the `WKWebViewConfiguration` object passed into the initializer for `WKWebView`. - -This example could easily be extended to perform more significant modifications to the page, such as removing advertisements, hiding comments, or maybe [changing all occurrences of the phrase "the cloud" to "my butt"](https://github.com/panicsteve/cloud-to-butt). - -### Message Handlers - -Communication from web to app has improved significantly as well, with message handlers. - -Just like `console.log` prints out information to the [Safari Web Inspector](https://developer.apple.com/library/safari/documentation/AppleApplications/Conceptual/Safari_Developer_Guide/Introduction/Introduction.html) debug console, information from a web page can be passed back to the app by invoking: - -```javascript -window.webkit.messageHandlers.{NAME}.postMessage() -``` - -> What's really great about this API is that JavaScript objects are _automatically serialized_ into native Objective-C or Swift objects. - -The name of the handler is configured in `addScriptMessageHandler()`, which registers a handler conforming to the `WKScriptMessageHandler` protocol: - -```swift -class NotificationScriptMessageHandler: NSObject, WKScriptMessageHandler { - func userContentController(userContentController: WKUserContentController, didReceiveScriptMessage message: WKScriptMessage!) { - print(message.body) - } -} - -let userContentController = WKUserContentController() -let handler = NotificationScriptMessageHandler() -userContentController.addScriptMessageHandler(handler, name: "notification") -``` - -Now, when a notification comes into the app, such as to notify the creation of a new object on the page, that information can be passed with: - -```javascript -window.webkit.messageHandlers.notification.postMessage({body: "..."}); -``` - -> Add User Scripts to create hooks for webpage events that use Message Handlers to communicate status back to the app. - -The same approach can be used to scrape information from the page for display or analysis within the app. - -For example, if someone were to build a browser specifically for NSHipster.com, it could have a button that listed related articles in a popover: - -```javascript -// document.location.href == "http://nshipster.com/webkit" -function getRelatedArticles() { - var related = []; - var elements = document.getElementById("related").getElementsByTagName("a"); - for (i = 0; i < elements.length; i++) { - var a = elements[i]; - related.push({href: a.href, title: a.title}); - } - - window.webkit.messageHandlers.related.postMessage({articles: related}); -} -``` - -```swift -let js = "getRelatedArticles();" -self.webView?.evaluateJavaScript(js) { (_, error) in - print(error) -} - -// Get results in previously-registered message handler -``` - -* * * - -If your app is little more than a thin container around web content, `WKWebView` is a game-changer. All of that performance and compatibility that you've longed for is finally available. It's everything you might have hoped for. - -If you're more of a native controls purist, you may be surprised at the power and flexibility afforded by the new technologies in iOS 8. It's a dirty secret that some stock apps like Messages use WebKit to render tricky content. The fact that you probably haven't noticed should be an indicator that web views actually have a place in app development best practices. diff --git a/2014-08-24-wkwebview.md b/2014-08-24-wkwebview.md new file mode 100644 index 00000000..2f3ea09e --- /dev/null +++ b/2014-08-24-wkwebview.md @@ -0,0 +1,386 @@ +--- +title: WKWebView +author: Mattt +category: Cocoa +excerpt: iOS has a complicated relationship with the web. + And it goes back to the very inception of the platform over a decade ago. +revisions: + "2014-08-24": Original publication + "2018-07-25": Updated for iOS 12 and macOS Mojave +status: + swift: 4.2 + reviewed: July 25, 2018 +--- + +iOS has a complicated relationship with the web. +And it goes back to the very inception of the platform over a decade ago. + +Although the design of the first iPhone seems like a foregone conclusion today, +the iconic touchscreen device we know and love today +was just one option on the table at the time. +Early prototypes explored the use of a physical keyboard +and a touchscreen + stylus combo, +with screen dimensions going up to 5×7". +Even the iPod click wheel was a serious contender for a time. + +But perhaps the most significant early decision to be made involved software, +not hardware. + +How should the iPhone run software? +Apps, like on macOS? +Or as web pages, using Safari? +That choice to fork macOS and build iPhoneOS had widespread implications +and remains a contentious decision to this day. + +Consider this infamous line from Steve Jobs' WWDC 2007 keynote: + +> The full Safari engine is inside of iPhone. +> And so, you can write amazing Web 2.0 and Ajax apps +> that look exactly and behave exactly like apps on the iPhone. +> And these apps can integrate perfectly with iPhone services. +> They can make a call, they can send an email, +> they can look up a location on Google Maps. + +The web had long been a second-class citizen on iOS, +which is ironic since the iPhone is largely responsible +for the mobile web as it exists today. +`UIWebView` was massive and clunky and leaked memory like a sieve. +It lagged behind Mobile Safari, +unable to take advantage of its faster JavaScript and rendering engines. + +However, all of this changed with the introduction of `WKWebView` +and the rest of the `WebKit` framework. + +--- + +`WKWebView` is the centerpiece of the modern WebKit API +introduced in iOS 8 & macOS Yosemite. +It replaces `UIWebView` in UIKit and `WebView` in AppKit, +offering a consistent API across the two platforms. + +Boasting responsive 60fps scrolling, +built-in gestures, +streamlined communication between app and webpage, +and the same JavaScript engine as Safari, +`WKWebView` was one of the most significant announcements at WWDC 2014. + +What was once a single class and protocol with `UIWebView` & `UIWebViewDelegate` +has been factored out into 14 classes and 3 protocols in the WebKit framework. +Don't be alarmed by the huge jump in complexity, though --- +this new architecture is much cleaner, +and allows for a ton of new features. + +## Migrating from UIWebView / WebView to WKWebView + +`WKWebView` has been the preferred API since iOS 8. +But if your app _still_ hasn't made the switch, +be advised that +**`UIWebView` and `WebView` are formally deprecated +in iOS 12 and macOS Mojave**, +and you should update to `WKWebView` as soon as possible. + +To help make that transition, +here's a comparison of the APIs of `UIWebView` and `WKWebView`: + +| UIWebView | WKWebView | +| -------------------------------------- | --------------------------------------------------- | +| `var scrollView: UIScrollView { get }` | `var scrollView: UIScrollView { get }` | +| | `var configuration: WKWebViewConfiguration { get }` | +| `var delegate: UIWebViewDelegate?` | `var UIDelegate: WKUIDelegate?` | +| | `var navigationDelegate: WKNavigationDelegate?` | +| | `var backForwardList: WKBackForwardList { get }` | + +### Loading + +| UIWebView | WKWebView | +| ------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------- | +| `func loadRequest(request: URLRequest)` | `func load(_ request: URLRequest) -> WKNavigation?` | +| `func loadHTMLString(string: String, baseURL: URL?)` | `func loadHTMLString(_: String, baseURL: URL?) -> WKNavigation?` | +| `func loadData(_ data: Data, mimeType: String, characterEncodingName: String, baseURL: URL) -> WKNavigation?` | | +| | `var estimatedProgress: Double { get }` | +| | `var hasOnlySecureContent: Bool { get }` | +| `func reload()` | `func reload() -> WKNavigation?` | +| | `func reloadFromOrigin(Any?) -> WKNavigation?` | +| `func stopLoading()` | `func stopLoading()` | +| `var request: URLRequest? { get }` | | +| | `var URL: URL? { get }` | +| | `var title: String? { get }` | + +### History + +| UIWebView | WKWebView | +| -------------------------------- | ---------------------------------------------------------------------------- | +| | `func goToBackForwardListItem(item: WKBackForwardListItem) -> WKNavigation?` | +| `func goBack()` | `func goBack() -> WKNavigation?` | +| `func goForward()` | `func goForward() -> WKNavigation?` | +| `var canGoBack: Bool { get }` | `var canGoBack: Bool { get }` | +| `var canGoForward: Bool { get }` | `var canGoForward: Bool { get }` | +| `var loading: Bool { get }` | `var loading: Bool { get }` | + +### Javascript Evaluation + +| UIWebView | WKWebView | +| ----------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | +| `func stringByEvaluatingJavaScriptFromString(script: String) -> String` | | +| | `func evaluateJavaScript(_ javaScriptString: String, completionHandler: ((AnyObject?, NSError?) -> Void)?)` | + +### Miscellaneous + +| UIWebView | WKWebView | +| --------------------------------------------- | ----------------------------------------------- | +| `var keyboardDisplayRequiresUserAction: Bool` | | +| `var scalesPageToFit: Bool` | | +| | `var allowsBackForwardNavigationGestures: Bool` | + +### Pagination + +`WKWebView` currently lacks equivalent APIs for paginating content. + +- `var paginationMode: UIWebPaginationMode` +- `var paginationBreakingMode: UIWebPaginationBreakingMode` +- `var pageLength: CGFloat` +- `var gapBetweenPages: CGFloat` +- `var pageCount: Int { get }` + +### Refactored into `WKWebViewConfiguration` + +The following properties on `UIWebView` +have been factored into a separate configuration object, +which is passed into the initializer for `WKWebView`: + +- `var allowsInlineMediaPlayback: Bool` +- `var allowsAirPlayForMediaPlayback: Bool` +- `var mediaTypesRequiringUserActionForPlayback: WKAudiovisualMediaTypes` +- `var suppressesIncrementalRendering: Bool` + +--- + +## JavaScript ↔︎ Swift Communication + +One of the major improvements over `UIWebView` +is how interaction and data can be passed back and forth +between an app and its web content. + +### Injecting Behavior with User Scripts + +`WKUserScript` allows JavaScript behavior to be injected +at the start or end of document load. +This powerful feature allows for web content to be manipulated +in a safe and consistent way across page requests. + +As a simple example, +here's how a user script can be injected +to change the background color of a web page: + +```swift +let source = """ + document.body.style.background = "#777"; +""" + +let userScript = WKUserScript(source: source, + injectionTime: .atDocumentEnd, + forMainFrameOnly: true) + +let userContentController = WKUserContentController() +userContentController.addUserScript(userScript) + +let configuration = WKWebViewConfiguration() +configuration.userContentController = userContentController +self.webView = WKWebView(frame: self.view.bounds, + configuration: configuration) +``` + +When you create a `WKUserScript` object, +you provide JavaScript code to execute, +specify whether it should be injected +at the start or end of loading the document, +and whether the behavior should be used for all frames or just the main frame. +The user script is then added to a `WKUserContentController`, +which is set on the `WKWebViewConfiguration` object +passed into the initializer for `WKWebView`. + +This example could easily be extended to perform more significant modifications, +such as [changing all occurrences of the phrase "the cloud" to "my butt"](https://github.com/panicsteve/cloud-to-butt). + +### Message Handlers + +Communication from web to app has improved significantly as well, +with the introduction of message handlers. + +Like how `console.log` prints out information to the +[Safari Web Inspector](https://developer.apple.com/safari/tools/), +information from a web page can be passed back to the app by invoking: + +```javascript +window.webkit.messageHandlers.<#name#>.postMessage() +``` + +> What's really great about this API is that JavaScript objects are +> _automatically serialized_ into native Objective-C or Swift objects. + +The name of the handler is configured in `add(_:name)`, +which registers a handler conforming to the `WKScriptMessageHandler` protocol: + +```swift +class NotificationScriptMessageHandler: NSObject, WKScriptMessageHandler { + func userContentController(_ userContentController: WKUserContentController, + didReceive message: WKScriptMessage) + { + print(message.body) + } +} + +let userContentController = WKUserContentController() +let handler = NotificationScriptMessageHandler() +userContentController.add(handler, name: "notification") +``` + +Now, when a notification comes into the app +(such as to notify the creation of a new object on the page) +that information can be passed with: + +```javascript +window.webkit.messageHandlers.notification.postMessage({ body: "..." }); +``` + +> Add User Scripts to create hooks for webpage events +> that use Message Handlers to communicate status back to the app. + +The same approach can be used to scrape information +from the page for display +or analysis within the app. + +For example, +if you wanted to build a browser specifically for NSHipster.com, +it could have a button that listed related articles in a popover: + +```javascript +// document.location.href == "https://nshipster.com/wkwebview" +const showRelatedArticles = () => { + let related = []; + const elements = document.querySelectorAll("#related a"); + for (const a of elements) { + related.push({ href: a.href, title: a.title }); + } + + window.webkit.messageHandlers.related.postMessage({ articles: related }); +}; +``` + +```swift +let js = "showRelatedArticles();" +self.webView?.evaluateJavaScript(js) { (_, error) in + print(error) +} + +// Get results in a previously-registered message handler +``` + +## Content Blocking Rules + +Though depending on your use case, +you may be able to skip the hassle of round-trip communication with JavaScript. + +As of iOS 11 and macOS High Sierra, +you can specify declarative content blocking rules for a `WKWebView`, +just like a +[Safari Content Blocker app extension](https://developer.apple.com/library/archive/documentation/Extensions/Conceptual/ContentBlockingRules/CreatingRules/CreatingRules.html). + +For example, +if you wanted to [Make Medium Readable Again](https://makemediumreadable.com) +in your web view, +you could define the following rules in JSON: + +```swift +let json = """ +[ + { + "trigger": { + "if-domain": "*.medium.com" + }, + "action": { + "type": "css-display-none", + "selector": ".overlay" + } + } +] +""" +``` + +Pass these rules to +`compileContentRuleList(forIdentifier:encodedContentRuleList:completionHandler:)` +and configure a web view with the resulting content rule list +in the completion handler: + +```swift +WKContentRuleListStore.default() + .compileContentRuleList(forIdentifier: "ContentBlockingRules", + encodedContentRuleList: json) +{ (contentRuleList, error) in + guard let contentRuleList = contentRuleList, + error == nil else { + return + } + + let configuration = WKWebViewConfiguration() + configuration.userContentController.add(contentRuleList) + + self.webView = WKWebView(frame: self.view.bounds, + configuration: configuration) +} +``` + +By declaring rules declaratively, +WebKit can compile these operations +into bytecode that can run much more efficiently +than if you injected JavaScript to do the same thing. + +In addition to hiding page elements, +you can use content blocking rules to +prevent page resources from loading (like images or scripts), +strip cookies from requests to the server, +and force a page to load securely over HTTPS. + +## Snapshots + +Starting in iOS 11 and macOS High Sierra, +the WebKit framework provides built-in APIs for taking screenshots of web pages. + +To take a picture of your web view's visible viewport +after everything is finished loading, +implement the `webView(_:didFinish:)` delegate method +to call the `takeSnapshot(with:completionHandler:)` method like so: + +```swift +func webView(_ webView: WKWebView, + didFinish navigation: WKNavigation!) +{ + var snapshotConfiguration = WKSnapshotConfiguration() + snapshotConfiguration.snapshotWidth = 1440 + + webView.takeSnapshot(with: snapshotConfiguration) { (image, error) in + guard let image = image, + error == nil else { + return + } + + <#...#> + } +} +``` + +Previously, +taking screenshots of a web page meant +messing around with view layers and graphics contexts. +So a clean, single method option is a welcome addition to the API. + +--- + +`WKWebView` truly makes the web feel like a first-class citizen. +Even if you consider yourself native purist, +you may be surprised at the power and flexibility afforded by WebKit. + +In fact, many of the apps you use every day rely on WebKit +to render especially tricky content. +The fact that you probably haven't noticed should be an indicator +that web views are consistent with app development best practices. diff --git a/2014-09-02-swift-default-protocol-implementations.md b/2014-09-02-swift-default-protocol-implementations.md index 18e6e039..11ba81e5 100644 --- a/2014-09-02-swift-default-protocol-implementations.md +++ b/2014-09-02-swift-default-protocol-implementations.md @@ -1,6 +1,6 @@ --- title: Swift Default Protocol Implementations -author: Mattt Thompson +author: Mattt category: Swift tags: swift excerpt: "Protocols are the foundation of generics in Swift, but suffer from the lack of a built-in way to provide default implementations for methods. However, there is an interesting workaround in Swift that you probably haven't noticed." @@ -12,7 +12,7 @@ Swift was announced 3 months ago to the day. For many of us, it was among the mo First came the infatuation period. We fixated on appearances, on surface-level features like Unicode support (`let 🐶🐮`!) and its new, streamlined syntax. Hell, even its _name_ was objectively better than its predecessor's. -Within a few weeks, though, after having a chance to go through the Swift manual a few times, we started to understand the full implications of this new multi-paradigm language. All of those folks who had affected the zealotry of functional programmers in order to sound smarter (generics!) learned enough to start backing it up. We finally got the distinction between `class` and `struct` down, and picked up a few tricks like [custom operators](http://nshipster.com/swift-operators/) and [literal convertibles](http://nshipster.com/swift-literal-convertible/) along the way. All of that initial excitement could now be channeled productively into apps and libraries and tutorials. +Within a few weeks, though, after having a chance to go through the Swift manual a few times, we started to understand the full implications of this new multi-paradigm language. All of those folks who had affected the zealotry of functional programmers in order to sound smarter (generics!) learned enough to start backing it up. We finally got the distinction between `class` and `struct` down, and picked up a few tricks like [custom operators](https://nshipster.com/swift-operators/) and [literal convertibles](https://nshipster.com/swift-literal-convertible/) along the way. All of that initial excitement could now be channeled productively into apps and libraries and tutorials. Next week's announcement effectively marks the end of the summer for iOS & OS X developers. It's time to reign in our experimentation and start shipping again. @@ -26,21 +26,21 @@ The underlying mechanism for generics are protocols. A Swift protocol, like an O > Within the Object-Oriented paradigm, types are often conflated with class identity. **When programming in Swift, though, think about polymorphism through _protocols_ first, before resorting to inheritance.** -The one major shortcoming of protocols, both in Swift and Objective-C, is the lack of a built-in way to provide default implementations for methods, as one might accomplish in other languages with [mixins](http://en.wikipedia.org/wiki/Mixin) or [traits](http://en.wikipedia.org/wiki/Trait_%28computer_programming%29). +The one major shortcoming of protocols, both in Swift and Objective-C, is the lack of a built-in way to provide default implementations for methods, as one might accomplish in other languages with [mixins](https://en.wikipedia.org/wiki/Mixin) or [traits](https://en.wikipedia.org/wiki/Trait_%28computer_programming%29). -...but that's not the end of the story. Swift is a fair bit more [Aspect-Oriented](http://en.wikipedia.org/wiki/Aspect-oriented_programming) than it initially lets on. +...but that's not the end of the story. Swift is a fair bit more [Aspect-Oriented](https://en.wikipedia.org/wiki/Aspect-oriented_programming) than it initially lets on. Consider the `Equatable` protocol, used throughout the standard library: -~~~{swift} +```swift protocol Equatable { - func ==(lhs: Self, rhs: Self) -> Bool + static func ==(lhs: Self, rhs: Self) -> Bool } -~~~ +``` Given an `Article` `struct` with a `title` and `body` field, implementing `Equatable` is straightforward: -~~~{swift} +```swift struct Article { let title: String let body: String @@ -51,11 +51,11 @@ extension Article: Equatable {} func ==(lhs: Article, rhs: Article) -> Bool { return lhs.title == rhs.title && lhs.body == rhs.body } -~~~ +``` With everything in place, let's show `Equatable` in action: -~~~{swift} +```swift let title = "Swift Custom Operators: Syntactic Sugar or Menace to Society?" let body = "..." @@ -64,7 +64,7 @@ let b = Article(title: title, body: body) a == b // true a != b // false -~~~ +``` Wait... where did `!=` come from? @@ -72,25 +72,25 @@ Wait... where did `!=` come from? `!=` is actually drawing its implementation from this function in the standard library: -~~~{swift} +```swift func !=(lhs: T, rhs: T) -> Bool -~~~ +``` Because `!=` is implemented as a generic function for `Equatable`, any type that conforms to `Equatable`, including `Article`, automatically gets the `!=` operator as well. If we really wanted to, we could override the implementation of `!=`: -~~~{swift} +```swift func !=(lhs: Article, rhs: Article) -> Bool { return !(lhs == rhs) } -~~~ +``` For equality, it's unlikely that we could offer something more efficient than the negation of the provided `==` check, but this might make sense in other cases. Swift's type inference system allows more specific declarations to trump any generic or implicit candidates. The standard library uses generic operators all over the place, like for bitwise operations: -~~~{swift} +```swift protocol BitwiseOperationsType { func &(_: Self, _: Self) -> Self func |(_: Self, _: Self) -> Self @@ -99,7 +99,7 @@ protocol BitwiseOperationsType { class var allZeros: Self { get } } -~~~ +``` Implementing functionality in this way significantly reduces the amount of boilerplate code needed to build on top of existing infrastructure. @@ -109,15 +109,15 @@ However, the aforementioned technique only really works for operators. Providing Consider a protocol `P` with a method `m()` that takes a single `Int` argument: -~~~{swift} +```swift protocol P { func m(arg: Int) } -~~~ +``` The closest one can get to a default implementation is to provide a top-level generic function that takes explicit `self` as the first argument: -~~~{swift} +```swift protocol P { func m() /* { f(self) @@ -125,9 +125,9 @@ protocol P { } func f(_ arg: T) { - // ... + <#...#> } -~~~ +``` > The commented-out code in the protocol helps communicate the provided functional implementation to the consumer. @@ -139,9 +139,9 @@ The Object-Oriented paradigm is based around the concept of objects that encapsu Take, for instance, the `contains` method: -~~~{swift} +```swift func contains(seq: S, x: S.Generator.Element) -> Bool -~~~ +``` Because of the constraint on the element of the sequence generator being `Equatable`, this cannot be declared on a generic container, without thereby requiring elements in that collection to conform to `Equatable`. diff --git a/2014-09-08-optionset.md b/2014-09-08-optionset.md new file mode 100644 index 00000000..28cb0c2a --- /dev/null +++ b/2014-09-08-optionset.md @@ -0,0 +1,296 @@ +--- +title: OptionSet +author: Mattt +category: Swift +excerpt: >- + Objective-C uses the `NS_OPTIONS` macro + to define set of values that may be combined together. + Swift imports those types as structures + conforming to the `OptionSet` protocol. + But could new language features in Swift provide a better option? +revisions: + "2014-09-09": Original publication + "2018-11-07": Updated for Swift 4.2 +status: + swift: 4.2 + reviewed: November 7, 2018 +--- + +Objective-C uses the +[`NS_OPTIONS`](https://nshipster.com/ns_enum-ns_options/) +macro to define option types, +or sets of values that may be combined together. +For example, +values in the `UIViewAutoresizing` type in UIKit +can be combined with the bitwise OR operator (`|`) +and passed to the `autoresizingMask` property of a `UIView` +to specify which margins and dimensions should automatically resize: + +```objc +typedef NS_OPTIONS(NSUInteger, UIViewAutoresizing) { + UIViewAutoresizingNone = 0, + UIViewAutoresizingFlexibleLeftMargin = 1 << 0, + UIViewAutoresizingFlexibleWidth = 1 << 1, + UIViewAutoresizingFlexibleRightMargin = 1 << 2, + UIViewAutoresizingFlexibleTopMargin = 1 << 3, + UIViewAutoresizingFlexibleHeight = 1 << 4, + UIViewAutoresizingFlexibleBottomMargin = 1 << 5 +}; +``` + +Swift imports this and other types defined using the `NS_OPTIONS` macro +as a structure that conforms to the `OptionSet` protocol. + +```swift +extension UIView { + struct AutoresizingMask: OptionSet { + init(rawValue: UInt) + + static var flexibleLeftMargin: UIView.AutoresizingMask + static var flexibleWidth: UIView.AutoresizingMask + static var flexibleRightMargin: UIView.AutoresizingMask + static var flexibleTopMargin: UIView.AutoresizingMask + static var flexibleHeight: UIView.AutoresizingMask + static var flexibleBottomMargin: UIView.AutoresizingMask + } +} +``` + +{% info %} +The renaming and nesting of imported types +are the result of a separate mechanism. +{% endinfo %} + +At the time `OptionSet` was introduced (and `RawOptionSetType` before it), +this was the best encapsulation that the language could provide. +Towards the end of this article, +we'll demonstrate how to take advantage of +language features added in Swift 4.2 +to improve upon `OptionSet`. + +...but that's getting ahead of ourselves. + +This week on NSHipster, +let's take a by-the-books look at using imported `OptionSet` types, +and how you can create your own. +After that, we'll offer a different option +for setting options. + +## Working with Imported Option Set Types + +[According to the documentation](https://developer.apple.com/documentation/swift/optionset), +there are over 300 types in Apple SDKs that conform to `OptionSet`, +from `ARHitTestResult.ResultType` to `XMLNode.Options`. + +No matter which one you're working with, +the way you use them is always the same: + +To specify a single option, +pass it directly +(Swift can infer the type when setting a property +so you can omit everything up to the leading dot): + +```swift +view.autoresizingMask = .flexibleHeight +``` + +`OptionSet` conforms to the +[`SetAlgebra`](https://developer.apple.com/documentation/swift/setalgebra) +protocol, +so to you can specify multiple options with an array literal --- +no bitwise operations required: + +```swift +view.autoresizingMask = [.flexibleHeight, .flexibleWidth] +``` + +To specify no options, +pass an empty array literal (`[]`): + +```swift +view.autoresizingMask = [] // no options +``` + +## Declaring Your Own Option Set Types + +You might consider creating your own option set type +if you have a property that stores combinations from a closed set of values +and you want that combination to be stored efficiently using a bitset. + +To do this, +declare a new structure that adopts the `OptionSet` protocol +with a required `rawValue` instance property +and type properties for each of the values you wish to represent. +The raw values of these are initialized with increasing powers of 2, +which can be constructed using the left bitshift (`<<`) operation +with incrementing right-hand side values. +You can also specify named aliases for specific combinations of values. + +For example, +here's how you might represent topping options for a pizza: + +```swift +struct Toppings: OptionSet { + let rawValue: Int + + static let pepperoni = Toppings(rawValue: 1 << 0) + static let onions = Toppings(rawValue: 1 << 1) + static let bacon = Toppings(rawValue: 1 << 2) + static let extraCheese = Toppings(rawValue: 1 << 3) + static let greenPeppers = Toppings(rawValue: 1 << 4) + static let pineapple = Toppings(rawValue: 1 << 5) + + static let meatLovers: Toppings = [.pepperoni, .bacon] + static let hawaiian: Toppings = [.pineapple, .bacon] + static let all: Toppings = [ + .pepperoni, .onions, .bacon, + .extraCheese, .greenPeppers, .pineapple + ] +} +``` + +Taken into a larger example for context: + +```swift +struct Pizza { + enum Style { + case neapolitan, sicilian, newHaven, deepDish + } + + struct Toppings: OptionSet { ... } + + let diameter: Int + let style: Style + let toppings: Toppings + + init(inchesInDiameter diameter: Int, + style: Style, + toppings: Toppings = []) + { + self.diameter = diameter + self.style = style + self.toppings = toppings + } +} + +let dinner = Pizza(inchesInDiameter: 12, + style: .neapolitan, + toppings: [.greenPeppers, .pineapple]) +``` + +Another advantage of `OptionSet` conforming to `SetAlgebra` is that +you can perform set operations like determining membership, +inserting and removing elements, +and forming unions and intersections. +This makes it easy to, for example, +determine whether the pizza toppings are vegetarian-friendly: + +```swift +extension Pizza { + var isVegetarian: Bool { + return toppings.isDisjoint(with: [.pepperoni, .bacon]) + } +} + +dinner.isVegetarian // true +``` + +## A Fresh Take on an Old Classic + +Alright, now that you know how to use `OptionSet`, +let's show you how not to use `OptionSet`. + +As we mentioned before, +new language features in Swift 4.2 make it possible +to have our cake pizza pie and eat it too. + +First, declare a new `Option` protocol +that inherits `RawRepresentable`, `Hashable`, and `CaseIterable`. + +```swift +protocol Option: RawRepresentable, Hashable, CaseIterable {} +``` + +Next, declare an enumeration with `String` raw values +that adopts the `Option` protocol: + +```swift +enum Topping: String, Option { + case pepperoni, onions, bacon, + extraCheese, greenPeppers, pineapple +} +``` + +Compare the structure declaration from before +to the following enumeration. +Much nicer, right? +Just wait --- it gets even better. + +Automatic synthesis of `Hashable` provides effortless usage with `Set`, +which gets us halfway to the functionality of `OptionSet`. +Using conditional conformance, +we can create an extension for any `Set` whose element is a `Topping` +and define our named topping combos. +As a bonus, `CaseIterable` makes it easy to order a pizza with _"the works"_: + +```swift +extension Set where Element == Topping { + static var meatLovers: Set { + return [.pepperoni, .bacon] + } + + static var hawaiian: Set { + return [.pineapple, .bacon] + } + + static var all: Set { + return Set(Element.allCases) + } +} + +typealias Toppings = Set +``` + +And that's not all `CaseIterable` has up its sleeves; +by enumerating over the `allCases` type property, +we can automatically generate the bitset values for each case, +which we can combine to produce the equivalent `rawValue` +for any `Set` containing `Option` types: + +```swift +extension Set where Element: Option { + var rawValue: Int { + var rawValue = 0 + for (index, element) in Element.allCases.enumerated() { + if self.contains(element) { + rawValue |= (1 << index) + } + } + + return rawValue + } +} +``` + +Because `OptionSet` and `Set` both conform to `SetAlgebra` +our new `Topping` implementation can be swapped in for the original one +without needing to change anything about the `Pizza` itself. + +{% warning %} +This approach assumes that the order of cases provided by `CaseIterable` +is stable across launches. +If it isn't, the generated raw value for combinations of options +may be inconsistent. +{% endwarning %} + +--- + +You're likely to encounter `OptionSet` +when working with Apple SDKs in Swift. +And although you _could_ create your own structure that conforms to `OptionSet`, +you probably don't need to. +You could use the fancy approach outlined at the end of this article, +or do with something more straightforward. + +Whichever option you choose, +you should be all set. diff --git a/2014-09-08-rawoptionsettype.md b/2014-09-08-rawoptionsettype.md deleted file mode 100644 index 922c239d..00000000 --- a/2014-09-08-rawoptionsettype.md +++ /dev/null @@ -1,184 +0,0 @@ ---- -title: RawOptionSetType -author: Mattt Thompson -category: Swift -tags: swift -excerpt: "Swift enumerations are a marked improvement over the `NS_ENUM` macro in Objective-C. Unfortunately, `NS_OPTIONS` does not compare as favorably." -status: - swift: 1.2 ---- - -In Objective-C, [`NS_ENUM` & `NS_OPTIONS`](http://nshipster.com/ns_enum-ns_options/) are used to annotate C `enum`s in such a way that sets clear expectations for both the compiler and developer. Since being introduced to Objective-C with Xcode 4.5, these macros have become a standard convention in system frameworks, and a best practice within the community. - -In Swift, enumerations are codified as a first-class language construct as fundamental as a `struct` or `class`, and include a number of features that make them even more expressive, like raw types and associated values. They're so perfectly-suited to encapsulating closed sets of fixed values, that developers would do well to actively seek out opportunities to use them. - -When interacting with frameworks like Foundation in Swift, all of those `NS_ENUM` declarations are automatically converted into an `enum`—often improving on the original Objective-C declaration by eliminating naming redundancies: - -~~~{swift} -enum UITableViewCellStyle : Int { - case Default - case Value1 - case Value2 - case Subtitle -} -~~~ - -~~~{objective-c} -typedef NS_ENUM(NSInteger, UITableViewCellStyle) { - UITableViewCellStyleDefault, - UITableViewCellStyleValue1, - UITableViewCellStyleValue2, - UITableViewCellStyleSubtitle -}; -~~~ - -Unfortunately, for `NS_OPTIONS`, the Swift equivalent is arguably worse: - -~~~{swift} -struct UIViewAutoresizing : RawOptionSetType { - init(_ value: UInt) - var value: UInt - static var None: UIViewAutoresizing { get } - static var FlexibleLeftMargin: UIViewAutoresizing { get } - static var FlexibleWidth: UIViewAutoresizing { get } - static var FlexibleRightMargin: UIViewAutoresizing { get } - static var FlexibleTopMargin: UIViewAutoresizing { get } - static var FlexibleHeight: UIViewAutoresizing { get } - static var FlexibleBottomMargin: UIViewAutoresizing { get } -} -~~~ - -~~~{objective-c} -typedef NS_OPTIONS(NSUInteger, UIViewAutoresizing) { - UIViewAutoresizingNone = 0, - UIViewAutoresizingFlexibleLeftMargin = 1 << 0, - UIViewAutoresizingFlexibleWidth = 1 << 1, - UIViewAutoresizingFlexibleRightMargin = 1 << 2, - UIViewAutoresizingFlexibleTopMargin = 1 << 3, - UIViewAutoresizingFlexibleHeight = 1 << 4, - UIViewAutoresizingFlexibleBottomMargin = 1 << 5 -}; -~~~ - -* * * - -`RawOptionsSetType` is the Swift equivalent of `NS_OPTIONS` (or at least as close as it gets). It is a protocol that adopts the `RawRepresentable`, `Equatable`, `BitwiseOperationsType`, and `NilLiteralConvertible` protocols. An option type can be represented by a `struct` conforming to `RawOptionsSetType`. - -Why does this suck so much? Well, the same integer bitmasking tricks in C don't work for enumerated types in Swift. An `enum` represents a type with a closed set of valid options, without a built-in mechanism for representing a conjunction of options for that type. An `enum` could, ostensibly, define a case for all possible combinations of values, but for `n > 3`, the combinatorics make this approach untenable. There are a few different ways `NS_OPTIONS` could be implemented in Swift, but `RawOptionSetType` is probably the least bad. - -Compared to the syntactically concise `enum` declaration, `RawOptionsSetType` is awkward and cumbersome, requiring over a dozen lines of boilerplate for computed properties: - -~~~{swift} -struct Toppings : RawOptionSetType, BooleanType { - private var value: UInt = 0 - - init(_ value: UInt) { - self.value = value - } - - // MARK: RawOptionSetType - - static func fromMask(raw: UInt) -> Toppings { - return self(raw) - } - - // MARK: RawRepresentable - - static func fromRaw(raw: UInt) -> Toppings? { - return self(raw) - } - - func toRaw() -> UInt { - return value - } - - // MARK: BooleanType - - var boolValue: Bool { - return value != 0 - } - - - // MARK: BitwiseOperationsType - - static var allZeros: Toppings { - return self(0) - } - - // MARK: NilLiteralConvertible - - static func convertFromNilLiteral() -> Toppings { - return self(0) - } - - // MARK: - - - static var None: Toppings { return self(0b0000) } - static var ExtraCheese: Toppings { return self(0b0001) } - static var Pepperoni: Toppings { return self(0b0010) } - static var GreenPepper: Toppings { return self(0b0100) } - static var Pineapple: Toppings { return self(0b1000) } -} -~~~ - -> As of Xcode 6 Beta 6, `RawOptionSetType` no longer conforms to `BooleanType`, which is required for performing bitwise checks. - -One nice thing about doing this in Swift is its built-in binary integer literal notation, which allows the bitmask to be computed visually. And once the options type is declared, the usage syntax is not too bad. - -Taken into a larger example for context: - -~~~{swift} -struct Pizza { - enum Style { - case Neopolitan, Sicilian, NewHaven, DeepDish - } - - struct Toppings : RawOptionSetType { ... } - - let diameter: Int - let style: Style - let toppings: Toppings - - init(inchesInDiameter diameter: Int, style: Style, toppings: Toppings = .None) { - self.diameter = diameter - self.style = style - self.toppings = toppings - } -} - -let dinner = Pizza(inchesInDiameter: 12, style: .Neopolitan, toppings: .Pepperoni | .GreenPepper) -~~~ - -A value membership check can be performed with the `&` operator, just like with unsigned integers in C: - -~~~{swift} -extension Pizza { - var isVegetarian: Bool { - return toppings & Toppings.Pepperoni ? false : true - } -} - -dinner.isVegetarian // false -~~~ - -* * * - -In all fairness, it may be too early to really appreciate what role option types will have in the new language. It could very well be that Swift's other constructs, like tuples or pattern matching—or indeed, even `enum`s—make options little more than a vestige of the past. - -Either way, if you're looking to implement an `NS_OPTIONS` equivalent in your code base, here's an [Xcode snippet](http://nshipster.com/xcode-snippets/)-friendly example of how to go about it: - -~~~{swift} -struct <# Options #> : RawOptionSetType, BooleanType { - let rawValue: UInt - init(nilLiteral: ()) { self.value = 0 } - init(_ value: UInt = 0) { self.value = value } - init(rawValue value: UInt) { self.value = value } - var boolValue: Bool { return value != 0 } - var rawValue: UInt { return value } - static var allZeros: <# Options #> { return self(0) } - - static var None: <# Options #> { return self(0b0000) } - static var <# Option #>: <# Options #> { return self(0b0001) } - // ... -} -~~~ diff --git a/2014-09-15-image-resizing.md b/2014-09-15-image-resizing.md deleted file mode 100644 index e43a170a..00000000 --- a/2014-09-15-image-resizing.md +++ /dev/null @@ -1,246 +0,0 @@ ---- -title: Image Resizing Techniques -author: Mattt Thompson -category: "" -excerpt: "Since time immemorial, iOS developers have been perplexed by a singular question: 'How do you resize an image?'. This article endeavors to provide a clear answer to this eternal question." -status: - swift: 2.0 - reviewed: September 30, 2015 -revisions: - "2014-09-15": Original publication. - "2015-09-30": Revised for Swift 2.0, `vImage` method added. ---- - -Since time immemorial, iOS developers have been perplexed by a singular question: "How do you resize an image?". It is a question of beguiling clarity, spurred on by a mutual mistrust of developer and platform. A thousand code samples litter web search results, each claiming to be the One True Solution, and all the others false prophets. - -It's embarrassing, really. - -This week's article endeavors to provide a clear explanation of the various approaches to image resizing on iOS (and OS X, making the appropriate `UIImage` → `NSImage` conversions), using empirical evidence to offer insights into the performance characteristics of each approach, rather than simply prescribing any one way for all situations. - -**Before reading any further, please note the following:** - -When setting a `UIImage` on a `UIImageView`, manual resizing is unnecessary for the vast majority of use cases. Instead, one can simply set the `contentMode` property to either `.ScaleAspectFit` to ensure that the entire image is visible within the frame of the image view, or `.ScaleAspectFill` to have the entire image view filled by the image, cropping as necessary from the center. - -```swift -imageView.contentMode = .ScaleAspectFit -imageView.image = image -``` - -* * * - -## Determining Scaled Size - -Before doing any image resizing, one must first determine the target size to scale to. - -### Scaling by Factor - -The simplest way to scale an image is by a constant factor. Generally, this involves dividing by a whole number to reduce the original size (rather than multiplying by a whole number to magnify). - -A new `CGSize` can be computed by scaling the width and height components individually: - -```swift -let size = CGSize(width: image.size.width / 2, height: image.size.height / 2) -``` - -...or by applying a `CGAffineTransform`: - -```swift -let size = CGSizeApplyAffineTransform(image.size, CGAffineTransformMakeScale(0.5, 0.5)) -``` - -### Scaling by Aspect Ratio - -It's often useful to scale the original size in such a way that fits within a rectangle without changing the original aspect ratio. `AVMakeRectWithAspectRatioInsideRect` is a useful function found in the AVFoundation framework that takes care of that calculation for you: - -```swift -import AVFoundation -let rect = AVMakeRectWithAspectRatioInsideRect(image.size, imageView.bounds) -``` - -## Resizing Images - -There are a number of different approaches to resizing an image, each with different capabilities and performance characteristics. - -### `UIGraphicsBeginImageContextWithOptions` & `UIImage -drawInRect:` - -The highest-level APIs for image resizing can be found in the UIKit framework. Given a `UIImage`, a temporary graphics context can be used to render a scaled version, using `UIGraphicsBeginImageContextWithOptions()` and `UIGraphicsGetImageFromCurrentImageContext()`: - -```swift -let image = UIImage(contentsOfFile: self.URL.absoluteString!) - -let size = CGSizeApplyAffineTransform(image.size, CGAffineTransformMakeScale(0.5, 0.5)) -let hasAlpha = false -let scale: CGFloat = 0.0 // Automatically use scale factor of main screen - -UIGraphicsBeginImageContextWithOptions(size, !hasAlpha, scale) -image.drawInRect(CGRect(origin: CGPointZero, size: size)) - -let scaledImage = UIGraphicsGetImageFromCurrentImageContext() -UIGraphicsEndImageContext() -``` - -`UIGraphicsBeginImageContextWithOptions()` creates a temporary rendering context into which the original is drawn. The first argument, `size`, is the target size of the scaled image. The second argument, `isOpaque` is used to determine whether an alpha channel is rendered. Setting this to `false` for images without transparency (i.e. an alpha channel) may result in an image with a pink hue. The third argument `scale` is the display scale factor. When set to `0.0`, the scale factor of the main screen is used, which for Retina displays is `2.0` or higher (`3.0` on the iPhone 6 Plus). - -### `CGBitmapContextCreate` & `CGContextDrawImage` - -Core Graphics / Quartz 2D offers a lower-level set of APIs that allow for more advanced configuration. Given a `CGImage`, a temporary bitmap context is used to render the scaled image, using `CGBitmapContextCreate()` and `CGBitmapContextCreateImage()`: - -```swift -let cgImage = UIImage(contentsOfFile: self.URL.absoluteString!).CGImage - -let width = CGImageGetWidth(cgImage) / 2 -let height = CGImageGetHeight(cgImage) / 2 -let bitsPerComponent = CGImageGetBitsPerComponent(cgImage) -let bytesPerRow = CGImageGetBytesPerRow(cgImage) -let colorSpace = CGImageGetColorSpace(cgImage) -let bitmapInfo = CGImageGetBitmapInfo(cgImage) - -let context = CGBitmapContextCreate(nil, width, height, bitsPerComponent, bytesPerRow, colorSpace, bitmapInfo.rawValue) - -CGContextSetInterpolationQuality(context, kCGInterpolationHigh) - -CGContextDrawImage(context, CGRect(origin: CGPointZero, size: CGSize(width: CGFloat(width), height: CGFloat(height))), cgImage) - -let scaledImage = CGBitmapContextCreateImage(context).flatMap { UIImage(CGImage: $0) } -``` - -`CGBitmapContextCreate` takes several arguments to construct a context with desired dimensions and amount of memory for each channel within a given colorspace. In the example, these values are fetched from the `CGImage`. Next, `CGContextSetInterpolationQuality` allows for the context to interpolate pixels at various levels of fidelity. In this case, `kCGInterpolationHigh` is passed for best results. `CGContextDrawImage` allows for the image to be drawn at a given size and position, allowing for the image to be cropped on a particular edge or to fit a set of image features, such as faces. Finally, `CGBitmapContextCreateImage` creates a `CGImage` from the context. - -### `CGImageSourceCreateThumbnailAtIndex` - -Image I/O is a powerful, yet lesser-known framework for working with images. Independent of Core Graphics, it can read and write between many different formats, access photo metadata, and perform common image processing operations. The framework offers the fastest image encoders and decoders on the platform, with advanced caching mechanisms and even the ability to load images incrementally. - -`CGImageSourceCreateThumbnailAtIndex` offers a concise API with different options than found in equivalent Core Graphics calls: - -```swift -import ImageIO - -if let imageSource = CGImageSourceCreateWithURL(self.URL, nil) { - let options: [NSString: NSObject] = [ - kCGImageSourceThumbnailMaxPixelSize: max(size.width, size.height) / 2.0, - kCGImageSourceCreateThumbnailFromImageAlways: true - ] - - let scaledImage = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, options).flatMap { UIImage(CGImage: $0) } -} -``` - -Given a `CGImageSource` and set of options, `CGImageSourceCreateThumbnailAtIndex` creates a thumbnail image. Resizing is accomplished by the `kCGImageSourceThumbnailMaxPixelSize`. Specifying the maximum dimension divided by a constant factor scales the image while maintaining the original aspect ratio. By specifying either `kCGImageSourceCreateThumbnailFromImageIfAbsent` or `kCGImageSourceCreateThumbnailFromImageAlways`, Image I/O will automatically cache the scaled result for subsequent calls. - -### Lanczos Resampling with Core Image - -Core Image provides a built-in [Lanczos resampling](http://en.wikipedia.org/wiki/Lanczos_resampling) functionality with the `CILanczosScaleTransform` filter. Although arguably a higher-level API than UIKit, the pervasive use of key-value coding in Core Image makes it unwieldy. - -That said, at least the pattern is consistent. The process of creating a transform filter, configuring it, and rendering an output image is just like any other Core Image workflow: - -```swift -let image = CIImage(contentsOfURL: self.URL) - -let filter = CIFilter(name: "CILanczosScaleTransform")! -filter.setValue(image, forKey: "inputImage") -filter.setValue(0.5, forKey: "inputScale") -filter.setValue(1.0, forKey: "inputAspectRatio") -let outputImage = filter.valueForKey("outputImage") as! CIImage - -let context = CIContext(options: [kCIContextUseSoftwareRenderer: false]) -let scaledImage = UIImage(CGImage: self.context.createCGImage(outputImage, fromRect: outputImage.extent())) -``` - -`CILanczosScaleTransform` accepts an `inputImage`, `inputScale`, and `inputAspectRatio`, all of which are pretty self-explanatory. A `CIContext` is used to create a `UIImage` by way of a `CGImageRef` intermediary representation, since `UIImage(CIImage:)` doesn't often work as expected. - -Creating a `CIContext` is an expensive operation, so a cached context should always be used for repeated resizing. A `CIContext` can be created using either the GPU or the CPU (much slower) for rendering—use the `kCIContextUseSoftwareRenderer` key in the options dictionary to specify which. - - -### `vImage` in Accelerate - -The [Accelerate framework](https://developer.apple.com/library/prerelease/ios/documentation/Accelerate/Reference/AccelerateFWRef/index.html#//apple_ref/doc/uid/TP40009465) includes a suite of `vImage` image-processing functions, with a [set of functions](https://developer.apple.com/library/prerelease/ios/documentation/Performance/Reference/vImage_geometric/index.html#//apple_ref/doc/uid/TP40005490-CH212-145717) that scale an image buffer. These lower-level APIs promise high performance with low power consumption, but at the cost of managing the buffers yourself. The following is a Swift version of a method [suggested by Nyx0uf on GitHub](https://gist.github.com/Nyx0uf/217d97f81f4889f4445a): - -```swift -let cgImage = UIImage(contentsOfFile: self.URL.absoluteString!).CGImage - -// create a source buffer -var format = vImage_CGImageFormat(bitsPerComponent: 8, bitsPerPixel: 32, colorSpace: nil, - bitmapInfo: CGBitmapInfo(rawValue: CGImageAlphaInfo.First.rawValue), - version: 0, decode: nil, renderingIntent: CGColorRenderingIntent.RenderingIntentDefault) -var sourceBuffer = vImage_Buffer() -defer { - sourceBuffer.data.dealloc(Int(sourceBuffer.height) * Int(sourceBuffer.height) * 4) -} - -var error = vImageBuffer_InitWithCGImage(&sourceBuffer, &format, nil, cgImage, numericCast(kvImageNoFlags)) -guard error == kvImageNoError else { return nil } - -// create a destination buffer -let scale = UIScreen.mainScreen().scale -let destWidth = Int(image.size.width * 0.5 * scale) -let destHeight = Int(image.size.height * 0.5 * scale) -let bytesPerPixel = CGImageGetBitsPerPixel(image.CGImage) / 8 -let destBytesPerRow = destWidth * bytesPerPixel -let destData = UnsafeMutablePointer.alloc(destHeight * destBytesPerRow) -defer { - destData.dealloc(destHeight * destBytesPerRow) -} -var destBuffer = vImage_Buffer(data: destData, height: vImagePixelCount(destHeight), width: vImagePixelCount(destWidth), rowBytes: destBytesPerRow) - -// scale the image -error = vImageScale_ARGB8888(&sourceBuffer, &destBuffer, nil, numericCast(kvImageHighQualityResampling)) -guard error == kvImageNoError else { return nil } - -// create a CGImage from vImage_Buffer -let destCGImage = vImageCreateCGImageFromBuffer(&destBuffer, &format, nil, nil, numericCast(kvImageNoFlags), &error)?.takeRetainedValue() -guard error == kvImageNoError else { return nil } - -// create a UIImage -let scaledImage = destCGImage.flatMap { UIImage(CGImage: $0, scale: 0.0, orientation: image.imageOrientation) } -``` - -The Accelerate APIs used here clearly operate at a lower-level than the other resizing methods. To use this method, you first create a source buffer from your CGImage using a `vImage_CGImageFormat` with `vImageBuffer_InitWithCGImage()`. The destination buffer is allocated at the desired image resolution, then `vImageScale_ARGB8888` does the actual work of resizing the image. Managing your own buffers when operating on images larger than your app's memory limit is left as an exercise for the reader. - - ---- - -## Performance Benchmarks - -So how do these various approaches stack up to one another? - -Here are the results of a set of [performance benchmarks](http://nshipster.com/benchmarking/) done on an iPhone 6 running iOS 8.4, via [this project](https://github.com/natecook1000/Image-Resizing): - -### JPEG - -Loading, scaling, and displaying a large, high-resolution (12000 ⨉ 12000 px 20 MB JPEG) source image from [NASA Visible Earth](http://visibleearth.nasa.gov/view.php?id=78314) at 1/10th the size: - -| Operation | Time _(sec)_ | σ | -|------------------------------------|--------------|------| -| `UIKit` | 0.612 | 14% | -| `Core Graphics` 1 | 0.266 | 3% | -| `Image I/O` | 0.255 | 2% | -| `Core Image` 2 | 3.703 | 33% | -| `vImage` 3 | -- | -- | - -### PNG - -Loading, scaling, and displaying a reasonably large (1024 ⨉ 1024 px 1MB PNG) rendering of the [Postgres.app](http://postgresapp.com) Icon at 1/10th the size: - -| Operation | Time _(sec)_ | σ | -|------------------------------------|--------------|------| -| `UIKit` | 0.044 | 30% | -| `Core Graphics` 4 | 0.036 | 10% | -| `Image I/O` | 0.038 | 11% | -| `Core Image` 5 | 0.053 | 68% | -| `vImage` | 0.050 | 25% | - -1, 4 Results were consistent across different values of `CGInterpolationQuality`, with negligible differences in performance benchmarks. - -3 The size of the NASA Visible Earth image was larger than could be processed in a single pass on the device. - -2, 5 Setting `kCIContextUseSoftwareRenderer` to `true` on the options passed on `CIContext` creation yielded results an order of magnitude slower than base results. - -## Conclusions - -- **UIKit**, **Core Graphics**, and **Image I/O** all perform well for scaling operations on most images. -- **Core Image** is outperformed for image scaling operations. In fact, it is specifically recommended in the [Performance Best Practices section of the Core Image Programming Guide](https://developer.apple.com/library/mac/documentation/graphicsimaging/Conceptual/CoreImaging/ci_performance/ci_performance.html#//apple_ref/doc/uid/TP30001185-CH10-SW1) to use Core Graphics or Image I/O functions to crop or downsample images beforehand. -- For general image scaling without any additional functionality, **`UIGraphicsBeginImageContextWithOptions`** is probably the best option. -- If image quality is a consideration, consider using **`CGBitmapContextCreate`** in combination with **`CGContextSetInterpolationQuality`**. -- When scaling images with the intent purpose of displaying thumbnails, **`CGImageSourceCreateThumbnailAtIndex`** offers a compelling solution for both rendering and caching. -- Unless you're already working with **`vImage`**, the extra work to use the low-level Accelerate framework for resizing doesn't pay off. - diff --git a/2014-09-16-phimagemanager.md b/2014-09-16-phimagemanager.md index 45fa2502..2d2c6934 100644 --- a/2014-09-16-phimagemanager.md +++ b/2014-09-16-phimagemanager.md @@ -1,18 +1,19 @@ --- title: PHImageManager -author: Mattt Thompson +author: Mattt category: Cocoa excerpt: "Yesterday's article described various techniques for resizing images using APIs from the UIKit, Core Graphics, Core Image, and Image I/O frameworks. However, that article failed to mention some rather extraordinary functionality baked into the new Photos framework which takes care of all of this for you." +retired: true status: - swift: 2.0 - reviewed: September 15, 2015 + swift: 2.0 + reviewed: September 15, 2015 --- -[Yesterday's article](http://nshipster.com/image-resizing/) described various techniques for resizing images using APIs from the UIKit, Core Graphics, Core Image, and Image I/O frameworks. However, that article failed to mention some rather extraordinary functionality baked into the new Photos framework which takes care of all of this for you. +[Yesterday's article](https://nshipster.com/image-resizing/) described various techniques for resizing images using APIs from the UIKit, Core Graphics, Core Image, and Image I/O frameworks. However, that article failed to mention some rather extraordinary functionality baked into the new Photos framework which takes care of all of this for you. For anyone developing apps that manage photos or videos, meet your new best friend: `PHImageManager`. -* * * +--- New in iOS 8, the Photos framework is something of a triumph for the platform. Photography is one of the key verticals for the iPhone: in addition to being the [most popular cameras in the world](https://www.flickr.com/cameras), photo & video apps are regularly featured on the App Store. This framework goes a long way to empower apps to do even more, with a shared set of tools and primitives. @@ -24,7 +25,7 @@ A great example of this is `PHImageManager`, which acts as a centralized coordin But first, here's a simple example of how a table view might asynchronously load cell images with asset thumbnails: -~~~{swift} +```swift import Photos var assets: [PHAsset] @@ -58,9 +59,9 @@ func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexP return cell } -~~~ +``` -~~~{objective-c} +```objc @import Photos; @property (nonatomic, strong) NSArray *assets; @@ -95,7 +96,7 @@ func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexP return cell; } -~~~ +``` API usage is pretty straightforward: the `defaultManager` asynchronously requests an image for the asset corresponding to the cell at a particular index path, and the cell image view is set whenever the result comes back. The only tricky part is handling cell reuse—(1) before assigning the resulting image to the cell's image view, we call `cellForRowAtIndexPath` to be sure we're working with the right cell, and (2) we use the cell's `tag` to keep track of image requests, in order to cancel any pending requests when a cell is reused. @@ -105,7 +106,7 @@ If there's reasonable assurance that most of a set of assets will be viewed at s For example, here's how the results of a `PHAsset` fetch operation can be pre-cached in order to optimize image availability: -~~~{swift} +```swift let cachingImageManager = PHCachingImageManager() let options = PHFetchOptions() @@ -127,9 +128,9 @@ cachingImageManager.startCachingImagesForAssets(assets, contentMode: .AspectFit, options: nil ) -~~~ +``` -~~~{objective-c} +```objc PHCachingImageManager *cachingImageManager = [[PHCachingImageManager alloc] init]; PHFetchOptions *options = [[PHFetchOptions alloc] init]; @@ -150,11 +151,11 @@ NSMutableArray *assets = [[NSMutableArray alloc] init]; targetSize:PHImageManagerMaximumSize contentMode:PHImageContentModeAspectFit options:nil]; -~~~ +``` Alternatively, Swift `willSet` / `didSet` hooks offer a convenient way to automatically start pre-caching assets as they are loaded: -~~~{swift} +```swift let cachingImageManager = PHCachingImageManager() var assets: [PHAsset] = [] { willSet { @@ -169,7 +170,7 @@ var assets: [PHAsset] = [] { ) } } -~~~ +``` ## PHImageRequestOptions @@ -197,7 +198,7 @@ Several of these properties take a specific `enum` type, which are all pretty se Using `PHImageManager` and `PHImageRequestOptions` to their full capacity allows for rather sophisticated functionality. One could, for example, use successive image requests to crop full-quality assets to any faces detected in the image. -~~~{swift} +```swift let asset: PHAsset @IBOutlet weak var imageView: UIImageView! @@ -251,9 +252,9 @@ override func viewDidLoad() { } } } -~~~ +``` -~~~{objective-c} +```objc @property (nonatomic, strong) PHAsset *asset; @property (nonatomic, weak) IBOutlet UIImageView *imageView; @property (nonatomic, weak) IBOutlet UIProgressView *progressView; @@ -309,13 +310,13 @@ override func viewDidLoad() { resultHandler:resultHandler]; } -~~~ +``` The initial request attempts to get the most readily available representation of an asset to pass into a `CIDetector` for facial recognition. If any features were detected, the final request would be cropped to them, by specifying the `normalizedCropRect` on the final `PHImageRequestOptions`. > `normalizedCropRect` is normalized for `origin` and `size` components within the inclusive range `0.0` to `1.0`. An affine transformation scaling on the inverse of the original frame makes for an easy calculation. -* * * +--- From its very inception, iOS has been a balancing act between functionality and integrity. And with every release, a combination of thoughtful affordances and powerful APIs have managed to expand the role third-party applications without compromising security or performance. diff --git a/2014-09-22-equatable-and-comparable.md b/2014-09-22-equatable-and-comparable.md new file mode 100644 index 00000000..133917ba --- /dev/null +++ b/2014-09-22-equatable-and-comparable.md @@ -0,0 +1,352 @@ +--- +title: Equatable and Comparable +author: Mattt +category: Swift +tags: swift +excerpt: >- + Objective-C required us to wax philosophic + about the nature of equality and identity. + To the relief of any developer less inclined towards discursive treatises, + this is not as much the case for Swift. +revisions: + "2014-09-22": Original publication + "2018-12-19": Updated for Swift 4.2 +status: + swift: 4.2 + reviewed: December 19, 2018 +--- + +Objective-C required us to +[wax philosophic](/equality/) +about the nature of equality and identity. +To the relief of any developer less inclined towards discursive treatises, +this is not as much the case for Swift. + +In Swift, +there's the `Equatable` protocol, +which explicitly defines the semantics of equality and inequality +in a manner entirely separate from the question of identity. +There's also the `Comparable` protocol, +which builds on `Equatable` to refine inequality semantics +to creating an ordering of values. +Together, the `Equatable` and `Comparable` protocols +form the central point of comparison throughout the language. + +--- + +## Equatable + +Values conforming to the `Equatable` protocol +can be evaluated for equality and inequality. +Conformance to `Equatable` requires +the implementation of the equality operator (`==`). + +As an example, +consider the following +[`Binomen`](https://en.wikipedia.org/wiki/Binomial_nomenclature) structure: + +```swift +struct Binomen { + let genus: String + let species: String +} + +let 🐺 = Binomen(genus: "Canis", species: "lupus") +let 🐻 = Binomen(genus: "Ursus", species: "arctos") +``` + +We can add `Equatable` conformance through an extension, +implementing the required type method for the `==` operator like so: + +```swift +extension Binomen: Equatable { + static func == (lhs: Binomen, rhs: Binomen) -> Bool { + return lhs.genus == rhs.genus && + lhs.species == rhs.species + } +} + +🐺 == 🐺 // true +🐺 == 🐻 // false +``` + +_Easy enough, right?_ + +Well actually, it's even easier than that --- +as of Swift 4.1, +the compiler can _automatically synthesize_ conformance +for structures whose stored properties all have types that are `Equatable`. +We could replace all of the code in the extension +by simply adopting `Equatable` in the declaration of `Binomen`: + +```swift +struct Binomen: Equatable { + let genus: String + let species: String +} + +🐺 == 🐺 // true +🐺 == 🐻 // false +``` + +### The Benefits of Being Equal + +Equatability isn't just about using the `==` operator --- +there's also the `!=` operator! +it also lets a value, +among other things, +be found in a collection and +matched in a `switch` statement. + +```swift +[🐺, 🐻].contains(🐻) // true + +func commonName(for binomen: Binomen) -> String? { + switch binomen { + case 🐺: return "gray wolf" + case 🐻: return "brown bear" + default: return nil + } +} +commonName(for: 🐺) // "gray wolf" +``` + +`Equatable` is also a requirement for conformance to +[`Hashable`](https://nshipster.com/hashable/), +another important type in Swift. + +This is all to say that +if a type has equality semantics --- +if two values of that type can be considered equal or unequal -- +it should conform to `Equatable`. + +### The Limits of Automatic Synthesis + +The Swift standard library and most of the frameworks in Apple SDKs +do a great job adopting `Equatable` for types that make sense to be. +In practice, you're unlikely to be in a situation +where the dereliction of a built-in type +spoils automatic synthesis for your own type. + +Instead, the most common obstacle to automatic synthesis involves tuples. +Consider this poorly-considered +[`Trinomen`](https://en.wikipedia.org/wiki/Trinomen) type: + +```swift +struct Trinomen { + let genus: String + let species: (String, subspecies: String?) // 🤔 +} + +extension Trinomen: Equatable {} +// 🛑 Type 'Trinomen' does not conform to protocol 'Equatable' +``` + +As described in our article about [`Void`](/void/), +tuples aren't nominal types, +so they can't conform to `Equatable`. +If you wanted to compare two trinomina for equality, +you'd have to write the conformance code for `Equatable`. + +_...like some kind of animal_. + +### Conditional Conformance to Equality + +In addition to automatic synthesis of `Equatable`, +Swift 4.1 added another critical feature: +conditional conformance. + +To illustrate this, +consider the following generic type +that represents a quantity of something: + +```swift +struct Quantity { + let count: Int + let thing: Thing +} +``` + +Can `Quantity` conform to `Equatable`? +We know that integers are equatable, +so it really depends on what kind of `Thing` we're talking about. + +What conditional conformance Swift 4.1 allows us to do is +create an extension on a type with a conditional clause. +We can use that here to programmatically express that +\_"a quantity of a thing is equatable if the thing itself is equatable": + +```swift +extension Quantity: Equatable where Thing: Equatable {} +``` + +And with that declaration alone, +Swift has everything it needs to synthesize conditional `Equatable` conformance, +allowing us to do the following: + +```swift +let oneHen = Quantity(count: 1, thing: "🐔") +let twoDucks = Quantity(count: 2, thing: "🦆") +oneHen == twoDucks // false +``` + +{% info %} +Conditional conformance is the same mechanism that provides for +an `Array` whose `Element` is `Equatable` to itself conform to `Equatable`: + +```swift +[🐺, 🐻] == [🐺, 🐻] // true +``` + +{% endinfo %} + +### Equality by Reference + +For reference types, +the notion of equality becomes conflated with identity. +It makes sense that two `Name` structures with the same values would be equal, +but two `Person` objects can have the same name and still be different people. + +For Objective-C-compatible object types, +the `==` operator is already provided from the [`isEqual:`](/equality/) method: + +```swift +import Foundation + +class ObjCObject: NSObject {} + +ObjCObject() == ObjCObject() // false +``` + +For Swift reference types (that is, classes), +equality can be evaluated using the identity equality operator (`===`): + +```swift +class Object: Equatable { + static func == (lhs: Object, rhs: Object) -> Bool { + return lhs === rhs + } +} + +Object() == Object() // false +``` + +That said, +`Equatable` semantics for reference types +are often not as straightforward as a straight identity check, +so before you add conformance to all of your classes, +ask yourself whether it actually makes sense to do so. + +## Comparable + +Building on `Equatable`, +the `Comparable` protocol allows for values to be considered +less than or greater than other values. + +`Comparable` requires implementations for the following operators: + +| Operator | Name | +| -------- | ------------------------ | +| `<` | Less than | +| `<=` | Less than or equal to | +| `>=` | Greater than or equal to | +| `>` | Greater than | + +...so it's surprising that you can get away with only implementing one of them: +the `<` operator. + +Going back to our binomial nomenclature example, +let's extend `Binomen` to conform to `Comparable` +such that values are ordered alphabetically +first by their genus name and then by their species name: + +```swift +extension Binomen: Comparable { + static func < (lhs: Binomen, rhs: Binomen) -> Bool { + if lhs.genus != rhs.genus { + return lhs.genus < rhs.genus + } else { + return lhs.species < rhs.species + } + } +} + + +🐻 > 🐺 // true ("Ursus" lexicographically follows "Canis") +``` + +{% warning %} +Implementing the `<` operator +for types that consider more than one property +is deceptively hard to get right the first time. +Be sure to write test cases to validate correct behavior. +{% endwarning %} + +This is _quite_ clever. +Since the implementations of each comparison operator +can be derived from just `<` and `==`, +all of that functionality is made available automatically through type inference. + +{% info %} +Contrast this with how Ruby and other languages derive +equality and comparison operators from a single operator, +`<=>` _(a.k.a the "UFO operator")_. +A few pitches to bring formalized ordering to Swift +have floated around over the years, +[such as this one](https://gist.github.com/CodaFi/f0347bd37f1c407bf7ea0c429ead380e), +but we haven't seen any real movement in this direction lately. +{% endinfo %} + +### Incomparable Limitations with No Equal + +Unlike `Equatable`, +the Swift compiler can't automatically synthesize conformance to `Comparable`. +But that's not for lack of trying --- _it's just not possible_. + +There are no implicit semantics for comparability +that could be derived from the types of stored properties. +If a type has more than one stored property, +there's no way to determine how they're compared relative to one another. +And even if a type had only a single property whose type was `Comparable`, +there's no guarantee how the ordering of that property +would relate to the ordering of the value as a whole + +### Comparable Benefits + +Conforming to `Comparable` confers a multitude of benefits. + +One such benefit is that +arrays containing values of comparable types +can call methods like `sorted()`, `min()`, and `max()`: + +```swift +let 🐬 = Binomen(genus: "Tursiops", species: "truncatus") +let 🌻 = Binomen(genus: "Helianthus", species: "annuus") +let 🍄 = Binomen(genus: "Amanita", species: "muscaria") +let 🐶 = Binomen(genus: "Canis", species: "domesticus") + +let menagerie = [🐺, 🐻, 🐬, 🌻, 🍄, 🐶] +menagerie.sorted() // [🍄, 🐶, 🐺, 🌻, 🐬, 🐻] +menagerie.min() // 🍄 +menagerie.max() // 🐻 +``` + +Having a defined ordering also lets you create ranges, like so: + +```swift +let lessThan10 = ..<10 +lessThan10.contains(1) // true +lessThan10.contains(11) // false + +let oneToFive = 1...5 +oneToFive.contains(3) // true +oneToFive.contains(7) // false +``` + +--- + +In the Swift standard library, +`Equatable` is a type without an equal; +`Comparable` a protocol without compare. +Take care to adopt them in your own types as appropriate +and you'll benefit greatly. diff --git a/2014-09-22-swift-comparison-protocols.md b/2014-09-22-swift-comparison-protocols.md deleted file mode 100644 index e2a05219..00000000 --- a/2014-09-22-swift-comparison-protocols.md +++ /dev/null @@ -1,268 +0,0 @@ ---- -title: Swift Comparison Protocols -author: Mattt Thompson -category: Swift -tags: swift -excerpt: "Objective-C required us to wax philosophic about the nature of equality and identity. To the relief of any developer less inclined towards handwavy treatises, this is not as much the case for Swift." -status: - swift: 1.2 ---- - -Objective-C required us to [wax philosophic](http://nshipster.com/equality/) about the nature of equality and identity. To the relief of any developer less inclined towards handwavy treatises, this is not as much the case for Swift. - -In Swift, `Equatable` is a fundamental type, from which `Comparable` and `Hashable` are both derived. Together, these protocols form the central point of comparison throughout the language. - -* * * - -## Equatable - -Values of the `Equatable` type can be evaluated for equality and inequality. Declaring a type as equatable bestows several useful abilities, notably the ability values of that type to be found in a containing `Array`. - -For a type to be `Equatable`, there must exist an implementation of the `==` operator function, which accepts a matching type: - -~~~{swift} -func ==(lhs: Self, rhs: Self) -> Bool -~~~ - -For value types, equality is determined by evaluating the equality of each component property. As an example, consider a `Complex` type, which takes a generic type `T`, which conforms to `SignedNumberType`: - -> `SignedNumberType` is a convenient choice for a generic number type, as it inherits from both `Comparable` (and thus `Equatable`, as described in the section) and `IntegerLiteralConvertible`, which `Int`, `Double`, and `Float` all conform to. - -~~~{swift} -struct Complex { - let real: T - let imaginary: T -} -~~~ - -Since a [complex number](http://en.wikipedia.org/wiki/Complex_number) is comprised of a real and imaginary component, two complex numbers are equal if and only if their respective real and imaginary components are equal: - -~~~{swift} -extension Complex: Equatable {} - -// MARK: Equatable - -func ==(lhs: Complex, rhs: Complex) -> Bool { - return lhs.real == rhs.real && lhs.imaginary == rhs.imaginary -} -~~~ - -The result: - -~~~swift -let a = Complex(real: 1.0, imaginary: 2.0) -let b = Complex(real: 1.0, imaginary: 2.0) - -a == b // true -a != b // false -~~~ - -> As described in [the article about Swift Default Protocol Implementations](http://nshipster.com/swift-default-protocol-implementations/), an implementation of `!=` is automatically derived from the provided `==` operator by the standard library. - -For reference types, the equality becomes conflated with identity. It makes sense that two `Name` structs with the same values would be equal, but two `Person` objects can have the same name, but be different people. - -For Objective-C-compatible object types, the `==` operator is already provided from the `isEqual:` method: - -~~~{swift} -class ObjCObject: NSObject {} - -ObjCObject() == ObjCObject() // false -~~~ - -For Swift reference types, equality can be evaluated as an identity check on an `ObjectIdentifier` constructed with an instance of that type: - -~~~{swift} -class Object: Equatable {} - -// MARK: Equatable - -func ==(lhs: Object, rhs: Object) -> Bool { - return ObjectIdentifier(lhs) == ObjectIdentifier(rhs) -} - -Object() == Object() // false -~~~ - -## Comparable - -Building on `Equatable`, the `Comparable` protocol allows for more specific inequality, distinguishing cases where the left hand value is greater than or less than the right hand value. - -Types conforming to the `Comparable` protocol provide the following operators: - -~~~{swift} -func <=(lhs: Self, rhs: Self) -> Bool -func >(lhs: Self, rhs: Self) -> Bool -func >=(lhs: Self, rhs: Self) -> Bool -~~~ - -What's interesting about this list, however, is not so much what is _included_, but rather what's _missing_. - -The first and perhaps most noticeable omission is `==`, since `>=` is a logical disjunction of `>` and `==` comparisons. As a way of reconciling this, `Comparable` inherits from `Equatable`, which provides `==`. - -The second omission is a bit more subtle, and is actually the key to understanding what's going on here: `<`. What happened to the "less than" operator? It's defined by the `_Comparable` protocol. Why is this significant? As described in [the article about Swift Default Protocol Implementations](http://nshipster.com/swift-default-protocol-implementations/), the Swift Standard Library provides a default implementation of the `Comparable` protocol based entirely on the existential type `_Comparable`. This is actually _really_ clever. Since the implementations of all of the comparison functions can be derived from just `<` and `==`, all of that functionality is made available automatically through type inference. - -> Contrast this with, for example, how Ruby derives equality and comparison operators from a single operator, `<=>` (a.k.a the "UFO operator"). [Here's how this could be implemented in Swift](https://gist.github.com/mattt/7e4db72ce1b6c8a18bb4). - -As a more complex example, consider a `CSSSelector` struct, which implements [cascade ordering](http://www.w3.org/TR/CSS2/cascade.html#cascading-order) of selectors: - -~~~{swift} -import Foundation - -struct CSSSelector { - let selector: String - - struct Specificity { - let id: Int - let `class`: Int - let element: Int - - init(_ components: [String]) { - var (id, `class`, element) = (0, 0, 0) - for token in components { - if token.hasPrefix("#") { - id++ - } else if token.hasPrefix(".") { - `class`++ - } else { - element++ - } - } - - self.id = id - self.`class` = `class` - self.element = element - } - } - - let specificity: Specificity - - init(_ string: String) { - self.selector = string - - // Naïve tokenization, ignoring operators, pseudo-selectors, and `style=`. - let components: [String] = self.selector.componentsSeparatedByCharactersInSet(NSCharacterSet.whitespaceCharacterSet()) - self.specificity = Specificity(components) - } -} -~~~ - -Where as CSS selectors are evaluated by specificity rank and order, two selectors are only really equal if they resolve to the same elements: - -~~~{swift} -extension CSSSelector: Equatable {} - -// MARK: Equatable - -func ==(lhs: CSSSelector, rhs: CSSSelector) -> Bool { - // Naïve equality that uses string comparison rather than resolving equivalent selectors - return lhs.selector == rhs.selector -} -~~~ - -Instead, selectors are actually compared in terms of their specificity: - -~~~{swift} -extension CSSSelector.Specificity: Comparable {} - -// MARK: Comparable - -func <(lhs: CSSSelector.Specificity, rhs: CSSSelector.Specificity) -> Bool { - return lhs.id < rhs.id || - (lhs.id == rhs.id && lhs.`class` < rhs.`class`) || - (lhs.id == rhs.id && lhs.`class` == rhs.`class` && lhs.element < rhs.element) -} - -// MARK: Equatable - -func ==(lhs: CSSSelector.Specificity, rhs: CSSSelector.Specificity) -> Bool { - return lhs.id == rhs.id && - lhs.`class` == rhs.`class` && - lhs.element == rhs.element -} -~~~ - -Bringing everything together: - -> For clarity, assume `CSSSelector` [conforms to `StringLiteralConvertible`](http://nshipster.com/swift-literal-convertible/). - -~~~{swift} -let a: CSSSelector = "#logo" -let b: CSSSelector = "html body #logo" -let c: CSSSelector = "body div #logo" -let d: CSSSelector = ".container #logo" - -b == c // false -b.specificity == c.specificity // true -c.specificity < a.specificity // false -d.specificity > c.specificity // true -~~~ - -## Hashable - -Another important protocol derived from `Equatable` is `Hashable`. - -Only `Hashable` types can be stored as the key of a Swift `Dictionary`: - -~~~{swift} -struct Dictionary : CollectionType, DictionaryLiteralConvertible { ... } -~~~ - -For a type to conform to `Hashable`, it must provide a getter for the `hashValue` property. - -~~~{swift} -protocol Hashable : Equatable { - /// Returns the hash value. The hash value is not guaranteed to be stable - /// across different invocations of the same program. Do not persist the hash - /// value across program runs. - /// - /// The value of `hashValue` property must be consistent with the equality - /// comparison: if two values compare equal, they must have equal hash - /// values. - var hashValue: Int { get } -} -~~~ - -Determining the [optimal hashing value](http://en.wikipedia.org/wiki/Perfect_hash_function) is way outside the scope of this article. Fortunately, most values can derive an adequate hash value from an `XOR` of the hash values of its component properties. - -The following built-in Swift types implement `hashValue`: - -- `Double` -- `Float`, `Float80` -- `Int`, `Int8`, `Int16`, `Int32`, `Int64` -- `UInt`, `UInt8`, `UInt16`, `UInt32`, `UInt64` -- `String` -- `UnicodeScalar` -- `ObjectIdentifier` - -Based on this, here's how a struct representing [Binomial Nomenclature in Biological Taxonomy](http://en.wikipedia.org/wiki/Binomial_nomenclature): - -~~~{swift} -struct Binomen { - let genus: String - let species: String -} - -// MARK: Hashable - -extension Binomen: Hashable { - var hashValue: Int { - return genus.hashValue ^ species.hashValue - } -} - -// MARK: Equatable - -func ==(lhs: Binomen, rhs: Binomen) -> Bool { - return lhs.genus == rhs.genus && lhs.species == rhs.species -} -~~~ - -Being able to hash this type makes it possible to key common name to the "Latin name": - -~~~{swift} -var commonNames: [Binomen: String] = [:] -commonNames[Binomen(genus: "Canis", species: "lupis")] = "Grey Wolf" -commonNames[Binomen(genus: "Canis", species: "rufus")] = "Red Wolf" -commonNames[Binomen(genus: "Canis", species: "latrans")] = "Coyote" -commonNames[Binomen(genus: "Canis", species: "aureus")] = "Golden Jackal" -~~~ diff --git a/2014-09-30-uialertcontroller.md b/2014-09-30-uialertcontroller.md index 58332dc7..aeabf8aa 100644 --- a/2014-09-30-uialertcontroller.md +++ b/2014-09-30-uialertcontroller.md @@ -1,6 +1,6 @@ --- title: UIAlertController -author: Mattt Thompson +author: Mattt category: Cocoa excerpt: "Did you know that `UIAlertView` and `UIActionSheet` (as well as their respective delegate protocols) are deprecated in iOS 8? It's true." status: @@ -43,7 +43,7 @@ Rather than specifying all of an alert's buttons in an initializer, instances of ### A Standard Alert -![A Standard Alert]({{ site.asseturl }}/uialertcontroller-alert-defautl-style.png) +![A Standard Alert]({% asset uialertcontroller-alert-defautl-style.png @path %}) #### The Old Way: UIAlertView @@ -56,7 +56,7 @@ alertView.show() func alertView(alertView: UIAlertView, clickedButtonAtIndex buttonIndex: Int) { switch buttonIndex { - // ... + <#...#> } } ``` @@ -67,25 +67,25 @@ func alertView(alertView: UIAlertView, clickedButtonAtIndex buttonIndex: Int) { let alertController = UIAlertController(title: "Default Style", message: "A standard alert.", preferredStyle: .Alert) let cancelAction = UIAlertAction(title: "Cancel", style: .Cancel) { (action) in - // ... + <#...#> } alertController.addAction(cancelAction) let OKAction = UIAlertAction(title: "OK", style: .Default) { (action) in - // ... + <#...#> } alertController.addAction(OKAction) self.presentViewController(alertController, animated: true) { - // ... + <#...#> } ``` ### A Standard Action Sheet -![A Standard Action Sheet]({{ site.asseturl }}/uialertcontroller-action-sheet-automatic-style.png) +![A Standard Action Sheet]({% asset uialertcontroller-action-sheet-automatic-style.png @path %}) -#### The Old Way: UIActionSheet +#### UIActionSheet ```swift let actionSheet = UIActionSheet(title: "Takes the appearance of the bottom bar if specified; otherwise, same as UIActionSheetStyleDefault.", delegate: self, cancelButtonTitle: "Cancel", destructiveButtonTitle: "Destroy", otherButtonTitles: "OK") @@ -101,18 +101,18 @@ func actionSheet(actionSheet: UIActionSheet, clickedButtonAtIndex buttonIndex: I } ``` -#### The New Way: UIAlertController +#### UIAlertController ```swift let alertController = UIAlertController(title: nil, message: "Takes the appearance of the bottom bar if specified; otherwise, same as UIActionSheetStyleDefault.", preferredStyle: .ActionSheet) let cancelAction = UIAlertAction(title: "Cancel", style: .Cancel) { (action) in - // ... + <#...#> } alertController.addAction(cancelAction) let OKAction = UIAlertAction(title: "OK", style: .Default) { (action) in - // ... + <#...#> } alertController.addAction(OKAction) @@ -122,7 +122,7 @@ let destroyAction = UIAlertAction(title: "Destroy", style: .Destructive) { (acti alertController.addAction(destroyAction) self.presentViewController(alertController, animated: true) { - // ... + <#...#> } ``` @@ -132,7 +132,7 @@ self.presentViewController(alertController, animated: true) { ### Alert with Destructive Button -![Alert with Destructive Button]({{ site.asseturl }}/uialertcontroller-alert-cancel-destroy.png) +![Alert with Destructive Button]({% asset uialertcontroller-alert-cancel-destroy.png @path %}) The type of an action is specified by `UIAlertActionStyle`, which has three values: @@ -156,13 +156,13 @@ let destroyAction = UIAlertAction(title: "Destroy", style: .Destructive) { (acti alertController.addAction(destroyAction) self.presentViewController(alertController, animated: true) { - // ... + <#...#> } ``` ### Alert with >2 Buttons -![Alert with More Than 2 Buttons]({{ site.asseturl }}/uialertcontroller-alert-one-two-three-cancel.png) +![Alert with More Than 2 Buttons]({% asset uialertcontroller-alert-one-two-three-cancel.png @path %}) With one or two actions, buttons in an alert are stacked horizontally. Any more than that, though, and it takes on a display characteristic closer to an action sheet: @@ -180,7 +180,7 @@ alertController.addAction(cancelAction) ### Creating a Login Form -![Creating a Login Form]({{ site.asseturl }}/uialertcontroller-alert-username-password-login-forgot-password-cancel.png) +![Creating a Login Form]({% asset uialertcontroller-alert-username-password-login-forgot-password-cancel.png @path %}) iOS 5 added the `alertViewStyle` property to `UIAlertView`, which exposed much sought-after private APIs that allowed login and password fields to be displayed in an alert, as seen in several built-in system apps. @@ -218,7 +218,7 @@ alertController.addAction(cancelAction) ### Creating a Sign Up Form -![Creating a Sign Up Form]({{ site.asseturl }}/uialertcontroller-alert-sign-up.png) +![Creating a Sign Up Form]({% asset uialertcontroller-alert-sign-up.png @path %}) `UIAlertController` goes even further to allow any number of text fields, each with the ability to be configured and customized as necessary. This makes it possible to create a fully-functional signup form in a single modal alert: diff --git a/2014-10-06-swift-system-version-checking.md b/2014-10-06-swift-system-version-checking.md index 0c0a2125..ec437771 100644 --- a/2014-10-06-swift-system-version-checking.md +++ b/2014-10-06-swift-system-version-checking.md @@ -1,11 +1,11 @@ --- title: Swift System Version Checking -author: Mattt Thompson +author: Mattt category: Swift tags: swift excerpt: "C uses preprocessor directives capable of unspeakable evil. Swift has a safe subset of preprocessor directives. So how do we check system version for API compatibility?" status: - swift: 1.0 + swift: 4.0 --- While it's not accurate to say that Swift is "Objective-C without the C", it's for lack of resemblance to Objective-C, not the absence of C. Swift is _vehemently_ **_not_** C. @@ -13,71 +13,72 @@ While it's not accurate to say that Swift is "Objective-C without the C", it's f Swift certainly draws inspiration from Haskell, Rust, Python, D, and other modern languages, but one can perhaps best understand the language as a rejection of everything that's broken in C: - C is **unsafe** by default. Swift is **safe** by default _(hence the `unsafe` naming of pointer manipulation functions)_. -- C has **undefined behavior**. Swift has **well-defined behavior** _(or at least theoretically; the compiler tools still have some catching up to do)_. +- C has **undefined behavior**. Swift has **well-defined behavior**. - C uses **preprocessor directives capable of unspeakable evil**. Swift has a **safe subset of preprocessor directives**. > One could go as far to say that Swift's type system was specifically designed out of _spite_ for C++. In Objective-C, checking for the availability of an API was accomplished through a combination of C preprocessor directives, conditionals on `class`, `respondsToSelector:`, and `instancesRespondToSelector:`: -~~~{objective-c} +```objc #if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000 if ([NSURLSession class] && [NSURLSessionConfiguration respondsToSelector:@selector(backgroundSessionConfigurationWithIdentifier:)]) { - // ... + <#...#> } #endif -~~~ +``` -However, as noted previously, Swift's compiler directives are [extremely constrained](https://developer.apple.com/library/ios/documentation/Swift/Conceptual/BuildingCocoaApps/InteractingWithCAPIs.html#//apple_ref/doc/uid/TP40014216-CH8-XID_20), allowing only for compiler flags and conditional compilation against specific operating systems and architectures: +However, as noted previously, Swift's compiler directives are [extremely constrained](https://developer.apple.com/library/ios/documentation/Swift/Conceptual/BuildingCocoaApps/InteractingWithCAPIs.html#//apple_ref/doc/uid/TP40014216-CH8-XID_20), allowing only for compiler flags and conditional compilation against [specific operating systems, architectures, and language versions](https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Statements.html#//apple_ref/doc/uid/TP40014097-CH33-ID539): -~~~{swift} +```swift #if DEBUG println("OTHER_SWIFT_FLAGS = -D DEBUG") #endif -~~~ +``` -| Function | Valid Arguments | -|----------|------------------------------------| -| `os()` | `OSX`, `iOS` | -| `arch()` | `x86_64`, `arm`, `arm64`, `i386` | +| Function | Valid Arguments | +|-----------|--------------------------------------------| +| `os()` | `macOS`, `iOS`, `watchOS`, `tvOS`, `Linux` | +| `arch()` | `x86_64`, `arm`, `arm64`, `i386` | +| `swift()` | `>=` followed by a version number | -~~~{swift} +```swift #if os(iOS) var image: UIImage? -#elseif os(OSX) +#elseif os(macOS) var image: NSImage? #endif -~~~ +``` -Unfortunately, `os()` does not offer any insight into the specific version of OS X or iOS, which means that checks must be made at runtime. And with Swift's less-forgiving [treatment of `nil`](http://nshipster.com/nil/), checking for constants Objective-C-style results in a crash. +Unfortunately, `os()` doesn't offer any insight into the specific version of macOS or iOS, which means that checks must be made at runtime. And with Swift's less-forgiving [treatment of `nil`](http://nshipster.com/nil/), checking for constants Objective-C-style results in a crash. So how do you check the system version in Swift to determine API availability? Read on to find out. * * * -## NSProcessInfo +## ProcessInfo -Anticipating the need for a Swift-friendly API for determining API version at runtime, iOS 8 introduces the `operatingSystemVersion` property and `isOperatingSystemAtLeastVersion` method on `NSProcessInfo`. Both APIs use a new `NSOperatingSystemVersion` value type, which contains the `majorVersion`, `minorVersion`, and `patchVersion`. +Anticipating the need for a Swift-friendly API for determining API version at runtime, iOS 8 introduces the `operatingSystemVersion` property and `isOperatingSystemAtLeast` method on `ProcessInfo`. Both APIs use a new `OperatingSystemVersion` value type, which contains the `majorVersion`, `minorVersion`, and `patchVersion`. > Apple software releases follow [semantic versioning](http://semver.org) conventions. -### isOperatingSystemAtLeastVersion +### isOperatingSystemAtLeast -For a simple check, like "is this app running on iOS 9?", `isOperatingSystemAtLeastVersion` is the most straightforward approach. +For a simple check, like "is this app running on iOS 9?", `isOperatingSystemAtLeast` is the most straightforward approach. -~~~{swift} -if NSProcessInfo().isOperatingSystemAtLeastVersion(NSOperatingSystemVersion(majorVersion: 9, minorVersion: 0, patchVersion: 0)) { - println("iOS >= 9.0.0") +```swift +if ProcessInfo().isOperatingSystemAtLeast(OperatingSystemVersion(majorVersion: 9, minorVersion: 0, patchVersion: 0)) { + print("iOS >= 9.0.0") } -~~~ +``` ### operatingSystemVersion For more involved version comparison, the `operatingSystemVersion` can be inspected directly. Combine this with Swift pattern matching and `switch` statements for syntactic concision: -~~~{swift} -let os = NSProcessInfo().operatingSystemVersion +```swift +let os = ProcessInfo().operatingSystemVersion switch (os.majorVersion, os.minorVersion, os.patchVersion) { case (8, 0, _): println("iOS >= 8.0.0, < 8.1.0") @@ -89,46 +90,44 @@ default: // this code will have already crashed on iOS 7, so >= iOS 10.0 println("iOS >= 10.0.0") } -~~~ +``` ## UIDevice systemVersion -Ironically, the new `NSProcessInfo` APIs aren't especially useful at the time of writing, since they're unavailable for iOS 7. - As an alternative, one can use the `systemVersion` property `UIDevice`: -~~~{swift} -switch UIDevice.currentDevice().systemVersion.compare("8.0.0", options: NSStringCompareOptions.NumericSearch) { -case .OrderedSame, .OrderedDescending: - println("iOS >= 8.0") -case .OrderedAscending: - println("iOS < 8.0") +```swift +switch UIDevice.current.systemVersion.compare("8.0.0", options: .numeric) { +case .orderedSame, .orderedDescending: + print("iOS >= 8") +case .orderedAscending: + print("iOS < 8.0") } -~~~ +``` -> Use `NSStringCompareOptions.NumericSearch` when comparing version number strings to ensure that, for example, `"2.5" < "2.10"`. +> Use `String.CompareOptions.numeric` when comparing version number strings to ensure that, for example, `"2.5" < "2.10"`. -String comparison and `NSComparisonResult` aren't as sexy as a dedicated value type like `NSOperatingSystemVersion`, but it gets the job done all the same. +String comparison and `ComparisonResult` aren't as sexy as a dedicated value type like `OperatingSystemVersion`, but it gets the job done all the same. -## NSAppKitVersionNumber +## NSAppKitVersion Another approach to determining API availability is to check framework version numbers. Unfortunately, Foundation's `NSFoundationVersionNumber` and Core Foundation's `kCFCoreFoundationVersionNumber` have historically been out of date, missing constants for past OS releases. -This is a dead-end for iOS, but OS X can pretty reliably check against the version of AppKit, with `NSAppKitVersionNumber`: +This is a dead-end for iOS, but macOS can pretty reliably check against the version of AppKit, with `NSAppKitVersion`: -~~~{swift} -if rint(NSAppKitVersionNumber) > NSAppKitVersionNumber10_9 { - println("OS X >= 10.10") +```swift +if NSAppKitVersion.current.rawValue >= .macOS10_10.rawValue { + println("macOS >= 10.10") } -~~~ +``` -> Apple uses `rint` in sample code to round off version numbers for `NSAppKitVersionNumber` comparison. +If you pair this with an extension to make `NSAppKitVersion` conform to `Comparable`, you can remove the `.rawValue`s as well. * * * To summarize, here's what you need to know about checking the system version in Swift: -- Use `#if os(iOS)` preprocessor directives to distinguish between iOS (UIKit) and OS X (AppKit) targets. -- For minimum deployment targets of iOS 8.0 or above, use `NSProcessInfo` `operatingSystemVersion` or `isOperatingSystemAtLeastVersion`. -- For minimum deployment targets of iOS 7.1 or below, use `compare` with `NSStringCompareOptions.NumericSearch` on `UIDevice` `systemVersion`. -- For OS X deployment targets, compare `NSAppKitVersionNumber` against available AppKit constants. +- Use `#if os(iOS)` preprocessor directives to distinguish between iOS (UIKit) and macOS (AppKit) targets. +- For minimum deployment targets of iOS 8.0 or above, use `ProcessInfo` `operatingSystemVersion` or `isOperatingSystemAtLeast`. +- For minimum deployment targets of iOS 7.1 or below, use `compare` with `String.CompareOptions.numeric` on `UIDevice` `systemVersion`. +- For macOS deployment targets, compare `NSAppKitVersion` against available AppKit constants. diff --git a/2014-10-17-inter-process-communication.md b/2014-10-17-inter-process-communication.md index c21104e8..c63aafc9 100644 --- a/2014-10-17-inter-process-communication.md +++ b/2014-10-17-inter-process-communication.md @@ -1,15 +1,13 @@ --- title: Inter-Process Communication -author: Mattt Thompson -category: "" +author: Mattt +category: Miscellaneous tags: cfhipsterref excerpt: "In many ways, the story of Apple has been about fusing together technologies through happy accidents of history to create something better than before: OS X as a hybrid of MacOS & NeXTSTEP. Objective-C as the combination of Smalltalk's OOP paradigm and C. iCloud as the byproduct of MobileMe and actual clouds (presumably)." status: - swift: t.b.c. + swift: t.b.c. --- -IPC Postman, illustrated by Conor Heelan - In many ways, the story of Apple has been about fusing together technologies through happy accidents of history to create something better than before: OS X as a hybrid of MacOS & NeXTSTEP. Objective-C as the combination of Smalltalk's OOP paradigm and C. iCloud as the byproduct of MobileMe and _actual_ clouds (presumably). While this is true for many aspects of Apple's technology stack, inter-process communication is a flagrant counter-example. @@ -33,7 +31,7 @@ Mach ports are light-weight and powerful, but poorly documented Sending a message over a given Mach port comes down to a single `mach_msg_send` call, but it takes a bit of configuration in order to build the message to be sent: -~~~{objective-c} +```objc natural_t data; mach_port_t port; @@ -62,13 +60,13 @@ message.type = (mach_msg_type_descriptor_t) { mach_msg_return_t error = mach_msg_send(&message.header); if (error == MACH_MSG_SUCCESS) { - // ... + <#...#> } -~~~ +``` Things are a little easier on the receiving end, since the message only needs to be declared, not initialized: -~~~{objective-c} +```objc mach_port_t port; struct { @@ -82,26 +80,26 @@ mach_msg_return_t error = mach_msg_receive(&message.header); if (error == MACH_MSG_SUCCESS) { natural_t data = message.type.pad1; - // ... + <#...#> } -~~~ +``` Fortunately, higher-level APIs for Mach ports are provided by Core Foundation and Foundation. `CFMachPort` / `NSMachPort` are wrappers on top of the kernel APIs that can be used as a runloop source, while `CFMessagePort` / `NSMessagePort` facilitate synchronous communication between two ports. `CFMessagePort` is actually quite nice for simple one-to-one communication. In just a few lines of code, a local named port can be attached as a runloop source to have a callback run each time a message is received: -~~~{objective-c} +```objc static CFDataRef Callback(CFMessagePortRef port, SInt32 messageID, CFDataRef data, void *info) { - // ... + <#...#> } CFMessagePortRef localPort = CFMessagePortCreateLocal(nil, - CFSTR("com.example.app.port.server"), + CFSTR("com.example.app.port"), Callback, nil, nil); @@ -112,18 +110,18 @@ CFRunLoopSourceRef runLoopSource = CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource, kCFRunLoopCommonModes); -~~~ +``` Sending data is straightforward as well. Just specify the remote port, the message payload, and timeouts for sending and receiving. `CFMessagePortSendRequest` takes care of the rest: -~~~{objective-c} +```objc CFDataRef data; SInt32 messageID = 0x1111; // Arbitrary CFTimeInterval timeout = 10.0; CFMessagePortRef remotePort = CFMessagePortCreateRemote(nil, - CFSTR("com.example.app.port.client")); + CFSTR("com.example.app.port")); SInt32 status = CFMessagePortSendRequest(remotePort, @@ -134,11 +132,11 @@ SInt32 status = NULL, NULL); if (status == kCFMessagePortSuccess) { - // ... + <#...#> } -~~~ +``` -## Distributed Notifications +## Distributed Notifications There are many ways for objects to communicate with one another in Cocoa: @@ -148,14 +146,14 @@ Each application manages its own `NSNotificationCenter` instance for infra-appli 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: -~~~{objective-c} +```objc static void Callback(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef userInfo) { - // ... + <#...#> } CFNotificationCenterRef distributedCenter = @@ -170,11 +168,11 @@ CFNotificationCenterAddObserver(distributedCenter, CFSTR("notification.identifier"), NULL, behavior); -~~~ +``` Sending a distributed notification is even simpler; just post the identifier, object, and user info: -~~~{objective-c} +```objc void *object; CFDictionaryRef userInfo; @@ -186,17 +184,17 @@ CFNotificationCenterPostNotification(distributedCenter, object, userInfo, true); -~~~ +``` Of all of the ways to link up two applications, distributed notifications are by far the easiest. It wouldn't be a great idea to use them to send large payloads, but for simple tasks like synchronizing preferences or triggering a data fetch, distributed notifications are perfect. -## Distributed Objects +## 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. Vending an object with DO is just a matter of setting up an `NSConnection` and registering it with a particular name: -~~~{objective-c} +```objc @protocol Protocol; id vendedObject; @@ -204,14 +202,14 @@ id vendedObject; NSConnection *connection = [[NSConnection alloc] init]; [connection setRootObject:vendedObject]; [connection registerName:@"server"]; -~~~ +``` Another application would then create a connection registered for that same registered name, and immediately get an atomic proxy that functioned as if it were that original object: -~~~{objective-c} +```objc id proxy = [NSConnection rootProxyForConnectionWithRegisteredName:@"server" host:nil]; [proxy setProtocolForProxy:@protocol(Protocol)]; -~~~ +``` Any time a distributed object proxy is messaged, a Remote Procedure Call (RPC) would be made over the `NSConnection` to evaluate the message against the vended object and return the result back to the proxy. @@ -231,7 +229,7 @@ All that's really left are traces of the annotations used by Distributed Objects - `bycopy`: Return a copy of the object - `byref`: Return a proxy of the object -## AppleEvents & AppleScript +## 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. @@ -241,11 +239,11 @@ AppleScript uses a natural language syntax, intended to be more accessible to no 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: -~~~{Applescript} +```applescript tell application "Safari" - set the URL of the front document to "http://nshipster.com" + set the URL of the front document to "https://nshipster.com" end tell -~~~ +``` In many ways, AppleScript's natural language syntax is more of a liability than an asset. English, much like any other spoken language, has a great deal of redundancy and ambiguity built into normal constructions. While this is perfectly acceptable for humans, computers have a tough time resolving all of this. @@ -253,36 +251,36 @@ Even for a seasoned Objective-C developer, it's nearly impossible to write Apple Fortunately, the Scripting Bridge provides a proper programming interface for Cocoa applications. -### Cocoa Scripting Bridge +### Cocoa Scripting Bridge In order to interact with an application through the Scripting Bridge, a programming interface must first be generated: -~~~ +``` $ sdef /Applications/Safari.app | sdp -fh --basename Safari -~~~ +``` `sdef` generates scripting definition files for an application. These files can then be piped into `sdp` to be converted into another format—in this case, a C header. The resulting `.h` file can then be added and `#import`-ed into a project to get a first-class object interface to that application. Here's the same example as before, expressed using the Cocoa Scripting Bridge: -~~~{objective-c} +```objc #import "Safari.h" SafariApplication *safari = [SBApplication applicationWithBundleIdentifier:@"com.apple.Safari"]; for (SafariWindow *window in safari.windows) { if (window.visible) { - window.currentTab.URL = [NSURL URLWithString:@"http://nshipster.com"]; + window.currentTab.URL = [NSURL URLWithString:@"https://nshipster.com"]; break; } } -~~~ +``` 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). 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 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. @@ -290,24 +288,24 @@ On OS X there's `NSPasteboard`, and on iOS there's `UIPasteboard`. They're prett Programmatically writing to the Pasteboard is nearly as simple as invoking `Edit > Copy` in a GUI application: -~~~{objective-c} +```objc NSImage *image; NSPasteboard *pasteboard = [NSPasteboard generalPasteboard]; [pasteboard clearContents]; [pasteboard writeObjects:@[image]]; -~~~ +``` The reciprocal paste action is a bit more involved, requiring an iteration over the Pasteboard contents: -~~~{objective-c} +```objc NSPasteboard *pasteboard = [NSPasteboard generalPasteboard]; if ([pasteboard canReadObjectForClasses:@[[NSImage class]] options:nil]) { NSArray *contents = [pasteboard readObjectsForClasses:@[[NSImage class]] options: nil]; NSImage *image = [contents firstObject]; } -~~~ +``` What makes Pasteboard especially compelling as a mechanism for transferring data is the notion of simultaneously providing multiple representations of content copied onto a pasteboard. For example, a selection of text may be copied as both rich text (RTF) and plain text (TXT), allowing, for example, a WYSIWYG editor to preserve style information by grabbing the rich text representation, and a code editor to use just the plain text representation. @@ -315,7 +313,7 @@ These representations can even be provided on an on-demand basis by conforming t Each representation is identified by a Unique Type Identifier (UTI), a concept discussed in greater detail in the next chapter. -## XPC +## 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. The 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. @@ -323,9 +321,9 @@ It's a replacement for `NSTask`, and a whole lot more. 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: -~~~{bash} +```bash $ find /Applications -name \*.xpc -~~~ +``` 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 application. Common themes in applications emerge, like services for image and video conversion, system calls, webservice integration, and 3rd party authentication. @@ -339,7 +337,7 @@ XPC services either reside within an application bundle or are advertised to run Services call `xpc_main` with an event handler to receive new XPC connections: -~~~{objective-c} +```objc static void connection_handler(xpc_connection_t peer) { xpc_connection_set_event_handler(peer, ^(xpc_object_t event) { peer_event_handler(peer, event); @@ -352,28 +350,28 @@ int main(int argc, const char *argv[]) { xpc_main(connection_handler); exit(EXIT_FAILURE); } -~~~ +``` -Each XPC connection is one-to-one, meaning that the service operates on distinct connections, with each call to `xpc_connection_create` creating a new peer. : +Each XPC connection is one-to-one, meaning that the service operates on distinct connections, with each call to `xpc_connection_create` creating a new peer. : -~~~{objective-c} +```objc xpc_connection_t c = xpc_connection_create("com.example.service", NULL); xpc_connection_set_event_handler(c, ^(xpc_object_t event) { - // ... + <#...#> }); xpc_connection_resume(c); -~~~ +``` When a message is sent over an XPC connection, it is automatically dispatched onto a queue managed by the runtime. As soon as the connection is opened on the remote end, messages are dequeued and sent. Each message is a dictionary, with string keys and strongly-typed values: -~~~{objective-c} +```objc xpc_dictionary_t message = xpc_dictionary_create(NULL, NULL, 0); xpc_dictionary_set_uint64(message, "foo", 1); xpc_connection_send_message(c, message); xpc_release(message) -~~~ +``` XPC objects operate on the following primitive types: @@ -391,7 +389,7 @@ XPC objects operate on the following primitive types: XPC offers a convenient way to convert from the `dispatch_data_t` data type, which simplifies the workflow from GCD to XPC: -~~~{objective-c} +```objc void *buffer; size_t length; dispatch_data_t ddata = @@ -401,26 +399,26 @@ dispatch_data_t ddata = DISPATCH_DATA_DESTRUCTOR_MUNMAP); xpc_object_t xdata = xpc_data_create_with_dispatch_data(ddata); -~~~ +``` -~~~{objective-c} +```objc 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 +### Registering Services XPC can also be registered as launchd jobs, configured to automatically start on matching IOKit events, BSD notifications or CFDistributedNotifications. These criteria are specified in a service's `launchd.plist` file: .launchd.plist -~~~{xml} +```xml LaunchEvents com.apple.iokit.matching @@ -440,14 +438,14 @@ XPC can also be registered as launchd jobs, configured to automatically start on -~~~ +``` A recent addition to `launchd` property lists is the `ProcessType` key, which describe at a high level the intended purpose of the launch agent. Based on the prescribed contention behavior, the operating system will automatically throttle CPU and I/O bandwidth accordingly. #### Process Types and Contention Behavior | Process Type | Contention Behavior | -|--------------|---------------------------------------------------| +| ------------ | ------------------------------------------------- | | Standard | Default value | | Adaptive | Contend with apps when doing work on their behalf | | Background | Never contend with apps | @@ -455,7 +453,7 @@ A recent addition to `launchd` property lists is the `ProcessType` key, which de To register a service to run approximately every 5 minutes (allowing a grace period for system resources to become more available before scheduling at a more aggressive priority), a set of criteria is passed into `xpc_activity_register`: -~~~{objective-c} +```objc xpc_object_t criteria = xpc_dictionary_create(NULL, NULL, 0); xpc_dictionary_set_int64(criteria, XPC_ACTIVITY_INTERVAL, 5 * 60); xpc_dictionary_set_int64(criteria, XPC_ACTIVITY_GRACE_PERIOD, 10 * 60); @@ -474,4 +472,4 @@ xpc_activity_register("com.example.app.activity", xpc_activity_set_state(activity, XPC_ACTIVITY_STATE_DONE); }); }); -~~~ +``` diff --git a/2014-10-28-cmdevicemotion.md b/2014-10-28-cmdevicemotion.md index cb72f168..7fcc527a 100644 --- a/2014-10-28-cmdevicemotion.md +++ b/2014-10-28-cmdevicemotion.md @@ -1,233 +1,387 @@ --- title: CMDeviceMotion -author: Nate Cook +author: Nate Cook, Mattt category: Cocoa -excerpt: "Beneath the smooth glass of each shiny iPhone, nestled on a logic board between touch screen controllers and Apple-designed SoCs, the gyroscope and accelerometer sit largely neglected." +excerpt: > + Beneath the smooth glass of each iPhone + an array of sensors sits nestled on the logic board, + sending a steady stream of data to a motion coprocessor. +revisions: + "2014-10-28": Original publication + "2018-09-12": Updated for Swift 4.2 status: - swift: 2.2 - reviewed: April 10, 2016 + swift: 4.2 + reviewed: September 12, 2018 --- -Beneath the smooth glass of each shiny iPhone, nestled on a logic board between touch screen controllers and Apple-designed SoCs, the gyroscope and accelerometer sit largely neglected. +Beneath the smooth glass of each iPhone +an array of sensors sits nestled on the logic board, +sending a steady stream of data to a motion coprocessor. -Need it be so? The *[Core Motion framework](https://developer.apple.com/library/ios/documentation/coremotion/reference/coremotion_reference/index.html)* makes it surprisingly easy to harness these sensors, opening the door to user interactions above and beyond the tapping and swiping we do every day. - -> For devices that include the M7 or M8 motion processor, the Core Motion framework also provides access to stored motion activity, such as step counts, stairs climbed, and movement type (walking, cycling, etc.). +The [Core Motion framework](https://developer.apple.com/documentation/coremotion) +makes it surprisingly easy to harness these sensors, +opening the door to user interactions above and beyond +the tapping and swiping we do every day. --- -Core Motion allows a developer to observe and respond to the motion and orientation of an iOS device by inspecting the raw and processed data from a combination of built-in sensors, including the accelerometer, gyroscope, and magnetometer. +Core Motion lets you observe and respond to changes in +the position and orientation of an iOS or watchOS device. +Thanks to their dedicated motion coprocessor, +iPhones, iPads, and Apple Watches can continuously read and process inputs +from built-in sensors without taxing the CPU or draining the battery. + +Accelerometer and gyroscope data is projected into a 3D coordinate space, +with the center of the device at the origin. -Both accelerometer and gyroscope data are presented in terms of three axes that run through the iOS device. For an iPhone held in portrait orientation, the X-axis runs through the device from left (negative values) to right (positive values), the Y-axis through the device from bottom (-) to top (+), and the Z-axis runs perpendicularly through the screen from the back (-) to the front (+). +![Device X-, Y-, and Z-axes]({% asset cmdm-axes.png @path %}) -The composited device motion data are presented in a few different ways, each with their own uses, as we'll see below. +For an iPhone held in portrait orientation: -![Device X-, Y-, and Z-axes]({{ site.asseturl }}/cmdm-axes.png) +- The X-axis runs the width of the device + from left (negative values) to right (positive values), +- The Y-axis runs the height of the device + from bottom (-) to top (+), +- The Z-axis runs perpendicularly through the screen + from the back (-) to the front (+). ## CMMotionManager -The `CMMotionManager` class provides access to all the motion data on an iOS device. Interestingly, Core Motion provides both "pull" and "push" access to motion data. To "pull" motion data, you can access the current status of any sensor or the composited data as read-only properties of `CMMotionManager`. To receive "pushed" data, you start the collection of your desired data with a block or closure that receives updates at a specified interval. +The `CMMotionManager` class is responsible for +providing data about the motion of the current device. +To keep performance at the highest level, +create and use a single shared `CMMotionManager` instance throughout your app. + +`CMMotionManager` provides four different interfaces for sensor information, +each with corresponding properties and methods +to check hardware availability and access measurements. + +- The accelerometer measures acceleration, + or changes in velocity over time. +- The gyroscope measures attitude, + or the orientation of the device. +- The magnetometer is essentially a compass, + and measures the Earth's magnetic field relative to the device. + +In addition to these individual readings, +`CMMotionManager` also provides a unified "device motion" interface, +which uses sensor fusion algorithms to combine readings +from each of these sensors into a unified view of the device in space. -To keep performance at the highest level, Apple recommends using a single shared `CMMotionManager` instance throughout your app. +### Checking for Availability -`CMMotionManager` provides a consistent interface for each of the four motion data types: `accelerometer`, `gyro`, `magnetometer`, and `deviceMotion`. As an example, here are the ways you can interact with the gyroscope—simply replace `gyro` with the motion data type you need. +Although most Apple devices these days +come with a standard set of sensors, +it's still a good idea to check for the capabilities of the current device +before attempting to read motion data. -#### Checking for Availability +The following examples involve the accelerometer, +but you could replace the word "accelerometer" +for the type of motion data that you're interested in +(such as "gyro", "magnetometer", or "deviceMotion"): ```swift let manager = CMMotionManager() -if manager.gyroAvailable { - // ... +guard manager.isAccelerometerAvailable else { + return } ``` -> To make things simpler and equivalent between Swift and Objective-C, assume we've declared a `manager` instance as a view controller property for all the examples to come. +{% comment %} -#### Setting the Update Interval +> Too keep things concise, +> assume that each of the following examples declares +> a `manager` instance as a view controller property. -```swift -manager.gyroUpdateInterval = 0.1 -``` +{% endcomment %} + +### Push vs. Pull + +Core Motion provides both "pull" and "push" access to motion data. + +To "pull" motion data, +you access the current reading from +using one of the read-only properties of `CMMotionManager`. -This is an `NSTimeInterval`, so specify your update time in seconds: lower for smoother responsiveness, higher for less CPU usage. +To receive "pushed" data, +you start the collection of your desired data +with a closure that receives updates at a specified interval. #### Starting Updates to "pull" Data ```swift -manager.startGyroUpdates() +manager.startAccelerometerUpdates() ``` -After this call, `manager.gyroData` is accessible at any time with the device's current gyroscope data. +After this call, +`manager.accelerometerData` is accessible at any time +with the device's current accelerometer data. + +```swift +manager.accelerometerData +``` + +You can also check whether motion data is available by +reading the corresponding "is active" property. + +```swift +manager.isAccelerometerActive +``` #### Starting Updates to "push" Data ```swift -let queue = NSOperationQueue.mainQueue() -manager.startGyroUpdatesToQueue(queue) { - (data, error) in - // ... +manager.startAccelerometerUpdates(to: .main) { (data, error) in + guard let data = data, error == nil else { + return + } + + <#...#> } ``` -The handler closure will be called at the frequency given by the update interval. +The passed closure is called at the frequency provided by the update interval. +(Actually, Core Motion enforces a minimum and maximum frequency, +so specifying a value outside of that range causes that value to be normalized; +you can determine the effective interval rate of the current device +by checking the timestamps of motion events over time.) #### Stopping Updates ```swift -manager.stopGyroUpdates() +manager.stopAccelerometerUpdates() ``` -## Using the Accelerometer +## Accelerometer in Action -Let's say we want to give the splash page of our app a fun effect, with the background image staying level no matter how the phone is tilted. +Let's say we want to give the splash page of our app a fun effect, +such that the background image remains level no matter how the phone is tilted. Consider the following code: -First, we check to make sure our device makes accelerometer data available, next we specify a very high update rate, and then we begin updates to a closure that will rotate a `UIImageView` property: - ```swift -if manager.accelerometerAvailable { +if manager.isAccelerometerAvailable { manager.accelerometerUpdateInterval = 0.01 - manager.startAccelerometerUpdatesToQueue(NSOperationQueue.mainQueue()) { - [weak self] (data: CMAccelerometerData?, error: NSError?) in - if let acceleration = data?.acceleration { - let rotation = atan2(acceleration.x, acceleration.y) - M_PI - self?.imageView.transform = CGAffineTransformMakeRotation(CGFloat(rotation)) + manager.startAccelerometerUpdates(to: .main) { + [weak self] (data, error) in + guard let data = data, error == nil else { + return } - } -} -``` -```objective-c -RotationViewController * __weak weakSelf = self; -if (manager.accelerometerAvailable) { - manager.accelerometerUpdateInterval = 0.01f; - [manager startAccelerometerUpdatesToQueue:[NSOperationQueue mainQueue] - withHandler:^(CMAccelerometerData *data, NSError *error) { - double rotation = atan2(data.acceleration.x, data.acceleration.y) - M_PI; - weakSelf.imageView.transform = CGAffineTransformMakeRotation(rotation); - }]; + let rotation = atan2(data.acceleration.x, + data.acceleration.y) - .pi + self?.imageView.transform = + CGAffineTransform(rotationAngle: CGFloat(rotation)) + } } ``` -Each packet of `CMAccelerometerData` includes an `x`, `y`, and `z` value—each of these shows the amount of acceleration in Gs (where G is one unit of gravity) for that axis. That is, if your device were stationary and straight up in portrait orientation, it would have acceleration `(0, -1, 0)`; laying flat on its back on the table would be `(0, 0, -1)`; and tilted forty-five degrees to the right would be something like `(0.707, -0.707, 0)`. - -We're calculating the rotation by computing the [`arctan2`](http://en.wikipedia.org/wiki/Atan2) of the `x` and `y` components from the accelerometer data and then using that rotation in a `CGAffineTransform`. Our image should stay right-side up no matter how the phone is turned—here it is in a hypothetical app for the *National Air & Space Museum* (my favorite museum as a kid): - -![Rotation with accelerometer]({{ site.asseturl }}/cmdm-accelerometer.gif) - -The results are not terribly satisfactory—the image movement is jittery, and moving the device in space affects the accelerometer as much as or even more than rotating. These issues *could* be mitigated by sampling multiple readings and averaging them together, but instead let's look at what happens when we involve the gyroscope. - - +First, we check to make sure our device makes accelerometer data available. +Next we specify a high update frequency. +And then finally, +we begin updates to a closure that will rotate a `UIImageView` property: + +Each `CMAccelerometerData` object includes an `x`, `y`, and `z` value --- +each of these shows the amount of acceleration in G-forces +(where 1G = the force of gravity on Earth) +for that axis. +If your device were stationary and standing straight up in portrait orientation, +it would have acceleration `(0, -1, 0)`; +laying flat on its back on the table, +it would be `(0, 0, -1)`; +tilted forty-five degrees to the right, +it would be something like `(0.707, -0.707, 0)` _(dat √2 tho)_. + +We calculate the rotation with the +[two-argument arctangent function (`atan2`)](https://en.wikipedia.org/wiki/Atan2) +using the `x` and `y` components from the accelerometer data. +We then initialize a `CGAffineTransform` using that calculate rotation. +Our image should stay right-side-up, no matter how the phone is turned --- +here, it is in a hypothetical app for the _National Air & Space Museum_ +(my favorite museum as a kid): + +![Rotation with accelerometer]({% asset cmdm-accelerometer.gif @path %}) + +The results are not terribly satisfactory --- +the image movement is jittery, +and moving the device in space affects the accelerometer +as much as or even more than rotating. +These issues _could_ be mitigated by +sampling multiple readings and averaging them together, +but instead let's look at what happens when we involve the gyroscope. ## Adding the Gyroscope -Rather than use the raw gyroscope data that we would get with `startGyroUpdates...`, let's get composited gyroscope *and* accelerometer data from the `deviceMotion` data type. Using the gyroscope, Core Motion separates user movement from gravitational acceleration and presents each as its own property of the `CMDeviceMotion` instance that we receive in our handler. The code is very similar to our first example: +Rather than use the raw gyroscope data that we would get +by calling the `startGyroUpdates...` method, +let's get composited gyroscope _and_ accelerometer data +by requesting the unified "device motion" data. +Using the gyroscope, +Core Motion separates user movement from gravitational acceleration +and presents each as its own property of the `CMDeviceMotion` object. +The code is very similar to our first example: ```swift -if manager.deviceMotionAvailable { +if manager.isDeviceMotionAvailable { manager.deviceMotionUpdateInterval = 0.01 - manager.startDeviceMotionUpdatesToQueue(NSOperationQueue.mainQueue()) { - [weak self] (data: CMDeviceMotion?, error: NSError?) { - if let gravity = data?.gravity { - let rotation = atan2(data.gravity.x, data.gravity.y) - M_PI - self?.imageView.transform = CGAffineTransformMakeRotation(CGFloat(rotation)) + manager.startDeviceMotionUpdates(to: .main) { + [weak self] (data, error) in + + guard let data = data, error == nil else { + return } - } -} -``` -```objective-c -RotationViewController * __weak weakSelf = self; -if (manager.deviceMotionAvailable) { - manager.deviceMotionUpdateInterval = 0.01f; - [manager startDeviceMotionUpdatesToQueue:[NSOperationQueue mainQueue] - withHandler:^(CMDeviceMotion *data, NSError *error) { - double rotation = atan2(data.gravity.x, data.gravity.y) - M_PI; - weakSelf.imageView.transform = CGAffineTransformMakeRotation(rotation); - }]; + let rotation = atan2(data.gravity.x, + data.gravity.y) - .pi + self?.imageView.transform = + CGAffineTransform(rotationAngle: CGFloat(rotation)) + } } ``` -Much better! +_Much better!_ -![Rotation with gravity]({{ site.asseturl }}/cmdm-gravity.gif) +![Rotation with gravity]({% asset cmdm-gravity.gif @path %}) ## UIClunkController -We can also use the other, non-gravity portion of this composited gyro/acceleration data to add new methods of interaction. In this case, let's use the `userAcceleration` property of `CMDeviceMotion` to navigate backward whenever a user taps the left side of her device against her hand. +We can also use the other, non-gravity portion +of this composited gyro / acceleration data +to add new methods of interaction. +In this case, let's use the `userAcceleration` property of `CMDeviceMotion` +to navigate backward whenever +the user taps the left side of the device against their hand. -Remember that the X-axis runs laterally through the device in our hand, with negative values to the left. If we sense a *user* acceleration to the left of more than 2.5 Gs, that will be the cue to pop our view controller from the stack. The implementation is only a couple lines different from our previous example: +Remember that the X-axis runs laterally through the device in our hand, +with negative values to the left. +If we sense a _user_ acceleration to the left of more than 2.5 Gs, +that's our cue to pop the view controller from the stack. +The implementation is only a couple lines different from our previous example: ```swift -if manager.deviceMotionAvailable { - manager.deviceMotionUpdateInterval = 0.02 - manager.startDeviceMotionUpdatesToQueue(NSOperationQueue.mainQueue()) { - [weak self] (data: CMDeviceMotion?, error: NSError?) in +if manager.isDeviceMotionAvailable { + manager.deviceMotionUpdateInterval = 0.01 + manager.startDeviceMotionUpdates(to: .main) { + [weak self] (data, error) in - if data?.userAcceleration.x < -2.5 { + guard let data = data, error == nil else { + return + } + if data.userAcceleration.x < -2.5 { self?.navigationController?.popViewControllerAnimated(true) } } } ``` -```objective-c -ClunkViewController * __weak weakSelf = self; -if (manager.deviceMotionAvailable) { - manager.deviceMotionUpdateInterval = 0.01f; - [manager startDeviceMotionUpdatesToQueue:[NSOperationQueue mainQueue] - withHandler:^(CMDeviceMotion *data, NSError *error) { - if (data.userAcceleration.x < -2.5f) { - [weakSelf.navigationController popViewControllerAnimated:YES]; - } - }]; -} -``` - -And it works like a charm—tapping the device in a detail view immediately takes us back to the list of exhibits: - -![Clunk to go back]({{ site.asseturl }}/cmdm-clunk.gif) +_Works like a charm!_ +Tapping the device in a detail view +immediately takes us back to the list of exhibits: +![Clunk to go back]({% asset cmdm-clunk.gif @path %}) ## Getting an Attitude -Better acceleration data isn't the only thing we gain by including gyroscope data—we now also know the device's true orientation in space. We find this data in the `attitude` property of `CMDeviceMotion`, an instance of `CMAttitude`. `CMAttitude` contains three different representations of the device's orientation: Euler angles, a quaternion, and a rotation matrix. Each of these is in relation to a given reference frame. +Better acceleration data isn't the only thing we gain +by including gyroscope data: +we now also know the device's true orientation in space. +This data is accessed via the `attitude` property of a `CMDeviceMotion` object +and encapsulated in a `CMAttitude` object. +`CMAttitude` contains three different representations of the device's orientation: -### Finding a Frame of Reference +- Euler angles, +- A quaternion, +- A rotation matrix. -You can think of a reference frame as the resting orientation of the device from which an attitude is calculated. All four possible reference frames describe the device laying flat on a table, with increasing specificity about the direction it's pointing. +Each of these is in relation to a given reference frame. -- `CMAttitudeReferenceFrameXArbitraryZVertical` describes a device laying flat (vertical Z-axis) with an "arbitrary" X-axis. In practice, the X-axis is fixed to the orientation of the device when you *first* start device motion updates. -- `CMAttitudeReferenceFrameXArbitraryCorrectedZVertical` is essentially the same but uses the magnetometer to correct possible variation in the gyroscope's measurement over time. Using the magnetometer adds a CPU (and therefore battery) cost. -- `CMAttitudeReferenceFrameXMagneticNorthZVertical` describes a device laying flat, with its X-axis (i.e., the right side of the device) pointed toward magnetic north. This setting may require your user to perform that figure-eight motion with their device to calibrate the magnetometer. -- `CMAttitudeReferenceFrameXTrueNorthZVertical` is the same as the last, but this adjusts for the magnetic/true north discrepancy and therefore requires location data in addition to the magnetometer. +### Finding a Frame of Reference -For our purposes, the default "arbitrary" reference frame will be fine - you'll see why in a moment. +You can think of a reference frame as the resting orientation of the device +from which an attitude is calculated. +All four possible reference frames describe the device laying flat on a table, +with increasing specificity about the direction it's pointing. + +- `CMAttitudeReferenceFrameXArbitraryZVertical` + describes a device laying flat (vertical Z-axis) + with an "arbitrary" X-axis. + In practice, the X-axis is fixed to the orientation of the device + when you _first_ start device motion updates. +- `CMAttitudeReferenceFrameXArbitraryCorrectedZVertical` + is essentially the same, + but uses the magnetometer to correct + possible variation in the gyroscope's measurement over time. +- `CMAttitudeReferenceFrameXMagneticNorthZVertical` + describes a device laying flat, + with its X-axis + (that is, the right side of the device in portrait mode when it's facing you) + pointed toward magnetic north. + This setting may require the user to perform + that figure-eight motion with their device to calibrate the magnetometer. +- `CMAttitudeReferenceFrameXTrueNorthZVertical` + is the same as the last, + but it adjusts for magnetic / true north discrepancy + and therefore requires location data in addition to the magnetometer. + +For our purposes, +the default "arbitrary" reference frame will be fine +(you'll see why in a moment). ### Euler Angles -Of the three attitude representations, Euler angles are the most readily understood, as they simply describe rotation around each of the axes we've already been working with. `pitch` is rotation around the X-axis, increasing as the device tilts toward you, decreasing as it tilts away; `roll` is rotation around the Y-axis, decreasing as the device rotates to the left, increasing to the right; and `yaw` is rotation around the (vertical) Z-axis, decreasing clockwise, increasing counter-clockwise. - -> Each of these values follows what's called the "right hand rule": make a cupped hand with your thumb pointing up and point your thumb in the direction of any of the three axes. Turns that move toward your fingertips are positive, turns away are negative. +Of the three attitude representations, +Euler angles are the most readily understood, +as they simply describe rotation +around each of the axes we've already been working with. + +- `pitch` is rotation around the X-axis, + increasing as the device tilts toward you, + decreasing as it tilts away +- `roll` is rotation around the Y-axis, + decreasing as the device rotates to the left, + increasing to the right +- `yaw` is rotation around the (vertical) Z-axis, + decreasing clockwise, increasing counter-clockwise. + +> Each of these values follows what's called the "right hand rule": +> make a cupped hand with your thumb pointing up +> and point your thumb in the direction of any of the three axes. +> Turns that move toward your fingertips are positive, +> turns away are negative. ### Keep It To Yourself -Lastly, let's try using the device's attitude to enable a new interaction for a flash-card app, designed to be used by two study buddies. Instead of manually switching between the prompt and the answer, we'll automatically switch the view as the device turns around, so the quizzer sees the answer while the person being quizzed only sees the prompt. - -Figuring out this switch from the reference frame would be tricky. To know which angles to monitor, we would somehow need to take into account the starting orientation of the device and then determine which direction the device is pointing. Instead, we can save a `CMAttitude` instance and use it as the "zero point" for an adjusted set of Euler angles, calling the `multiplyByInverseOfAttitude()` method to translate all future attitude updates. - -When the quizzer taps the button to begin the quiz, we first configure the interaction—note the "pull" of the deviceMotion for `initialAttitude`: +Lastly, let's try using the device's attitude to enable a new interaction +for a flash-card app designed to be used by two study buddies. +Instead of manually switching between the prompt and the answer, +we'll automatically flip the view as the device turns around, +so the quizzer sees the answer +while the person being quizzed sees only the prompt. + +Figuring out this switch from the reference frame would be tricky. +To know which angles to monitor, +we would somehow need to account for the starting orientation of the device +and then determine which direction the device is pointing. +Instead, we can save a `CMAttitude` instance +and use it as the "zero point" for an adjusted set of Euler angles, +calling the `multiply(byInverseOf:)` method +to translate all future attitude updates. + +When the quizzer taps the button to begin the quiz, +we first configure the interaction +(note the "pull" of the deviceMotion for `initialAttitude`): ```swift // get magnitude of vector via Pythagorean theorem -func magnitudeFromAttitude(attitude: CMAttitude) -> Double { - return sqrt(pow(attitude.roll, 2) + pow(attitude.yaw, 2) + pow(attitude.pitch, 2)) +func magnitude(from attitude: CMAttitude) -> Double { + return sqrt(pow(attitude.roll, 2) + + pow(attitude.yaw, 2) + + pow(attitude.pitch, 2)) } // initial configuration -var initialAttitude = manager.deviceMotion!.attitude +var initialAttitude = manager.deviceMotion.attitude var showingPrompt = false // trigger values - a gap so there isn't a flicker zone @@ -235,125 +389,92 @@ let showPromptTrigger = 1.0 let showAnswerTrigger = 0.8 ``` -```objective-c -// --- class method to get magnitude of vector via Pythagorean theorem -+ (double)magnitudeFromAttitude:(CMAttitude *)attitude { - return sqrt(pow(attitude.roll, 2.0f) + pow(attitude.yaw, 2.0f) + pow(attitude.pitch, 2.0f)); -} - -// --- In @IBAction handler -// initial configuration -CMAttitude *initialAttitude = manager.deviceMotion.attitude; -__block BOOL showingPrompt = NO; - -// trigger values - a gap so there isn't a flicker zone -double showPromptTrigger = 1.0f; -double showAnswerTrigger = 0.8f; -``` - -Then, in our now familiar call to `startDeviceMotionUpdates`, we calculate the magnitude of the vector described by the three Euler angles and use that as a trigger to show or hide the prompt view: +Then, +in our now familiar call to `startDeviceMotionUpdates`, +we calculate the magnitude of the vector described by the three Euler angles +and use that as a trigger to show or hide the prompt view: ```swift -if manager.deviceMotionAvailable { - manager.startDeviceMotionUpdatesToQueue(NSOperationQueue.mainQueue()) { - [weak self] (data: CMDeviceMotion?, error: NSError?) in - - guard let data = data else { return } - +if manager.isDeviceMotionAvailable { + manager.startDeviceMotionUpdates(to: .main) { // translate the attitude - data.attitude.multiplyByInverseOfAttitude(initialAttitude) + data.attitude.multiply(byInverseOf: initialAttitude) // calculate magnitude of the change from our initial attitude - let magnitude = magnitudeFromAttitude(data.attitude) ?? 0 + let magnitude = magnitude(from: data.attitude) ?? 0 // show the prompt if !showingPrompt && magnitude > showPromptTrigger { - if let promptViewController = self?.storyboard?.instantiateViewControllerWithIdentifier("PromptViewController") as? PromptViewController { + if let promptViewController = + self?.storyboard?.instantiateViewController( + withIdentifier: "PromptViewController" + ) as? PromptViewController + { showingPrompt = true - promptViewController.modalTransitionStyle = UIModalTransitionStyle.CrossDissolve - self?.presentViewController(promptViewController, animated: true, completion: nil) + promptViewController.modalTransitionStyle = .crossDissolve + self?.present(promptViewController, + animated: true, completion: nil) } } // hide the prompt if showingPrompt && magnitude < showAnswerTrigger { showingPrompt = false - self?.dismissViewControllerAnimated(true, completion: nil) + self?.dismiss(animated: true, completion: nil) } } } ``` -```objective-c -FacingViewController * __weak weakSelf = self; -if (manager.deviceMotionAvailable) { - [manager startDeviceMotionUpdatesToQueue:[NSOperationQueue mainQueue] - withHandler:^(CMDeviceMotion *data, NSError *error) { - - // translate the attitude - [data.attitude multiplyByInverseOfAttitude:initialAttitude]; - - // calculate magnitude of the change from our initial attitude - double magnitude = [FacingViewController magnitudeFromAttitude:data.attitude]; - - // show the prompt - if (!showingPrompt && (magnitude > showPromptTrigger)) { - showingPrompt = YES; - - PromptViewController *promptViewController = [weakSelf.storyboard instantiateViewControllerWithIdentifier:@"PromptViewController"]; - promptViewController.modalTransitionStyle = UIModalTransitionStyleCrossDissolve; - [weakSelf presentViewController:promptViewController animated:YES completion:nil]; - } - - // hide the prompt - if (showingPrompt && (magnitude < showAnswerTrigger)) { - showingPrompt = NO; - [weakSelf dismissViewControllerAnimated:YES completion:nil]; - } - }]; -} -``` - -Having implemented all that, let's take a look at the interaction. As the device rotates, the display automatically switches views and the quizee never sees the answer: +Having implemented all that, +let's take a look at the interaction. +As the device rotates, +the display automatically switches views and the quizee never sees the answer: -![Prompt by turning the device]({{ site.asseturl }}/cmdm-prompt.gif) +![Prompt by turning the device]({% asset cmdm-prompt.gif @path %}) ### Further Reading -I skimmed over the [quaternion](http://en.wikipedia.org/wiki/Quaternions_and_spatial_rotation) and [rotation matrix](http://en.wikipedia.org/wiki/Rotation_matrix) components of `CMAttitude` earlier, but they are not without intrigue. The quaternion, in particular, has [an interesting history](http://en.wikipedia.org/wiki/History_of_quaternions), and will bake your noodle if you think about it long enough. - +I skimmed over the +[quaternion](https://en.wikipedia.org/wiki/Quaternions_and_spatial_rotation) and +[rotation matrix](https://en.wikipedia.org/wiki/Rotation_matrix) +components of `CMAttitude` earlier, +but they are not without intrigue. +The quaternion, in particular, +has [an interesting history](https://en.wikipedia.org/wiki/History_of_quaternions), +and will bake your noodle if you think about it long enough. ## Queueing Up -To keep the code examples readable, we've been sending all our `CoreMotionManager` updates to the main queue. As a best practice, it would be better to have these updates on their own queue so they can't slow down user interaction, but then we'll need to get back on the main queue to update user interface elements. [`NSOperationQueue`](http://nshipster.com/nsoperation/) makes this easy with its `addOperationWithBlock` method: +To keep the code examples readable, +we've been sending all of our motion updates to the main queue. +A better approach would be to schedule these updates on their own queue +and dispatch back to main to update the UI. ```swift -let queue = NSOperationQueue() -manager.startDeviceMotionUpdatesToQueue(queue) { - [weak self] (data: CMDeviceMotion?, error: NSError?) in +let queue = OperationQueue() +manager.startDeviceMotionUpdates(to: queue) { + [weak self] (data, error) in // motion processing here - NSOperationQueue.mainQueue().addOperationWithBlock { + DispatchQueue.main.async { // update UI here } } ``` -```objective-c -NSOperationQueue *queue = [[NSOperationQueue alloc] init]; -[manager startDeviceMotionUpdatesToQueue:queue - withHandler: -^(CMDeviceMotion *data, NSError *error) { - // motion processing here - - [[NSOperationQueue mainQueue] addOperationWithBlock:^{ - // update UI here - }]; -}]; -``` - ----- +--- -As a final note, clearly not all interactions made possible by Core Motion are good ones. Navigation through motion can be fun but also hard to discover or easy to accidentally trigger; purposeless animations can make it harder to focus on the task at hand. Prudent developers will skip over gimmicks that distract and find ways to use device motion that enrich their apps and delight their users. +Remember that not all interactions made possible by Core Motion are good ones. +Navigation through motion can be fun, +but it can also be +hard to discover, +easy to accidentally trigger, +and may not be accessible to all users. +Similar to purposeless animations, +overuse of fancy gestures can make it harder to focus on the task at hand. + +Prudent developers will skip over gimmicks that distract +and find ways to use device motion that enrich apps and delight users. diff --git a/2014-11-03-uisplitviewcontroller.md b/2014-11-03-uisplitviewcontroller.md index aeed156b..147ecdd1 100644 --- a/2014-11-03-uisplitviewcontroller.md +++ b/2014-11-03-uisplitviewcontroller.md @@ -2,150 +2,255 @@ title: UISplitViewController author: Natasha Murashev category: Cocoa -excerpt: "The introduction of iPhone 6+ brought on a new importance for UISplitViewController. With just a few little tweaks, an app can now become Universal, with Apple handling most of the UI logic for all the different screen sizes." +excerpt: > + Although user interface idioms have made way + for the broader concept of size classes, + `UISplitViewController` remains a workhorse API for writing Universal apps. +revisions: + "2014-11-03": Original publication + "2018-09-26": Updated for iOS 12 and Swift 4.2 status: - swift: 2.0 - reviewed: September 11, 2015 + swift: 4.2 + reviewed: September 26, 2018 --- -The introduction of iPhone 6+ brought on a new importance for `UISplitViewController`. With just a few little tweaks, an app can now become Universal, with Apple handling most of the UI logic for all the different screen sizes. +In the beginning, there was the iPhone. +And it was good. -Check out the `UISplitViewController` doing its magic on iPhone 6+: +Some years later, the iPad was introduced. +And with some adaptations, an iOS app could be made Universal +to accommodate both the iPhone and iPad in a single bundle. -