|
| 1 | +#Runtime |
| 2 | + |
| 3 | +An Objective-C simulator written in Swift. |
| 4 | + |
| 5 | +##Goals |
| 6 | + |
| 7 | +With few exceptions, this project aims to simulate what Objective-C code is translated to in assembly (i.e. calls to`objc_msgSend`, inserted ARC functions, literal class-refs in class method calls, etc) as opposed to mirroring Objective-C style code and dynamism, which Swift can do already by using`@objc` classes. |
| 8 | + |
| 9 | +This project could theoretically be used as a dynamic runtime backend for a transpiled progamming language to transpile to, and as such, this framework and its conventions were crafted with that in mind. Many of the constructs here may seem like anything but type-safe, but it's all perfectly safe if the code is generated by some other, more type-safe language. |
| 10 | + |
| 11 | +##Features |
| 12 | + |
| 13 | +- Dynamic method dispatch |
| 14 | +- Method swizzling / replacing |
| 15 | +- Creating entire classes at runtime |
| 16 | +- Non-fragile ivars |
| 17 | + |
| 18 | +See`Person.Swift` for an examples of everything mentioned in the readme. |
| 19 | + |
| 20 | +##Overview |
| 21 | + |
| 22 | +Runtime metadata types provided by this framework mirrors that of the public Objective-C runtime interface as closely as possible, declaring types such as`Class`,`Ivar`,`Method`, etc, all of which provide about as much information as their Objective-C counterparts. |
| 23 | + |
| 24 | +###Defining classes |
| 25 | + |
| 26 | +A base class,`RootObject`, is provided for other classes to inherit from if they wish. New classes are defined by declaring a`struct` type to enclose the`Class` object in, with the class object itself being declared as a`static let`, followed by method variables. |
| 27 | + |
| 28 | +```objc |
| 29 | +struct Person { |
| 30 | +static let `class` = Class( |
| 31 | + isa: Person_meta.class, |
| 32 | + superclass: RootObject.class, |
| 33 | + name: "Person", |
| 34 | + ivars: [ |
| 35 | + (name: "_name", type: .string), |
| 36 | + (name: "_age", type: .integer) |
| 37 | + ], |
| 38 | + methods: [_init, name, setName_, age, setAge_, description], |
| 39 | + properties: [ |
| 40 | +Property(name: "name", getter: name, setter: setName_), |
| 41 | + Property(name: "age", getter: age, setter: setAge_), |
| 42 | +], |
| 43 | + protocols:[] |
| 44 | + ) |
| 45 | + |
| 46 | + // Methods go here as static vars |
| 47 | + |
| 48 | + static var_init = Method("init", returns: .object("this")) { this,_cmd, args in |
| 49 | + func init$(_ this: id, __cmd: SEL) -> id { |
| 50 | +_msgSend(this, "setName_", ("Bob")) |
| 51 | +_msgSend(this, "setAge_", (18)) |
| 52 | + |
| 53 | + return msgSend(super: true, this, _cmd) |
| 54 | + } |
| 55 | + |
| 56 | + return init$(this, _cmd) |
| 57 | +} |
| 58 | + |
| 59 | +static var name = Method("name", returns: .string) ... |
| 60 | +... |
| 61 | +} |
| 62 | + |
| 63 | +privatestruct Person_meta { |
| 64 | +static let `class` = Class( |
| 65 | + isa: nil, |
| 66 | + superclass: nil, |
| 67 | + name: "Person.meta", |
| 68 | + ... |
| 69 | +) |
| 70 | +} |
| 71 | +``` |
| 72 | + |
| 73 | +It is good practice to declare a struct for the class itself and another for the metaclass, as above, to reduce ambiguity between class members and instance members (methods, properties, etc). The metaclass stores class members. |
| 74 | + |
| 75 | +`isa:` should be the class's metaclass (or`nil` if the class is a metaclass itself).`superclass:` should be the superclass. |
| 76 | + |
| 77 | +####The Metaclass |
| 78 | + |
| 79 | +Metaclasses inherit from the super-metaclass, not the superclass. It is convention to declare the compile-time variable like`MyClass_meta` and name it`"MyClass.meta"`. So,`Person` inherits from`Object.class`, and`Person_meta` inherits from`Object_meta.class`. |
| 80 | + |
| 81 | +Each metaclass can be looked up by using`Class.named("Foo").isa` or directly by name with`Class.named("Foo.meta")`. |
| 82 | + |
| 83 | +####Methods |
| 84 | +######Declaration |
| 85 | + |
| 86 | +`Method`s should be defined as`static var/let` as well (as opposed to right inside the`methods:` argument to the`.class` initializer as I have done with`properties:`), in case you need to reference the method as an argument to a`Property` at compile-time. Declaring them inline also makes the initializer very hard to parse visually since method declarations are typically no less than 7 or 8 lines. |
| 87 | + |
| 88 | +######Method.init() structure |
| 89 | + |
| 90 | +The`Method` initializer takes the name of the method, the return and argument types (`Type`) an implementation (`IMP`). The return and argument types default to`.void` and`[]`. For initializers, it is convention to return`.object("self")` where you would use`instancetype` in Objective-C. You could use`.object("anything you want")`, but I find that`"self"` makes the most sense here. In cases where you return another object of a fixed type, use`.object("ClassName")`. This runtime aims to provide as much metadata for method type signatures as Objective-C does for property type signatures. |
| 91 | + |
| 92 | +######IMP arguments |
| 93 | + |
| 94 | +Like Objective-C, all methods take two fixed arguments:`this` in place of`self`, and`_cmd`. However, due to limitations in the Swift type system, all method`IMP`s must return the same thing,`Any`, and without using assembly, they must all take`Any` as the variable arguments, even if a method takes no other arguments. An`IMP` is invoked by passing`this`,`_cmd`, and`args` where`args` is a tuple of the non-fixed arguments to the method. |
| 95 | + |
| 96 | +######Implementation conventions |
| 97 | + |
| 98 | +To counteract the lack of type safety and enhance readability, I find it helpful to declare a function within the scope of the method`IMP` named with a traling`$` to represent the actual type signature of the method (and to hold the non-trivial implementation), like so: |
| 99 | + |
| 100 | +```swift |
| 101 | +staticvar add__=Method(…) { this, _cmd, argsin |
| 102 | +// Actual implementation and type signature of method |
| 103 | +func add__$(_ this: id,_ _cmd: SEL,a:Int,b:Int)->Int { |
| 104 | +return a+ b |
| 105 | + } |
| 106 | + |
| 107 | +// Cast out arguments and call method |
| 108 | +let args= argsas! (Int,Int) |
| 109 | +return add__$(this, _cmd, args.0, args.1) |
| 110 | +} |
| 111 | +``` |
| 112 | + |
| 113 | +Arguments must be cast from`Any` to their actual types as a tuple before being used. |
| 114 | + |
| 115 | +######Overriding methods |
| 116 | + |
| 117 | +To override a method, simply give your subclass another method with the same name as the method you wish to override. If you need to call the`super` implementation, simply pass`super: true` to your call to`msgSend`: |
| 118 | + |
| 119 | +```swift |
| 120 | +staticvar _init=Method("init",...) { this, _cmd, argsin |
| 121 | +funcinit$(_ this: id,_ _cmd: SEL)->id { |
| 122 | +returnmsgSend(super:true, this, _cmd) |
| 123 | +print("init override:\(this)") |
| 124 | + } |
| 125 | + |
| 126 | +returninit$(this, _cmd) |
| 127 | +} |
| 128 | +``` |
| 129 | + |
| 130 | +######Init |
| 131 | + |
| 132 | +If you're familiar with Swift, you may know that Swift doesn't allow you to use`self` before all ivars have been initialized. With some exceptions, the same is true here. That said, all ivars are initialized to`0` or`nil`, so it is not necessary to initialize primitive integral types to`nil` or`0`. |
| 133 | + |
| 134 | +>Technically, if a class has no stored complex Swift structures in it (such as`String`), it should be safe to use prior to ivar initialization. I plan to make a wrapper for`String` and`Array`, etc, to counteract these edge cases. |
| 135 | +
|
| 136 | + |
| 137 | +####Instance variables |
| 138 | + |
| 139 | +Ivars are passed to the`Class` initializer as a tuple of their name and type. Their offset is detremined at runtime, and as a result, classes do not have fragile ivars. |
| 140 | + |
| 141 | +>Metaclasses can not have any instance variables; trying to use ivars on a metaclass is undefined behavior. |
| 142 | +
|
| 143 | +####Properties |
| 144 | + |
| 145 | +Properties take a name and one or two implementations. A property's`type` comes from its`getter`. |
| 146 | + |
| 147 | +-- |
| 148 | + |
| 149 | +###Creating objects |
| 150 | + |
| 151 | +Instances of objects are allocated by calling`class.createInstance()`, i.e.: |
| 152 | + |
| 153 | +```swift |
| 154 | +let instance1= Person.class.createInstance() |
| 155 | +let instance2= Class.named("Person").createInstance() |
| 156 | +``` |
| 157 | + |
| 158 | +###Calling methods |
| 159 | + |
| 160 | +Like Objective-C, this runtime uses dynamic dispatch via the`msgSend` and`_msgSend` functions.`_msgSend` only exists as a shortcut for void-returning methods, or cases where you want to discard the return value. |
| 161 | + |
| 162 | +```swift |
| 163 | +let bob: id=msgSend(Person.class.createInstance(),"init") |
| 164 | +let name:String=msgSend(bob,"name") |
| 165 | +let age:Int=msgSend(bob,"age") |
| 166 | +let description:String=msgSend(bob,"description") |
| 167 | +``` |
| 168 | + |
| 169 | +###Accessing ivars |
| 170 | + |
| 171 | +Ivar access works similarly to how it works in Objective-C. You must retrieve the offset from the runtime and add it to`this` to access the ivar. A lot of casting is involved, and I've provided some operators to ease the pain: |
| 172 | + |
| 173 | +```swift |
| 174 | +let offset= this|.getClass.getIvarOffset("_someInt")! |
| 175 | +let pointer: Pointer<Int>=~pointer+ offset |
| 176 | +let ivarValue= pointer.pointee |
| 177 | +``` |
| 178 | + |
| 179 | +`this|` is shorthand for`this.pointee`.`~pointer` is shorthand for`unsafeBitCast(pointer, to: T.self)`. Note that the runtime uses its own`Pointer` type, which allows`+` to offset it by bytes at at time. |
| 180 | + |
| 181 | +The above is still pretty convoluted and heavily repeated, so I've provided yet another operator which returns`ivarValue` above: |
| 182 | + |
| 183 | +```swift |
| 184 | +let ivarValue:Int= this|"_someInt" |
| 185 | +``` |
| 186 | + |
| 187 | +In general,`|` provides some form of dereferencing an object pointer. Here is another operator which can be used to set an ivar`_foo` to`5`: |
| 188 | + |
| 189 | +```swift |
| 190 | +this|= (5,"_foo") |
| 191 | +``` |
| 192 | + |
| 193 | +-- |
| 194 | + |
| 195 | +###Type system "gotchas" |
| 196 | + |
| 197 | +####You're stuck with`id` |
| 198 | +Since new classes are weakly defined as runtime metadata and not as concrete types in Swift code, you cannot declare a`Pointer` to a custom type directly. That is, all object references are typed as`Pointer<Object>` aka`id`, as defined by`Object.swift` (not to be confused with`RootObject`, which is akin to`NSObject`). |
| 199 | + |
| 200 | +If you really want to declare a`Pointer<Vehicle>` for example, you could declare members on your`Vehicle` struct like so, alongside the`static let class` declaration: |
| 201 | + |
| 202 | +```swift |
| 203 | +structVehicle { |
| 204 | +let _super: Object |
| 205 | +let _capacity:Int |
| 206 | +... |
| 207 | + |
| 208 | +staticlet `class`=Class(isa:...) |
| 209 | +} |
| 210 | + |
| 211 | +/// Vehicle subclass |
| 212 | +structCar { |
| 213 | +let _super: Vehicle |
| 214 | +let make:String |
| 215 | +let model:String |
| 216 | +let year:Int |
| 217 | +... |
| 218 | + |
| 219 | +staticlet `class`=Class(isa:...) |
| 220 | +} |
| 221 | +``` |
| 222 | + |
| 223 | +Now, you could possibly do the following: |
| 224 | + |
| 225 | +```swift |
| 226 | +let fiesta: Pointer<Car>=msgSend( |
| 227 | + Car.class.createInstance(), |
| 228 | +"init", |
| 229 | + ("Ford","Fiesta",2014,...) |
| 230 | +) |
| 231 | +fiesta.year=2017 |
| 232 | +``` |
| 233 | + |
| 234 | +Be sure to continue to declare all ivars and methods inside the`Class` variable. Statically declaring the layout like this is only useful for extra type-safety and direct ivar access if you wish to bypass non-fragile ivar lookup. |
| 235 | + |
| 236 | +####Using`Class`es as objects |
| 237 | + |
| 238 | +`Class` instances could only be made possible by making`Class` a Swift`class` and not a`struct`, due to limitations in Swift's type system and several abstractions Swift imposes on the user. Therefore, they do not have the same underlying structure as`Object` does (that is,`Class` does not start with the`isa` defined by the`Object` declaration). To call a class method on a class, pass`.ref` as`this`: |
| 239 | + |
| 240 | +```swift |
| 241 | +_msgSend(Person.class.ref, "someClassMethod") |
| 242 | +``` |
| 243 | +In general, use `class.ref` whenever you wish to treat a `Class` as an object. |
| 244 | + |
| 245 | +--- |
| 246 | + |
| 247 | +##To-do |
| 248 | + |
| 249 | +- More tests |
| 250 | +- Zeroing deallocated references |
| 251 | +- Suggestions welcome! |