Swift Diary #13: The Addiction

I’m at the point with Swift where I get on a roll sometimes. That’s when it gets fun.

* * *

I know the saying that programming isn’t typing — it’s thinking, and with autocompletion these days it really doesn’t matter how much typing a language requires.

Except that that’s not entirely true. Programming is also typing.

Or, put another way: a whole bunch of programming is housekeeping. And, for the most part, Objective-C requires a lot more housekeeping than Swift does. You end up with longer lines, twice the amount of files to maintain, imports to manage, types to type, and so on.

With Swift you get more logic per page with less effort.

* * *

I’m doing my best to understand exactly what sculptures come from this new type of rock. I design like an Objective-C programmer, but I’m learning how to design like a Swift programmer.

I do still wish for things — especially, 1) the ability to treat objects that conform to the same protocol as the same type, and 2) something like KVC.

* * *

But here’s what happens now. Sometimes I go to write some Objective-C code and I sigh at the effort — because I know the Swift version is half as long. I sigh at jumping to the top of the file and adding an import, and I sigh at switching to the .h file and adding a method.

Part of me still wishes that Swift had been something like a cross between Objective-C and Ruby. I wanted a concise, expressive, and dynamic scripting language where I could be massively productive. Instead I got a concise and expressive programming language that’s less dynamic than I’d like — but where I could still be substantially more productive (once I learn it) than in Objective-C.

And that’s where I am now — starting to feel that boost in productivity with Swift, and getting a little bit addicted to it.

On Scripting

Graham Lee writes of The death of scripting and The paradox of scripting.

But how can scripting be dead? There’s bash, and powershell, and ruby, and…even Perl is still popular among sysadmins. There’s never been a better time to be a programmer or other IT professional trying to automate a task.

True, but there’s never been a worse time for someone who doesn’t care about computers to use a computer to automate a task. Apps are in-your-face “experiences” to be “used”, and for the most part can’t be glued together.

There are counter-examples, of course — the apps I work on (Mac versions of OmniFocus and OmniOutliner) are highly scriptable. But the trend toward silos, sandboxing, and highly-controlled experiences is clear.

(First thing I did was look to see if Slack has a scripting dictionary. Of course not. Neither does HipChat. Apps these days.)

If you’re thinking about adding AppleScript support to your app, read these articles from objc.io last year:

Making Your Mac App’s Data Scriptable

Scripting from a Sandbox

In the first of these, I write:

When adding AppleScript support — which is also JavaScript support, as of OS X 10.10 — it’s best to start with your app’s data. Scripting isn’t a matter of automating button clicks; it’s about exposing the model layer to people who could use your app in their workflows.

While that’s usually a small minority of users, they’re power users — the kind of people who recommend apps to friends and family. They blog and tweet about apps, and people listen to them. They can be your app’s biggest evangelists.

Overall, the best reason to add scripting support is that it’s a matter of professionalism. But it doesn’t hurt that the effort is worth the reward.

OmniDev Blog

At Omni we started a new blog. (With an RSS feed.)

I wrote an article: Making Tab-Switching and Scrolling Faster in OmniFocus for Mac.

And Tim Ekl has written the first two parts of a series on building push-triggered sync:

Building Push-Triggered Sync, Part One: Choosing a Language

Building Push-Triggered Sync, Part Two: First Steps

Whistle-whetter: we’re using Go. Read all about it.

PS I’ve been on my yearly beach vacation. Back now.

Swift Diary #12: The P Word

Guy English writes a Swift Protocol Proposal. It’s worth reading the Twitter discussion.

Wooji Juice disagrees, and writes about the genius of Swift protocols.

Daniel Jalkut writes about The Seven Stages of Swift. I think I’m inhabiting several of them at once.

* * *

I’ll try to re-state my issue with protocols again in a simple way.

I’m writing a Finder replacement, let’s say. The UI has folders and files. There’s a Folder class and a File class. They’re quite different things, so there’s no class inheritance.

I want to represent the file system internally as a tree of Folders and Files — let’s say I want to use Sets. A Folder has a children property, which is a Set that contains both Folders and Files.

I want Folder and File both to have a writable name property so the UI can edit their names.

Something like this ought to come naturally and easily to a language, or else that language is not helping me write apps.

This isn’t some weird, made-up situation. It’s super-common. Look at Mail’s sidebar, for instance — there are a bunch of different things. (Or look at Xcode’s sidebar.)

Yes. There are ways to deal with this in Swift, including using @objc protocols and collections. Or proxy objects or base classes (ugh) or whatever.

But the most natural way is protocols.

If my point was just to get my work done and ship a great app as soon as possible, I wouldn’t be using Swift. I’d be using what I know: Objective-C.

But I’m also taking the opportunity to learn Swift, and the best way to really understand it is to use, as much as possible, pure Swift, rather than Swift-with-objc. And if, along the way, I run into questions or things that don’t help me write high-quality apps more quickly, then I’ll ask questions and even criticize when warranted.

My hope (and belief) is that the language designers take the feedback in the spirit intended. I want to help make Swift a great language for writing apps.

The designers may not give me what I want — it’s possible that I’m just asking for a faster horse, and they’re delivering a Model T, after all — but feedback from experienced app-writers ought to warrant attention. (Which I think it gets, which makes me glad.)

Swift Diary Page

I collected my recent articles on working with Swift on a single page. It’s linked-to at the bottom of every page on the site, so you can find it later.

People Were Quite Rightly Outraged at Me

In 1990 I was an editor of the weekly college newspaper at Seattle Central Community College. Each week we printed a comic strip drawn by a student.

The strip — “Red Ruffensor,” as in red, rough, ‘n’ sore — was a liberal satire of action hero comics. It was community college student work, but pretty good for that.

We were criticized, though, for not including anyone but white men and women in the strip. The position of the cartoonist was that this was a satire of a genre that doesn’t include anyone but white men and women (mostly men), and that to include anyone else would actually be mean. (Every character in the strip was a jerk.) So he didn’t.

Until, under continuing pressure, he changed his mind.

One day, the last panel of one of the strips included a character with stereotypical and exaggerated Asian features, and a speech bubble: “Me finally get a line!”

Awful, right? It fit, though, because the whole point of the strip was that action hero comics are homophobic and racist and sexist (and yet homoerotic), and this was a liberal college that, we thought, got it.

I was 21 years old. I okayed it. It ran.

This became a huge (but localized) controversy. Seattle newspapers wrote it up, and some people (though not me) discussed it on 1090 talk radio. There were numerous discussions at school, and afterward the newspaper staff attended sensitivity training.

What my 21-year-old self didn’t get was that that last panel was mean-spirited, even if, to our young minds, it made sense. The integrity of the comic as satirical speech would not have been harmed by leaving it out — and, even if it would have been, would that have been so important? (No. Consider the context.)

I don’t remember if I apologized then or stuck to my guns. At any rate, now I say: I apologize. I’m sorry for running that. I should not have. It was wrong.

* * *

If I had the ability to send myself a message back in time, I would send myself a message to before the cartoonist wrote that panel. I would tell myself how to deal with the pressure to make the strip more inclusive.

Here’s what I’d write:

Dear Brent,

Hi. I’m 47 years old now. Life is good. You’re still sitting in front of a Mac.

I hear — actually, I remember — that you’re under a whole lot of pressure to make Red Ruffensor inclusive.

Well, thing one: you, young man, have no idea what real pressure is yet. But I remember how this felt, and you need some advice. Luckily, this isn’t that hard.

The solution is, in part, the same solution you’ve always used and always will use: start writing.

Step one:

Write an editorial explaining the comic — it won’t be hurt by explaining that it’s satirical, and that everyone in the strip is awful, and it’s all about pointing out the homophobia (and latent homosexuality) of action hero comics. Let people know that Red Ruffensor actually means “red, rough, ‘n’ sore” — because I guarantee you that they don’t know.

Let people know that, in this particular comic, inclusiveness is a bad idea. Explain that the cartoonist is throwing punches — haymakers — at everybody in the strip.

Also write that the paper is committed to inclusiveness, and so you’re looking for more student cartoonists. And if anyone reading this might be that cartoonist, please get in touch!

And make a pledge: no Red Ruffensor in a given week unless there’s at least one other strip to run.

Step two:

Actually find another cartoonist. Or more than one. And run their work, and write an editorial that first week introducing the new strip (or strips), and thank the people who asked that the paper be inclusive, since it’s now better for it.

That’s it. Pretty easy. Be good!

Regards, your pal,
Brent

Swift Diary #11: Objective-Swift

My main problems with Swift are:

  • Collections of protocol-conforming objects don’t work as I need them to.

  • No KVC.

  • No equivalent of NSClassFromString.

Now, these aren’t really problems with Swift — they’re problems with pure Swift.

But there’s no such thing as a pure Swift app. The frameworks are Objective-C frameworks. You can’t load nibs without Objective-C.

Given that, I made the pragmatic decision to start using @objc protocols, classes, and collection types where those things make sense, where Swift fought against my design.

And suddenly the language is a joy to use. It’s like Objective-C but with type inference, no .h files, fewer imports, shorter syntax — and I get the things I was missing.

I’m still not sure I like optionals, and there’s more casting than I’d like, but overall it feels like Objective-C with less housekeeping. In this language — I’ll call it Objective-Swift — I can go fast.

* * *

More about optionals…

For those who don’t know Objective-C: it’s based on message passing, and sending a message to a nil receiver is fine. It just doesn’t do anything. If it has a return type, it returns nil. (Roughly speaking.)

In other words: say foo is nil. The following in Objective-C is a-okay — nothing happens when foo is nil:

[foo doSomething:someArgument];

The Objective-C syntax is weird to everyone who isn’t used to it, I’ll grant. The above could be expressed in another language as foo.doSomething(someArgument)

What’s cool about this is that you can do nil-checking less often. Say you call a method that returns an object or returns nil. If it returns an object, you want to tell that object to do something, or you want to get one of its properties — or you want nothing to happen if the object is nil.

You can just skip the nil check and write your code as if the object isn’t nil.

I’ve relied on that behavior thousands of times. As an Objective-C developer it’s second nature.

There are obvious problems with that, though. One is that it’s not clear at all in the code that the object may be nil and nothing’s happening and it’s okay (it’s by design). Someone reading it — including future me — may not realize it, and it may be important.

The second, and related, problem is that nil as an argument rather than as a receiver is a completely different story. Plenty of methods will crash if you pass nil as an argument. So it’s not true that Objective-C code is free of nil-checking.

Along comes optionals in Swift, which make it clear when a thing may or may not be nil. When a variable is defined as optional, and you want to do something with it, you have to handle the nil case, and that’s enforced by the compiler.

That’s good — very good, in fact — except for all those times when I’m bugged because I have to satisfy the compiler when I feel like I shouldn’t have to, when years of experience tell me it’s quite okay to ignore the possibility of nil.

So I’m of two minds on optionals. Like many other things with Swift, I don’t really care about it for code that’s entirely mine — because I know my own style so well, because I know what mistakes I make and don’t make — but I do care about it for code that will have many authors.

Swift Diary #10: Changes to Properties

Both of the apps I’m working on have a custom persistence engine (built on top of FMDB and SQLite). Core Data isn’t a great fit for these apps. (This isn’t me being contrary. I’m doing very database-y stuff, and Core Data is an object persistence system, not a database. Seriously: you should use Core Data.)

The model objects are pure Swift objects. And I’d like to borrow one of Core Data’s awesome features: when a property is set on an object, the persistence system should get notified. It will record the change, then coalesce and queue updates to the database.

* * *

Let’s say I’m writing a Twitter client. (This an example used for historical reasons — because, if you look at history, you’ll find that people used to write Twitter clients — and should not be taken as indicative.)

Let’s pretend it has a Tweet class, a pure Swift class, with about 20 properties (some from the server, some local) — and setting any one of those should trigger the persistence system to do its thing.

I can do what I want if I add a didSet code block to each property. One of the cool things is that didSet is ignored during init, which means I can initialize an object with data without didSet getting called and triggering an unnecessary database update.

Each didSet block looks like a variation on this:

recordPropertyChange(self, key: "somePropertyName", newValue: somePropertyName)

It’s a pain to do this for every single property in every single class that’s database-backed.

* * *

Were this Objective-C, the situation would be fairly easily dealt with via dynamic method resolution. Each property would be marked as @dynamic, and methods that do the right thing (update in-memory storage and notify the persistence system) would be provided at runtime.

That’s not available to pure Swift classes, though.

I could ask for that feature — but it had me thinking that there might be a better feature request to make. Classes could optionally (perhaps when conforming to a PropertyObserver protocol) implement a class method that gets notified when any property changes in the same circumstances where it would call didSet. (It should still call didSet, when it’s there.)

Something like:

class func didSetProperty<T, U>(object: T, key: String, newValue: U) {
  recordPropertyChange​(object, key: key, newValue: newValue)
}

(I’m totally unsure that the above is the right way to express this. Maybe object should be Self? I’m still learning my way around generics and protocols.)

If there already is a way to solve this, and I don’t need to make this feature request, then I’m keen to hear the solution. It would be useful.

Swift Diary #9: Where I’m Stuck

Let’s say I’m writing a multi-platform social networking client. I know a person, and I have it on strong authority that Jack Dorsey’s going to re-open the Twitter APIs. (I can say this out loud because it sounds like I’m just pretending.) So my social networking client will support Twitter, App.net, LinkedIn, and a handful of other systems. (Let’s say.)

I’ve got the problem that I’ve written about before — protocols and collections don’t mix well.

I’m using a protocol for the Account type. There are classes that conform to the Account protocol: TwitterAccount, AppNetAccount, LinkedInAccount, etc.

There’s also an AccountManager class that manages the accounts list. The list is a Set<Account>, as it should be (since no account should appear more than once; since ordering isn’t important).

Except, of course, no it isn’t, because Account is not Hashable.

In reality it’s an array — [Account] — that holds all the accounts. That’s fine. That works.

Except when it doesn’t: except when I want to see if the array contains an account object. Error: cannot invoke 'contains' with an argument list of type '(Account)'

So I can’t do manually what a Set would do automatically, which is to ensure that a given account appears only once.

And, here’s the thing: this scenario doesn’t appear once but a bunch of times. (A major example is the TimelineItem protocol. Each different type of account has one or more different TimelineItem-conforming classes, and a given timeline can contain multiple different concrete TimelineItem objects.)

I’ve worked around some of these, and I’m sure I can come up with work-arounds for each case. But it’s getting increasingly frustrating. It’s slowing me down.

I have some options:

  • Continue with the work-arounds, despite the frustration. (Even though these projects are supposed to be fun.)

  • Switch to Swift plus Objective-C types — that is, make Account and TimelineObject @objc protocols and make the classes that conform to those protocols @objc types. Then use Foundation collection types NSSet and NSArray. (This is like writing Objective-C with Swift syntax.)

  • Switch to Objective-C, which handles my design perfectly. But is not Swift.

I don’t know what to do. What would you do?

* * *

Update 10:40 am: Some people are asking why Account can’t be Hashable. It’s because it can’t be Equatable. (For starters. There might be other reasons too.)

protocol Account: Equatable {
  var accountID: String {get}
}
func ==(lhs: Account, rhs: Account) -> Bool {
  return lhs.accountID == rhs.accountID
}

Error: protocol 'Account' can only be used as a generic constraint because it has Self or associated type requirements.

If there’s an answer to this, then I’d be extremely keen to know what it is!

* * *

Update 11:00 am: Got Equatable working, thanks to Kyle Sluder, but not Hashable. Here’s my current gist.

* * *

Update 11:10 am: I spoke too quickly. Equatable is still the problem. Here’s the gist.

Which means I’m faced with the original point of this post. What to do?

* * *

Update: 11:35 am: Several people have suggested using a base class instead of a protocol. That should do the trick, yes.

But there were reasons to use protocols in the first place:

  • I dislike class inheritance in general.

  • I don’t like using a base class purely to satisfy the type system.

  • Requiring a base class makes doing a plug-in API more difficult. (It’s simpler to have plug-ins conform to a protocol. Though I have to admit I haven’t done any research with Swift plug-ins, so it’s possible I’m completely optimistic here anyway.)

I’m pragmatic, though, and I may end up doing this as a work-around.

Mapping with Wil

Wil Shipley writes about eliminating loops in Swift by using map and filter.

Archive

Ads via The Deck