Frameworks • iOS, OS X • 49:52
The Foundation framework provides the "nuts and bolts" classes for both iPhone OS and Mac OS programming, and an understanding of the Foundation framework is essential for building great software on the Mac, iPhone, and iPad. Learn about the wide-ranging capabilities of the Foundation framework and discover how to best use features like collections, strings, archiving, notifications, preferences, bundles, and more.
Speaker: Tony Parker
Unlisted on Apple Developer site
Downloads from Apple
Transcript
This transcript has potential transcription errors. We are working on an improved version.
My name is Tony Parker, and I'm an engineer on the Cocoa Team at Apple, and this is Understanding Foundation. So first, let's go over what we're going to talk about today. We'll start by going over what the Foundation framework is, then we'll see how Foundation fits in with your application and the rest of the system, and then we're going to spend most of the time of the talk today going over the most important Foundation features.
We're going to see a high-level overview of many of the Foundation classes, talk about the most important features on those classes, and see examples of how to use them. So, just a couple of prereqs. I'm going to assume that you've worked on an app or you're working on an app for iPhone OS or Mac OS X and that you have familiarity with the basic Foundation concepts like retain and release.
So first, what is Foundation? Foundation provides building block classes. And these building block classes are the fundamental types that are used by all applications on the system. They can be assembled by higher-level software, other frameworks, and applications like yours. Foundation also introduces consisting conventions. One of those I just mentioned, that's retain and release, but there are others and we'll a few examples of that today.
And finally, Foundation is designed to raise lower-level concepts. It can wrap up complex OS level tasks so you can focus on your problem domain instead of nuts and bolts of how exactly to do things. So, where does Foundation fit in a system? If you look at the system as a stack with your application on top and the Core OS pieces on the bottom, then in the middle, we have our frameworks. And Foundation is one of these frameworks. Now, Foundation relies on lower-level frameworks itself, for example, the CFNetwork framework for networking support and Core Foundation.
And there are higher level frameworks that rely on the Foundation framework, on Mac OS X, there's the application kit, and iOS, there's the UI Kit. And generally, the dividing line between those frameworks and Foundation is how closely they relate to the user interface. OK, let's move on to the majority of our talk, and that's going over building blocks. So, first we'll talk about collections, then strings, dates, times, formatters and locales, persistence and archiving, files and URLs, bundles, and we'll wrap up by going over operation queues. So, first up is collections.
So, what's a collection? It's just a place to put your stuff. And there are many features you use on collections. What we're going to talk about today is iteration, sorting the contents of an array, and filtering the contents of a collection. Now, in the Foundation framework, we have three major collections. The first is NSArray.
NSArray's prime directive, if you will, is to preserve the order of the objects that you put into it. An example of where you might use one is if you're storing the results of a 50-meter dash. In that case, of course, it's very important to remember who came first, second, and third. The second major collection is NSDictionary. NSDictionary maps key objects to value objects. You can think of it as an association table.
An example of where you might use one is in a phone book where you map a person's name to their phone number. And the third major collection is NSSet. NSSet maintains unique, unordered objects. An example might be a suggestion box. There, perhaps, you don't care about how many copies of suggestion you get, and you also don't care about the order they are received in. All three of these collections have mutable variants.
And mutable means that it can be modified, so objects can be added or removed from the mutable versions of these collections. Now, the first topic is iterating over the contents of a collection. You've probably already used a for loop, but there are others like NSEnumerator and a while loop. Fast enumeration using the for-in syntax, and new in Snow Leopard and iOS 4 is the use of blocks.
So when you're using a for loop, of course, you're going to get the count first and then you iterate over every object in the array by incrementing that index and retrieving the object from the array at that index. And your code goes inside that loop. You can also use this technique with a dictionary or a set, you just first get the allObjects array and then follow the same technique. And this enumerator is a Foundation object that can keep track of your location and iteration for you.
Here, I'm going to get an object enumerator, that is an enumerator that iterates over the objects in an array or a set. Then I set up a while loop and while next object returns a non-nil value, I have another object to enumerate over. Now note, if you're using this technique with an NSArray, you'll need to keep track of the index separately, if you need it.
For a dictionary, you can enumerate over the keys using a key enumerator and then get the object that corresponds with that key using NSDictionary's objectForKey method. Or if you just need the objects, you can also use an object enumerator. If you're targeting Mac OS 10.5 Leopard or any version of iPhone OS or iOS, then you can use Fast enumeration. And I think it's fast for two reasons. The first is that it actually performs faster.
It uses some implementation details of how these collections are created to retrieve groups of objects and actually have better performance. But I think it's also fast because you have to write less code. So, here we're saying just for-in. Then we don't have to set up the enumerator ourselves. Here the object variable will be assigned to the next object in an array or set until we're finished. And again, there's no easy access to the index, so if you need it, you'll have to keep track of it yourself.
When you fast enumerate over a dictionary, the objects you're getting back are the keys. That means that if you need the objects, you should call objectForKey and get the value that you need in your loop. So, new in Snow Leopard and iOS 4, as I mentioned, is iteration using blocks. I'm not going to go into the details of the block syntax here. The key parts, remember, is that this method, enumerateObjectsUsingBlock, takes a block parameter.
You can tell because there's a caret in front there. And this block is a snippet of code. And that code has some parameters. Here, we're going to get the object, the index of that object, and we have the option to stop the iteration early with that third parameter. And your code goes inside.
For dictionaries, you get the keys and the objects together, so you don't need an extra step to retrieve the object. And for sets, you get the objects. Now, these are the short versions of these methods. One of the benefits of using the block-based iteration is that there is another method, another version which lets you specify things like enumerating reverse or enumerating concurrently. So, how do you choose which of these iteration methods to use? I recommend that you choose based on two things.
The first is what version of the operating system you're targeting. And the second is understanding what data you need. For example, do you need the index in an array or do you need the objects in a dictionary? So, for any version of Mac OS X or iOS, you can use a for loop or an NSEnumerator. If you're targeting Leopard or any version of iOS, then you can use a for loop or Fast enumeration. And on Snow Leopard, or iOS 4 and later, then use Fast enumeration or try our new block-based enumeration methods.
Next is sorting an array. So there are several ways that you can tell NSArray how to determine its sort order. You can use a C function, you can use an objective C method, you can use an NSSort descriptor. This is a class that lets you specify which properties on an array or an object in an array that you want to sort on. And on iOS 4 or Snow Leopard or later, you can use a block-based method.
Now, when you're sorting an immutable or immutable array, there's a sort method that returns a new object, a new collection object. For mutable arrays only, there's a different method you can call, which will sort it in place. So, let's see an example of sorting in place using blocks.
Here, I have an array of names and I want to sort these names by the length of the string, their strings. I want to sort these names by their length. So, I'm going to use this block-based method, NSArray's sortUsingComparator. And you see the parameters of block, and that block has a left and right object. NSArray will call this block as many times as necessary with the appropriate objects to determine what the correct sort order of the contents of your collection are.
Our job is to return an NSComparison results. To determine what the answer is, I'm going to get the length of the left and right strings and then do my comparison. If the left is less than the right, I'm going to return OrderedAscending . If it's the opposite, it's OrderedDescending. And if the lengths are equal, NSOrderedSame.
Now note that this does not mean that the objects are equal, just that they have the same length and they should be sorted equally. And when this method returns, the NSArray will be sorted in place. So, next is filtering. One thing that trips up people sometimes when they're new to Foundation is that they attempt to mutate a collection while they're enumerating it using a block-based method or NSEnumerator or Fast enumeration. And that causes an exception. So, the proper way to filter a collection is to mutate a copy of the collection or to gather the changes on the side and then apply them later.
Let's see an example of the latter. Here, I have another immutable array of strings. This one contains strings with file names in them. And I want to filter out every file name that starts with a dot, perhaps because I'm going to display it to a user. So, I'm going to create another collection. This is an NSIndexSet, which is like an NSSet, but for indexes instead of objects.
And it's called To Remove. The contents of this index set will the items in that array that I want to filter out. And I create it by using one of our new block-based methods, files, indexes of objects passing test, and then, inside this block is where I put my test.
If that string starts with a dot, return Yes. That means put this index in that index set, otherwise return No. And when it's finished, I call remove objects to indexes with that index set and my array will be filtered. There are many more features on collections. You can search collections. You can apply a selector to each item. That's another way of getting the same code run on every object. And this array has slicing and concatenation features. And NSSet has intersection, union and set subtraction features.
So next up is strings. So, strings, and Foundation's string object is called NSString. NSString is the object container for almost all the tests you see on the system, on iOS or Mac OS X. And conceptually, strings are actually very simple. They're just an array of Unicode characters. However, there are many complexities in dealing with strings at the Unicode character level.
So, what we really recommend is that you treat strings as opaque containers. And to help you do that, NSString has a ton of methods on it that let you manipulate strings and do other kinds of string operations. Some of the most common are comparing strings, searching strings and converting the encoding of strings. So, first up is comparing.
There's a simple way to do that, this method called Compare. The parameter is the string that you're comparing the receiver to and the result is another NSComparison result, the same type as we saw a minute ago. If you're comparing strings in a manner that will eventually be displayed to the user, you need to sort them in a -- compare them in a localized manner. And this method is for that, localizedCompare. You see, it takes a string parameter and returns an NSComparison result s well. And furthermore, in Snow Leopard and iOS 4, we added this method, localized Standard Compare.
This encapsulates what we think are the best practices for the options to compare strings with if you're going to display them in a manner to the user. This will take into account the user's current locale, all of their preferences and so on. And finally, if you need to specify more options, there's compare:options:range:locale. An option you might specify is a case insensitive compare. Or you may want to specify only a subset of the string to compare.
Quick example. Here I have two strings, A and B. And when I compare them, the result will be NSOrderAscending. String A comes before string B. I mentioned earlier that you can sort an array based on an objective C method. Here's an example of that. I have an array with Larry, Curley, and Moe in it. And I want to sort this in a localized manner for my user. So, what I do is called the NSArray method, sortedArrayUsingSelector.
I pass in selectorLocalizeCompare. That localize compare message will be sent to every object in that array, and NSArray will use it to determine the sort order. The result will be Curley, Larry, and Moe. For searching strings, there's a similar easy method. It's called rangeOfString. The parameters of the string you're looking for in the receiver. And the result is an NSRange structure.
There's a long form of this, too. It's called rangeOfString:options:range:locale. And you could specify some of the same options like case insensitive. Now, some people ask, why do we return a range? If I passed in the string I'm looking for, I already know the length of it. So why do I need the location and length of the found string as well? Let me show you an example of why that's important.
Here, I have a string that contains San Jose. San Jose is a city about 40 miles south of here, where I live. And I'm going to search this string for the second part, Jose, an easy range of string. And the result will be in NSRange, as I mentioned, with the location of 4 pointing to the capital J at the start and the length of 4 to cover the 4 characters in that string. Now somebody might come along and say, you know that San Jose, the E has an accent over it, like this.
And I'd say, you're completely right. I'm sorry I made that mistake. And instead, I'll change my search string to look for an E with an accent over it. And when I search for it, the result is a range with a location of 4 and a length of 4.
Same as before, right? Well, there's another completely valid way for this string to be stored in memory, and that's like this, with the E and the accent separated as two separate characters. My search string hasn't changed. But when I look for it, the location is 4 and the length is 5. So this is why it's always important to honor the length part of the returned NSRange. This is called a decomposed character, by the way.
A couple other notes about searching strings. If you're looking for something that's not there, you'll get a length of 0. And new in iPhone OS 3.2, we added NSRegularExpressionSearch, a long-requested feature. Here, I'm searching for the word go, with lowercase g. And I'm going to find the first going for this search expression. In iOS 4, we added a lot more regex support. And if you missed it, you should check out this talk on video, Advanced Text Handling for iPhone OS, where we talked about all the different regex options you have available to you now. Next is string encodings.
So, sometimes when people are looking at the string creation methods, they see this encoding parameter and they're a little confused about what it means. But, encodings are not that complicated. It's just a map of numbers to characters. One that you might know already is ASCII. In ASCII, which Foundation represents with NSASCIIStringEncoding, you might have values like 65, 97, and so forth. And those map to the characters of capital A, lowercase a, and you see special characters like tilde also have a mapping.
So, when you create a string from data, it's always important to know what encoding that data is stored in. Here, I have some data from a file or a network or wherever. I'm going to create a string out of that using initWithData encoding. I know in advance that the encoding is UTF8StringEncoding. If you guess about the encoding of your data, it's important to provide your users with a way to override your guess, because if you guess wrong, the result can be characters that look like complete gibberish.
You can also create data in a specified encoding. Here, I've got a string that's going to go to Windows, where UTF16 is a lot more prevalent. So, I call data used in encoding, and I pass an NSUTF16StringEncoding on that-- as a parameter to that method. And my data will be created in the correct form.
One final note about encodings, if in the course of your development you find that you need a char star to pass through a system call, like Open, you should use this NSString convenience method: fileSystemRepresentation. This will get you a character pointer that points to the data in the correct encoding for the system.
And one thing to note about this is that the data that's pointed to by that char star is autoreleased. NSString has many more features, as well. It's got printf-style formatting, enumeration of substrings, lines and paragraphs. And it can do this in a language-independent way, which is a really powerful feature that's hidden behind some very simple API in NSString.
You can do replacement of substrings, path completion, and there's a whole mutable class-- a mutable subclass called NSMutableString, that you can use when you need mutability. Next is dates and times. So, Foundation's date and time classes encapsulate the complexity of date and time calculations. And I'm going to show you an example of where that happens in a second.
They automatically handle user preferences. That means their locale, their language, their settings and so on. Most commonly, you're going to use these classes if you need to represent a date in a calendar, if you need to find the amount of time between two dates, and we have formatter classes that let you format a date or a number correctly for a user no matter where they live.
So, why use these classes? You can do it yourself, right? No problem. Let's do an example. I've got an app which tells somebody how long they need to bake their cake for. And I've got a really huge cake that takes 12 hours to bake. So, we're just going to display the time 12 hours from now to let them know when to go get their cake.
So, as I make my app, I test it. And the date will be November 5, 2006 at 10:00 p.m. I'm going to bake my cake over night. So I expect the answer to be 12 hours from now, to be November 6, 2006 at 10:00 a.m. And the correct answer is the 6th at 10:00 a.m. So, no difference. It worked.
Chip it, right? Except that a year later, a user comes back to you says, you know, I put my cake in on November 4, 2007 at 10:00 p.m. to bake over night, but it came out burnt. It should have been November 5, 2007 at 9:00 a.m. And the reason is because Daylight Savings Time ended. And you say, no problem. I can encode those rules in my app. And actually, the laws about Daylight Savings Time changed between 2006 and 2007. But no problem.
I can put that in my app, and the next time that this happens, I'm going to expect 9:00 a.m. as a result. And then the user comes and says, it didn't work. The correct answer should have been November 5 at 10 a.m. And you might ask why. It's because that the user lives in Phoenix, where Daylight Savings Time isn't used.
So these are just a couple examples of the hundreds or thousands of rules that make up the complexities of handling dates and times. And in Foundation, we have a few classes that can do this for you. So, let's see what those are. The first is NSDate. NSDate represents an invariant point in time. This is really important. Dates are calendar and time zone independent. You can't present an NSDate to a user without knowing a calendar and time zone.
Dates measure their time in seconds, so it's a reference point. The value is a double, so you can represent fractional seconds. The next class is NSCalendar. NSCalendar is what defines the beginning, length, divisions and end of a year. One that you're familiar with is the Gregorian calendar, June 10, 2010, for example. There are others, like the Hebrew calendar. And finally, NSDateComponents. NSDateComponents is basically a simple object structure. It holds storage for years, months, days, minutes and so on.
Again, the exact meaning of these properties depends on the calendar. And in fact, the values in this day components object can be relative or absolute, depending on how you use them. So let's see another example. I'm going to calculate the number of shopping days between the U.S. Thanksgiving holiday and Christmas. Christmas, of course, is on the same date every year, December 25. In the United States, Thanksgiving falls on the fourth Thursday in November. And I'm going to define the shopping season as starting on the day after Thanksgiving, also known as Black Friday, and ending on Christmas Eve.
So, first let's find Christmas. I create a calendar. Remember, we need a calendar. This one is a Gregorian calendar, which is what's used in the United States and we're talking about U.S. Thanksgiving, so it's appropriate. Now, I create one of those date components objects. I'm going to fill it out with some values. I set the year to this year, the month to 12 for December and the day to 25. Then, I call NSCalendars method, dateFromComponents. I pass in my date components object and I get an NSDate result in the current time zone.
I could find Thanksgiving in a similar way. I create another date components object. I set the year, the month to 11 for November, the weekday to 5, because 5 means Thursday in the NS Gregorian calendar, and the weekday ordinal to 4. That means find the fourth Thursday in November of 2010.
I use the same method, dateFromComponents, to get a date representing Thanksgiving in the current time zone. Now, I need to find Black Friday. And I can do that by adding a date component object. Here, you see I'm going to create another one and set its day value to 1. So here, it's a relative value, not an absolute value as we saw on some of the previous slides.
Then I use the NSCalendar method, dateByAddingComponents: toDate: options. I pass in Thanksgiving, and the date components we're adding, the plus 1 day. And the result is a date representing Black Friday. And finally, I can get the answer I was looking for, which is the amount of time between two dates. The result, here, is an NSDate components object. And I call NSCalendarsComponents:fromDate:toDate:options. The components parameter is not a date components object, but a set of flags, which tells NSCalendar which values in the date components we want filled out. I'm interested in days.
You can also specify years, minutes, seconds or any combination of those. The from date is Black Friday, the to date is Christmas, midnight on Christmas will be the end time. And no options. The result, in this case, is 29 days. There are many more features on dates and time classes. You can find the day of the week for a date. Your homework is to find the day of the week that Christmas falls on this year.
There are time zone calculation APIs and methods to convert between calendars. Next is NSFormatter. Foundation has a class called NSFormatter that is for converting values to and from strings. And there are two main NSFormatter subclasses that I want to talk about today. The first is NSDateFormatter that converts dates to or from strings. And the second is NSNumberFormatter, which converts numbers to or from strings.
Both of these have a set of predefined styles, or you can use your own. But I recommend that you stick with the predefined styles, and here's why. Here in the Language and Text Preferences on Mac OS X, you can see I'm in the Formats tab. And my region is set to United States.
These are the standard date formats in that region. There's a full, long, medium and short style. And those correspondents and constants you can use with NSDateFormatter. Let's focus on the short style. In the United States, we put the month first, then the day, and then a two-digit year. And if we look a little bit further down on here, we see a number as well. In the United States, a comma is used as a thousand separator and there's a decimal point.
Now, if instead, your user lives in, say, South Africa, in South Africa, the formats are completely different. The short style, the year, is 2010, there. It's four digits instead of two. The month is zero padded and then the day comes last. And in the numbers section, you see that there's a space for the thousand separator instead of a comma, and a comma instead of a decimal point.
So when you use the predefined styles for date formatters and number formatters, your dates and numbers will look correct for a user no matter where they live. And using them is actually really easy. Here, I'm going to create a date formatter object. I'm going to format the Thanksgiving date that we found earlier. So, I don't care about the time because Thanksgiving lasts all day. And I want to use the long style for the date part.
Then I call stringFromDate, pass in Thanksgiving and the result will be November 25, 2010. Now, here I used the current system time zone and calendar, because I haven't specified them to the formatter. If instead, you're parsing a date, you can use a formatter for that as well. I'm going to create the day formatter, set its date format string. And the contents of this string describe to the date formatter what values I'm looking for in my string that I'm going to pass in, in a second.
Here, it's important, especially if you're parsing a file, to use your own time zone. If you don't set it, it will use the system -- the current system time zone and calendar, which may not be what you're expecting. I know in advance that this is -- these dates I'm parsing are from Los Angeles's time zone. And the calendar will be the Gregorian calendar that we created earlier. Then I pass in a string.
I pass in about now and I will have an NSDate result. I want to point out that the format string uses this Unicode standard, TR35-6. Before you use a string in this method, make sure that you know exactly what the characters in there mean. There are many subtle variations of strings that mean completely different things. So, as a quick summary of dates and formatters, I think this is very important to remember. NSDate and NSCalendar go together when you're using time calculations.
And remember to always use formatters when you're displaying a date or any numbers to your user. That way, it will look correct no matter where they live. Next is persistence and archiving. Persistence is all about storing stuff on disk, and presumable reading it back at a later time.
In Foundation, we have many methods to help you with persistence. The first is property lists. We also have user preferences. Those are kind of persisted data. Keyed archiving. And I also want to touch on large, complex data structures as well. So first, property lists. In Foundation, the NSPropertyListSerialization is the class you're going to use to interact with property lists. Property lists, or plists, for short, store a small amount of structured data, the operative word there being small.
Property lists have the benefit of being cross-platform and human readable when you use the XML format. Plists also have a binary format that is better performing. So you can choose which format to use depending on what your requirements are. There's a very strict set of types that are allowed in a plist. For collections, you may use an NSArray or an NSDictionary.
Strings, dates, numbers, in integer floating point and Boolean forms and NSData. Now, the presence of NSData means that you can stop anything in a data and put it in a plist. But again, I want to emphasize that plists are designed for small amounts of data, so don't put huge files using NSData in property lists.
Let's see a quick example. This morning, I decided I was going to learn Spanish. And so I've made some progress on mapping Spanish color names to English color names. And I want to save that. So, I am going to serialize it to an NSData in XML format. I use NSPropertyListSerialization method, dataWithPropertyList:format:options: error. You see my first parameter is the colors dictionary that I'm saving. The second parameter is the format I want this plist in, XML in this case.
Now, this method, this exact form of this method, is new in Snow Leopard and iOS 4. There are other versions of this method which are very similar that go back to the start of Mac OS X. I chose this one because I want to highlight a pattern that you see across all of Foundation and Cocoa and Cocoa Touch, and that is the use of the error parameter. You see, I passed in the address of an error object, an address of an address of an error object. And I want to know if this method worked.
The correct answer when dealing with error parameters is always to check the return value of the method first, like this. If plist is nil, then the error parameter has been filled out. And I can present it to the user like I do here. NSErrors are designed to be presented to the user. If plist is not nil, the error parameter that you pass in will be completely untouched. Foundation will not modify it in any way. So don't assume that it will be set to nil if there's no error.
The proper way to do this is check the result and then use the error. Now, to read that data out, I'm going to read it from a file, perhaps, using data with contents of URL on NSData. And we'll, of course, talk about URLs later. But now I have my data and I'll pass it to propertyListWithData: options: format: error. I pass in my read data, and the result is the dictionary that we had persisted earlier. And again, make sure that you do the right thing with the error parameter. So next is user defaults.
User defaults are also for small values, but small values that represent user preferences. We organize user defaults by what we call domains. A domain is just a group of defaults. When you read a default, we search domains in a specific order. First is the arguments to the executable. This is what we all a volatile domain. Volatile means that the contents of this domain are not persisted to disk.
Next is the one that you'll probably see most frequently, that's the application domain. These are stored in the user home directory. There's also a global domain, a language domain for locale-specific preferences, and finally, a registration domain. The registration domain is for the factory settings of your application. And let's see an example of how to use that first. So, we recommend that you set up those factory settings early in the start up of your app. Here, in the plus initialize method of a class which uses user defaults, I am going to create some default values.
Here, I'm going to create a dictionary. My first value is a Boolean, FrogBlastVentCore. And next I have a string and also an integer value. Now, to interact with our user default system, you call the NSUser defaults method, standardUserDefaults, to get the user default singleton object. Then, call the registerDefaults method on that object. Pass in your dictionary with your default values and you're good to go.
So, sometime later, we're going to read that value. Here, I'm going to use the NSUser defaults convenience method, boolForKey. I pass in FrogBlastVentCore and the result will be YES, because we searched through our domains and we ended up in the registration domain. And we read the value that we had just set up.
I want to show you how the argument domain works, because it's a really useful debugging tip. Here, I pass in ConferencName and WWDC. I can get that value by just calling the user defaults method stringForKey. And it will check the first domain, which is the argument domain, and get the value out of there, WWDC in this case. Setting user defaults is also very simple.
Here, I'm going to change FrogBlastVentCore to a value of NO by using setBool forKey. And then, sometime later, presumably after my application has quit and restarted, or not, you can call boolForKey, pass in FrogBlastVentCore and the result will be NO, because we found that default in the application domain where it was persisted.
Keyed archiving is next. Keyed archiving is for storing an arbitrary graph of objects. Keyed archiving's primary design purpose is to enable easy backward and forward compatibility. That's because in a keyed archive, the way it works is you associated keys with your object values. That means that you can add a key in a future version of your app, and the past versions of your app don't have to read that key, and vice versa.
Keyed archiving also allows for substitutions during encoding and decoding of objects, and it has the benefit that the objects you store in a keyed archive don't need to be one of those set of property lists types. The way that the keyed archiver knows how to encode and decode your objects is by the NSCoding protocol that your objects implement. So, let's see an example of how you do that. Here, I have a robot object, which implements NSCoding, as you can see. And my robot has several properties, a name, which is an NSString object, another robot, which is this robot's nemesis, and a model number.
Now, to implement NSCoding, I need to fill out just two methods. The first is encodeWithCoder. The parameter to this method will be the keyed archiver. To encode our object, we need to tell the keyed archiver how to store the data that we have as our properties. We do that using methods like these: encodeObject: forKey, encodeInteger: forKey, and so forth. You see them giving every object value in my class a name, like name and nemesis and model. The other method you need to implement initWithCoder. In initWithCoder, the parameter will be the keyed unarchiver.
To init ourselves first, this is an init method, so we need to call Super and return Self at the end. And one caveat here, if you're Super class implements NSCoding, you should call nitiWithCoder and also, in encodeWithCoder, call your Super class as well. Here, my Super class is NSObjects, so this is fine.
And I need to ask the keyed unarchiver for the data that we had given it earlier, using methods like these: decodeObject:forKey, and decodeInteger:forKey. Now, remember that for the objects, you're going to need to copy or retain them as appropriate. Integers, of course, don't need to be copied or retained.
As a quick sample to see that this works, I've got two robot objects. I'm going to set some values on those robot objects. And you notice that R1 and R2 are each other's nemesis. So there's a cycle there. But keyed archiver can handle that. I call ArchivedDataWithRootObject, pass in R2 and the result is an NSData that holds all of the data in that object graph.
To unarchive it, call unarchiveObjectWithData, pass in our data, and the result will be our new robot R3 object. And to verify that it worked, I'm going to check with R3's nemesis' name is and it is Bender, as we set up on our previous slide. I mentioned larger, more complex data structures. I want to point you towards the Core Data framework. Core Data is its own framework. We have support for it in Foundation as well. Core Data has many features. It supports keyed value coding and keyed value observing directly.
You can validate the values that your user inputs and maintain consistency of the relationships between objects. It supports change tracking and undo, sophisticated queries, and unlike the previous forms of persistence that we've seen, incremental editing. There was a session yesterday on Core Data, called Mastering Core Data, that you could check out. And I believe they have another lab this week, as well. If not, check out the documentation online. Core Data is a really powerful framework.
So, I showed you many methods of persistence. Again, how do you pick which one to use? So, if you're storing user preferences, use NSUserDefaults. If you have small files, and you want to cross platform file format, you can consider NSPropertyListSerialization with XML format. If you have an object graph with cycles and non-property lists types, you can consider using the NSKeyedArchiver.
And a real-world example of where the keyed archiver is used is nib files. All of your nib files are keyed archives. And if you have a large data set, or you need database-like features, check out the Core Data framework. And there's one more option which I don't want to neglect.
If you have a very complicated set of data, or a very specific set of data, and you don't need the features that are provided by our Foundation classes, you can use your own custom data format. You probably would be looking at the NSData class to help you out with that. Next is Files and URLs. So, NSURL is our preferred way to reference files and resources. And especially starting in Snow Leopard and iOS 4, you'll see a lot more URL taking methods.
If you're working with a file URL, then you will use our NSFileManager class. If you're working with a network URL, you will use our URL loading classes. So, let's look at some NSURL examples, and then we'll see the others. Here, I'm creating a file URL with a path to a file in my home directory.
NSURL has many methods to manipulate URLs, so you don't need to poke with the characters yourself. This one will delete the last path component and give me a URL that points to my home directory only. I can append a path component and get another file in my home directory. And for network URLs, you can create them like this: URLWithString, I pass in Apple.com, accessed over HTTP.
The NSURL class itself has other features as well. There's a concept of file reference URLs. The primary purpose of these is to maintain your tracking of a file independent of its location on disk. That is, you want a reference to a file and the user moves it. File reference URLs can help in that scenario. On Mac OS X, we have a rich system of file system resource properties. You use them like this: getResourceValue:forKey:error.
Here, I'm passing in the URL effective icon key and the result will be an NSImage that has the icon of that file. There are many more keys you can specify to get localized name, file size, creation, access and modification dates, and label color and many more. You should see the NSURL header on Mac OS X for a full list of all the properties that we support. NSFileManager lets you copy, move, link or delete files.
You use it like this: Create your file manager object, then call methods like: copyItemAtURL: toURL:error, and so forth. NSFileManager also has the capability to enumerate directory contents. This method, contentsOfDirectoryAtURL: includingPropertiesForKeys:options:error, does a shallow enumeration. That is, it won't descend into subdirectories. Now, here you see the second parameter is an array of properties. This is a really efficient way to get a set of properties for a bunch of files in a directory, like all of the icons, all of the modification dates, all of the file sizes and so forth.
NSFileManager also has another method, which lets you enumerate directories in a recursive subfashion, that is, descending into subdirectories. That method returns an NSEnumerator subclass, which you should remember from our first section, is pretty easy to use as well. It also supports finding system directories, application directory of the Desktop and so forth. It has delegate methods for detail control. So you can set up a file manager to call your delegate methods when you're moving files any time an error occurs on any file and scenarios like that.
And you can get and set permissions and other attributes on files. So, if you have a network URL, you're going to check out our URL loading system. The primary class there is NSURLConnection. This class performs the loading of a URL. And there are two modes of operation. The first is asynchronous, using run loops and delegate callbacks. And the second method is synchronous for background threads. Never block your user interface, your main thread, on network access. The other two classes are NSURLRequest and NSURLResponse. These are classes used to store data used when you're loading.
As an example, we are going to load the Apple Home Page with the Apple URL that we created earlier. I create my URL request, and then I create my URL connection object. I call initWithRequest:delegate:startImmediately. I pass in the request and I set ourselves as a delegate. And we'll see those methods in a second. And I tell it to start immediately. But this won't do anything until you run the run loop. So, if this is on your main thread, run the main run loop. And this is also how you avoid blocking your user interface.
These delegate methods will be called when something interesting happens. Before any data is received, you will receive the connection:didReceiveResponse method. Here, I'm going to set up some data to hold the received data that we have -- we're going to receive on our connection. Next is the connection:didReceiveData. This is called one or more times with the actual data that's been received. I'm just going to append it to our received data object.
If something goes wrong, this method will be called, connection:didFailWithError. You should do whatever error handling is appropriate for your app here. And finally, if everything worked great, we're going to get: connection:didFinishLoading. And there, you should use the received data. These classes support other features as well: cache management, authentication, cookies and support for protocols other than HTTP. Next up is bundles.
Bundles are for grouping code and resources. They allow you to include code for different platforms and architectures. For example, on Mac OS X, you can have an app which supports Power PC, Intel 32-bit and Intel 64 bit. Also bundles simplify the loading of localized resources. Let's see an example of that. So, here is an example bundle layout on Mac OS X.
You see, it's for my application, which is a directory. And in the contents, I have an info plist. This is a property list which describes the attributes of my application. Here is the actual executable code. And in the resources subdirectory are non-localized resources. This is the icon for my application.
There are also directories for all of the languages that my application supports. I support English and French. And inside those directories, we have localized resources. Here is a localized image, a main menu, the entire nim file is localized, and localized strings. Now, to load a localized resource, I'm going to first get the application bundle. And I can do that with the NSBundles main bundle method.
Then I call URL for resource with extension, pass in my localized image name and extension, and if my user's running on an English system, they'll get this wonderful image of a stop sign. If instead, they're running on a French system, they will get this wonderful image of a stop sign, which is the same, but in French. And you notice that we didn't have to change our code at all. This behavior is entirely driven by NSBundleCode and the resources that you have in your application bundle.
Now, sometimes it's easy to confuse what the difference between a bundle and a package. Bundles, as we've mentioned, the primary class is NSBundle. Use it for code and resources. An example of a bundle is a framework, like Foundation framework. If you open up System/Library/Frameworks/Foundation.framework, it's a directory. You double-click on it in the finder, you'll see the Foundation executable parts, the headers the resources and so forth. A file package, on the other hand, the primary class is NSFileWrapper.
NSFileWrapper is in Foundation on iOS 4 and the application kit on Mac OS X. File packages are used for user documents. An example might be a rich text document with an image attachment. In that case, the example extension is rftd. The point here is to treat that directory that contains all the appropriate resources and attachments to the text and so forth, as one document in the finder. So the user can't lose associated pieces of their documents. When they double-click on that rich text document, it will open in the default editor.
There are also things which are both bundles and file packages. The most common example of this your application. As you see in its directory, you're still going to use NSBundle, and it stores your application code and resources. But, if the user double-clicks on it in the finder, it opens up the application. It doesn't open up like a directory. Bundle also supports the ability to load other bundles.
This would be a plug in system. You can locate the various components of a bundle, list all the resources of a type. Like I want all the sound files in this specific subdirectory. And, of course, bundle is where you're going to find support for all of your localized strings.
Next up is operations and operation queues. These classes are an object-oriented way to describe work. We put them in there to simplify the design of concurrent applications. There are two main classes. The first is NSOperation. This is a class which encapsulates your work unit. And you use it by subclassing and overriding the main method to define what your work is.
Or use one of our Foundation-provided subclasses. There's NSInvocationOperation, which lets you specify a target and selector. And there's NSBlockOperation, which is new in Snow Leopard and iOS 4, that lets you specify your work as a block. The other main class is NSOperationQueue. This class maintains the list of operations that need to be performed.
And you can think of it as the engine which runs your operations concurrently or serially. As an example, I have an operation queue here with a maximum concurrent operation count of 2. I've put five operations in this queue, and you can see the first two are already running.
Now, as those operations finish executing, the other operations in this queue will begin. And operations can be added to this queue at any time. Those operations will run as well, when there is space available. Let's see how you subclass NSOperation. This is the simplest possible. Class called MyOp and I override main and put my work there. Now, you may want to put initMethods or GettersandSetters or other methods that you can do something more meaningful here. But this will also work. Then later, create your operation queue, create your operation object and add it to the operation queue.
And your operation will start running. So, you might wonder why to bother using all of these complicated, is seems, classes. I can create threads, I can use run loops, but operation queues let you take advantage of a lot of power by adding very minimal complexity. To demonstrate why, I made a sample app that loops through all images, in this case, which is 20 images, and performs some image processing on each of them. Now, when I ran this on my MacBook Pro with two cores, this one will run in a single-threaded fashion. And it took 2.22 seconds to run.
To see if using operationQueue will provide me with a benefit, I added a grand total of four lines of code, create the operation queue, add the middle part as a block operation to my operation queue, close the block and, to simulate the single-threaded case, wait until all those operations are finished. And the result was dramatic, 1.25 seconds. Almost a full second faster.
And all I did was add four lines of code. So, not all problems are as easy to make concurrent as this one is, of course. But, it's worth taking a look at your application, seeing where you might be able to take advantage of the hardware that is in these kinds of computers and add very minimal complexity to your code.
Operation has a few more features. You can set up dependencies between operations even if those operations are in different queues. You can set up priorities of operations within a queue. It has KVO-compatible properties, so you can find out when it's running, when it's finished and so forth. There's new in iOS 4 and Snow Leopard, a completion block, so you can have a block run when you're operation is finished. So, we looked at a lot of classes today. So I want to do a quick summary. We talked about collections and how they are a place to store your stuff.
We talked about iteration, filtering and sorting. Then we talked about strings and the methods that NSString has to let you treat those strings as opaque containers. We talked about dates and times and the classes you can use to avoid burning your users' cakes. Then we talked about persistence and archiving, how you store things on disk and get them back. We went over the files and URLs that let you efficiently perform file system operations. We looked at bundles and how they simplify cross-platform development and localization. And we talked about operation queues that let you take advantage of multi-core computers with minimal complexity.