Initial commit
This commit is contained in:
9
objc/.mgit/bridgesupport.sh
Normal file
9
objc/.mgit/bridgesupport.sh
Normal file
@@ -0,0 +1,9 @@
|
||||
# Copy bridgesupport files from an OSX older than 10.13 to luapower/bridgesupport directory.
|
||||
# NOTE: Not sure if *.bridgesupport files are copyrightable/redistributable. Use at your own risk.
|
||||
|
||||
usage() { echo "Usage: mgit bridgesupport copy VOLUME_DIR"; exit 1; }
|
||||
[ "$1" == copy ] || usage
|
||||
[ -d "$2" ] || usage
|
||||
|
||||
echo "Copying $2/System/Library/Frameworks/*.bridgesupport to bridgesupport ..."
|
||||
find "$2/System/Library/Frameworks" -name '*.bridgesupport' -exec cp '{}' bridgesupport \;
|
2403
objc/objc.lua
Normal file
2403
objc/objc.lua
Normal file
File diff suppressed because it is too large
Load Diff
562
objc/objc.md
Normal file
562
objc/objc.md
Normal file
@@ -0,0 +1,562 @@
|
||||
---
|
||||
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
|
260
objc/objc_dispatch.lua
Normal file
260
objc/objc_dispatch.lua
Normal file
@@ -0,0 +1,260 @@
|
||||
|
||||
--Grand Central Dispatch binding by Fjölnir Ásgeirsson (c) 2012, MIT license.
|
||||
--modified for luapower/objc by Cosmin Apreutesei, public domain.
|
||||
--not used yet, thus not finished. the API will probably change in the future.
|
||||
|
||||
local ffi = require'ffi'
|
||||
local objc = require'objc'
|
||||
local C = ffi.C
|
||||
local dispatch = {}
|
||||
objc.dispatch = dispatch
|
||||
|
||||
ffi.cdef[[
|
||||
// base.h
|
||||
typedef void *dispatch_object_t;
|
||||
typedef void (*dispatch_function_t)(void *);
|
||||
|
||||
// object.h
|
||||
void dispatch_debug(dispatch_object_t object, const char *message, ...);
|
||||
void dispatch_debugv(dispatch_object_t object, const char *message, va_list ap);
|
||||
void dispatch_retain(dispatch_object_t object);
|
||||
void dispatch_release(dispatch_object_t object);
|
||||
void *dispatch_get_context(dispatch_object_t object);
|
||||
void dispatch_set_context(dispatch_object_t object, void *context);
|
||||
void dispatch_set_finalizer_f(dispatch_object_t object, dispatch_function_t finalizer);
|
||||
void dispatch_suspend(dispatch_object_t object);
|
||||
void dispatch_resume(dispatch_object_t object);
|
||||
|
||||
// time.h
|
||||
typedef uint64_t dispatch_time_t;
|
||||
dispatch_time_t dispatch_time(dispatch_time_t when, int64_t delta);
|
||||
dispatch_time_t dispatch_walltime(const struct timespec *when, int64_t delta);
|
||||
|
||||
// queue.h
|
||||
typedef struct dispatch_queue_s *dispatch_queue_t;
|
||||
typedef struct dispatch_queue_attr_s *dispatch_queue_attr_t;
|
||||
typedef long dispatch_queue_priority_t;
|
||||
|
||||
struct dispatch_queue_s _dispatch_main_q;
|
||||
struct dispatch_queue_attr_s _dispatch_queue_attr_concurrent;
|
||||
|
||||
typedef id dispatch_block_t;
|
||||
void dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
|
||||
void dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
|
||||
|
||||
void dispatch_async_f(dispatch_queue_t queue, void *context, dispatch_function_t work);
|
||||
void dispatch_sync_f(dispatch_queue_t queue, void *context, dispatch_function_t work);
|
||||
void dispatch_apply_f(size_t iterations, dispatch_queue_t queue, void *context, void (*work)(void *, size_t));
|
||||
dispatch_queue_t dispatch_get_current_queue(void);
|
||||
dispatch_queue_t dispatch_get_global_queue(dispatch_queue_priority_t priority, unsigned long flags);
|
||||
dispatch_queue_t dispatch_queue_create(const char *label, dispatch_queue_attr_t attr);
|
||||
const char *dispatch_queue_get_label(dispatch_queue_t queue);
|
||||
void dispatch_set_target_queue(dispatch_object_t object, dispatch_queue_t queue);
|
||||
void dispatch_main(void);
|
||||
void dispatch_after_f(dispatch_time_t when, dispatch_queue_t queue, void *context, dispatch_function_t work);
|
||||
void dispatch_barrier_async_f(dispatch_queue_t queue, void *context, dispatch_function_t work);
|
||||
void dispatch_barrier_sync_f(dispatch_queue_t queue, void *context, dispatch_function_t work);
|
||||
void dispatch_queue_set_specific(dispatch_queue_t queue, const void *key, void *context, dispatch_function_t destructor);
|
||||
void *dispatch_queue_get_specific(dispatch_queue_t queue, const void *key);
|
||||
void *dispatch_get_specific(const void *key);
|
||||
|
||||
// source.h
|
||||
typedef struct dispatch_source_s *dispatch_source_t;
|
||||
typedef const struct dispatch_source_type_s *dispatch_source_type_t;
|
||||
typedef unsigned long dispatch_source_mach_send_flags_t;
|
||||
typedef unsigned long dispatch_source_proc_flags_t;
|
||||
typedef unsigned long dispatch_source_vnode_flags_t;
|
||||
|
||||
const struct dispatch_source_type_s _dispatch_source_type_data_add;
|
||||
const struct dispatch_source_type_s _dispatch_source_type_data_or;
|
||||
const struct dispatch_source_type_s _dispatch_source_type_mach_send;
|
||||
const struct dispatch_source_type_s _dispatch_source_type_mach_recv;
|
||||
const struct dispatch_source_type_s _dispatch_source_type_proc;
|
||||
const struct dispatch_source_type_s _dispatch_source_type_read;
|
||||
const struct dispatch_source_type_s _dispatch_source_type_signal;
|
||||
const struct dispatch_source_type_s _dispatch_source_type_timer;
|
||||
const struct dispatch_source_type_s _dispatch_source_type_vnode;
|
||||
const struct dispatch_source_type_s _dispatch_source_type_write;
|
||||
|
||||
dispatch_source_t dispatch_source_create(dispatch_source_type_t type, uintptr_t handle,
|
||||
unsigned long mask, dispatch_queue_t queue);
|
||||
void dispatch_source_set_event_handler_f(dispatch_source_t source, dispatch_function_t handler);
|
||||
void dispatch_source_set_cancel_handler_f(dispatch_source_t source, dispatch_function_t cancel_handler);
|
||||
void dispatch_source_cancel(dispatch_source_t source);
|
||||
long dispatch_source_testcancel(dispatch_source_t source);
|
||||
uintptr_t dispatch_source_get_handle(dispatch_source_t source);
|
||||
unsigned long dispatch_source_get_mask(dispatch_source_t source);
|
||||
unsigned long dispatch_source_get_data(dispatch_source_t source);
|
||||
void dispatch_source_merge_data(dispatch_source_t source, unsigned long value);
|
||||
void
|
||||
dispatch_source_set_timer(dispatch_source_t source, dispatch_time_t start, uint64_t interval, uint64_t leeway);
|
||||
void dispatch_source_set_registration_handler_f(dispatch_source_t source, dispatch_function_t registration_handler);
|
||||
|
||||
// group.h
|
||||
typedef struct dispatch_group_s *dispatch_group_t;
|
||||
|
||||
dispatch_group_t dispatch_group_create(void);
|
||||
void dispatch_group_async_f(dispatch_group_t group, dispatch_queue_t queue, void *context, dispatch_function_t work);
|
||||
long dispatch_group_wait(dispatch_group_t group, dispatch_time_t timeout);
|
||||
void dispatch_group_notify_f(dispatch_group_t group, dispatch_queue_t queue, void *context, dispatch_function_t work);
|
||||
void dispatch_group_enter(dispatch_group_t group);
|
||||
void dispatch_group_leave(dispatch_group_t group);
|
||||
|
||||
// semaphore.h
|
||||
typedef struct dispatch_semaphore_s *dispatch_semaphore_t;
|
||||
|
||||
dispatch_semaphore_t dispatch_semaphore_create(long value);
|
||||
long dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);
|
||||
long dispatch_semaphore_signal(dispatch_semaphore_t dsema);
|
||||
|
||||
// once.h
|
||||
typedef long dispatch_once_t;
|
||||
|
||||
void dispatch_once_f(dispatch_once_t *predicate, void *context, dispatch_function_t function);
|
||||
|
||||
// data.h (Requires blocks for all of it's functionality, see http://github.com/aptiva/tlc if you need it)
|
||||
typedef struct dispatch_data_s *dispatch_data_t;
|
||||
|
||||
struct dispatch_data_s _dispatch_data_empty;
|
||||
|
||||
// io.h (Requires blocks for all of it's functionality, see http://github.com/aptiva/tlc if you need it)
|
||||
typedef int dispatch_fd_t;
|
||||
]]
|
||||
|
||||
-- Types
|
||||
dispatch.object_t = ffi.typeof'dispatch_object_t'
|
||||
dispatch.function_t = ffi.typeof'dispatch_function_t'
|
||||
dispatch.time_t = ffi.typeof'dispatch_time_t'
|
||||
dispatch.queue_t = ffi.typeof'dispatch_queue_t'
|
||||
dispatch.queue_attr_t = ffi.typeof'dispatch_queue_attr_t'
|
||||
dispatch.queue_priority_t = ffi.typeof'dispatch_queue_priority_t'
|
||||
dispatch.source_t = ffi.typeof'dispatch_source_t'
|
||||
dispatch.source_type_t = ffi.typeof'dispatch_source_type_t'
|
||||
dispatch.source_mach_send_flags_t = ffi.typeof'dispatch_source_mach_send_flags_t'
|
||||
dispatch.source_proc_flags_t = ffi.typeof'dispatch_source_proc_flags_t'
|
||||
dispatch.source_vnode_flags_t = ffi.typeof'dispatch_source_vnode_flags_t'
|
||||
dispatch.group_t = ffi.typeof'dispatch_group_t'
|
||||
dispatch.semaphore_t = ffi.typeof'dispatch_semaphore_t'
|
||||
dispatch.once_ = ffi.typeof'dispatch_once_t'
|
||||
dispatch.data_t = ffi.typeof'dispatch_data_t'
|
||||
dispatch.fd_t = ffi.typeof'dispatch_fd_t'
|
||||
|
||||
if ffi.os ~= 'OSX' then
|
||||
error('platform not OSX', 2)
|
||||
end
|
||||
|
||||
-- Contants
|
||||
dispatch.emptyData = ffi.cast(dispatch.data_t, C._dispatch_data_empty)
|
||||
dispatch.defaultDataDestructor = nil
|
||||
|
||||
dispatch.nsecPerSec = 1000000000ULL
|
||||
dispatch.nsecPerMsec = 1000000ULL
|
||||
dispatch.usecPerSec = 1000000ULL
|
||||
dispatch.nsecPerUsec = 1000ULL
|
||||
|
||||
dispatch.timeNow = 0
|
||||
dispatch.timeForever = bit.bnot(0)
|
||||
|
||||
objc.DISPATCH_QUEUE_PRIORITY_HIGH = ffi.cast(dispatch.queue_priority_t, 2)
|
||||
objc.DISPATCH_QUEUE_PRIORITY_DEFAULT = ffi.cast(dispatch.queue_priority_t, 0)
|
||||
objc.DISPATCH_QUEUE_PRIORITY_LOW = ffi.cast(dispatch.queue_priority_t, -2)
|
||||
objc.DISPATCH_QUEUE_PRIORITY_BACKGROUND = ffi.cast(dispatch.queue_priority_t, -32768) -- INT16_MIN
|
||||
|
||||
dispatch.main_queue = C._dispatch_main_q
|
||||
dispatch.serialQueueAttr = nil
|
||||
dispatch.queue_attr_concurrent = C._dispatch_queue_attr_concurrent
|
||||
dispatch.defaultTargetQueue = nil
|
||||
|
||||
dispatch.machSendDead = ffi.cast(dispatch.source_proc_flags_t, 0x1)
|
||||
dispatch.procExit = ffi.cast(dispatch.source_proc_flags_t, 0x80000000)
|
||||
dispatch.procFork = ffi.cast(dispatch.source_proc_flags_t, 0x40000000)
|
||||
dispatch.procExec = ffi.cast(dispatch.source_proc_flags_t, 0x20000000)
|
||||
dispatch.procSignal = ffi.cast(dispatch.source_proc_flags_t, 0x08000000)
|
||||
|
||||
dispatch.sourceTypeDataAdd = ffi.cast(dispatch.source_type_t, C._dispatch_source_type_data_add)
|
||||
dispatch.sourceTypeDataOr = ffi.cast(dispatch.source_type_t, C._dispatch_source_type_data_or)
|
||||
dispatch.sourceTypeMachSend = ffi.cast(dispatch.source_type_t, C._dispatch_source_type_mach_send)
|
||||
dispatch.sourceTypeMachRecv = ffi.cast(dispatch.source_type_t, C._dispatch_source_type_mach_recv)
|
||||
dispatch.sourceTypeProc = ffi.cast(dispatch.source_type_t, C._dispatch_source_type_proc)
|
||||
dispatch.sourceTypeRead = ffi.cast(dispatch.source_type_t, C._dispatch_source_type_read)
|
||||
dispatch.sourceTypeSignal = ffi.cast(dispatch.source_type_t, C._dispatch_source_type_signal)
|
||||
dispatch.sourceTypeTimer = ffi.cast(dispatch.source_type_t, C._dispatch_source_type_timer)
|
||||
dispatch.sourceTypeVnode = ffi.cast(dispatch.source_type_t, C._dispatch_source_type_vnode)
|
||||
dispatch.sourceTypeWrite = ffi.cast(dispatch.source_type_t, C._dispatch_source_type_write)
|
||||
dispatch.vNodeDelete = ffi.cast(dispatch.source_vnode_flags_t, 0x1)
|
||||
dispatch.vNodeWrite = ffi.cast(dispatch.source_vnode_flags_t, 0x2)
|
||||
dispatch.vNodeExtend = ffi.cast(dispatch.source_vnode_flags_t, 0x4)
|
||||
dispatch.vNodeAttrib = ffi.cast(dispatch.source_vnode_flags_t, 0x8)
|
||||
dispatch.vNodeLink = ffi.cast(dispatch.source_vnode_flags_t, 0x10)
|
||||
dispatch.vNodeRename = ffi.cast(dispatch.source_vnode_flags_t, 0x20)
|
||||
dispatch.vNodeRevoke = ffi.cast(dispatch.source_vnode_flags_t, 0x40)
|
||||
|
||||
|
||||
-- Functions
|
||||
dispatch.debug = C.dispatch_debug
|
||||
dispatch.debugv = C.dispatch_debugv
|
||||
dispatch.retain = C.dispatch_retain
|
||||
dispatch.release = C.dispatch_release
|
||||
dispatch.dispatch_get_context = C.dispatch_get_context
|
||||
dispatch.set_context = C.dispatch_set_context
|
||||
dispatch.set_finalizer = C.dispatch_set_finalizer_f
|
||||
dispatch.suspend = C.dispatch_suspend
|
||||
dispatch.resume = C.dispatch_resume
|
||||
|
||||
dispatch.time = C.dispatch_time
|
||||
dispatch.walltime = C.dispatch_walltime
|
||||
|
||||
dispatch.async_f = C.dispatch_async_f
|
||||
dispatch.sync_f = C.dispatch_sync_f
|
||||
dispatch.apply = C.dispatch_apply_f
|
||||
dispatch.get_current_queue = C.dispatch_get_current_queue
|
||||
dispatch.get_global_queue = C.dispatch_get_global_queue
|
||||
dispatch.queue_create = C.dispatch_queue_create
|
||||
dispatch.dispatch_queue_get_label = C.dispatch_queue_get_label
|
||||
dispatch.set_target_queue = C.dispatch_set_target_queue
|
||||
dispatch.main = C.dispatch_main
|
||||
dispatch.after = C.dispatch_after_f
|
||||
dispatch.barrier_async = C.dispatch_barrier_async_f
|
||||
dispatch.barrier_sync = C.dispatch_barrier_sync_f
|
||||
dispatch.queue_set_specific = C.dispatch_queue_set_specific
|
||||
dispatch.dispatch_queue_get_specific = C.dispatch_queue_get_specific
|
||||
dispatch.dispatch_get_specific = C.dispatch_get_specific
|
||||
|
||||
dispatch.source_create = C.dispatch_source_create
|
||||
dispatch.source_set_event_handler = C.dispatch_source_set_event_handler_f
|
||||
dispatch.source_set_cancel_handler = C.dispatch_source_set_cancel_handler_f
|
||||
dispatch.source_cancel = C.dispatch_source_cancel
|
||||
dispatch.source_testcancel = C.dispatch_source_testcancel
|
||||
dispatch.source_get_handle = C.dispatch_source_get_handle
|
||||
dispatch.source_get_mask = C.dispatch_source_get_mask
|
||||
dispatch.source_get_data = C.dispatch_source_get_data
|
||||
dispatch.source_merge_data = C.dispatch_source_merge_data
|
||||
dispatch.source_set_timer = C.dispatch_source_set_timer
|
||||
dispatch.source_set_registration_handler = C.dispatch_source_set_registration_handler_f
|
||||
|
||||
dispatch.group_create = C.dispatch_group_create
|
||||
dispatch.group_async = C.dispatch_group_async_f
|
||||
dispatch.group_wait = C.dispatch_group_wait
|
||||
dispatch.group_notify = C.dispatch_group_notify_f
|
||||
dispatch.group_enter = C.dispatch_group_enter
|
||||
dispatch.group_leave = C.dispatch_group_leave
|
||||
|
||||
dispatch.semaphore_create = C.dispatch_semaphore_create
|
||||
dispatch.semaphore_wait = C.dispatch_semaphore_wait
|
||||
dispatch.semaphore_signal = C.dispatch_semaphore_signal
|
||||
|
||||
dispatch.once = C.dispatch_once_f
|
||||
|
||||
dispatch.DISPATCH_QUEUE_SERIAL = nil
|
||||
|
||||
--note: do not use with queues that call back from a different thread!
|
||||
function dispatch.async(queue, block)
|
||||
C.dispatch_async(queue, objc.block(block))
|
||||
end
|
||||
jit.off(dispatch.async)
|
||||
|
||||
function dispatch.sync(queue, block)
|
||||
C.dispatch_sync(queue, objc.block(block))
|
||||
end
|
||||
|
||||
return dispatch
|
246
objc/objc_inspect.lua
Normal file
246
objc/objc_inspect.lua
Normal file
@@ -0,0 +1,246 @@
|
||||
local objc = require'objc'
|
||||
|
||||
local inspect = {}
|
||||
objc.inspect = inspect
|
||||
|
||||
--pretty helpers
|
||||
|
||||
local _ = string.format
|
||||
|
||||
local function p(...) --formatted line
|
||||
print(_(...))
|
||||
end
|
||||
|
||||
local function isort(iter, method) --sort an iterator of object by a method
|
||||
local t = {}
|
||||
while true do
|
||||
local v = iter()
|
||||
if v == nil then break end
|
||||
t[#t+1] = v
|
||||
end
|
||||
table.sort(t, function(a, b) return a[method](a) < b[method](b) end)
|
||||
local i = 0
|
||||
return function()
|
||||
i = i + 1
|
||||
if t[i] == nil then return end
|
||||
return t[i]
|
||||
end
|
||||
end
|
||||
|
||||
local function icount(iter)
|
||||
local n = 0
|
||||
for _ in iter do
|
||||
n = n + 1
|
||||
end
|
||||
return n
|
||||
end
|
||||
|
||||
--class header
|
||||
|
||||
local function protocols_spec(protocols)
|
||||
local t = {}
|
||||
for proto in isort(protocols, 'name') do
|
||||
t[#t+1] = proto:name() .. protocols_spec(proto:protocols())
|
||||
end
|
||||
return #t > 0 and _(' <%s>', table.concat(t, ', ')) or ''
|
||||
end
|
||||
|
||||
local function class_spec(cls, indent)
|
||||
indent = indent or 1
|
||||
local super_spec = objc.superclass(cls) and
|
||||
_('\n%s<- %s', (' '):rep(indent), class_spec(objc.superclass(cls), indent + 1)) or ''
|
||||
return objc.classname(cls) .. protocols_spec(objc.protocols(cls)) .. super_spec
|
||||
end
|
||||
|
||||
local function protocol_spec(proto)
|
||||
return proto:name() .. protocols_spec(proto:protocols())
|
||||
end
|
||||
|
||||
function inspect.class_header(cls)
|
||||
p('Class %s', class_spec(objc.class(cls)))
|
||||
end
|
||||
|
||||
--classes
|
||||
|
||||
function inspect.classes()
|
||||
for cls in objc.classes() do
|
||||
p('%-50s protocols: %-5s properties: %-5s ivars: %-5s methods (i): %-5s methods (c): %-5s',
|
||||
objc.classname(cls),
|
||||
icount(objc.protocols(cls)),
|
||||
icount(objc.properties(cls)),
|
||||
icount(objc.ivars(cls)),
|
||||
icount(objc.methods(cls)),
|
||||
icount(objc.methods(objc.metaclass(cls))))
|
||||
end
|
||||
end
|
||||
|
||||
--protocols
|
||||
|
||||
function inspect.protocols()
|
||||
for proto in objc.protocols() do
|
||||
p('%-50s %-10s protocols: %-5s properties: %-5s methods (i/o): %-5s methods (i/r): %-5s methods (c/o): %-5s methods (c/r): %-5s',
|
||||
proto:name(), proto.formal and 'formal' or 'informal',
|
||||
icount(proto:protocols()),
|
||||
icount(proto:properties()),
|
||||
icount(proto:methods(true, false)),
|
||||
icount(proto:methods(true, true)),
|
||||
icount(proto:methods(false, false)),
|
||||
icount(proto:methods(false, true)))
|
||||
end
|
||||
end
|
||||
|
||||
--properties
|
||||
|
||||
local function inspect_properties(name, props)
|
||||
for prop in isort(props, 'name') do
|
||||
p('%-30s %-40s %-8s %-20s %-20s %-20s %-20s',
|
||||
name, prop:name(), prop:readonly() and 'r/o' or 'r/w',
|
||||
prop:ctype(), prop:ivar() or '', prop:getter(), prop:setter() or '')
|
||||
end
|
||||
end
|
||||
|
||||
local function not_(arg, list, process, ...)
|
||||
if arg then return end
|
||||
for arg in list() do
|
||||
process(arg, ...)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function inspect.protocol_properties(proto)
|
||||
if not_(proto, objc.protocols, inspect.protocol_properties) then return end
|
||||
proto = objc.protocol(proto)
|
||||
inspect_properties(proto:name(), proto:properties())
|
||||
end
|
||||
|
||||
function inspect.class_properties(cls)
|
||||
if not_(cls, objc.classes, inspect.class_properties) then return end
|
||||
cls = objc.class(cls)
|
||||
inspect_properties(objc.classname(cls), objc.properties(cls))
|
||||
end
|
||||
|
||||
--methods
|
||||
|
||||
function inspect.class_methods(cls, inst)
|
||||
if not_(cls, objc.classes, inspect.class_methods, inst) then return end
|
||||
cls = inst and objc.class(cls) or objc.metaclass(cls)
|
||||
for meth in isort(objc.methods(cls), 'name') do
|
||||
p('%-40s %-50s %-50s %s', objc.classname(cls), meth:name(),
|
||||
objc.ftype_ctype(objc.method_ftype(cls, meth:selector(), meth)), meth:mtype())
|
||||
end
|
||||
end
|
||||
|
||||
function inspect.protocol_methods(proto, inst, required)
|
||||
if not_(proto, objc.protocols, inspect.protocol_methods, inst, required) then return end
|
||||
proto = objc.protocol(proto)
|
||||
for selname, mtype in proto:methods(inst or false, required or false) do
|
||||
p('%-40s %-50s %-50s %s', proto:name(), selname, objc.ftype_ctype(objc.mtype_ftype(mtype)), mtype)
|
||||
end
|
||||
end
|
||||
|
||||
--ivars
|
||||
|
||||
function inspect.class_ivars(cls)
|
||||
if not_(cls, objc.classes, inspect.ivars) then return end
|
||||
cls = objc.class(cls)
|
||||
for ivar in isort(objc.ivars(cls), 'name') do
|
||||
p('%-40s %-50s %-50s %-5s %s', objc.classname(cls), ivar:name(), ivar:ctype(),
|
||||
tonumber(ivar:offset()), ivar:stype())
|
||||
end
|
||||
end
|
||||
|
||||
--full class inspection
|
||||
|
||||
function inspect.class(cls)
|
||||
print''
|
||||
inspect.class_header(cls)
|
||||
print'\nProperties:\n'
|
||||
inspect.class_properties(cls)
|
||||
print'\nIvars:\n'
|
||||
inspect.class_ivars(cls)
|
||||
print'\nMethods:\n'
|
||||
inspect.class_methods(cls)
|
||||
end
|
||||
|
||||
function inspect.protocol(proto)
|
||||
print''
|
||||
print(objc.protocol(proto):name())
|
||||
print'\nProperties:\n'
|
||||
inspect.protocol_properties(proto)
|
||||
print'\nMethods (i/o):\n'
|
||||
inspect.protocol_methods(proto, true, false)
|
||||
print'\nMethods (i/r):\n'
|
||||
inspect.protocol_methods(proto, true, true)
|
||||
print'\nMethods (c/o):\n'
|
||||
inspect.protocol_methods(proto, false, false)
|
||||
print'\nMethods (c/r):\n'
|
||||
inspect.protocol_methods(proto, false, true)
|
||||
end
|
||||
|
||||
function inspect.find(patt)
|
||||
--TODO: find framework / load all frameworks
|
||||
local function find_in_class(prefix, cls)
|
||||
for meth in objc.methods(cls) do
|
||||
if meth:name():find(patt) then
|
||||
p('%-20s [%s %s]', prefix..' method', objc.classname(cls), meth:name())
|
||||
end
|
||||
end
|
||||
for prop in objc.properties(cls) do
|
||||
if prop:name():find(patt) then
|
||||
p('%-20s %s.%s', prefix..' property', objc.classname(cls), prop:name())
|
||||
end
|
||||
end
|
||||
for ivar in objc.ivars(cls) do
|
||||
if ivar:name():find(patt) then
|
||||
p('%-20s %s.%s', prefix..' ivar', objc.classname(cls), ivar:name())
|
||||
end
|
||||
end
|
||||
end
|
||||
for cls in objc.classes() do
|
||||
if objc.classname(cls):find(patt) then
|
||||
p('%-20s %s', 'class', objc.classname(cls))
|
||||
end
|
||||
find_in_class('instance', cls)
|
||||
find_in_class('class', cls)
|
||||
end
|
||||
local function find_proto_method(proto, postfix, inst, required)
|
||||
for selname in proto:methods(inst, required) do
|
||||
if selname:find(patt) then
|
||||
p('%-20s [%s %s]', 'protocol method ('..postfix..')', proto:name(), selname)
|
||||
end
|
||||
end
|
||||
end
|
||||
for proto in objc.protocols() do
|
||||
if proto:name():find(patt) then
|
||||
p('%-20s %s', 'protocol', proto:name())
|
||||
end
|
||||
find_proto_method(proto, 'i/o', true, false)
|
||||
find_proto_method(proto, 'i/r', true, true)
|
||||
find_proto_method(proto, 'c/o', false, false)
|
||||
find_proto_method(proto, 'c/r', false, true)
|
||||
for prop in proto:properties() do
|
||||
if prop:name():find(patt) then
|
||||
p('%-20s %s.%s', 'protocol property', proto:name(), prop:name())
|
||||
end
|
||||
end
|
||||
end
|
||||
local function find_global(title, prefix, namespace)
|
||||
for k in pairs(namespace) do
|
||||
if type(k) == 'string' and k:find(patt) then
|
||||
p('%-20s %s%s', title, prefix, k)
|
||||
end
|
||||
end
|
||||
end
|
||||
find_global('global', 'objc.', objc)
|
||||
find_global('C global', 'objc.', objc.debug.cnames.global)
|
||||
find_global('C struct', '', objc.debug.cnames.struct)
|
||||
end
|
||||
|
||||
|
||||
if not ... then
|
||||
for k,v in pairs(inspect) do
|
||||
print(_('%-10s %s', type(v), 'objc.inspect.'..k))
|
||||
end
|
||||
end
|
||||
|
||||
return inspect
|
855
objc/objc_test.lua
Normal file
855
objc/objc_test.lua
Normal file
@@ -0,0 +1,855 @@
|
||||
local glue = require'glue'
|
||||
local objc = require'objc'
|
||||
local ffi = require'ffi'
|
||||
local pp = require'pp'
|
||||
|
||||
io.stdout:setvbuf'no'
|
||||
io.stderr:setvbuf'no'
|
||||
|
||||
setmetatable(_G, {__index = objc})
|
||||
|
||||
--test options
|
||||
|
||||
local subprocess = false --run each bridgesupport test in a subprocess
|
||||
objc.debug.lazyfuncs = true
|
||||
objc.debug.checkredef = false
|
||||
objc.debug.printcdecl = false
|
||||
objc.debug.loaddeps = false
|
||||
objc.debug.loadtypes = true
|
||||
|
||||
local bsdir = '_bridgesupport' --path where *.bridgesupport files are on Windows (tree or flat doesn't matter)
|
||||
local luajit = ffi.os == 'Windows' and 'luajit' or './luajit' --luajit command for subprocess running
|
||||
|
||||
if ffi.os == 'OSX' then
|
||||
objc.load'Foundation'
|
||||
pool = NSAutoreleasePool:new()
|
||||
end
|
||||
|
||||
--test helpers
|
||||
|
||||
local function printf(...)
|
||||
print(string.format(...))
|
||||
end
|
||||
|
||||
local function hr()
|
||||
print(('-'):rep(80))
|
||||
end
|
||||
|
||||
local n = 0
|
||||
local function genname(prefix)
|
||||
if not prefix then return genname'MyClass' end
|
||||
n = n + 1
|
||||
return prefix..n
|
||||
end
|
||||
|
||||
local function errpcall(patt, ...) --pcall that should fail with a specific message
|
||||
local ok, err = pcall(...)
|
||||
assert(not ok)
|
||||
assert(err:find(patt))
|
||||
end
|
||||
|
||||
--test namespace
|
||||
|
||||
local test = {} --{name = test_func}
|
||||
local eyetest = {} --{name = test_func}
|
||||
local demo = {}
|
||||
local tests = {tests = test, ['eye tests'] = eyetest, demos = demo}
|
||||
|
||||
function test.parsing()
|
||||
assert(stype_ctype('[8^c]', 'arr') == 'char *arr[8]') --array of pointers
|
||||
assert(stype_ctype('^[8c]', 'arr') == 'char (*arr)[8]') --pointer to array
|
||||
assert(stype_ctype('[8[4c]]', 'arr') == 'char arr[8][4]') --multi-dim. array
|
||||
assert(stype_ctype('[3^[8^c]]', 'arr') == 'char *(*arr[3])[8]')
|
||||
assert(stype_ctype('{?="x"i"y"i""(?="ux"I"uy"I)}', nil, 'cdef') ==
|
||||
'struct {\n\tint x;\n\tint y;\n\tunion {\n\t\tunsigned int ux;\n\t\tunsigned int uy;\n\t};\n}'
|
||||
) --nested unnamed anonymous structs
|
||||
|
||||
local function mtype_ctype(mtype, ...)
|
||||
return ftype_ctype(mtype_ftype(mtype), ...)
|
||||
end
|
||||
assert(mtype_ctype('@"Class"@:{_NSRect={_NSPoint=ff}{_NSSize=ff}}^{?}^?', 'globalFunction') ==
|
||||
'id globalFunction (id, SEL, struct _NSRect, void *, void *)') --unseparated method args
|
||||
assert(mtype_ctype('{_NSPoint=ff}iii', nil, true) ==
|
||||
'void (*) (int, int, int)') --struct return value not supported
|
||||
assert(mtype_ctype('iii{_NSPoint=ff}ii', nil, true) ==
|
||||
'int (*) (int, int)') --pass-by-value struct not supported, stop at first encounter
|
||||
assert(mtype_ctype('{_NSPoint=ff}ii{_NSPoint=ff}i', nil, true) ==
|
||||
'void (*) (int, int)') --combined case
|
||||
end
|
||||
|
||||
function eyetest.indent()
|
||||
--_NXEvent (test indent for nested unnamed anonymous structs)
|
||||
print(stype_ctype('{?="type"i"location"{?="x"i"y"i}"time"Q"flags"i"window"I"service_id"Q"ext_pid"i"data"(?="mouse"{?="subx"C"suby"C"eventNum"s"click"i"pressure"C"buttonNumber"C"subType"C"reserved2"C"reserved3"i"tablet"(?="point"{_NXTabletPointData="x"i"y"i"z"i"buttons"S"pressure"S"tilt"{?="x"s"y"s}"rotation"S"tangentialPressure"s"deviceID"S"vendor1"s"vendor2"s"vendor3"s}"proximity"{_NXTabletProximityData="vendorID"S"tabletID"S"pointerID"S"deviceID"S"systemTabletID"S"vendorPointerType"S"pointerSerialNumber"I"uniqueID"Q"capabilityMask"I"pointerType"C"enterProximity"C"reserved1"s})}"mouseMove"{?="dx"i"dy"i"subx"C"suby"C"subType"C"reserved1"C"reserved2"i"tablet"(?="point"{_NXTabletPointData="x"i"y"i"z"i"buttons"S"pressure"S"tilt"{?="x"s"y"s}"rotation"S"tangentialPressure"s"deviceID"S"vendor1"s"vendor2"s"vendor3"s}"proximity"{_NXTabletProximityData="vendorID"S"tabletID"S"pointerID"S"deviceID"S"systemTabletID"S"vendorPointerType"S"pointerSerialNumber"I"uniqueID"Q"capabilityMask"I"pointerType"C"enterProximity"C"reserved1"s})}"key"{?="origCharSet"S"repeat"s"charSet"S"charCode"S"keyCode"S"origCharCode"S"reserved1"i"keyboardType"I"reserved2"i"reserved3"i"reserved4"i"reserved5"[4i]}"tracking"{?="reserved"s"eventNum"s"trackingNum"i"userData"i"reserved1"i"reserved2"i"reserved3"i"reserved4"i"reserved5"i"reserved6"[4i]}"scrollWheel"{?="deltaAxis1"s"deltaAxis2"s"deltaAxis3"s"reserved1"s"fixedDeltaAxis1"i"fixedDeltaAxis2"i"fixedDeltaAxis3"i"pointDeltaAxis1"i"pointDeltaAxis2"i"pointDeltaAxis3"i"reserved8"[4i]}"zoom"{?="deltaAxis1"s"deltaAxis2"s"deltaAxis3"s"reserved1"s"fixedDeltaAxis1"i"fixedDeltaAxis2"i"fixedDeltaAxis3"i"pointDeltaAxis1"i"pointDeltaAxis2"i"pointDeltaAxis3"i"reserved8"[4i]}"compound"{?="reserved"s"subType"s"misc"(?="F"[11f]"L"[11i]"S"[22s]"C"[44c])}"tablet"{?="x"i"y"i"z"i"buttons"S"pressure"S"tilt"{?="x"s"y"s}"rotation"S"tangentialPressure"s"deviceID"S"vendor1"s"vendor2"s"vendor3"s"reserved"[4i]}"proximity"{?="vendorID"S"tabletID"S"pointerID"S"deviceID"S"systemTabletID"S"vendorPointerType"S"pointerSerialNumber"I"uniqueID"Q"capabilityMask"I"pointerType"C"enterProximity"C"reserved1"s"reserved2"[4i]})}', nil, 'cdef'))
|
||||
end
|
||||
|
||||
function eyetest.tostring()
|
||||
print(objc.NSNumber:numberWithDouble(0))
|
||||
print(objc.NSString:alloc():initWithUTF8String'') --empty string = NSFConstantString with hi address
|
||||
print(objc.NSString:alloc():initWithUTF8String'asdjfah') --real object
|
||||
end
|
||||
|
||||
--test parsing of bridgesupport files.
|
||||
--works on Windows too - just copy your bridgesupport files into whatever you set `bsdir` above.
|
||||
function eyetest.bridgesupport(bsfile)
|
||||
|
||||
local function list_func(cmd)
|
||||
return function()
|
||||
return coroutine.wrap(function()
|
||||
local f = io.popen(cmd)
|
||||
for s in f:lines() do
|
||||
coroutine.yield(s)
|
||||
end
|
||||
f:close()
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
local bsfiles
|
||||
if ffi.os == 'Windows' then
|
||||
bsfiles = list_func('dir /B /S '..bsdir..'\\*.bridgesupport')
|
||||
elseif ffi.os == 'OSX' then
|
||||
bsfiles = list_func('find /System/Library/frameworks -name \'*.bridgesupport\'')
|
||||
else
|
||||
error'can\'t run on this OS'
|
||||
end
|
||||
|
||||
local loaded = {}
|
||||
local n = 0
|
||||
|
||||
local objc_load = objc.debug.load_framework --keep it, we'll patch it
|
||||
|
||||
function objc.debug.load_framework(path) --either `name.bridgesupport` or `name.framework` or `name.framework/name`
|
||||
local name
|
||||
if path:match'%.bridgesupport$' then
|
||||
name = path:match'([^/\\]+)%.bridgesupport$'
|
||||
else
|
||||
name = path:match'/([^/]+)%.framework$' or path:match'([^/]+)$'
|
||||
if ffi.os == 'Windows' then
|
||||
path = bsdir..'\\'..name..'.bridgesupport'
|
||||
else
|
||||
path = path .. '/Resources/BridgeSupport/' .. name .. '.bridgesupport'
|
||||
end
|
||||
end
|
||||
if loaded[name] then return end
|
||||
loaded[name] = true
|
||||
if glue.canopen(path) then
|
||||
|
||||
if ffi.os == 'OSX' then
|
||||
|
||||
--load the dylib first (needed for function aliases)
|
||||
local dpath = path:gsub('Resources/BridgeSupport/.*$', name)
|
||||
if glue.canopen(dpath) then
|
||||
pcall(ffi.load, dpath, true)
|
||||
end
|
||||
|
||||
--load the dylib with inlines first (needed for function aliases)
|
||||
local dpath = path:gsub('bridgesupport$', 'dylib')
|
||||
if glue.canopen(dpath) then
|
||||
pcall(ffi.load, dpath, true)
|
||||
end
|
||||
end
|
||||
|
||||
objc.debug.load_bridgesupport(path)
|
||||
n = n + 1
|
||||
--print(n, '', name)
|
||||
else
|
||||
print('! not found', name, path)
|
||||
end
|
||||
end
|
||||
|
||||
local function status()
|
||||
pp('errors', objc.debug.errcount)
|
||||
print('globals: '..objc.debug.cnames.global[1])
|
||||
print('structs: '..objc.debug.cnames.struct[1])
|
||||
end
|
||||
|
||||
if bsfile then
|
||||
objc.debug.load_framework(bsfile)
|
||||
else
|
||||
for bsfile in bsfiles() do
|
||||
if bsfile:match'Python' then
|
||||
print('skipping '..bsfile) --python bridgesupport files are non-standard and deprecated
|
||||
else
|
||||
--print(); print(bsfile); print(('='):rep(80))
|
||||
if subprocess then
|
||||
os.execute(luajit..' '..arg[0]..' bridgesupport '..bsfile)
|
||||
else
|
||||
objc.debug.load_framework(bsfile)
|
||||
end
|
||||
end
|
||||
end
|
||||
status()
|
||||
end
|
||||
|
||||
objc.debug.load_framework = objc_load --put it back
|
||||
end
|
||||
|
||||
function test.selectors()
|
||||
assert(tostring(SEL'se_lec_tor') == 'se:lec:tor')
|
||||
assert(tostring(SEL'se_lec_tor_') == 'se:lec:tor:')
|
||||
assert(tostring(SEL'__se_lec_tor') == '__se:lec:tor')
|
||||
assert(tostring(SEL'__se:lec:tor:') == '__se:lec:tor:')
|
||||
end
|
||||
|
||||
--class, superclass, metaclass, class protocols
|
||||
function test.class()
|
||||
--arg. checking
|
||||
errpcall('already', class, 'NSObject', 'NSString')
|
||||
errpcall('superclass', class, genname(), 'MyUnknownClass')
|
||||
errpcall('protocol', class, genname(), 'NSObject <MyUnknownProtocol>')
|
||||
|
||||
--class overloaded constructors
|
||||
local cls = class('MyClassX', false) --root class
|
||||
assert(classname(cls) == 'MyClassX')
|
||||
assert(not superclass(cls))
|
||||
|
||||
--derived class
|
||||
local cls = class(genname(), 'NSArray')
|
||||
assert(isa(cls, 'NSArray'))
|
||||
|
||||
--derived + conforming
|
||||
local cls = class(genname(), 'NSArray <NSStreamDelegate, NSLocking>')
|
||||
assert(isa(cls, 'NSArray'))
|
||||
|
||||
assert(conforms(cls, 'NSStreamDelegate'))
|
||||
assert(conforms(cls, 'NSLocking'))
|
||||
|
||||
local t = {0}
|
||||
for proto in protocols(cls) do
|
||||
t[proto:name()] = true
|
||||
t[1] = t[1] + 1
|
||||
end
|
||||
assert(t[1] == 2)
|
||||
assert(t.NSStreamDelegate)
|
||||
assert(t.NSLocking)
|
||||
|
||||
--class hierarchy queries
|
||||
assert(superclass(cls) == NSArray)
|
||||
assert(metaclass(cls))
|
||||
assert(superclass(metaclass(cls)) == metaclass'NSArray')
|
||||
assert(metaclass(superclass(cls)) == metaclass'NSArray')
|
||||
assert(metaclass(metaclass(cls)) == nil)
|
||||
assert(isa(cls, 'NSObject'))
|
||||
assert(ismetaclass(metaclass(cls)))
|
||||
assert(isclass(cls))
|
||||
assert(not ismetaclass(cls))
|
||||
assert(not isobj(cls))
|
||||
assert(isclass(metaclass(cls)))
|
||||
|
||||
local obj = cls:new()
|
||||
assert(isobj(obj))
|
||||
assert(not isclass(obj))
|
||||
end
|
||||
|
||||
function test.refcount()
|
||||
local cls = class(genname(), 'NSObject')
|
||||
local inst, inst2, inst3
|
||||
|
||||
inst = cls:new()
|
||||
assert(inst:retainCount() == 1)
|
||||
|
||||
inst2 = inst:retain() --same class, new cdata, new reference
|
||||
assert(inst:retainCount() == 2)
|
||||
|
||||
inst3 = inst:retain()
|
||||
assert(inst:retainCount() == 3)
|
||||
|
||||
inst3 = nil --release() on gc
|
||||
collectgarbage()
|
||||
assert(inst:retainCount() == 2)
|
||||
|
||||
inst3 = inst:retain()
|
||||
assert(inst:retainCount() == 3)
|
||||
|
||||
inst:release() --manual release()
|
||||
assert(inst:retainCount() == 2)
|
||||
|
||||
inst = nil --object already disowned by inst, refcount should not decrease
|
||||
collectgarbage()
|
||||
assert(inst2:retainCount() == 2)
|
||||
|
||||
inst, inst2, inst3 = nil
|
||||
collectgarbage()
|
||||
end
|
||||
|
||||
function test.luavars()
|
||||
local cls = class(genname(), 'NSObject')
|
||||
|
||||
--class vars
|
||||
cls.myclassvar = 'doh1'
|
||||
assert(cls.myclassvar == 'doh1') --intialized
|
||||
cls.myclassvar = 'doh'
|
||||
assert(cls.myclassvar == 'doh') --updated
|
||||
|
||||
--inst vars
|
||||
local inst = cls:new()
|
||||
|
||||
inst.myinstvar = 'DOH1'
|
||||
assert(inst.myinstvar == 'DOH1') --initialized
|
||||
inst.myinstvar = 'DOH'
|
||||
assert(inst.myinstvar == 'DOH') --updated
|
||||
|
||||
--class vars from instances
|
||||
assert(inst.myclassvar == 'doh') --class vars are readable from instances
|
||||
inst.myclassvar = 'doh2'
|
||||
assert(cls.myclassvar == 'doh2') --and they can be updated from instances
|
||||
assert(inst.myclassvar == 'doh2')
|
||||
|
||||
--soft ref counting
|
||||
local inst2 = inst:retain()
|
||||
assert(inst.myinstvar == 'DOH') --2 refs
|
||||
inst = nil
|
||||
collectgarbage()
|
||||
assert(inst2.myinstvar == 'DOH') --1 ref
|
||||
inst2:release() --0 refs; instance gone, vars gone (no way to test, memory was freed)
|
||||
assert(cls.myclassvar == 'doh2') --class vars still there
|
||||
|
||||
local i = 0
|
||||
function NSObject:myMethod() i = i + 1 end
|
||||
local str = 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)
|
||||
assert(i == 2)
|
||||
|
||||
function NSObject:myMethod() i = i - 1 end --override
|
||||
str:myMethod() --instance method (str passed as self)
|
||||
objc.NSString:myMethod() --class method (NSString passed as self)
|
||||
assert(i == 0)
|
||||
end
|
||||
|
||||
function test.override()
|
||||
objc.debug.logtopics.addmethod = true
|
||||
|
||||
local cls = class(genname(), 'NSObject')
|
||||
local metacls = metaclass(cls)
|
||||
local obj = cls:new()
|
||||
local instdesc = 'hello-instance'
|
||||
local classdesc = 'hello-class'
|
||||
|
||||
function metacls:description() --override the class method
|
||||
return classdesc --note: we can return the string directly.
|
||||
end
|
||||
|
||||
function cls:description() --override the instance method
|
||||
return instdesc --note: we can return the string directly.
|
||||
end
|
||||
|
||||
assert(objc.tolua(cls:description()) == classdesc) --class method was overriden
|
||||
assert(objc.tolua(obj:description()) == instdesc) --instance method was overriden and it's different
|
||||
|
||||
--subclass and test again
|
||||
|
||||
local cls2 = class(genname(), cls)
|
||||
local metacls2 = metaclass(cls2)
|
||||
local obj2 = cls2:new()
|
||||
|
||||
function metacls2:description() --override the class method
|
||||
return objc.callsuper(self, 'description'):UTF8String() .. '2'
|
||||
end
|
||||
|
||||
function cls2:description(callsuper) --override the instance method
|
||||
return objc.callsuper(self, 'description'):UTF8String() .. '2'
|
||||
end
|
||||
|
||||
assert(objc.tolua(cls2:description()) == classdesc..'2') --class method was overriden
|
||||
assert(objc.tolua(obj2:description()) == instdesc..'2') --instance method was overriden and it's different
|
||||
end
|
||||
|
||||
function test.ivars()
|
||||
local obj = NSDocInfo:new()
|
||||
|
||||
if ffi.abi'64bit' then
|
||||
assert(ffi.typeof(obj.time) == ffi.typeof'long long')
|
||||
else
|
||||
assert(type(obj.time) == 'number')
|
||||
end
|
||||
assert(type(obj.mode) == 'number') --unsigned short
|
||||
assert(ffi.typeof(obj.flags) == ffi.typeof(obj.flags)) --anonymous struct (assert that it was cached)
|
||||
|
||||
obj.time = 123
|
||||
assert(obj.time == 123)
|
||||
|
||||
assert(obj.flags.isDir == 0)
|
||||
obj.flags.isDir = 3 --1 bit
|
||||
assert(obj.flags.isDir == 1) --1 bit was set (so this is not a luavar or anything)
|
||||
end
|
||||
|
||||
test.properties = objc.with_properties(function()
|
||||
--TODO: find another class with r/w properties. NSProgress is not public on 10.7.
|
||||
local pr = NSProgress:progressWithTotalUnitCount(123)
|
||||
assert(pr.totalUnitCount == 123) --as initialized
|
||||
pr.totalUnitCount = 321 --read/write property
|
||||
assert(pr.totalUnitCount == 321)
|
||||
assert(not pcall(function() pr.indeterminate = true end)) --attempt to set read-only property
|
||||
assert(pr.indeterminate == false)
|
||||
end)
|
||||
|
||||
local timebase, last_time
|
||||
function timediff()
|
||||
objc.load'System'
|
||||
local time
|
||||
time = mach_absolute_time()
|
||||
if not timebase then
|
||||
timebase = ffi.new'mach_timebase_info_data_t'
|
||||
mach_timebase_info(timebase)
|
||||
end
|
||||
local d = tonumber(time - (last_time or 0)) * timebase.numer / timebase.denom / 10^9
|
||||
last_time = time
|
||||
return d
|
||||
end
|
||||
|
||||
function test.blocks()
|
||||
--objc.debug.logtopics.block = true
|
||||
local times = 20000
|
||||
|
||||
timediff()
|
||||
|
||||
--take 1: creating blocks in inner loops with automatic memory management of blocks.
|
||||
local s = NSString:alloc():initWithUTF8String'line1\nline2\nline3'
|
||||
for i=1,times do
|
||||
local t = {}
|
||||
--note: the signature of the block arg for enumerateLinesUsingBlock was taken from bridgesupport.
|
||||
s:enumerateLinesUsingBlock(function(line, pstop)
|
||||
t[#t+1] = line:UTF8String()
|
||||
if #t == 2 then --stop at line 2
|
||||
pstop[0] = 1
|
||||
end
|
||||
end)
|
||||
assert(#t == 2)
|
||||
assert(t[1] == 'line1')
|
||||
assert(t[2] == 'line2')
|
||||
--note: callbacks are slow, expensive to create, and limited in number. we have to release them often!
|
||||
if i % 200 == 0 then
|
||||
collectgarbage()
|
||||
end
|
||||
end
|
||||
|
||||
printf('take 1: block in loop (%d times): %4.2fs', times, timediff())
|
||||
|
||||
--take 2: creating a single block in the outer loop (we must give its type).
|
||||
local t
|
||||
local blk = toarg(NSString, 'enumerateLinesUsingBlock', 1, function(line, pstop)
|
||||
t[#t+1] = line:UTF8String()
|
||||
if #t == 2 then --stop at line 2
|
||||
pstop[0] = 1
|
||||
end
|
||||
end)
|
||||
local s = NSString:alloc():initWithUTF8String'line1\nline2\nline3'
|
||||
for i=1,times do
|
||||
t = {}
|
||||
s:enumerateLinesUsingBlock(blk)
|
||||
assert(#t == 2)
|
||||
assert(t[1] == 'line1')
|
||||
assert(t[2] == 'line2')
|
||||
end
|
||||
|
||||
printf('take 2: single block (%d times): %4.2fs', times, timediff())
|
||||
|
||||
end
|
||||
|
||||
function test.tolua()
|
||||
local n = toobj(123.5)
|
||||
assert(isa(n, 'NSNumber'))
|
||||
assert(tolua(n) == 123.5)
|
||||
|
||||
local s = toobj'hello'
|
||||
assert(isa(s, 'NSString'))
|
||||
assert(tolua(s) == 'hello')
|
||||
|
||||
local a = {1,2,6,7}
|
||||
local t = toobj(a)
|
||||
assert(t:count() == #a)
|
||||
for i=1,#a do
|
||||
assert(t:objectAtIndex(i-1):doubleValue() == a[i])
|
||||
end
|
||||
a = tolua(t)
|
||||
assert(#a == 4)
|
||||
assert(a[3] == 6)
|
||||
|
||||
local d = {a = 1, b = 'baz', d = {1,2,3}, [{x=1}] = {y=2}}
|
||||
local t = toobj(d)
|
||||
assert(t:count() == 4)
|
||||
assert(tolua(t:valueForKey(toobj'a')) == d.a)
|
||||
assert(tolua(t:valueForKey(toobj'b')) == d.b)
|
||||
assert(tolua(t:valueForKey(toobj'd'))[2] == 2)
|
||||
end
|
||||
|
||||
function test.args()
|
||||
local s = NSString:alloc():initWithUTF8String'\xE2\x82\xAC' --euro symbol
|
||||
--return string
|
||||
assert(s:UTF8String() == '\xE2\x82\xAC')
|
||||
--return boolean (doesn't work for methods)
|
||||
assert(s:isAbsolutePath() == false)
|
||||
--return null
|
||||
assert(type(s:cStringUsingEncoding(NSASCIIStringEncoding)) == 'nil')
|
||||
--selector arg
|
||||
assert(s:respondsToSelector'methodForSelector:' == true)
|
||||
--class arg
|
||||
assert(NSArray:isSubclassOfClass'NSObject' == true)
|
||||
assert(NSArray:isSubclassOfClass'XXX' == false)
|
||||
--string arg
|
||||
assert(NSString:alloc():initWithString('hey'):UTF8String() == 'hey')
|
||||
--table arg for array
|
||||
local a = NSArray:alloc():initWithArray{6,25,5}
|
||||
assert(a:objectAtIndex(1):doubleValue() == 25)
|
||||
--table arg for dictionary
|
||||
local d = NSDictionary:alloc():initWithDictionary{a=5,b=7}
|
||||
assert(d:valueForKey('b'):doubleValue() == 7)
|
||||
end
|
||||
|
||||
function demo.window()
|
||||
objc.load'AppKit'
|
||||
|
||||
local NSApp = class('NSApp', 'NSApplication <NSApplicationDelegate>')
|
||||
|
||||
--we need to add methods to the class before creating any objects!
|
||||
--note: NSApplicationDelegate is an informal protocol brought from bridgesupport.
|
||||
|
||||
function NSApp:applicationShouldTerminateAfterLastWindowClosed()
|
||||
print'last window closed...'
|
||||
collectgarbage()
|
||||
return true
|
||||
end
|
||||
|
||||
function NSApp:applicationShouldTerminate()
|
||||
print'terminating...'
|
||||
return true
|
||||
end
|
||||
|
||||
local app = NSApp:sharedApplication()
|
||||
app:setDelegate(app)
|
||||
app:setActivationPolicy(NSApplicationActivationPolicyRegular)
|
||||
|
||||
local NSWin = class('NSWin', 'NSWindow <NSWindowDelegate>')
|
||||
|
||||
--we need to add methods to the class before creating any objects!
|
||||
--note: NSWindowDelegate is a formal protocol brought from the runtime.
|
||||
|
||||
function NSWin:windowWillClose()
|
||||
print'window will close...'
|
||||
end
|
||||
|
||||
local style = bit.bor(
|
||||
NSTitledWindowMask,
|
||||
NSClosableWindowMask,
|
||||
NSMiniaturizableWindowMask,
|
||||
NSResizableWindowMask)
|
||||
|
||||
local win = NSWin:alloc():initWithContentRect_styleMask_backing_defer(
|
||||
NSMakeRect(300, 300, 500, 300), style, NSBackingStoreBuffered, false)
|
||||
win:setDelegate(win)
|
||||
win:setTitle"▀▄▀▄▀▄ [ Lua Rulez ] ▄▀▄▀▄▀"
|
||||
|
||||
app:activateIgnoringOtherApps(true)
|
||||
win:makeKeyAndOrderFront(nil)
|
||||
|
||||
app:run()
|
||||
end
|
||||
|
||||
function demo.speech()
|
||||
objc.load'AppKit'
|
||||
local speech = NSSpeechSynthesizer:new()
|
||||
voiceid = NSSpeechSynthesizer:availableVoices():objectAtIndex(11)
|
||||
speech:setVoice(voiceid)
|
||||
speech:startSpeakingString'Calm, fitter, healthier, and more productive; A pig. In a cage. On antibiotics.'
|
||||
while speech:isSpeaking() do
|
||||
os.execute'sleep 1'
|
||||
end
|
||||
end
|
||||
|
||||
function demo.http() --what a dense word soup just to make a http request
|
||||
objc.load'AppKit'
|
||||
local app = NSApplication:sharedApplication()
|
||||
|
||||
local post = NSString:stringWithFormat('firstName=%@&lastName=%@&eMail=%@&message=%@',
|
||||
toobj'Dude', toobj'Edud', toobj'x@y.com', toobj'message')
|
||||
local postData = post:dataUsingEncoding(NSUTF8StringEncoding)
|
||||
local postLength = NSString:stringWithFormat('%ld', postData:length())
|
||||
NSLog('Post data: %@', post)
|
||||
local request = NSMutableURLRequest:new()
|
||||
request:setURL(NSURL:URLWithString'http://posttestserver.com/post.php')
|
||||
request:setHTTPMethod'POST'
|
||||
request:setValue_forHTTPHeaderField(postLength, 'Content-Length')
|
||||
request:setValue_forHTTPHeaderField('application/x-www-form-urlencoded', 'Content-Type')
|
||||
request:setHTTPBody(postData)
|
||||
|
||||
NSLog('%@', request)
|
||||
|
||||
local CD = class('ConnDelegate', 'NSObject <NSURLConnectionDelegate, NSURLConnectionDataDelegate>')
|
||||
|
||||
function CD:connection_didReceiveData(conn, data)
|
||||
self.webData:appendData(data)
|
||||
NSLog'Connection received data'
|
||||
end
|
||||
|
||||
function CD:connection_didReceiveResponse(conn, response)
|
||||
NSLog'Connection received response'
|
||||
NSLog('%@', response:description())
|
||||
end
|
||||
|
||||
function CD:connection_didFailWithError(conn, err)
|
||||
NSLog('Connection error: %@', err:localizedDescription())
|
||||
app:terminate(nil)
|
||||
end
|
||||
|
||||
function CD:connectionDidFinishLoading(conn)
|
||||
NSLog'Connection finished loading'
|
||||
local html = NSString:alloc():initWithBytes_length_encoding(self.webData:mutableBytes(),
|
||||
self.webData:length(), NSUTF8StringEncoding)
|
||||
NSLog('OUTPUT:\n%@', html)
|
||||
app:terminate(nil)
|
||||
end
|
||||
|
||||
local cd = ConnDelegate:new()
|
||||
cd.webData = NSMutableData:new()
|
||||
|
||||
local conn = NSURLConnection:alloc():initWithRequest_delegate_startImmediately(request, cd, false)
|
||||
conn:start()
|
||||
|
||||
app:run()
|
||||
end
|
||||
|
||||
function demo.http_gcd()
|
||||
objc.load'AppKit'
|
||||
local app = NSApplication:sharedApplication()
|
||||
|
||||
local url = NSURL:URLWithString'http://posttestserver.com/post.php'
|
||||
local req = NSURLRequest:requestWithURL(url)
|
||||
|
||||
local queue = dispatch.main_queue --dispatch.get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
|
||||
|
||||
objc.debug.logtopics.block = true
|
||||
|
||||
local n = 0
|
||||
local blk = block(function()
|
||||
n = n + 1
|
||||
print('called', n)
|
||||
|
||||
local response = ffi.new'id[1]'
|
||||
local err = ffi.new'id[1]'
|
||||
local data = NSURLConnection:sendSynchronousRequest_returningResponse_error(req, response, err)
|
||||
|
||||
print(tolua(NSString:alloc():initWithBytes_length_encoding(
|
||||
data:mutableBytes(), data:length(), NSUTF8StringEncoding)))
|
||||
|
||||
if n == 2 then
|
||||
print'---- Done. Hit Ctrl+C twice ----'
|
||||
end
|
||||
end)
|
||||
dispatch.async(queue, blk) --increase refcount
|
||||
dispatch.async(queue, blk) --increase refcount
|
||||
print'queued'
|
||||
blk = nil; collectgarbage() --decrease refcount (stil queued)
|
||||
print'released'
|
||||
app:run()
|
||||
end
|
||||
|
||||
-- inspection ------------------------------------------------------------------------------------------------------------
|
||||
|
||||
local function load_many_frameworks()
|
||||
objc.debug.loaddeps = true
|
||||
for s in string.gmatch([[
|
||||
AGL
|
||||
AVFoundation
|
||||
AVKit
|
||||
Accelerate
|
||||
Accounts
|
||||
AddressBook
|
||||
AppKit
|
||||
AppKitScripting
|
||||
AppleScriptKit
|
||||
AppleScriptObjC
|
||||
AppleShareClientCore
|
||||
ApplicationServices
|
||||
AudioToolbox
|
||||
AudioUnit
|
||||
AudioVideoBridging
|
||||
Automator
|
||||
CFNetwork
|
||||
CalendarStore
|
||||
Carbon
|
||||
Cocoa
|
||||
Collaboration
|
||||
CoreAudio
|
||||
CoreAudioKit
|
||||
CoreData
|
||||
CoreFoundation
|
||||
CoreGraphics
|
||||
CoreLocation
|
||||
CoreMIDI
|
||||
CoreMedia
|
||||
CoreMediaIO
|
||||
CoreServices
|
||||
CoreText
|
||||
CoreVideo
|
||||
CoreWLAN
|
||||
DVComponentGlue
|
||||
DVDPlayback
|
||||
DirectoryService
|
||||
DiscRecording
|
||||
DiscRecordingUI
|
||||
DiskArbitration
|
||||
DrawSprocket
|
||||
EventKit
|
||||
ExceptionHandling
|
||||
FWAUserLib
|
||||
ForceFeedback
|
||||
Foundation
|
||||
GLKit
|
||||
GLUT
|
||||
GSS
|
||||
GameController
|
||||
GameKit
|
||||
ICADevices
|
||||
IMServicePlugIn
|
||||
IOBluetooth
|
||||
IOBluetoothUI
|
||||
IOKit
|
||||
IOSurface
|
||||
ImageCaptureCore
|
||||
ImageIO
|
||||
InputMethodKit
|
||||
InstallerPlugins
|
||||
InstantMessage
|
||||
JavaFrameEmbedding
|
||||
JavaScriptCore
|
||||
Kerberos
|
||||
LDAP
|
||||
LatentSemanticMapping
|
||||
MapKit
|
||||
MediaAccessibility
|
||||
MediaLibrary
|
||||
MediaToolbox
|
||||
NetFS
|
||||
OSAKit
|
||||
OpenAL
|
||||
OpenCL
|
||||
OpenDirectory
|
||||
OpenGL
|
||||
PCSC
|
||||
PreferencePanes
|
||||
PubSub
|
||||
QTKit
|
||||
Quartz
|
||||
QuartzCore
|
||||
QuickLook
|
||||
SceneKit
|
||||
ScreenSaver
|
||||
Scripting
|
||||
ScriptingBridge
|
||||
Security
|
||||
SecurityFoundation
|
||||
SecurityInterface
|
||||
ServiceManagement
|
||||
Social
|
||||
SpriteKit
|
||||
StoreKit
|
||||
SyncServices
|
||||
System
|
||||
SystemConfiguration
|
||||
TWAIN
|
||||
Tcl
|
||||
Tk
|
||||
VideoDecodeAcceleration
|
||||
VideoToolbox
|
||||
WebKit
|
||||
]], '([^\n\r]+)') do
|
||||
pcall(objc.load, s)
|
||||
end
|
||||
objc.debug.loaddeps = false
|
||||
end
|
||||
|
||||
function eyetest.inspect_classes()
|
||||
load_many_frameworks()
|
||||
inspect.classes()
|
||||
end
|
||||
|
||||
function eyetest.inspect_protocols()
|
||||
load_many_frameworks()
|
||||
inspect.protocols()
|
||||
end
|
||||
|
||||
function eyetest.inspect_class_properties(cls)
|
||||
load_many_frameworks()
|
||||
inspect.class_properties(cls)
|
||||
end
|
||||
|
||||
function eyetest.inspect_protocol_properties(proto)
|
||||
load_many_frameworks()
|
||||
inspect.protocol_properties(proto)
|
||||
end
|
||||
|
||||
local function req(s)
|
||||
return s and s ~= '' and s or nil
|
||||
end
|
||||
|
||||
function eyetest.inspect_class_methods(cls, inst)
|
||||
load_many_frameworks()
|
||||
inspect.class_methods(req(cls), inst == 'inst')
|
||||
end
|
||||
|
||||
function eyetest.inspect_protocol_methods(proto, inst, required)
|
||||
load_many_frameworks()
|
||||
inspect.protocol_methods(req(proto), inst == 'inst', required == 'required')
|
||||
end
|
||||
|
||||
function eyetest.inspect_class_ivars(cls)
|
||||
load_many_frameworks()
|
||||
inspect.class_ivars(req(cls))
|
||||
end
|
||||
|
||||
function eyetest.inspect_class(cls)
|
||||
load_many_frameworks()
|
||||
inspect.class(cls)
|
||||
end
|
||||
|
||||
function eyetest.inspect_protocol(proto)
|
||||
load_many_frameworks()
|
||||
inspect.protocol(proto)
|
||||
end
|
||||
|
||||
function eyetest.inspect_find(patt)
|
||||
load_many_frameworks()
|
||||
inspect.find(patt)
|
||||
end
|
||||
|
||||
--------------
|
||||
|
||||
local function test_all(tests, ...)
|
||||
for k,v in glue.sortedpairs(tests) do
|
||||
if k ~= 'all' then
|
||||
print(k)
|
||||
hr()
|
||||
tests[k](...)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function test.all(...)
|
||||
test_all(test)
|
||||
end
|
||||
|
||||
--cmdline interface
|
||||
|
||||
local function run(...)
|
||||
local testname = ...
|
||||
if not testname then
|
||||
print('Usage: '..luajit..' '..arg[0]..' <test>')
|
||||
for k,t in glue.sortedpairs(tests) do
|
||||
printf('%s:', k)
|
||||
for k in glue.sortedpairs(t) do
|
||||
print('', k)
|
||||
end
|
||||
end
|
||||
else
|
||||
local test = test[testname] or eyetest[testname] or demo[testname]
|
||||
if not test then
|
||||
printf('Invalid test "%s"', tostring(testname))
|
||||
os.exit(1)
|
||||
end
|
||||
test(select(2, ...))
|
||||
print'ok'
|
||||
end
|
||||
end
|
||||
|
||||
run(...)
|
Reference in New Issue
Block a user