Transcript
>> 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.