rsi/objc/objc.md

563 lines
22 KiB
Markdown

---
tagline: Obj-C & Cocoa bridge
platforms: osx64
---
## `local objc = require'objc'`
## Features
* Coverage
* full access to Cocoa classes, protocols, C functions, structs, enums, constants
* access to methods, properties and ivars
* creating classes and overriding methods
* exploring and searching the Objective-C runtime
* Platforms
* tested with __OSX 10.7 to 10.12__ (__32bit__ and __64bit__)
* Dependencies
* none for Cocoa (XML parser included), [expat] for non-standard bridgesupport files
* Type Bridging
* methods and functions return Lua booleans
* Lua numbers, strings and tables can be passed for NSNumber, NSStrings, NSArray and NSDictionary args
* string names can be passed for class and selector args
* Lua functions can be passed for block and function-pointer args without specifying a type signature
* overriding methods does not require specifying the method type signature
* method signatures are inferred from existing supermethods and conforming protocols
* formal and informal protocols supported
* function-pointer args on overriden methods and blocks can be called without specifying a type signature
* GC Bridging
* attaching Lua variables to classes and objects
* Lua variables follow the lifetime of Obj-C objects
* Lua variables attached to classes are inherited
* automatic memory management of objects and blocks
* blocks are refcounted and freed when their last owner releases them
* Speed
* aggressive caching all-around
* no gc pressure in calling methods after the first invocation
* fast, small embedded XML parser
## Limitations
### FFI callback limitations
Blocks, function callbacks and overriden methods are based on ffi callbacks
which come with some limitations:
* can't access the vararg part of the function, for variadic functions/methods
* can't access the pass-by-value struct args or any arg after the first pass-by-value struct arg
* can't return structs by value
To counter this, you can use [cbframe] as a workaround. Enable it with
`objc.debug.cbframe = true` and now all problem methods and blocks
will receive a single arg: a pointer to a [D_CPUSTATE] struct that you have
to pick up args from and write the return value into. Note that self
isn't passed in this case, the cpu state is the only arg.
[D_CPUSTATE]: https://github.com/luapower/cbframe/blob/master/cbframe_x86_h.lua
### Broken bridgesupport files in OSX 10.13+
`*.bridgesupport` files are required to get extra type information not covered
by the objc RTTI API. In OSX 10.13 and above, these files are broken, so you
need to deploy your own and put them in the `bridgesupport` directory.
Parsable files from OSX 10.12.6 are available at https://github.com/luapower/bridgesupport
(just use `mgit clone bridgesupport` if you're using mgit).
## Quick Tutorial
### Loading frameworks
~~~{.lua}
--load a framework by name; `objc.searchpaths` says where the frameworks are. you can also use full paths.
--classes and protocols are loaded, but also C constants, enums, functions, structs and even macros.
objc.load'Foundation'
--you can also load sub-frameworks like this:
objc.load'Carbon.HIToolbox'
--which is the same as using relative paths:
objc.load'Carbon.framework/Versions/Current/Frameworks/HIToolbox'
~~~
### Creating and using objects
~~~{.lua}
--instantiate a class. the resulting object is retained and released on gc.
--you can call `release()` on it too, for a more speedy destruction.
local str = objc.NSString:alloc():initWithUTF8String'wazza'
--call methods with multiple arguments using underscores for ':'. last underscore is optional.
--C constants, enums and functions are in the objc namespace too.
local result = str:compare_options(otherStr, objc.NSLiteralSearch)
~~~
### Subclassing
~~~{.lua}
--create a derived class. when creating a class, say which protocols you wish it conforms to,
--so that you don't have to deal with type encodings when implementing its methods.
objc.class('NSMainWindow', 'NSWindw <NSWindowDelegate>')
--add methods to your class. the selector `windowWillClose` is from the `NSWindowDelegate` protocol
--so its type encoding is inferred from the protocol definition.
function objc.NSMainWindow:windowWillClose(notification)
...
end
--override existing methods. use `objc.callsuper` to call the supermethod.
function objc.NSMainWindow:update()
...
return objc.callsuper(self, 'update')
end
~~~
### Converting between Lua and Obj-C types
~~~{.lua}
local str = objc.toobj'hello' --create a NSString from a Lua string
local num = objc.toobj(3.14) --create a NSNumber from a Lua number
local dic = objc.toobj{a = 1, b = 'hi'} --create a NSDictionary from a Lua table
local arr = objc.toobj{1, 2, 3} --create a NSArray from a Lua table
local s = objc.tolua(str)
local n = objc.tolua(num)
local t1 = objc.tolua(dic)
local t2 = objc.tolua(arr)
~~~
### Adding Lua variables (luavars)
~~~{.lua}
--add Lua variables to your objects - their lifetime is tied to the lifetime of the object.
--you can also add class variables - they will be accessible through the objects too.
objc.NSObject.myClassVar = 'I can live forever'
local obj = objc.NSObject:new()
obj.myInstanceVar = 'I live while obj lives'
obj.myClassVar = 5 --change the class var (same value for all objects)
~~~
### Adding Lua methods
Lua methods are just Lua variables which happen to have a function-type value.
You can add them to a class or to an instance, but that doesn't make them
"class methods" or "instance methods" in OOP sense. Instead, this distinction
comes about when you call them:
~~~{.lua}
function objc.NSObject:myMethod() end
local str = objc.toobj'hello' --create a NSString instance, which is a NSObject
str:myMethod() --instance method (str passed as self)
objc.NSString:myMethod() --class method (NSString passed as self)
~~~
As you can see, luavars attached to a class are also inherited.
> If this looks like a lot of magic, it is. The indexing rules for class and instance
objects (i.e. getting and setting object and class fields) are pretty complex.
Have a look at the API sections "object fields" and "class fields" to learn more.
### Accessing properties & ivars
~~~{.lua}
--get and set class and instance properties using the dot notation.
local pr = objc.NSProgress:progressWithTotalUnitCount(123)
print(pr.totalUnitCount) --prints 123
pr.totalUnitCount = 321 --sets it
--get and set ivars using the dot notation.
local obj = objc.NSDocInfo:new()
obj.time = 123
print(obj.time) --prints 123
~~~
### Creating and using blocks
~~~{.lua}
--blocks are created automatically when passing a Lua function where a block is expected.
--their lifetime is auto-managed, for both synchronous and asynchronous methods.
local str = objc.NSString:alloc():initWithUTF8String'line1\nline2\nline3'
str:enumerateLinesUsingBlock(function(line, stop)
print(line:UTF8String()) --'char *' return values are also converted to Lua strings automatically
end)
--however, blocks are slow to create and use ffi callbacks which are very limited in number.
--create your blocks outside loops if possible, or call `collectgarbage()` every few hundred iterations.
--create a block with its type signature inferred from usage.
--in this case, its type is that of arg#1 to NSString's `enumerateLinesUsingBlock` method.
local block = objc.toarg(objc.NSString, 'enumerateLinesUsingBlock', 1, function(line, stop)
print(line:UTF8String())
end)
str:enumerateLinesUsingBlock(block)
--create a block with its method type encoding given manaully.
--for type encodings see:
-- https://code.google.com/p/jscocoa/wiki/MethodEncoding
local block = objc.block(function(line, stop)
print(line:UTF8String())
end, 'v@^B'}) --retval is 'v' (void), line is '@' (object), stop is '^B' (pointer to BOOL)
str:enumerateLinesUsingBlock(block)
~~~
### More goodies
Look up anything in Cocoa by a Lua pattern:
./luajit objc_test.lua inspect_find foo
Then inspect it:
./luajit objc_test.lua inspect_class PAFootprint
### Even more goodies
Check out the unit test script, it also contains a few demos, not just tests. \
Check out the undocumented `objc_inspect` module, it has a simple cmdline inspection API.
## Memory management
Memory management in objc is automatic. Cocoa's reference counting system is
tied to the Lua's garbage collector so that you don't have to worry about
retain/release. The integration is not air-tight though, so you need to know
how it's put together to avoid some tricky situations.
### Strong and weak references
Ref. counting systems are fragile: they require that retain() and release()
calls on an object be perfectly balanced. If they're not, you're toast.
Thinking of object relationships in in terms of weak and strong references
can help a lot with that.
A strong reference is a retained reference, guaranteed to be available until
released. A weak reference is not retained and its availability depends on
context.
A strong reference has a finalizer that calls release() when collected.
A weak reference doesn't have a finalizer.
Calling release() on a strong reference releases the reference, and removes
the finalizer, turning it into a weak reference. You should not call
release() on a weak reference.
### Return values are strong
Cocoa's rules say that if you alloc an object, you get a strong (retained)
reference on that object. Other method calls that return an object return
a weak (non-retained) reference to that object. Lua retains all object return
values so you always get a strong reference. This is required for the
alloc():init() sequence to work, and it's generally convenient.
### Callback arguments are weak
Object arguments passed to overriden methods (including the self argument),
blocks and function pointers, are weak references, not tied to Lua's garbage
collector. If you want to keep them around outside the scope of the callback,
you need to retain them:
~~~{.lua}
local strong_ref
function MySubClass:overridenMethod()
strong_ref = self:retain() --self is a weak ref. it needs to be retained.
end
~~~
### Luavars and object ownership
You should only use luavars on objects that you own. Luavars go away
when the last strong reference to an object goes away. Setting Lua vars
on an object with only weak references will leak those vars! Even worse,
those vars might show up as vars of other objects!
### Strong/weak ambiguities
If you create a `NSWindow`, you don't get an _unconditionally_ retained
reference to that window, contrary to Cocoa's rules, because if the user
closes the window, it is your reference that gets released. The binding
doesn't know about that and on gc it calls release again, giving you a crash
at an unpredictable time (`export NSZombieEnabled=YES` can help here).
To fix that you can either tell Cocoa that your ref is strong by calling
`win:setReleasedWhenClosed(false)`, or tell the gc that your ref is weak by
calling `ffi.gc(win, nil)`. If you chose the latter, remember that you can't
use luavars on that window!
## Main API
----------------------------------------------------------- --------------------------------------------------------------
__global objects__
`objc` namespace for loaded classes, C functions,
function aliases, enums, constants, and this API
__frameworks__
`objc.load(name|path[, option])` load a framework given its name or its full path \
option 'notypes': don't load bridgesupport file
`objc.searchpaths = {path1, ...}` search paths for frameworks
`objc.findframework(name|path) -> path, name` find a framework in searchpaths
__classes__
`objc.class'name' -> cls` class by name (`objc.class'Foo'` == `objc.Foo`)
`objc.class(obj) -> cls` class of instance
`objc.class('Foo', 'SuperFoo <Protocol1, ...>') -> cls` create a class which conforms to protocols
`objc.class('Foo', 'SuperFoo', 'Protocol1', ...) -> cls` create a class (alternative way)
`objc.classname(cls) -> s` class name
`objc.isclass(x) -> true|false` check for Class type
`objc.isobj(x) -> true|false` check for id type
`objc.ismetaclass(cls) -> true|false` check if the class is a metaclass
`objc.superclass(cls|obj) -> cls|nil` superclass
`objc.metaclass(cls|obj) -> cls` metaclass
`objc.isa(cls|obj, supercls) -> true|false` check the inheritance chain
`objc.conforms(cls|obj, protocol) -> true|false` check if a class conforms to a protocol
`objc.responds(cls, sel) -> true|false` check if instances of cls responds to a selector
`objc.conform(cls, protocol) -> true|false` declare that a class conforms to a protocol
__object fields__
`obj.field` \ access an instance field, i.e. try to get, in order: \
`obj:method(args...)` - an instance luavar \
- a readable instance property \
- an ivar \
- an instance method \
- a class field (see below)
`obj.field = val` \ set an instance field, i.e. try to set, in order: \
- an existing instance luavar \
- a writable instance property \
- an ivar \
- an existing class field (see below) \
- a new instance luavar
__class fields__
`cls.field` \ access a class field, i.e. try to get, in order: \
`cls:method(args...)` - a class luavar \
- a readable class property \
- a class method \
- a class luavar from a superclass
`cls.field = val` \ set a class field, i.e. try to set, in order: \
`function cls:method(args...) end` - an existing class luavar \
- a writable class property \
- an instance method \
- a conforming instance method \
- a class method \
- a conforming class method \
- an existing class luavar in a superclass \
- a new class luavar
__type conversions__
`objc.tolua(x) -> luatype` convert a NSNumber, NSString, NSDictionary, NSArray
to a Lua number, string, table respectively.
anything else passes through.
`objc.toobj(x) -> objtype` convert a Lua number, string, or table to a
NSNumber, NSString, NSDictionary, NSArray respectively.
anything else passes through.
`objc.ipairs(arr) -> next, arr, 0` ipairs for NSarray.
__overriding__
`objc.override(cls, sel, func[,mtype|ftype]) -> true|false` override an existing method, or add a method
which conforms to one of the conforming protocols.
returns true if the method was found and overriden.
`objc.callsuper(obj, sel, args...) -> retval` call the method implementation of the superclass
of an object.
`objc.swizle(cls, sel1, sel2[, func])` swap implementations between sel1 and sel2.
if sel2 is not an existing selector, func is required.
__selectors__
`objc.SEL(name|sel) -> sel` create/find a selector by name
`sel:name() -> s` selector name (same as tostring(sel))
__blocks and callbacks__
`objc.toarg(cls, sel, argindex, x) -> objtype` convert a Lua value to an objc value - used specifically
to create blocks and function callbacks with an appropriate
type signature for a specific method argument.
`objc.block(func, mtype|ftype) -> block` create a block with a specific type encoding.
----------------------------------------------------------- --------------------------------------------------------------
## Reflection API
----------------------------------------------------------- --------------------------------------------------------------
__protocols__
`objc.protocols() -> iter() -> proto` loaded protocols (formal or informal)
`objc.protocol(name|proto) -> proto` get a protocol by name (formal or informal)
`proto:name() -> s` protocol name (same as tostring(proto))
`proto:protocols() -> iter() -> proto` inherited protocols
`proto:properties() -> iter() -> prop` get properties (inherited ones not included)
`proto:property(proto, name, required, readonly) -> prop` find a property
`proto:methods(proto, inst, req) -> iter() -> sel, mtype` get method names and raw, non-annotated type encodings
`proto:mtype(proto, sel, inst, req) -> mtype` find a method and return its raw type encoding
`proto:ctype(proto, sel, inst, req[, for_cb]) -> ctype` find a method and return its C type encoding
__classes__
`objc.classes() -> iter() -> cls` loaded classes
`objc.protocols(cls) -> iter() -> proto` protocols which a class conforms to (formal or informal)
objc.properties(cls) -> iter() -> prop` instance properties \
use metaclass(cls) to get class properties
`objc.property(cls, name) -> prop` instance property by name (looks in superclasses too)
`objc.methods(cls) -> iter() -> meth` instance methods \
use metaclass(cls) to get class methods
`objc.method(cls, name) -> meth` instance method by name (looks in superclasses too)
`objc.ivars(cls) -> iter() -> ivar` ivars
`objc.ivar(cls) -> ivar` ivar by name (looks in superclasses too)
__properties__
`prop:name() -> s` property name (same as tostring(prop))
`prop:getter() -> s` getter name
`prop:setter() -> s` setter name (if not readonly)
`prop:stype() -> s` type encoding
`prop:ctype() -> s` C type encoding
`prop:readonly() -> true|false` readonly check
`prop:ivar() -> s` ivar name
__methods__
`meth:selector() -> sel` selector
`meth:name() -> s` selector name (same as tostring(meth))
`meth:mtype() -> s` type encoding
`meth:implementation() -> IMP` implementation (untyped)
__ivars__
`ivar:name() -> s` name (same as tostring(ivar))
`ivar:stype() -> s` type encoding
`ivar:ctype() -> s` C type encoding
`ivar:offset() -> n` offset
----------------------------------------------------------- --------------------------------------------------------------
## Debug API
----------------------------------------------------------- --------------------------------------------------------------
__logging__
`objc.debug.errors` (true) log errors to stderr
`objc.debug.printcdecl` (false) print C declarations on stdout
`objc.debug.logtopics= {topic = true}` (empty) enable logging on some topic (see source code)
`objc.debug.errcount = {topic = count}` error counts
__solving C name clashes__
`objc.debug.rename.string.foo = bar` load a string constant under a different name
`objc.debug.rename.enum.foo = bar` load an enum under a different name
`objc.debug.rename.typedef.foo = bar` load a type under a different name
`objc.debug.rename.const.foo = bar` load a const under a different name
`objc.debug.rename.function.foo = bar` load a global function under a different name
__loading frameworks__
`objc.debug.loadtypes` (true) load bridgesupport files
`objc.debug.loaddeps` (false) load dependencies per bridgesupport file (too many to be useful)
`objc.debug.lazyfuncs` (true) cdef functions on the first call instead of on load
`objc.debug.checkredef` (false) check incompatible redefinition attempts (makes parsing slower)
`objc.debug.usexpat` (false) use expat to parse bridgesupport files
__gc bridging__
`objc.debug.noretain.foo = true` declare that method `foo` already retains the object it returns
----------------------------------------------------------- --------------------------------------------------------------
## Future developments
> NOTE: I don't plan to work on these, except on requests with a use case. Patches/pull requests welcome.
### Bridging
* function-pointer args on function-pointer args (recorded but not used - need use cases)
* test for overriding a method that takes a function-pointer (not a block) arg and invoking that arg from the callback
* auto-coercion of types for functions/methods with format strings, eg. NSLog
* format string parser - apply to variadic functions and methods that have the `printf_format` attribute
* return pass-by-reference out parameters as multiple Lua return values
* record type modifiers O=out, N=inout
* auto-allocation of out arrays using array type annotations
* `c_array_length_in_result` - array length is the return value
* `c_array_length_in_arg` - array length is an arg
* `c_array_delimited_by_null` - vararg ends in null - doesn't luajit do that already?
* `c_array_of_variable_length` - ???
* `c_array_of_fixed_length` - specifies array size? doesn't seem so
* `sel_of_type`, `sel_of_type64` - use cases?
* core foundation stuff
* `cftypes` xml node - use cases?
* `already_retained` flag
* operator overloading (need good use cases)
### Inspection
* list all frameworks in searchpaths
* find framework in searchpaths
* report conforming methods, where they come from and mark the required ones, especially required but not implemented
* inspection of instances
* print class, superclasses and protocols in one line
* print values of luavars, ivars, properties
* listing sections: ivars, properties, methods, with origin class/protocol for each
### Type Cache
The idea is to cache bridgesupport data into Lua files for faster loading of frameworks.
* one Lua cache file for each framework to be loaded with standard 'require'
* dependencies also loaded using standard 'require'
* save dependency loading
* save cdecls - there's already a pretty printer and infrastructure for recording those
* save constants and enums
* save function wrappers
* save mtas (find a more compact format for annotations containing only {retval='B'} ?)
* save informal protocols