WWDC2010 Session 138

Transcript

>> Ali Ozer: Good afternoon, everyone.
Welcome to API Design for Cocoa and Cocoa Touch.
My name is Ali Ozer.
I'm the Manager of the Cocoa Frameworks team at Apple.
OK. So first, there's lot of you here, let's just make sure
we're on the same page with regards to what we mean by API.
Now, I looked it up.
API turns out to have many meanings.
But we're not here talking about
Active Pharmaceutical Ingredients.
We're not talking about Armor-Piercing Incendiaries.
Definitely not the American Pirate Industries.
And it's not the American Pain Institute.
Now, these all sound like fun topics for
talks, but this is not what we're covering.
Instead, we're talking about Application
Programming Interfaces.
Now, APIs, Application Programming Interfaces, are very
important because they allow your code to interface
with the system, you know, let you use
the facilities of the operating system.
But well-designed APIs go much further.
They make you more productive.
When you're using well-designed APIs, you don't
have to look at documentation for every single API.
You've learned how the APIs work in general.
You can predict how they're going to behave.
Well-designed APIs allow you to leverage existing code.
Not only can you reuse something, you can
go ahead and extend it to your own needs.
Well-designed APIs also let your write
code that works as the user expects.
That's because well-designed APIs set policy.
And by using these APIs, you make apps that,
for instance, do the HI guidelines correctly,
so the user is happy because your
code works like other code.
Now, APIs live for a long time so that's
why we consider API design very important.
Once we've put an API out here and it's
adopted, it's very hard for us to get rid of it.
So, if we made some mistake, it takes
a while to correct that mistake.
And one final factor is that API
design is UI design for developers.
So, just like Apple takes UI design of its software
and its hardware very seriously, we
also take API design very seriously.
Here are the five factors we consider important when we're
designing APIs, and the talk is going to cover, it's going--
the talk is going to have these
five sections covering these areas.
Each section is going to start with why
these areas are, these factors are important,
and now I'm going to give examples of how
these factors are reflected in our APIs,
and how you yourself can also use these--
use these factors in your own APIs as well.
Now, this talk assumes knowledge of Objective-C, and
you've hopefully programmed a bit in Cocoa or Cocoa Touch.
A lot of the examples I'm going to use are from Foundation,
since it's a common substrate to AppKit and UIKit.
However, in some cases, we'll also use
examples from AppKit or UIKit as well.
OK. So with that, let's begin to our
first section which is consistency.
And the main topic in this section is naming conventions.
Now, I'll just cover some of the
naming conventions we use in our APIs.
Now, why is consistency important?
Well, as I said earlier, it eliminates the need
to refer to documentation for every single API.
You can read about the general guidelines,
the general rules, the general philosophy,
and now you don't have to look up every
single API to understand what it does
or what other APIs there might be with it.
Consistency also allows different subsystems
to plug-in to each other more easily.
You know if you get one Objective-C API from
someone and another one from someone else,
with this consistency, it should work well together.
And finally as, well not finally because there
are many, many reasons why it's important,
but one other way it's important is that being--
having consistent APIs actually can
improve performance as we'll see later.
OK. So let's dive into naming conventions.
And the first thing I want to talk
about is naming of classes.
I'm sure many of you have seen some of the classes we
have in our APIs: NSString, UIView, CLLocation, et cetera.
And typically, class names are nouns.
And one thing you'll notice is that we use prefixes.
Now, prefixes protect against collisions,
and they also differentiate functional areas.
A name like view or location is highly generic.
It's possible that two different
developers might use the same name.
But in addition, using the prefix like
CL allows you to immediately recognize
that this class comes from Core Location.
So in your own designs, in your application, you don't have
to use prefixes since you're managing the whole namespace,
but it might be a good idea for you to use prefixes as well
in which case I recommend either using three-letter prefixes
or maybe even more complete longer prefixes for your class
names, where it may be it's the name of your company.
Method names, you've undoubtedly seen many
methods if not before during this week.
Here are some examples.
With method names, we focus on readability.
For instance, the name "is Editable" there.
We put the "is" in there as a general rule because
it makes it easier to read, myTextField.isEditable.
That reads very naturally.
Now, some other things you'll notice here.
We use camel case.
What camel case means is the subwords
inside our names are actually capitalized.
We don't use underbars or anything like that.
We actually use uppercase letters.
And one other thing you might notice
is we choose clarity over brevity.
In some cases, our names are not short
and we also name all the arguments.
Now, I'll talk about these last two now.
So, here's an example method from
NSMutableArray removeObjectAtIndex.
You might be wondering why all those words.
Well, this method is trying to communicate to you that
removes an object at an index, so the argument is an index.
In fact, we have another method called
removeObject which removes an object.
So you can immediately see that these two are distinguished
by those words, and it's really clear what each one does.
So, those of you wondering why isn't there just a
method called remove, well you can now understand why
that the method remove would be ambiguous
in the presence of these other two methods.
Remove by itself just does not
communicate either one of these.
And for that reason, we often try to name, you
know, with a noun what the argument is about.
Naming all the arguments.
Here's a method that's not the longer
method names in our frameworks.
It comes from NSBitmapImageRep, in AppKit.
You can see all the keywords I'm talking about,
you know, the pixelsWide, pixelsHigh and so on.
Now, here's a secret, but I don't
want you to take it out of this room.
In fact, you should forget about it
after the beer bash wil help with that.
I want you to forget about it after you see it.
But you can create a method name like this
where you actually removed all those keywords.
But we don't want you to do that.
As you can see, you immediately start losing, you
know, some of the what-- you're trying to communicate.
When you're using this method in
code, here's what this looks like.
You know, you have all these numbers like 32 YES, NO, 0.
Without those names, you know,
really this will be lot less clear.
In fact, let me give you a concrete example.
Back about year 2000, we're experimenting with Java and,
you know, a lot of other people were a lot of crazy kids.
But we didn't inhale.
[ Laughter ]
[ Applause ]
Anyway, so here's what this-- and we have this
class method actually existed in Java as well.
Here is what it look like.
New NSBitmapImageRep, and as you can see, you immediately
lose all what all those arguments mean - 32, true, false --
you know, you have to go and look up what this means.
So, this is the reason why those arguments are important.
OK. Naming accessors, getters, and setters.
Here are some examples:- color,
setColor, isEditable, setEditable, so on.
Now, one thing to note, as I mentioned earlier, for boolean
properties that are adjectives, we use "is" on the getter.
So, it's "setEditable", but the getter is "isEditable".
Another thing you might notice is that we do not
embellish the getter with "get" or other verbs.
So, for instance, the color method is not called "getColor".
It just reads better to have it be "color".
Another example is we don't put words
like "compute" on the accessors.
For instance, the last one here, even if
the thumbnail image is computed, you know,
when you ask an object for its thumbnail image,
even if that object computes the thumbnail image,
it's usually not interesting to communicate
the fact that it's being computed.
Just have the property, call it
ThumbnailImage and that's good enough.
Only in the cases where it is important to communicate that
or distinguish it would you want to put those words in.
Now, there are some acceptable uses of
"get", and that's one case is when we use,
we use get on accessors that return values by reference.
For instance, the NSData method getBytes: which returns
an arbitrary number of bytes given the range argument,
it really needs a buffer so to get method.
Another example is this method from UIBezierPath
which returns three related arguments,
three related properties at once.
In this case, we actually have a get
method that returns three different values.
They are all by reference.
You'll note that the caller can pass NULL in
for the arguments they don't want in such cases.
For accessors, the simple accessors, you
can certainly go ahead and use @property
and we encourage that whenever it's possible.
You know, our accessors in this case would
look like this, property (copy) UIColor *color.
For the editable one, we have a special getter because
@property does not know about our rule about putting "is",
so you might have seen this in our
APIs where we specify a special getter.
Now, let me talk a bit about functions.
Function names you probably seen a few.
Here's what they look like.
Typically, for functions, the rule is to use a framework
prefix followed by the type or functionality area,
and then followed by whatever is happening.
For instance, CFRangeMake, CGPathAddLines.
And the common prefix allows easier
searching, sorting, et cetera.
Enum values and constants are very similar.
Here are some examples.
Now, we also have sometimes common suffixes.
Notification is one of them.
We use that for NSNotification contants.
Key is another one.
That's for symbols that are dictionary keys.
Now, one thing to note about functions, enum values and
constants, and it's actually true for some other APIs,
our naming conventions have evolved over time.
So, although we have some examples of, you know,
great naming, which follow our conventions,
you will also occasionally run
across names that are not that great.
These are mostly some of the older names that
we haven't either had a chance to deprecate
or we're just leaving it in there for old time sake.
You can see some examples here.
So, not everything you run across especially in some of
the older APIs may be correct but, you know, in general,
for new APIs, we definitely follow the latest conventions.
Do not abbreviate arbitrarily.
Here is a fine-looking method,
which is maybe a little too wordy,
but you know, as I said again, there is value in that.
You might make it just a little bad
by arbitrarily abbreviating point
to pnt, you know, setFloatingPntFormat, OK.
I mean this generally help you because
Xcode usually helps you at typing.
Or you can make it really ugly by removing all
the vowels out of it, if you're a vowel hater.
But you know, we don't recommend this.
So, the first one is really the best one here.
Now, there are some acceptable abbreviations
though, and it's good to be consistent.
Our documentation lists some of these acceptable
abbreviations: alloc, dealloc, et cetera.
There are also commonly used acronyms such
PDF and USB, and we recommend you use these.
Because if you went ahead and spelled
out portable document formats,
some people might not even know what you're referring to.
You know, people are so used to terms like
USB and PDF, so it's best to stick to them.
It's also good to stick to consistent terminology.
Earlier, I gave you the example of remove, you know,
maybe one possibility is, hey, instead of remove objects,
let's use delete, and instead of remove
this way, let's use some other term.
So, you know, there might be a tendency to
look in the source and come up with synonyms.
There are a lot of great synonyms.
You know, vaporizeObjectsAtIndex, sounds great.
[ Laughter ]
However, it really does make APIs less predictable,
so it's, you know, good to stick to consistent words
and use more descriptive words
to describe your functionality.
And avoid names that are ambiguous.
You know, at first glance, these
names don't look so bad, but sendPort,
is that a method that sends a port
or is that a port used for sending?
displayName, is this a directive to display
a name or is it name used for display?
Center, is that a verb or is it a noun?
It turns out center is a verb in AppKit and a noun
in UIKit, so there's one we missed out of the gate.
So, it's good to try to disambiguate these
names and choose a little more clear names.
Here are some suggestions here.
One is this portForSending.
localizedNames is actually one we do use whenever we can.
And a middle, maybe center as a noun, middle might be a
better term, we don't use it, but maybe we should consider.
And let me talk a bit about Block naming.
Blocks, as you know, are new in Snow Leopard and iOS 4.
Block itself can be ambiguous because Block as a
verb is like block, you know stop, block processing.
So, we try to be careful with that.
So, we use the term Block only in generic
cases such as enumerateObjectsusingBlock.
But in other cases where we can have more descriptive
names, we have come up with a list of names that we use.
For instance, indexOfObjectsPassingTest, a Block that
returns a boolean that cites or testing is a test.
We also have Comparator, handler,
completionHandler, ExpirationHandler, et cetera.
So, these are some of the terms
we are using to describe blocks.
OK. So, the last item I want to discuss
in this section is object ownership.
Now, most of you are probably familiar with object
ownership rules in Cocoa and the memory management rules.
Object ownership is not transferred across calls,
and the only methods that actually do are exemptions
to this rule are methods whose names begin with
alloc, new, copy.mutableCopy or the retain method.
So, what this means is whenever you're looking
an API, you never have to ask the question,
do I have to release the results from calling this method.
And similarly, if you're designing your
own API, you have a good guideline.
Now, there might be mistakes in APIs especially if a
subsystem was designed without API guidelines in mind,
maybe it's an internal subsystem of your application.
And if you're on the Clang Static Analyzer, it will
mark these erroneous names and give you warnings
that you're over-releasing an object
or you're not releasing it enough.
We do have some attribute, a decorator
you can add to methods for this purpose.
For instance, if you had a method name, full name
that erroneously returned and retained object,
you could put NS_RETURNS_RETAINED
on there to sort of describe
to the analyzer, hey, this returns your retained object.
However, please use this only for
Static Analyzer's purposes.
You know, do not use it to define
bad APIs or just iffy APIs.
We intend not to have any APIs, you know,
in this system that use this, for instance.
If we did, that would be an error and we'd want to fix it.
OK. With that, let's get to the performance section.
In here, we're going to talk about Impedance matching, I'll
describe what I mean by that, mutability, and concurrency.
So, first of all, why is performance important?
Well, you know, it seems like that's an obvious question.
Users love it, you know, when the applications are
fast in response, so that seems like a clear one.
But another reason performance is important is that even
though an application might seem responsive and fast,
maybe it's not fast enough or maybe it's not doing things
fast enough where it's using more power than needed.
So, being especially focused on performance, you
know, making sure you're getting everything as fast
as possible can improve battery life and make users happier.
So, let me talk about impedance matching.
Impedance matching, to describe what it means, it's a term,
I think, from electrical engineering where it's talking
about input and output connections,
and it's done in such a way
that the power is maximized, the throughput is maximized.
So, it's basically a term that, you know, where you
can connect things together with highest efficiency.
You might think of it as plug and
play, really, easy plug and play.
Now, what I mean here is this concept of using small number
of basic data types in APIs, and not having redundancy.
That improves consistency.
You can plug things together more easily.
Code fits together more easily.
In addition, it eliminates the need to do conversions.
If you have two different representations for the
same concept, every time you cross an API boundary,
you have to do conversions on those,
and that just reduces performance.
So, for to this end, we have a small
number of basic data types for strings.
You know, we use NSStrings everywhere,
in all API for strings.
NSDates for dates.
NSURLs to represent file paths.
For ordered collections or association tables
or map tables, we use NSArray, NSDictionary.
There's also NSSet, but it is used less commonly in APIs.
In addition, we have this other
higher level types, UIColor and UIKit,
NSColor in AppKit, and types for font and image as well.
So, I recommend that anytime you want to represent an image
in your application or in you API, you use either UIImage
or NSImage with regard to UIKit or AppKit,
and there are few other types that fit here.
Now, not all basic data types are objects.
You might know that we have some
C types in our APIs as well.
For instance, NSInteger and NSUInteger.
These are actually simple typedefs on top of int or long.
CGFloat is another type like that.
It's either a float or a double.
Now, we do have an NSNumber type.
It's an object to represent numbers.
But you've probably have noticed that we do not use
NSNumbers in our APIs to represent numerical quantities,
we only use NSNumbers to wrap these
basic types when necessary, for instance,
when you're putting them in dictionaries or arrays.
Now, we also have some struct types.
And for widely use types, you know, things they are using
all over the place where abstraction is not important,
it's not valuable, it's OK to use structs.
We have four widely use structs,
points, ranges, sizes, and rects.
Consider this point, you know, CGPoint, it's got X and a Y.
We have, you know, we do not pretend
that anybody will want to extend CGPoint,
nobody is going to want to subclass CGPoint.
We have a bunch of utility functions and that's good enough.
It's OK for you to go in there and group
the X and the Y values, you know, it's OK.
It's a public struct, we all understand it,
it works well, points aren't going to change.
With that in mind, structs are OK.
Structs are good because you can, you know, stack allocate
them, and you can use them, you know, pretty flexibly.
Now, once upon a time when we were
designing some of the basic types,
we made the mistake of designing
the color type to be a struct.
So, it was basically a struct with RGBA,
and we quickly realized our mistake,
you know, and color is much richer type than that.
You know, you have different color representation, CMYK.
You have color matching information.
So, we could quickly dump that
idea and switch to a color object.
So that was an example where our struct is not OK.
Now, so far, I've been start talking about reducing a number
of types, you know, et cetera, but then I'm also talking
about sometimes multiple types that seem equivalent
and that seems to violate what I'm saying.
So, you know, what's up with CGPoint and NSPoint?
Why do I have two types to represent points?
Well, those of you working on UIKit probably have never
even come across NSPoint because that's an Appkit concept.
NSPoint was the original type for NSPoint in Appkit.
CGPoint was added later.
In fact, we have made the two equivalent in our
64-bit by making them the exact same typedef.
So, for all purposes, these two should be
considered equal when you're working in Appkit.
And in UIKit, you only have CGPoint.
How about NSInteger and NSUInteger?
These represent signed and unsigned integer value.
Why do you we have these?
Why don't we just use int?
Well, our API is used to all the in terms of ints
or unsigned ints, but then when we moved to 64-bit
on the desktop, we decided to add these
typedefs to sort of abstract that away.
So, all our APIs are not in terms
of NSInteger or NSUInteger,
and these are defined to be int
or long as needed by the platform.
So again, these are just basic types on
those and they are just simple covers.
And finally, the CFStringRef versus NSString.
Some of you might have played with Core
Foundation, used the CFStringRef type for instance,
and you might be wondering why
do we have two types for strings.
Well, it turns out these two types
are actually totally equivalent.
You can just typecast between them,
no conversion is necessary.
The reason we have CFStringRef is to satisfy
the needs of very low level implementations
that do not use object-oriented programming.
For all object-oriented implementations
based on foundation or above, use NSString.
It plays much better with the object-oriented environment.
And it's also on the desktop, especially.
This is important.
It's garbage collection ready out of the box.
So, we refer to this relationship between
these two types as toll-free bridging.
And this relationship also exists for CFArray and NSArray
or CFDictionary, NSDictionary and also a few other types.
OK. So with that, let me talk about mutability.
Now, mutable means changeable,
sort of like the term "mutate."
Many objects are by nature mutable-- only
mutable, for instance, NSWindow or UIScrollView.
It's got many properties that can
change, and it's a changeable object,
you can't even think of it as an
unchangeable, immutable object.
But then there are other types such as
strings or colors that can be considered
in immutable forms and these are usually value objects.
And what I mean by a value object is object whose important
characteristic is its value not its object identity.
If you have two strings and their contents are both "hello",
you would consider those two strings to be equal, right?
So, those are value objects.
And in those cases, the immutable approach makes sense.
And in some cases, having both a mutable
and an immutable variant also make sense.
An example is NSString.
If you have an immutable NSString, here is how
you would append to it, you have your string,
you call stringByAppendingString and you
get back a new string that has a result.
If you have an NSMutableString and you want to
append to it, you would just call appendString
and it just modifies that sting in place.
So, looking at that, you're like, "Hm.
Mutable string seems more efficient here.
We don't have to create an intermediate object.
Why do we even have the immutable version?"
Well, there are some reasons and
actually performance is one of them.
If you have a string which has five characters in it
like hello and you know it's never going to change,
the storage for that string can be very optimized.
It can store those five characters in its base object.
In fact it doesn't even have to create
another malloc block for instance.
This also yields to simpler implementation.
No mutation is needed.
You don't have to keep track of all sorts of editing state.
This yields to thread safety.
You know, if you're not changing,
you can be a lot more thread safe.
And this also allows for easier analysis of program logic.
If you have an object at point A whose value is hello
and it's immutable, you know that later at point B,
it's going to have the same value, so that makes
some things about your program easier to understand.
OK. Now, which one to use in APIs?
For instance, let's say you're a window or a button and
you have a title, here's what your API could look like.
NSString title to give you a new title, you
know, the callers exactly give you a new title.
Or you can have an API like this, NSMutableString
title where the expectation is that if somebody wants
to change your title, they basically
reach in there and mutate that string.
As you probably realize, this latter is not what we'd like.
We almost always recommend the immutable version; that's
because sort of having immutable title and reaching in there
to change it feels like you're totally breaking
the encapsulation of a window or a button object.
However, oh, and by the way, this is-- if you're using
properties, which as I said is usually, you know, good idea,
this adheres to the way how you would declare
this property, property (copy) NSString*(title).
Now, there are few exceptions to this.
For instance, there's an NSAttributedString class.
It has a string method, however, its subclass
NSMutableAttributedString has a mutableString method.
And this method, just because it's so special, it
returns NSMutableString value and has the word "mutable"
in the name actually expects you to mutate that.
It's ready for you to mutate that.
So, this is, and in this case, when you mutate that
string, the containing mutable attributed string,
it sees the changes and takes appropriate action.
So, these exceptions are almost always going to be
named with the word "mutable" in them one way or another
to make it clear to you that yes, this is OK to mutate.
OK. Let me now talk about concurrency.
Now, concurrency is a way to achieve
higher performance on multi-core machines.
As you know, on the desktop, we're adding more
and more cores instead of cranking up CPU speeds.
So, being able to run things concurrently
means you can achieve higher performance.
Now, we've-- you know, all along we
have various ways to do concurrency --
we had thread objects, we had runloop objects,
we had notification queues, and so on.
But now we have a way to represent even more granular,
smaller grained work, and blocks are of course that tool.
Blocks are a good fit for presenting concurrent work.
They can be processed by Grand Central Dispatch.
They can be executed by NSOperationQueue.
But even more importantly, they can capture state.
Now, if you're going to have some concurrent work
happening, it's great to be able to encapsulate that work,
throw it out there, and have that
block capture all the state it needs
so it doesn't rely on you staying around anymore.
So, from that point of view, blocks
are also good hit for concurrent work,
but note that not all block usage is necessarily
concurrent, and we'll see examples of this later.
Now, in our-- in the APIs we're adding, when we want to
enable concurrency, there's often an explicit option,
for instance, enumeration, sorting, searching.
Here's an NSArray method
enumerateObjectsWithOptions:usingBlock.
Here's a usage case for it.
If you provide the option NSEnumerationConcurrent, the
block is executed concurrently with elements in that array,
so that's an explicit way to get
concurrency out of enumeration.
If you're at the What's New In Foundation talk, Tuesday,
you probably saw this in a lot of the other related APIs
that we introduced in iOS 4 and Snow Leopard.
Here's another example, NSNotifications.
Observers can now indicate that they want
to receive notifications concurrently.
For that, we have a new method,
addObserverForName object queue usingBlock.
If the observer indicates a non-nil queue, the block
will be executed concurrently for that observer.
So, this is again, an explicit way to
indicate your find for concurrency.
And here's one more example out of
the spell, NSSpellChecker class.
NSSpellChecker can do the spell checking.
This class does the spell checking,
grammar checking, et cetera.
It can do the checking either in a synchronous fashion with
this method where this method simply returns the result
as its return value, which makes sense for a synchronous
approach, and then returns additional results here
in these two argument, or it can do it asynchronously
with this method where the results are communicated
in the completion handler block at a later time,
and the results are returned in
these arguments, passed to the block.
And also note one more thing, the sequence number passed to
the block is actually the integer value that was returned
by this method so that you can associate the request
and the reply at a later time when the reply comes in.
OK. So, with that, we get to our safety section.
And here, I'm going to talk about runtime
errors, programming errors, and also atomicity.
So, safety is important because, you know, you can
reduce the chance of crashes with safer programming.
You can make debugger, debugging more easy, and also
writing more robust apps, which even if errors occur,
it can indicate to the user why errors occur
then allow the user to recover is a good idea.
Now, there are two types of errors that we're going to talk
about, expected errors which we also call runtime errors
and programming errors which are sort of these unexpected
errors that are caused by often bugs in the program.
Now runtime errors are errors that are expected to occur.
For instance unreadable file, a file gets corrupt,
the user tries to open it, they get an error --
out of disc space, user is trying to save a file but the
disk was full, lost network connection, invalid user input,
you're trying to make a trade and you indicate
billions instead of millions you know it might happen.
So these are the kind of errors that
your program should guard against.
Now these are typically handled with
return values in an optional NSerror.
The return value is often a boolean or an object
that returns an initialized object or nil.
And in cases where you want to report an error
you would use NSError argument, here's an example.
Here the ID returns, you know, returns in your
object, if it returns nil then the error argument,
NSError** that's a by reference
argument indicates what the error is.
And of course as before you can pass NULL here
if you don't want to hear about that error.
Now sometimes NSErrors can be passed by other means
for instance here is a UIWebView delegate method,
if an error occurs during loading, this delegate method
is called and the error is simply passed to the delegate.
And here's another example: NSWorkspace.
This is a method to NSWorkspace to make copies of
files just like the finder does on the desktop.
This is an asynchronous call in a way to you pass
the URLs to copy and the completion handler is called
with the NSDictionary indicating to you what the new
names of the copied files are and in addition NSError
which could be nil if no error occurred or maybe
an error that indicates a partial error like 3
of the files could not be copied but the rest were.
So this is actually a pretty novel way
of using and indicating a partial error.
Now not all APIs need NSError in fact if you look
at our APIs you probably see only a
few percent of them have NSErrors.
That's because NSError usage is really best confined
to API's you know where you really want the caller
to either look at the error and make
decision based on the error type.
But even that's a chore you know having to look through the
NSError, figure out the code, have a big switch statement
and then in the future new codes
are added you know that's not fun.
So the other, the more compelling reason to add
NSError is that the error may be reported to the user.
What I mean by that is you get an error from an API.
You know, you can just put it up to the user and
in fact have the error automatically provide ways
of recovery from that error and NSError has that.
It has the failure reason which is
localized and it's got recovery suggestion
and even a way to provide recovery options.
And the errors we provide out of our frameworks
usually have well localized error messages
and sometimes recovery suggestions as well.
And if you're using the App Kit there's this responder API
that will let you present an error
either modally or as a sheet.
But on UIKit as well you can actually take an NSError
and show the various localized
pieces in whatever fashion you want.
OK. So let's talk about programming errors.
And these are errors often caused by misuse of APIs.
Here's an example.
This is out of bounds access into an array.
Here's another example where you're calling a method which
expects a caller but you're calling it with a string,
you know sort of maybe looks like a caller.
And an invalid parameter value, we often consider
nil not to be a valid string when passed to APIs
and you know this is a bad call here setStringValue of nil.
Now we do not indicate programming errors by return values
because programming errors are really errors in programming
in program logic and these should be fixed and
dealt with before you ship the application.
It's not something that you'd expect to
deal with on the user's machine at runtime.
You want to deal with it before you ship so you, you
know, you test, you find these bugs, you fix them.
So there's no need to deal with them at runtime.
So therefore you will never see an API like this,
setBackgroundColor, which returns an error code.
And if you happen to pass in a string instead, it
will return an error indicating a string passed in.
I mean how do you deal with the set runtime is just
like why would you do that, you just fix this bug.
In fact, the compiler will probably tell you
that you're passing a wrong argument
if you have a type checking happening.
So, not a good idea.
We indicate these errors via exceptions using NSException
and it's important to note that in this fashion,
Cocoa uses exceptions in somewhat different ways than
some other frameworks than some other platforms do,
so that is the difference in the way
exceptions are used on our system.
And you know, we do not expect you to handle exceptions.
For instance, you shouldn't have to write code where you
set a background color and then put that tri-cache block
around it just in case a string was passed
and so you can cache it or run deal with it.
Again. don't write this kind of code.
Just listen to the compiler and try to fix these bugs--
discover and fix this bug before you ship your application.
Now, it might still be a good idea to have a
top-level exception handling in your application.
So if these kinds of exceptions do occur for the
user, you know, you should be aware, hopefully.
You can alert the user something bad happened
or you can try to recover and let them say--
now, if you write the Cocoa chips-- Tips and Tricks
Talk on Tuesday at 2 p.m., you saw a technique a way
of doing this on the-- in the context AppKit.
So, the last safety topic I want to talk about is atomicity.
Objective-C 2.0 properties are by default atomic but they
can be made non-atomic by putting non-atomic keywords.
And many APIs in iOS, in iPhone OS are
actually have non-atomic properties.
Now, here's what atomic does.
It guarantees that when one thread is setting or another--
and another thread is getting a value,
you're guaranteed to get a good value.
And the good value is either the previous value or the next
value, but you're guaranteed not to get some corrupt value
that will make you crash or some half-set value,
for instance a rectangle whose origin comes
from some place and size comes from some place else.
So that's what atomic guarantee is.
It's not very much.
So it gives you a basic low-level of thread safety for
single property but it can actually prevent crashes
in the presence of threading in your application.
So it's a good idea.
Now, it's important to note, this does not
provide consistency between properties.
It does not give you high level of thread safety.
For instance, let's say you have one part of your program
setting the first name and last name on an object,
on a person object and some other part of your
program reading the first name and last name.
If this guy sets the first name while-- and then
this guy comes in and then fetches the first name
and last name, it will get back an invalid name.
That's what I mean by how to look consistency.
You know, it might get back a name like Bill Jobs
or Steve Gates, you know, that would be good.
An abomination.
[Laughter] So you want to-- you know, that's
that high-level consistency I'm taking about.
Now, atomicity is still often a good idea to use.
Leave properties atomic.
It is safer.
When you might concern an atomic is, if you
notice when you do use instruments for instance,
if you know this calls to objc_getProperty or setProperty
keep appearing in your back traces and taking some CPU time.
That's when you might want to concern using anatomic.
Another case you might want to drop atomic is if
you already have higher level synchronization.
You know, I just mentioned an example of where
you might use higher level synchronization.
So if you already have locks or if you're already using
cues or if you already have single-threaded access
to your objects, you know, as long
as those objects are confined
to those contexts, you might want to non-atomic properties.
The Cocoa Performance Techniques talk, from
two years ago, if you can find the video
in our developer's site, has some more about this topic.
OK. So with that, I want to move to the
next major section which is Reusability.
And by Reusability, I also mean Extensibility.
In this section, we'll talk about subclassing
categories on patterns for communicating changes.
So why is Reusability important?
Well, clearly, there's no need
reinvent the wheel over and over.
If you can reuse something that
somebody else did, that's good.
And from our point of view, we want to give you API's
that allow you to do what every application does,
so that you can go ahead write the code on
top of it that distinguishes your application.
So we want you to be able to reuse
our code as much as possible
so you can do the creative things
that make your application shine.
And also, you, yourself, can write
code which can be reused elsewhere.
Maybe you can write some objects in
this application and then use them
in another project you're doing by writing reusable code.
So subclassing is the fundamental object-oriented
programming feature, of course, for your Reusability.
You know, we all know about it.
But it's not very commonly used in
Cocoa and Cocoa Touch for customization.
Most of our classes are concrete, meaning they're
usable as is and they're fairly customizable.
So often, rather than using subclassing
to customize objects,
you just set various properties to
make them behave in different ways.
However, we do have some classes
which are meant for subclassing.
Sometimes, they're abstract, meaning
you have to subclass them.
And other times, they're semi-abstract, meaning their
functional as is, but they probably want to be subclassed.
Examples are NSObject, UIView, et cetera, NSDocument.
These are classes that often subclassed.
In these cases you want to identify
which methods are there for overwriting.
NSObject init for instance, if you
don't have a custom initialization.
If you don't have custom drawing in
your UI view, drawRect, and so on.
But then there's this other subtle scenario that we have
in our frameworks, and I wanted to talk about that now.
Some foundation classes such as NSString, NSData, et
cetera, are absesetract, but they're also fully usable,
which means you can alloc init these and you
will get back a fully functional instance.
You know, you've done this many
times in your code, I'm sure.
However, if you try to subclass these, you find
that subclasses do not work unless you've gone ahead
and implemented a few methods, overridden a few methods,
and these are Primitive-- what we call Primitive methods.
Primitive methods are the minimal
API to implement a new subclass.
In the case of NSString, there is just two methods.
Just by overriding these two methods in your subclass
of NSString, you have a fully functional NSString
that does everything any NSString can do.
So why do we have this approach?
Why are these classes abstract?
Well, for one reason.
We have a bunch private implementation classes that are
hidden behind a facade, like NSString, an abstract class,
and we don't want to expose those private classes.
So we have this behavior where subclassing
is actually giving you the abstract class.
But another important reason is that we do not want to
encourage subclassing these classes for the wrong reason,
and the wrong reason being to add additional properties.
And that's because it changes fundamental
meaning of this object, what this object stores.
And let me explain that to you.
Here's NSString, it's got two fundamental
methods, length and characterAtIndex.
This is how an NSString is defined.
Let's say you subclass NSString with a RichString
because you-- RichString is just like a string.
It's got length, characterAtIndex, but it also has a font.
Now, let's try to use these two classes in a program.
You create a string, its value is hello.
You create a RichString, its value is
hello, but it also has the font, Helvetica.
Now you ask the string, is it equal to the RichString?
So the string says, "Hey, my contests are hello.
That other strings contents are hello."
because it only knows how to look at the
character values, and it says, "Yeah, sure.
We're equal."
Of course, if you ask the RichString, "Are you equal
to this poor string, the RichString puts on a monocle,
takes a look and says, "No, that string is not Helvetica.
We're not equal."
So suddenly, you're violating some law, law of physics
or math, I don't know, but you have A is equal to B--
[ Laughter ]
But B is not equal to A, and that's
just a very bad thing in programming.
So therefore, this is a bad idea.
Let's take a look at NSString versus NSAttributedSring
and how we designed those just to solve this problem.
So NSString has length and characterAtIndex.
We have this other class NSAttributedString,
I already mentioned.
It has a string and it has attributesAtIndex
to represent the attributes.
So rather than NSAttributedString being
a subclass of NSString, it has a string.
And both of these are subclasses of NSObject,
and that solves this problem I'm talking about.
So this is a good design here.
OK. So then, the question is, why would
you subclass NSString, NSData, et cetera,
if you know, we're making it hard to subclass?
Well, there're still reasons to subclass it, and the biggest
one is that you want to provide your own implementation.
For instance, you could override the Primitives
and have your own storage your data,
for instance, goes to a file lazily.
You know, I often get the question, how can you make
NSString read a four gigabyte file and efficiently?
Well, you know, you could subclass it to do that, or
various other ways of approaching large data storage.
You can have NSString with a tree
backing storage if you want.
Just by overriding those two methods,
you get that behaviors.
The Class Clusters documentation from the Cocoa Fundamentals
Guide goes into more details on this Class Cluster approach.
So we were talking about subclassing so far,
and that's one way we can extend our classes.
But another way we extend our classes is via Categories.
Categories is a language feature,
and it allows adding methods
on existing classes where all instances are affected.
This enables several things.
One of them is that you can actually distribute your
methods across multiple header files, which is nice.
But the other more interesting behavior is that
you can extend a class without subclassing,
so that all instances of that class are affected.
For instance, NSString isn't Foundation.
Foundation knows nothing about
drawing, so NSString cannot draw.
However, both UIKit and AppKit add new
functionality, add joint functionality to NSString.
For instance, UIKit adds methods,
drawInRect withFont, drawAtPoint withFont.
So let me show you how this works.
Let's say we have a drawable string subclass.
So here's NSString, its got length,
characterAtIndex, and bunch of other methods.
We go ahead and give you a drawable string subclass
which knows how to drink-- how-- not to drink.
How to--
[ Laughter ]
How to draw.
[ Laughter ]
[ Applause ]
OK. And then if you create, if you have other
subclasses, some subclass and another subclass,
you can see that these subclasses do not know how to draw,
so to draw a string you'd have to take these other instances
and convert them to drawable strings before they can draw.
So this sort of violates that all
impedance matching thing, you know,
where you have to convert types just to make them draw.
So, this is not a very good design, but by using categories,
you have NSString, the category is added into the string
at runtime so some of the NSString knows how to draw.
It replies to drawInRect, drawAtPoint, et cetera.
And then your subclasses are now magically also able
to draw although, you know they have nothing to do it
because these methods are part of the super implementation.
So that's what categories enable.
Now, in addition to subclassing and
categories, one other way to, of course--
another way to extend behaviors of classes
is to have helper classes, helper objects.
And so, we have several patterns for communicating changes.
You're probably familiar with these: delegation,
notification, key-value observing, target-action.
If not, we do have some good documentation on this
in the Cocoa Design Patterns documentation
in the Cocoa Fundamentals Guide.
But let me just go over how these
are used to extend behaviors.
Delegation allows an object to act on behalf of another.
It's not a language feature unlike categories.
Classes explicitly support delegates.
UITextView for instance declares that it has a
delegate as you can see here with this @property
and the delegate responds to a protocol
and this protocol lists the methods
that the delegate is expected to implement.
In this case, they're all optional so delegate
might choose to implement only some of them.
For instance, textViewShouldBeginEditing, et cetera.
Now, delegation allows an object to help another object.
It even allows one object to help many other objects.
For instance, you might have one text
view and you might have a delegate.
You might have another text view and
it might share the same delegate.
In fact, you might have a table view and
it also might share the same delegate.
If you do this, of course, that delegate should
respond to the UITableView delegate protocol as well.
As you can see I just added, they are in that bubble.
Now, delegates are also flexible.
There are other object that are also sort of like
delegates, for instance UITableView also has a data source
and that could be another object, even
a distinct object then its delegate.
So delegation allows proper subdivision of
responsibility and it's fairly flexible.
You can make it flexible.
So delegation, you know, if I were to give you an analogy,
a delegate is like almost your doctor
or your lawyer or maybe your spouse.
You know somebody who you trust in and somebody who makes
some of the decisions for you or maybe gives you some advice
on how to do things, so that's sort of
an appropriate analogy for delegation.
You know, for instance, if you have an
NSWindow and the NSWindow is being closed,
decision whether that window should be closed or
not, it's not really the window's place to decide.
It's really the place of whoever has content being displayed
in that window to decide whether it's OK to close that.
So that's why the window asks its
delegate should the window close.
Notification is another pattern.
Notification allows-- I was going to use the word
"event" here but "event" is so overloaded I said
"happenings," which is an interesting word.
It allows happenings to be broadcast
to a set of unrelated observers.
These observers observe but they don't interfere.
This is unlike delegation where the
delegates do have the ability to interfere.
This also is in a language feature.
There is a class NSNotification center and classes
have to explicitly declare notifications they post.
For instance, we have many notifications
both in UIKit and AppKit,
UIPasteboardChangedNotification,
NSWindowWillCloseNotification and so on.
Now, note that sometimes delegate methods, you know they
might also be delegate methods that correspond to this
and so the delegate might also
act as an observer in some cases.
Now notifications is pretty flexible.
You know you can have multiple observers.
There is an indirection between the objects so that
observers can actually choose to observe any notification
from a particular object or a notification from any object.
That's because, you know, here are the objects we have.
Here are observers.
There is intermediate notification center.
All the notifications are posted there and the
notification center sends the notifications
to appropriate interested objects.
So, if I were to give an analogy here, this is
almost like, you know, somebody whose twittering
or I know you're doing it now, but it's
almost like twittering or blogging.
Let's say your blogging away telling the world
about interesting things that happen to you.
That might turn out nobody is reading your blog
or maybe some people signed up for your RSS feeds.
So those are the observers, and you actually
don't even know how many observers you have.
You know, they are just independently signing up
for your blog and you're just telling the world
of what you think is important,
so that's like notifications.
You choose to tell the world what's important
and they choose to listen if they want to.
Key-value observing is another pattern,
I'll just quickly go over this one.
This one allows objects to broadcast
updates to individual properties.
You know, like a window can say, my title changed.
An employee can say, my salary changed, and so on.
This also is on a language feature.
Classes decide which properties
they want to implement this for.
However, unlike delegation notification,
this doesn't require defining any new APIs,
any new symbols, and any new protocols.
You know, a class just says, hey, you
know, my salary property is observable.
In fact, if those properties are key-value coding compliant,
which is pretty much automatic if you use that property
for instance, the key-value observing is
also automatically available in most cases.
Now, key-value observing is most appropriate when
you have UIElements showing various properties.
For instance, you have a text field showing the salary.
That text field is interested in hearing a lot
of changes to that salary field and, you know,
not necessarily any other notifications
coming from that object,
so you would use key-value observing for cases like that.
And finally, target action, this is
a fairly specialized simple approach.
It allows UIControl syndicate user interaction.
It's simple and it works well in Interface Builder.
You know, control simply sends a
customizable action to a customizable target.
This becomes more flexible when you have your target
specified as nil because then the responder chain is used
to find the appropriate target at runtime.
That's why for instance when you hit command
C, an application to copy, the appropriate,
whatever the current text field
is active, gets to command C.
We don't have to specify that target
ahead of time and continuously change it.
The responder chain is a fairly powerful concept that
you can also use for your own purposes if you need to.
For instance, we use a responder
chain also in error presentation.
We just throw the error down the responder chain in
the appropriate window, and if there's no window,
maybe the application will be shown there.
The one final thing I'm going to talk about in this
section is Model View Controller and this is a huge topic.
In fact, I talked about it earlier.
I just want to have one slide.
Model-View-Controller is a pattern that defines three clear
functional roles for objects, the model which owns the data,
view which displays and lets the user edit the
data, and the controller which coordinates things.
Each piece is separately replaceable, customizable,
and that's why this pattern is so powerful.
You can choose to replace the model or the view.
For instance if you're going to write a cross platform
app that works well on the iPad, the iPhone, and the Mac,
you would use the ModelViewController paradigm to split
your pieces so you can reuse your model, for instance.
This is used for overall app design, but also
various subsystems through our APIs use this pattern.
The Cocoa text system, the bindings architecture,
UITableView, so individual classes even like UITableView,
NSTableView, UIViewController use this pattern.
So two talks during the week touched upon
this pattern, the model-view controller talk
and Advanced Cocoa Text Tips & Tricks talks both
showed examples of how we use MVC in our APIs.
OK, so with that, now we're in the convenience section,
we're going to talk about convenience APIs and blocks.
So, why is convenience important?
Well, there are three reasons.
One is that it allows you to be more productive.
Convenience APIs allow you to do more with even less code.
It also makes coding fun, you know, effortless.
You know, it seems more easy to do things.
Now, the third reason is that I found out when I was looking
at a definition of convenience, I don't know why I did that.
Apparently convenience means public
toilet in British English.
[ Laughter ]
And that's important.
[ Laughter ]
And let me use it in a sentence.
At the bash tonight, you will probably need
a convenience if you drink too much beer.
Anyway, so with that-- so, convenience APIs are APIs that
simplify or combine a number of other calls into one.
For instance, here's an NSString method that
does comparisons and it's got four arguments.
We have a convenience method on it,
compare: that just has one argument
and basically fills in the other three arguments.
Now, we clearly don't have convenience
APIs everywhere we could.
You know, otherwise, sometimes, you look for a
convenience API and you don't see it and you have
to call a bigger method and you're like,
I wish there was a convenience API.
You know if you had convenience APIs
everywhere possible, that'd be a lot of APIs,
so we usually consider these APIs only in cases
where the implementation is more than two lines
or there are some additional value in the convenience
or there's some valuable abstraction in the convenience,
and let me explain what I mean by those last two.
Here are the two methods I showed you earlier.
Here's the way the compare method, the
single argument one is implemented.
It simply calls the four argument method
with a bunch of default arguments.
I mean, if you look at it, it's really
a one line implementation, right?
I mean I graphed it so it looks
good, but it's really one line.
You know, you're thinking why is
there a convenience method here?
Well, that's because there is additional
value in this method.
The compare method allows you-- allows--
you know it can be used in sorting methods.
For instance, NSArray has sort using selector.
It takes a selector with one argument.
So, you can just pass the compare method right in there.
In fact, we have more NSString comparison
conveniences such as case insensitive compare,
localized compare and so on just for this purpose.
So the-- there's additional value in
these methods, in these conveniences.
Another reason is valuable abstraction.
Here's a method we recently added in Snow Leopard and iOS 4.
This does a comparison just like the system does and we
described that this method's behavior may change over time.
Here's the implementation we tell you.
This is-- if you look in the release nodes,
this is the exact implementation today.
However, as I said, we may change
this implementation over time.
So here, this method is valuable because of its
abstraction that the implementation may change,
so it's more than in fact the convenience in this case.
By using it, you will get this abstraction
of comparing just like the system does.
OK, so the next topic here is blocks.
Now, when you think about it, blocks
are mostly for convenience.
Blocks do not really enable anything
that was impossible before.
You know, they bring a lot of convenience.
You can specify a piece of code in line.
You don't have to create an extra function, and they have
this ability to capture state, which you can also emulate.
Now, I don't want to sell blocks short.
Blocks are an amazing feature.
In fact they are so amazing we added them to the language.
In fact, we added them to C, you know, for which the
bar is pretty high, so they are an amazing feature.
But in the end, they really bring a lot of convenience.
For instance, we use blocks as callbacks and we're
replacing the places where they appear as callbacks.
In fact, all those compare variants
are really not that necessary anymore.
For instance before, if you did not have a compare
method you might have wanted like a numeric search,
you would have had to go and define your
own custom function and then pass it
to myArray, you know, NSArray sort using function.
While before with blocks, you can just do
sortUsingComparator and pass the block right in there
and the code you want to do is right
in there, so this is pretty convenient.
You don't need all those compare variants anymore.
Another thing we use blocks for is completions,
for instance new APIs for presenting
sheets in AppKit and in the same panel.
Here's what the Leopard API looks like and
here's what the Snow Leopard API looks like.
It's considerably simpler.
Note that we replace these last three arguments to delegate
the selector and the context in full with just the block.
Also note that one thing we've done here is
get rid of this pesky void star argument.
Refining that, void star arguments are really more trouble
than they're worth and we're looking at getting rid of them
from our APIs as much as possible, and for one thing they
really do not behave very well under GC for instance.
Here, one final example of blocks as
conveniences, the new APIs we've added in UIView,
for instance animateWithDuration
animation looks fairly convenient.
In fact, it replaces three lines of boilerplate code
like here, that this is the code you would have had to use
on iPhone OS 3 with just one line where you're
specifying the animations you want right there in the body
of the block, so again, super convenient.
Hopefully, here you've heard about how good API is
very important part of Cocoa and Cocoa Touch design.
We take a good API design very seriously and I
hope I've been able to communicate how we do that
and how you can also do it yourself in your
own applications and in your own designs.
Now modeling your APIs as ours will
allow your APIs to be more predictable
so you can actually know almost all the new method names, just
roll off your tongue, you don't have to worry about them.
Widely reusable, you can use it in
other projects, give it to other people,
and they will also be better performing
as you hopefully saw.
And one final warning, you know,
API design is an evolving art.
We have examples of some APIs that are not perfectly named.
So, if you ever run across something
that looks a bit fishy, maybe it is.
So don't take, you know, everything strictly, but again
most of our APIs are going in the right direction.
For more information, you know, you can contact
our evangelist and we have lot of great documentation,
just the starting point, developer.apple.com.
You can do searches for the various keywords I gave you, and
there is a lot of great background stuff on this.