So far, Objective-Swift objects have used prototypical inheritance, in which they supply some methods but also know about another object to which they can forward messages they don’t understand themselves. This pattern is used in languages like Self, JavaScript and Io but is not common to other languages that also call themselves object-oriented programming languages.
Most OO languages have the idea of a “class” that supplies the methods for each object. An object knows that it “is a” specific realisation of its class, so when it receives a message it can look up the corresponding method in that class’s method table.
Like this.
typealias Class = Dictionary // define a "Non-Standalone" Object that relies on a class for its methods. let NSObject : Class = [ "description": IMP.description({ _ in return "An NSObject" })] func newObject(isa : Class) -> Object { return { aSelector in if let anImplementation = isa[aSelector] { return anImplementation } else { return IMP.methodMissing({ _ in print("Does not recognize selector \(aSelector)") return nil }) } } } let anObject = newObject(NSObject) 📓(anObject) // "An NSObject"
Objects returned by the newObject()
function look up their methods in their class dictionary, a table of method implementations for the corresponding selectors.
Not like that.
Mapping things to other things, that’s a function, isn’t it? And indeed there already is a type of function that takes selectors to methods, the object. A class should just be an object. Let’s try that again.
typealias Class = Object let NSObject : Class = { aSelector in switch aSelector { case "description": return IMP.description({ _ in return "An NSObject" }) default: return IMP.methodMissing({ _ in print("Instance does not recognize selector \(aSelector)") return nil }) } } func newObject(isa : Class) -> Object { return { aSelector in return isa(aSelector) } } let anObject = newObject(NSObject) 📓(anObject) // "An NSObject"
Great. Classes are objects, just as we’re used to. And indeed, a class could get its own methods from another class: a metaclass.
Notice that class-based objects walk and quack like the earlier, prototypical objects, so interoperate completely. That’s the point of message passing. No object needs to know how any other object works, just that it will respond to messages.
That’s what makes all of the bridges that let other languages interoperate with Objective-C. As long as objc_msgSend()
can find your code and run it, we’re all good. In Objective-Swift, as long as passing you a selector gives me an IMP
, we’re all good.
Instance variables.
Methods defined by classes should be able to see the object’s instance variables. The instance variables should, per their definition, be unique to each instance. However the best place to define them is in the class, for three reasons:
- The class knows which instance variables its methods expect to be present.
- The class’s constructor function captured the values (or the initial values, if they’re mutable) of the instance variables.
- If I’m subclassing, I want to be able to add the subclass’s instance variables to the superclass’s, without having to redefine the whole shebang.
So a class should tell its objects what instance variables it needs, and the values. Now, how should an instance look up its instance variables? We need to go from the name of an instance variable to its value, and I’ll save us both some embarrassment by cutting straight to the conclusion that an object is a great way to do this.
Each object in the class-based system will therefore be composed of two others: the class encapsulates what the object is and the methods it responds to, while the instance variable store encapsulates what the object is made of and what variables it has. Both are accessed through selectors, in a design choice that follows Eiffel and its Uniform-Access Principle (and the later Self language).
The same selector cannot refer both to a method and an ivar, because they’re both in the same namespace. One of them has to win, and in this case I’ve chosen the instance variable lookup to win.
Here’s a non-standalone version of Point
, with its two instance variables. In the code shown here, o
is a previously-defined object on which all methods are missing.
func NSPoint(x:Int, y:Int) -> Class { let superclass = NSObject let ivars:Object = { variableName in switch variableName { case "x": return .method({_ in return Integer(x, proto: o)}) case "y": return .method({_ in return Integer(y, proto: o)}) default: return (superclass→"instanceVariables")!(variableName) } } let thisClass:Class = { aSelector in switch aSelector { case "instanceVariables": return .method({_ in return ivars}) case "distanceFromOrigin": return .method({(this, _cmd, args:Object...) in let thisX = ℹ︎(this→"x")! let thisY = ℹ︎(this→"y")! let distance = sqrt(Double(thisX*thisX + thisY*thisY)) return Integer(Int(distance), proto: o) }) default: return superclass(aSelector) } } return thisClass } let aPoint = newObject(NSPoint(3,y: 4)) 📓(aPoint) // "An NSObject" 📓(aPoint→"x") // "3" 📓(aPoint→"distanceFromOrigin") // "5"
Super calls.
You can see that NSPoint
is a subclass of NSObject
, although this isn’t amazingly useful. NSPoint
gets all zero of NSObject
‘s instance variables, and the not-particularly-descriptive description
method.
In what might be the worst design choice of this blog, I’ll say that a 3D point is a specialisation of a 2D point. I’ll need to override distanceFromOrigin
to calculate the distance in three dimensions, but I can use the result from the superclass’s two-dimensional calculation to do this.
As with Objective-C, this will require a different method lookup system, one that says “ask your superclass” instead of “ask yourself”. To do this, the object must know what its superclass is: it can ask its class. Here is a new method on NS3DPoint
:
case "superclass": return .method({ _ in return superclass })
Now, because I’m really running out of ideas on how to name these operators, here are some doubly-long operators that call into the superclass for an object:
infix operator .... {} func .... (receiver: Object?, _cmd:Selector) -> IMP? { guard let this = receiver else { return nil } let method = (this→"superclass")!(_cmd) switch(method) { case IMP.methodMissing(let f): return f(this, _cmd)...._cmd default: return method } } infix operator →→ {} func →→ (receiver: Object?, _cmd:Selector) -> Object? { guard let imp = receiver...._cmd else { return nil } switch imp { case .method(let f): return f(receiver!, _cmd) default: return nil } }
Now, with all of this notation in place, it’s possible to create a method NS3DPoint→distanceFromOrigin
that both overrides its parent method, and uses that overridden method in its calculation. Here’s the method:
case "distanceFromOrigin": return .method({(this, _cmd, args:Object...) in let twoDDistance = ℹ︎(this→→"distanceFromOrigin")! let thisZ = ℹ︎(this→"z")! let distance = sqrt(Double(twoDDistance*twoDDistance + thisZ*thisZ)) return Integer(Int(distance), proto: o) })
Here it is in use:
let anotherPoint = newObject(NS3DPoint(10, y: 12, z: 14)) 📓(anotherPoint→"distanceFromOrigin") // "20"
Conclusion
In many “classical” object-oriented programming systems, classes are just objects, which instances can use to find the methods they respond to. If you’ve already got objects that don’t use classes, it’s possible to make objects that do use classes by giving each object a class object. You need somewhere to hold the instance variables for an object, and it happens that objects are good for that too.
More useful to notice is that objects are really just a way to do late binding of functions to their names. It doesn’t really matter what rules you use, as long as the result is a map of names to functions you’ve got yourself an object that will work with other objects.
“typealias Class = Dictionary”
makes me think of JavaScript.
That’s it, nothing else :)
Pingback: Classes in objects in object-oriented programming in functional programming in Swift | Dinesh Ram Kali.