At the heart of Swift's design are two incredibly powerful ideas: protocol-oriented programming and first class value semantics. Each of these concepts benefit predictability, performance, and productivity, but together they can change the way we think about programming. Find out how you can apply these ideas to improve the code you write.
Dave Abrahams: Hi, everybody.
My name is Dave Abrahams, and I'm the technical lead
for the Swift standard library, and it is truly my privilege
to be with you here today.
It is great to see all of you in this room.
The next 40 minutes are about putting aside your usual way
of thinking about programming.
What we're going to do together here won't necessarily be easy,
but I promise you if you stick with me,
that it'll be worth your time.
I'm here to talk to you about themes at the heart
of Swift's design, and introduce you to a way of programming
that has the potential to change everything.
But first, let me introduce you to a friend of mine.
This is Crusty.
Now you've probably all worked with some version of this guy.
Crusty is that old-school programmer
who doesn't trust debuggers, doesn't use IDEs.
No, he favors an 80 x 24 terminal window
and plain text, thank you very much.
And he takes a dim view of the latest programming fads.
Now I've learned to expect Crusty
to be a little bit cynical and grumpy,
but even so it sometimes takes me by surprise.
Like last month we were talking about app development,
and said flat out, 'I don't do object-oriented.'
I could hardly believe my ears.
I mean, object-oriented programming has been
around since the 1970s, so it's not exactly some new-fangled
And, furthermore, lots of the amazing things
that we've all built together, you and I and the engineers
on whose shoulders we stand, were built with objects.
'Come on,' I said to him as I walked
over to his old-school chalkboard.
'OOP is awesome.
Look what you can do with classes.'
Yes. So first you can group related data and operations.
And then we can build walls to separate the inside of our code
from the outside, and that's what lets us
Then we use classes to represent relatable ideas, like window
or communication channel.
They give us a namespace, which helps prevent collisions
as our software grows.
They have amazing expressive syntax.
So we can write method calls and properties
and chain them together.
We can make subscripts.
We can even make properties that do computation.
Last, classes are open for extensibility.
So if a class author leaves something out that I need, well,
I can come along and add it later.
And, furthermore, together, these things,
these things let us manage complexity
and that's really the main challenge in programming.
These properties, they directly address the problems we're
trying to solve in software development.
At that point, I had gotten myself pretty inspired,
but Crusty just snorted and [sighed].
[hiss sound] He let all the air out of my balloon.
And if that wasn't bad enough,
a moment later he finished the sentence.
Because it's true, in Swift,
any type you can name is a first class citizen and it's able
to take advantage of all these capabilities.
So I took a step back and tried to figure
out what core capability enables everything we've accomplished
with object-oriented programming.
Obviously, it has to come from something that you can only do
with classes, like inheritance.
And this got me thinking specifically
about how these structures enable both code sharing
and fine-grained customization.
So, for example, a superclass can define a substantial method
with complex logic, and subclasses get all
of the work done by the superclass for free.
They just inherit it.
But the real magic happens when the superclass author breaks
out a tiny part of that operation
into a separate customization point
that the subclass can override,
and this customization is overlaid
on the inherited implementation.
That allows the difficult logic
to be reused while enabling open-ended flexibility
and specific variations.
And now, I was sure, I had him.
'Ha,' I said to Crusty.
'Obviously, now you have to bow
down before the power of the class.'
'Hold on just a darn tootin' minute,' he replied.
'First of all,
I do customization whatchamacallit all the time
with structs, and second, yes, classes are powerful
but let's talk about the costs.
I have got three major beefs with classes,' said Crusty.
And he started in on his list of complaints.
'First, you got your automatic sharing.'
Now you all know what this looks like.
A hands B some piece of perfectly sober looking data,
and B thinks, 'Great, conversation over.'
But now we've got a situation where A
and B each have their own very reasonable view of the world
that just happens to be wrong.
Because this is the reality: eventually A gets tired
of serious data and decides he likes ponies instead,
and who doesn't love a good pony?
This is totally fine until B digs up this data later,
much later, that she got from A
and there's been a surprise mutation.
B wants her data, not A's ponies.
Well, Crusty has a whole rant about how this plays out.
'First,' he says, 'you start copying everything like crazy
to squash the bugs in your code.
But now you're making too many copies,
which slows the code down.
And then one day you handle something on a dispatch queue
and suddenly you've got a race condition
because threads are sharing a mutable state,
so you start adding locks to protect your invariants.
But the locks slow the code down some more
and might even lead to deadlock.
And all of this is added complexity,
whose effects can be summed up in one word, bugs.'
But none of this is news to Cocoa programmers.
It's not news.
We've been applying a combination of language features
like @property(copy) and coding conventions
over the years to handle this.
And we still get bitten.
For example, there's this warning
in the Cocoa documentation
about modifying a mutable collection while you're
iterating through it.
Right? And this is all due to implicit sharing
of mutable state, which is inherent to classes.
But this doesn't apply to Swift.
Why not? It's because Swift collections are all value types,
so the one you're iterating
and the one you're modifying are distinct.
Okay, number two on Crusty's list,
class inheritance is too intrusive.
First of all, it's monolithic.
You get one and only one superclass.
So what if you need to model multiple abstractions?
Can you be a collection and be serialized?
Well, not if collection and serialized are classes.
And because class inheritance is single inheritance,
classes get bloated as everything
that might be related gets thrown together.
You also have to choose your superclass
at the moment you define your class,
not later in some extension.
Next, if your superclass had stored properties, well,
you have to accept them.
You don't get a choice.
And then because it has stored properties,
you have to initialize it.
And as Crusty says, 'designated convenience required, oh, my.'
So you also have to make sure that you understand how
to interact with your superclass without breaking its invariants.
Right? And, finally, it's natural for class authors
to write their code as though they know what their methods are
going to do, without using final and without accounting
for the chance that the methods might get overridden.
So, there's often a crucial but unwritten contract
about which things you're allowed
to actually override and, like, do you have to chain
to the superclass method?
And if you're going to chain to the superclass method,
is it at the beginning of your method, or at the end,
or in the middle somewhere?
So, again, not news to Cocoa programmers, right?
This is exactly why we use the delegate pattern all
over the place in Cocoa.
Okay, last on Crusty's list, classes just turn
out to be a really bad fit for problems
where type relationships matter.
So if you've ever tried to use classes
to represent a symmetric operation,
like Comparison, you know what I mean.
For example, if you want to write a generalized sort
or binary search like this, you need a way
to compare two elements.
And with classes, you end up with something like this.
Of course, you can't just write Ordered this way,
because Swift demands a method body for precedes.
So, what can we put there?
Remember, we don't know anything
about an arbitrary instance of Ordered yet.
So if the method isn't implemented by a subclass, well,
there's really nothing we can do other than trap.
Now, this is the first sign that we're fighting the type system.
And if we fail to recognize that,
it's also where we start lying to ourselves,
because we brush the issue aside, telling ourselves
that as long as each subclass
of Ordered implements precedes, we'll be okay.
Right? Make it the subclasser's problem.
So we press ahead and implement an example of Ordered.
So, here's a subclass.
It's got a double value
and we override precedes to do the comparison.
Right? Except, of course, it doesn't work.
See, "other" is just some arbitrary Ordered
and not a number, so we don't know that
"other" has a value property.
In fact, it might turn out to be a label,
which has a text property.
So, now we need to down-cast just to get to the right type.
But, wait a sec, suppose that "other" turns out to be a label?
Now, we're going to trap.
Right? So, this is starting to smell a lot
like the problem we had when writing the body for precedes
in the superclass, and we don't have a better answer now
than we did before.
This is a static type safety hole.
Why did it happen?
Well, it's because classes don't let us express this crucial type
relationship between the type of self and the type of other.
In fact, you can use this as a "code smell."
So, any time you see a forced down-cast in your code,
it's a good sign that some important type relationship has
been lost, and often that's due
to using classes for abstraction.
Okay, clearly what we need is a better abstraction mechanism,
one that doesn't force us to accept implicit sharing,
or lost type relationships,
or force us to choose just one abstraction and do it
at the time we define our types; one that doesn't force us
to accept unwanted instance data
or the associated initialization complexity.
And, finally, one that doesn't leave ambiguity
about what I need to override.
Of course, I'm talking about protocols.
Protocols have all these advantages, and that's why,
when we made Swift, we made the first protocol-oriented
So, yes, Swift is great for object-oriented programming,
but from the way for loops and string literals work
to the emphasis in the standard library on generics,
at its heart, Swift is protocol-oriented.
And, hopefully, by the time you leave here,
you'll be a little more protocol-oriented yourself.
So, to get you started off on the right foot,
we have a saying in Swift.
Don't start with a class.
Start with a protocol.
So let's do that with our last example.
Okay, first, we need a protocol, and right away Swift complains
that we can't put a method body here,
which is actually pretty good because it means
that we're going to trade that dynamic runtime check
for a static check, right, that precedes as implemented.
Okay, next, it complains that we're not overriding anything.
Well, of course we're not.
We don't have a baseclass anymore, right?
No superclass, no override.
And we probably didn't even want number to be a class
in the first place, because we want it to act like a number.
Right? So, let's just do two thing at once
and make that a struct.
Okay, I want to stop for a moment here and appreciate
where we are, because this is all valid code again.
Okay, the protocol is playing exactly the same role
that the class did in our first version of this example.
It's definitely a bit better.
I mean, we don't have that fatal error anymore,
but we're not addressing the underlying static type safety
hole, because we still need that forced down-cast because
"other" is still some arbitrary Ordered.
Okay. So, let's make it a number instead, and drop the type cast.
Well, now Swift is going to complain
that the signatures don't match up.
To fix this, we need to replace Ordered
in the protocol signature with Self.
This is called a Self-requirement.
So when you see Self in a protocol, it's a placeholder
for the type that's going to conform
to that protocol, the model type.
So, now we have valid code again.
Now, let's take a look at how you use this protocol.
So, this is the binary search that worked
when Ordered was a class.
And it also worked perfectly before we added
that Self-requirement to Ordered.
And this array of ordered here is a claim.
It's a claim that we're going
to handle a heterogeneous array of Ordered.
So, this array could contain numbers
and labels mixed together, right?
Now that we've made this change to Ordered
and added the Self-requirement, the compiler is going
to force us to make this homogeneous, like this.
This one says, 'I work on a homogeneous array
of any single Ordered type T.'
Now, you might think that forcing the array
to be homogeneous is too restrictive or, like,
a loss of functionality or flexibility or something.
But if you think about it,
the original signature was really a lie.
I mean, we never really handled the heterogeneous case other
than by trapping.
Right? A homogeneous array is what we want.
So, once you add a Self-requirement to a protocol,
it moves the protocol into a very different world,
where the capabilities have a lot less overlap with classes.
It stops being usable as a type.
Collections become homogeneous instead of heterogeneous.
An interaction between instances no longer implies an interaction
between all model types.
We trade dynamic polymorphism for static polymorphism, but,
in return for that extra type information we're giving the
compiler, it's more optimizable.
So, two worlds.
Later in the talk, I'll show you how to build a bridge
between them, at least one way.
Okay. So, I understood how the static aspect
of protocols worked, but I wasn't sure whether
to believe Crusty that protocols could really replace classes
and so I set him a challenge, to build something
for which we'd normally use OOP, but using protocols.
I had in mind a little diagramming app
where you could drag and drop shapes on a drawing surface
and then interact with them.
And so I asked Crusty to build the document and display model.
And here's what he came up with.
First, he built some drawing primitives.
Now, as you might imagine,
Crusty really doesn't really do GUI's.
He's more of a text man.
So his primitives just print
out the drawing commands you issue, right?
I grudgingly admitted that this was probably enough
to prove his point, and then he created a Drawable protocol
to provide a common interface for all of our drawing elements.
Okay, this is pretty straightforward.
And then he started building shapes like Polygon.
Now, the first thing to notice here
about Polygon is it's a value type,
built out of other value types.
It's just a struct that contains an array of points.
And to draw a polygon, we move to the last corner
and then we cycle through all the corners, drawing lines.
Okay, and here's a Circle.
Again, Circle is a value type, built out of other value types.
It's just a struct that contains a center point and a radius.
Now to draw a Circle, we make an arc that sweeps all the way
from zero to two pi radians.
So, now we can build a diagram out of circles and polygons.
'Okay,' said Crusty, 'let's take her for a spin.'
So, he did.
This is a diagram.
A diagram is just a Drawable.
It's another value type.
Why is it a value type?
Because all Drawables are value types, and so an array
of Drawables is also a value type.
Let's go back to that.
Wow. Okay, there.
An array of Drawables is also a value type and, therefore,
since that's the only thing in my Diagram,
the Diagram is also a value type.
So, to draw it, we just loop through all
of the elements and draw each one.
Okay, now let's take her for a spin.
So, we're going to test it.
So, Crusty created a Circle
with curiously specific center and radius.
And then, with uncanny Spock-like precision,
he added a Triangle.
And finally, he built a Diagram
around them, and told it to draw.
'Voila,' said Crusty, triumphantly.
'As you can plainly see, this is an equilateral triangle
with a circle, inscribed inside a circle.'
Well, maybe I'm just not as good at doing trigonometry
in my head, as Crusty is, but, 'No, Crusty,' I said,
'I can't plainly see that,
and I'd find this demo a whole lot more compelling
if I was doing something actually useful
for our app like, you know, drawing to the screen.'
After I got over my annoyance,
I decided to rewrite his Renderer to use CoreGraphics.
And I told him I was going to this and he said,
'Hang on just a minute there, monkey boy.
If you do that, how am I going to test my code?'
And then he laid out a pretty compelling case for the use
of plaintext in testing.
If something changes in what we're doing,
we'll immediately see it in the output.
Instead, he suggested we do a little
So he copied his Renderer and made the copy into a protocol.
Yeah, and then you have to delete the bodies, okay.
There it is.
And then he renamed the original Renderer and made it conform.
Now, all of this refactoring was making me impatient, like,
I really want to see this stuff on the screen.
I wanted to rush on and implement a Renderer
for CoreGraphics, but I had to wait
until Crusty tested his code again.
And when he was finally satisfied, he said to me, 'Okay,
what are you going to put in your Renderer?'
And I said, 'Well, a CGContext.
CGContext has basically everything a Renderer needs."
In fact, within the limits of its plain C interface,
it basically is a Renderer.
'Great,' said Crusty.
'Gimme that keyboard.'
And he snatched something away from me and he did something
so quickly I barely saw it.
'Wait a second,' I said.
'Did you just make every CGContext into a Renderer?'
He had. I mean, it didn't do anything yet,
but this was kind of amazing.
I didn't even have to add a new type.
'What are you waiting for?'
'Fill in those braces.'
So, I poured in the necessary CoreGraphics goop,
and threw it all into a playground, and there it is.
Now, you can download this playground,
which demonstrates everything I'm talking about here
in the talk, after we're done.
But back to our example.
Just to mess with me, Crusty then did this.
Now, it took me a second to realize why Drawing wasn't going
into an infinite recursion at this point, and if you want
to know more about that, you should go
to this session, on Friday.
But it also didn't change the display at all.
Eventually, Crusty decided to show me what was happening
in his plaintext output.
So it turns out that it was just repeating the same drawing
So, being more of a graphics-oriented guy,
I really wanted to see the results.
So, I built a little scaling adapter and wrapped it
around the Diagram and this is the result.
And you can see this in the playground, so I'm not going
to go into the scaling adapter here.
But that's kind of a demonstration
that with protocols, we can do all the same kinds of things
that we're used to doing with classes.
Adapters, usual design patterns.
Okay, now I'd like to just reflect a second
on what Crusty did with TestRenderer though,
because it's actually kind of brilliant.
See, by decoupling the document model from a specific Renderer,
he's able to plug in an instrumented component
that reveals everything that we do,
that our code does, in detail.
And we've since applied this approach throughout our code.
We find that, the more we decouple things with protocols,
the more testable everything gets.
This kind of testing is really similar to what you get
with mocks, but it's so much better.
See, mocks are inherently fragile, right?
You have to couple your testing code
to the implementation details of the code under test.
And because of that fragility, they don't play well
with Swift's strong static type system.
See, protocols give us a principled interface
that we can use, that's enforced by the language,
but still gives us the hooks to plug in all
of the instrumentation we need.
Okay, back to our example, because now we seriously need
to talk about bubbles.
Okay. We wanted this diagramming app to be popular with the kids,
and the kids love bubbles, of course.
So, in a Diagram, a bubble is just an inner circle offset
around the center of the outer circle that you use
to represent a highlight.
So, you have two circles.
Just like that.
And when I put this code in context though,
Crusty started getting really agitated.
All the code repetition was making him ornery,
and if Crusty ain't happy, ain't nobody happy.
'Look, they're all complete circles,' he shouted.
'I just want to write this.'
I said, 'Calm down, Crusty.
We can do that.
All we need to do is add another requirement to the protocol.
Then of course we update our models to supply it.
There's test Renderer.
And then the CGContext.'
Now, at this point Crusty's got his boot off and he's beating it
on the desk, because here we were again, repeating code.
He snatched the keyboard back from me, muttering something
about having to do everything his own self, and he proceeded
to school me using a new feature in Swift.
This is a protocol extension.
This says 'all models
of Renderer have this implementation of circleAt.'
Now we have an implementation that is shared among all
of the models of Renderer.
So, notice that we still have this circleAt requirement
You might ask, 'what does it means
to have a requirement that's also fulfilled immediately
in an extension?'
The answer is that a protocol requirement creates a
To see how this plays out, let's collapse this method body
and add another method to the extension.
One that isn't backed by a requirement.
And now we can extend Crusty's TestRenderer
to implement both of these methods.
And then we'll just call them.
Okay. Now, what happens here is totally unsurprising.
We're directly calling the implementations in TestRenderer
and the protocol isn't even involved, right?
We'd get the same result if we removed that conformance.
But now, let's change the context
so Swift only knows it has a Renderer, not a TestRenderer.
And here's what happens.
So because circleAt is a requirement,
our model gets the privilege of customizing it,
and the customization gets called.
That one. But rectangleAt isn't a requirement,
so the implementation in TestRenderer,
it only shadows the one in the protocol and in this context,
where you only know you have a Renderer and not a TestRenderer,
the protocol implementation is called.
Which is kind of weird, right?
So, does this mean
that rectangleAt should have been a requirement?
Maybe, in this case, it should,
because some Renderers are highly likely
to have a more efficient way to draw rectangles, say,
aligned with a coordinate system.
But, should everything in your protocol extension also be
backed by a requirement?
I mean, some APIs are just not intended
as customization points.
So, sometimes the right fix is
to just not shadow the requirement in the model,
not shadow the method in the model.
Okay. So, this new feature, incidentally,
it's revolutionized our work on the Swift Standard Library.
Sometimes what we can do with protocol extensions,
it just feels like magic.
I really hope that you'll enjoy working with the latest library
as much as we've enjoyed applying this
to it and updating it.
And I want to put our story aside for a second,
so I can show you some things that we did in Standard Library
with protocol extensions, and few other tricks besides.
So, first, there's a new indexOf method.
So, this just walks through the indices of the collection
until it finds an element that's equal to what we're looking for
and it returns that index.
And if it doesn't find one, it returns nil.
Simple enough, right?
But if we write it this way, we have a problem.
See the elements of an arbitrary collection can't be compared
So, to fix that, we can constrain the extension.
This is another aspect of this new feature.
So, by saying this extension applies when the element type
of the collection is Equatable,
we've given Swift the information it needs to allow
that equality comparison.
And now that we've seen a simple example
of a constrained extension, let's revisit our binary search.
And let's use it on an array of Int.
Hmm. Okay, Int doesn't conform to Ordered.
Well that's a simple fix, right?
We'll just add a conformance.
Okay, now what about Strings?
Well, of course, this doesn't work
for Strings, so we do it again.
Now before Crusty starts banging on his desk, we really want
to factor this stuff out, right?
The less-than operator is present
in the Comparable protocol, so we could do this
with an extension to comparable.
Now we're providing the precedes for those conformances.
So, on the one hand, this is really nice, right?
When I want a binary search for Doubles, well,
all I have to is add this conformance and I can do it.
On the other hand, it's kind of icky, because even
if I take away the conformance,
I still have this precedes function that's been glommed
onto Doubles, which already have enough of an interface, right?
We maybe would like to be a little bit more selective
about adding stuff to Double.
So, and even though I can do that,
I can't binarySearch with it.
So it's really, that precedes function buys me nothing.
Fortunately, I can be more selective
about what gets a precedes API,
by using a constrained extension on Ordered.
So, this says that a type that is Comparable and is declared
to be Ordered will automatically be able
to satisfy the precedes requirement,
which is exactly what we want.
I'm sorry, but I think that's just really cool.
We've got the same abstraction.
The same logical abstraction coming
from two different places,
and we've just made them interoperate seamlessly.
Thank you for the applause, but I just, I think it's cool.
Okay, ready for a palate cleanser?
That's just showing it work.
Okay. This is the signature of a fully generalized binarySearch
that works on any Collection
with the appropriate Index and Element types.
Now, I can already hear you guys getting uncomfortable out there.
I'm not going to write the body out here,
because this is already pretty awful to look at, right?
Swift 1 had lots of generic free functions like this.
In Swift 2, we used protocol extensions to make them
into methods like this, which is awesome, right?
Now, everybody focuses on the improvement this makes
at the call site, which is now clearly chock full
of method-y goodness, right?
But as the guy writing binarySearch,
I love what it did for the signature.
By separating the conditions under which this method applies
from the rest of the declaration,
which now just reads like a regular method.
No more angle bracket blindness.
Thank you very much.
Okay, last trick before we go back to our story.
This is a playground containing a minimal model
of Swift's new OptionSetType protocol.
It's just a struct
with a read-only Int property, called rawValue.
Now take a look at the broad Set-like interface you actually
get for free once you've done that.
All of this comes from protocol extensions.
And when you get a chance, I invite you to take a look
at how those extensions are declared
in the Standard Library, because several layers are working
together to provide this rich API.
Okay, so those are some of the cool things that you can do
with protocol extensions.
Now, for the piece de resistance, I'd like to return
to our diagramming example.
Always make value types equatable.
Why? Because I said so.
Also, eat your vegetables.
No, actually, if you want to know why, go to this session
on Friday, which I told you about already.
It's a really cool talk and they're going
to discuss this issue in detail.
Anyway, Equatable is easy for most types, right?
You just compare corresponding parts for equality, like this.
But, now, let's see what happens with Diagram.
Uh-oh. We can't compare two arrays of Drawable for equality.
All right, maybe we can do it
by comparing the individual elements,
which looks something like this.
Okay, I'll go through it for you.
First, you make sure they have the same number of elements,
then you zip the two arrays together.
If they do have the same number of elements, then you look
for one where you have a pair that's not equal.
All right, you can take my word for it.
This isn't the interesting part of the problem.
This is, the whole reason we couldn't compare the arrays is
because Drawables aren't equatable, right?
So, we didn't have an equality operator for the arrays.
We don't have an equality operator
for the underlying Drawables.
So, can we just make all Drawables Equatable?
We change our design like this.
Well, the problem with this is
that Equatable has Self-requirements,
which means that Drawable now has Self-requirements.
And a Self-requirement puts Drawable squarely
in the homogeneous, statically dispatched world, right?
But Diagram really needs a heterogeneous array
of Drawables, right?
So we can put polygons and circles in the same Diagram.
So Drawable has to stay in the heterogeneous,
dynamically dispatched world.
And we've got a contradiction.
Making Drawable equatable is not going to work.
We'll need to do something like this,
which means adding a new isEqualTo requirement
But, oh, no, we can't use Self, right?
Because we need to stay heterogeneous.
And without Self, this is just
like implementing Ordered with classes was.
We're now going to force all Drawables
to handle the heterogeneous comparison case.
Fortunately, there's a way out this time.
Unlike most symmetric operations, equality is special
because there's an obvious, default answer
when the types don't match up, right?
We can say if you have two different types,
they're not equal.
With that insight, we can implement isEqualTo
for all Drawables when they're Equatable.
So, let me walk you through it.
The extension is just what we said.
It's for all Drawables that are Equatable.
Okay, first we conditionally down-cast other
to the Self type.
Right? And if that succeeds, then we can go ahead
and use equality comparison,
because we have an Equatable conformance.
Otherwise, the instances are deemed unequal.
Okay, so, big picture, what just happened here?
We made a deal with the implementers of Drawable.
We said, 'If you really want to go
and handle the heterogeneous case, be my guest.
Go and implement isEqualTo.
But if you just want to just use the regular way we express
homogeneous comparison, we'll handle all the burdens
of the heterogeneous comparison for you.'
So, building bridges between the static
and dynamic worlds is a fascinating design space,
and I encourage you to look into more.
This particular problem we solved using a special property
of equality, but the problems aren't all like that,
and there's lots of really cool stuff you can do.
So, that property of equality doesn't necessarily apply,
but what does apply almost universally?
Okay, so, I want to say a few words before we wrap up about
when to use classes, because they do have their place.
Okay? There are times when you really do want implicit sharing,
for example, when the fundamental operations
of a value type don't make any sense, like copying this thing.
What would a copy mean?
If you can't figure out what that means,
then maybe you really do want it to be a reference type.
Or a comparison.
The same thing.
That's another fundamental part of being a value.
So, for example, a Window.
What would it mean to copy a Window?
Would you actually want to see, you know,
a new graphical Window?
What, right on top of the other one?
I don't know.
It wouldn't be part of your view hierarchy.
Doesn't make sense.
So, another case where the lifetime
of your instance is tied to some external side effect,
like files appearing on your disk.
Part of this is because values get created very liberally
by the compiler, and created and destroyed,
and we try to optimize that as well as possible.
It's the reference types that have this stable identity,
so if you're going to make something that corresponds
to an external entity, you might want
to make it a reference type.
A class. Another case is where the instances
of the abstraction are just "sinks."
Like, our Renderers, for example.
So, we're just pumping, we're just pumping information
into that thing, into that Renderer, right?
We tell it to draw a line.
So, for example, if you wanted to make a TestRenderer
that accumulated the text to output of these commands
into a String instead of just dumping them to the console,
you might do it like this.
But notice a couple of things about this.
First, it's final, right?
Second, it doesn't have a base class.
That's still a protocol.
I'm using the protocol for the abstraction.
Okay, a couple of more cases.
So, we live in an object-oriented world, right?
Cocoa and Cocoa Touch deal in objects.
They're going to give you baseclasses
and expect you to subclass them.
They're going to expect objects in their APIs.
Don't fight the system, okay?
That would just be futile.
But, at the same time, be circumspect about it.
You know, nothing in your program should ever get too big,
and that goes for classes just as well as anything else.
So, when you're refactoring and factoring something
out of class, consider using a value type instead.
Okay, to sum up.
Protocols, much greater than superclasses for abstraction.
Second, protocol extensions, this new feature
to let you do almost magic things.
Third, did I mention you should go see this talk on Friday?
Go see this talk on Friday.
Eat your vegetables.
Be like Crusty.
Thank you very much.
Looking for something specific? Enter a topic above and jump straight to the good stuff.
An error occurred when submitting your query. Please check your Internet connection and try again.