I have some simple rules that I always follow when dealing with mutable Foundation collection objects (plus mutable strings) in my Objective-C code.
I often use a mutable collection object when constructing a thing — array, dictionary, set, or string — and then pass it somewhere else.
Once it’s passed, ownership is relinquished. The construction code never continues to hold a reference to the thing it made.
Wherever that thing goes it’s treated as immutable (whether or not it really is).
No mutable collection objects in APIs
In my .h files, under no circumstances are properties or parameters allowed to be mutable collection objects.
(Or, well, it’s extremely rare. There could be a utility API that takes a mutable thing, but that API does some kind of work that doesn’t retain that collection, so it can’t mutate it later on.)
Mutable collection objects are internal to a .m file
An object may use mutable collection objects internally, but those aren’t allowed to escape the .m file they live in. There’s no chance, then, that one of these could be mutated without its owner knowing about it.
* * *
By following these rules — which, after all these years, I do without even having to think — I never run into an issue where I’ve passed a mutable array (or whatever) to another object, then held on that array and mutated it. It just can’t happen.
But, really, whatever — these days I write Swift code instead.
At work this morning I added a property to an Objective-C object that looked something like this:
@property (nonatomic, strong) NSArray *someArray;
And then it was suggested to me that
copy would be better than
strong in this case.
I have nothing against
copy — but with an immutable array, is that really necessary? The array can’t change, so why copy it?
Well… that’s not strictly true. A caller could use an
NSMutableArray — which would be perfectly valid — but then hold on that array and mutate it later on, which would be dumb.
So I ask: who would write code like that?
It’s not a class of mistake that I personally make. (I’ve internalized this so completely from years of writing Objective-C. I do make other classes of mistakes, of course.)
And then I think “Who would write code like that?” is the wrong question. The real question is, “Why wouldn’t I write code that minimizes the effect of a possible mistake?”
(Or even not a mistake. It could be entirely legitimate that the caller has a mutable array that it holds on to and mutates later on.)
* * *
This makes me wonder about the difference between code I write for myself and code I write as part of a team. Since I know that I wouldn’t make this particular mistake, I can safely use
strong instead of
copy in my own code.
But, really — sharp right turn here; buckle up! — doesn’t it make me wish everything was already in Swift, so we’d have the safer behavior automatically?
Yes. Yes it does.
My personal projects are mostly in Swift — converted to Swift 3 already — but, for obvious reasons, at work we often write new code in Swift but still have a large Objective-C codebase.
What I don’t need in life — because it adds complexity, and I don’t have time for that — is three separate coding styles: Swift, my Objective-C, and work Objective-C.
The only one I can get rid of is “my Objective-C.”
I saw Merlin Mann before the Talk Show last Tuesday, and he thanked me for letting him play in my internet treehouse.
I completely misunderstood. He was talking about a small and specific thing (I realized later) — while I thought, at the time, he was doing a thing Merlin would do, which is to thank a software developer for helping to build up the world of blogs and podcasts where Merlin does, indeed, get to play (and be awesome at it).
(What you don’t know about Merlin — or maybe you do — is that he’s one of the best at liking people. It’s creativity and Jonathan-Winters-meets-James-Joyce — but mainly effort and empathy. So he would say what I thought he said.)
Anyway, my answer didn’t make any sense, surely, when I said, “We built it all for you.”
(Because he’s thinking of this one small thing and I’m thinking of the web.)
But it struck me that, as a reply to what I thought he said, it was absolutely correct. We — and I mean me and many thousands of people like me — worked hard to make the decentralized web, the web of blogs and podcasts, a place where all the Merlins would thrive.
Blogs are the Pad Thai, the rib-eye steak, the bowls of spaghetti of the web. Podcasts are the mashed potatoes, the tacos, and the hummous. You could get by for a little while with Skittles (Twitter) and peanut butter cups (Facebook) — but eventually you need something more filling, something you can sit down with and take your time.
* * *
I realize that decentralized-web fanatics are often looked at as if we’re the Libertarians of the web. Or the crazy-conspiracy-theorist-uncles. Or as if we’re stuck, sadly, in a rosy past and we won’t move on.
Think whatever you will. But wonder if, if all this goes away, could there be any more Merlins.
Last year I was all set to go to WWDC — or, rather, to everything but the actual conference — with plane tickets and hotel rooms booked. I was going to speak at AltConf and play with the Breakpoints and hang out with old friends and make new ones.
But, right before, my father-in-law died of complications from the treatment of a year-long illness. We thought he would get better, and then he didn’t. (This is what prompted my In the Room post of last October, even though that talked more about my grandfather.)
I was very close to my father-in-law. He lived nearby, in the suburbs, and he was a big part of my life for 25 years.
Naturally I canceled my trip.
When he died, it was already clear that my mother-in-law was also sick — for entirely different reasons, and entirely coincidentally. So we went from one to the other without a break. I was as close to her as I was to him, and she died this past January.
Both were too young, and both had been marvelously healthy and energetic — and I still keep forgetting that they’re not just traveling or something and I’ll see them soon, and then I remember.
* * *
So this WWDC represents something for me. I missed it last year, but this year I can go and have fun — and that’s a real thing.
I’ll speak at AltConf, play with the Breakpoints, and hang out with old friends and make new ones. Same as last year would have been, but this year it’s different. Knowing that I missed it, but that I get to go this year, is helping me in a way it’s never had to help me before. It means something. And I thank everyone in advance just for literally being in SF at the same time as me next week.
I have a side project, a Mac app, that I could also do as an iOS app. I have no plans to do so — but the news about subscriptions and free trials makes me reconsider.
It might be sustainable with this new model.
But here’s the thing: the app is a stand-alone thing. I’m not running a backend web service for it. Would it be okay to use the subscription-based pricing? Here’s what Apple says:
Starting this fall, apps in all categories on the App Store will be eligible to offer in-app purchases for auto-renewable subscriptions to services or content. Users enjoy the reliability that comes with subscribing to a service that they love, and the experience must provide ongoing value worth the recurring payment for an auto-renewable subscription to make sense. Although all categories of apps will be eligible, this business model is not appropriate for every app.
What does “not appropriate” mean? Does that mean rejection? Or is that just a warning that it’s maybe not the best fit, but it’s okay to try it anyway?
I’ve spent years of my programming career on a team of one, and I ended up developing some idiosyncratic habits.
One of them could be called the “clean screen” habit. Here’s the scoop:
Xcode’s warnings and errors pane has to be clear at all times. If anything appears there, it has to be dealt with immediately. I enforce this in part by turning on treat-warnings-as-errors, so I’m forced to deal with everything.
Also: anything that appears in the Console pane must be dealt with immediately — that is, any message must be some kind of assertion failure or notification that something bad has happened. Otherwise it must be kept clear (with the exception of temporary caveman debugging).
Having these two panes clear tells me that the baseline health of the project is good. And it ensures that when something does appear, it’s an extraordinary event that I can’t miss — and can’t miss dealing with.
* * *
You could argue that this is pointless and fussy. After all, this doesn’t do anything to prove that the app is well-architected or that I’ve chosen good algorithms or that it uses memory efficiently.
But that’s like saying showers are worthless because they don’t make you a snappy dresser. Cleanliness is just a start, but it’s a good and necessary start.
Swift’s support of inner functions is one of my favorite features of the language.
Years ago, back in the ’90s, I used to write in UserTalk, the language that powered UserLand Frontier. It was a procedural scripting language — but remarkable for its integration with Frontier’s hierarchical, persistent database. (Storing a string in the database, for example, was just a matter of assigning to a location:
foo.bar = "baz".)
In the early days of web programming, we’d often build a page just by appending to a string. In UserTalk it looked something like this:
htmlText = htmlText + s + "\n"
// …build the rest of the page…
on add(s) bit was the inner function, and it could refer to variables in its enclosing scope.
That’s a simple example, of course — we did more with this feature than just appending to strings, but it gets the idea across.
Before we get to Swift, let’s start where I started, with Objective-C.
Some time last year I wrote a macro processor. The API looked something like this:
@interface MacroProcessor : NSObject
- (instancetype)initWithText:(NSString *)text values:(NSDictionary *)d;
@property (nonatomic, readonly) NSString *processedText;
That’s not an uncommon pattern. Initialize an object with some input, and then get the result (via the readonly
I’d call it like this:
MacroProcessor *macroProcessor = [[MacroProcessor alloc] initWithText:text values:values];
NSString *processedText = macroProcessor.processedText;
It sure just looks like a function call at that point, right? But weirdly split up into two lines.
So I added a C function to the API to make this more explicit:
TextWithMacrosProcessed(NSString *text, NSDictionary *values);
Now I could call it like this, instead:
NSString *processedText = TextWithMacrosProcessed(text, values);
That C function just wrapped up the two lines (alloc-init MacroProcessor and return
(I could have, alternately, made a class method on MacroProcessor. Either way’s fine.)
Then I thought: well, I have the API I want — a function call — so there’s no need to make
processedText public, so I removed everything but that C function from the .h file. Great. Fine.
That’s the beauty of interfaces, right? The important part is to get the API right. How it works under the hood (creating an object, etc.) is an implementation detail.
I could have stopped there.
But I didn’t stop there
Let’s be clear: MacroProcessor had unit tests and zero known bugs. It had no side effects. It was good, solid code. A smart developer just moves on to work on something else.
But, since this was part of a Ranchero project (not an Omni app) with no ship date and no hurry, I could allow myself some extra time.
Even though I had the API right, it bothered me that the implementation didn’t feel quite right. After all, if the API is a function, wouldn’t it be ideal if the implementation was also a function?
Is that even possible? I looked at the structure of MacroProcessor, and it was what you’d expect: three properties (initialText, values, and processedText) and a handful of methods.
For some reason it flashed in my head how I’d do this in the old days, back when I was writing UserTalk. There’d be a function with some local variables, and some inner functions that did things, and it would return the final processed text. Like that
buildPage example above.
And it occurred to me that the structure is actually the same: some functions have access to variables that nobody else has access to. Whether that’s an object, or it’s a function with variables and inner functions, amounts to the same thing.
So I approached it that way, and wrote it like I would have in the old days (only in Swift this time), and it worked perfectly. (Still has unit tests, zero known bugs, and no side effects.)
And the API is now
textWithMacrosProcessed(text: String, values: [String: String]) -> String, and the entire thing is just that one function. No object needed.
Good old days, bad old days
So one day I talk about how I don’t want to write code like I did in the old days. And today I’m happy because I’m writing code like the old days.
There’s no contradiction. Some stuff from the old days was awesome, and some was fine, and some was bad. Writing Swift this way to do something that I’d solve with an object in Objective-C — at least in the case where it really ought to be a pure function (takes input, does a thing, doesn’t look at or change anything else, returns output) — is great. Love it.
(Pure functions make me unreasonably excited. I think I get this from my Mom.)