There’s a common assumption when dealing with Objective-C protocols or Java interfaces (or abstract classes, I suppose): that you’re abstracting away the implementation of an object leaving just its interface. “Oh, don’t mind how I quack, all you need to know is that I do quack”. This assumption is unwarranted.
All protocols/interfaces actually tell you is that there is a list of messages, and you’ll be given an object that responds to those messages. Assuming that you can use it in the same way as any other object that responds to the same messages is a mistake. This is something I’ve written about before, but it’s time to give a concrete example.
I’ve been debugging some multi-threaded code recently, so I’ve been writing my own lock classes to try and inspect how the different threads interact. They implement the NSLocking
protocol, as all the Foundation lock classes do:
The
NSLocking
protocol declares the elementary methods adopted by classes that define lock objects. A lock object is used to coordinate the actions of multiple threads of execution within a single application. By using a lock object, an application can protect critical sections of code from being executed simultaneously by separate threads, thus protecting shared data and other shared resources from corruption.
It defines two methods, -lock
and -unlock
. So, given I have an object that conforms to NSLocking
, I can call -lock
and -unlock
just as I would on any other object that conforms to the protocol, right?
Wrong. With an NSRecursiveLock
(which conforms to the protocol), I can call -lock
multiple times in a row without calling -unlock
. Given an NSLock
(which also conforms to the protocol), I can’t. With NSLock
I can’t call -unlock
on a different thread than I called -lock
; I could write a class where that is permitted.
As I argued in the above-linked post, protocols are actually a poor approximation for what we want to use them as: agreements on how to interact with an object via its messaging interface.