diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ff7a3bb..df9d715 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -32,7 +32,9 @@ jobs: uses: actions/cache@v3 with: path: .scons-cache/ - key: ${{ matrix.platform }}-simulator=${{ matrix.simulator }}-${{ matrix.arch }}-${{ matrix.target }} + key: ${{ matrix.platform }}-simulator=${{ matrix.simulator }}-${{ matrix.arch }}-${{ matrix.target }}-${{ hashfiles('.gitmodules', 'src/**') }} + restore-keys: | + ${{ matrix.platform }}-simulator=${{ matrix.simulator }}-${{ matrix.arch }}-${{ matrix.target }}- - name: Build artifact run: | scons platform=${{ matrix.platform }} ios_simulator=${{ matrix.simulator }} arch=${{ matrix.arch }} target=${{ matrix.target }} diff --git a/README.md b/README.md index 0b9c462..3b6a85f 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,8 @@ Use at your own risk. - `to_dictionary` method for converting Objective-C objects that support enumeration and the `objectForKey:` message, like `NSDictionary`, to Godot `Dictionary` - Other useful methods, like `is_kind_of_class`, `responds_to_selector` and `conforms_to_protocol` - Supports creating blocks from a `Callable` and the corresponding Objective-C method signature +- Supports passing Godot objects to Objective-C if they implement `func methodSignatureForSelector(String) -> String`. + Check out [GDObject.gd](test/unit_tests/GDObject.gd) for sample code. ## Caveats diff --git a/src/GDCallableBlock.mm b/src/GDCallableBlock.mm index fbf0145..cff59f0 100644 --- a/src/GDCallableBlock.mm +++ b/src/GDCallableBlock.mm @@ -21,7 +21,7 @@ */ #include "GDCallableBlock.hpp" -#include "NSMethodSignature+ArgumentsFromData.hpp" +#include "NSMethodSignature+Godot.hpp" #include diff --git a/src/GDObject.hpp b/src/GDObject.hpp new file mode 100644 index 0000000..ce43d32 --- /dev/null +++ b/src/GDObject.hpp @@ -0,0 +1,49 @@ +/** + * Copyright (C) 2023 Gil Barbosa Reis. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the “Software”), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is furnished to do + * so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef __GDOOBJECT_HPP__ +#define __GDOOBJECT_HPP__ + +#include + +#include + +using namespace godot; + +/** + * Objective-C object that wraps Godot objects. + * + * Messaging this object will forward the calls to the Godot object, + * removing the last ":" and replacing the other ":" characters for "_". + */ +@interface GDObject : NSObject + +@property(readonly) Variant variant; + ++ (instancetype)objectWithObject:(Object *)object retainingReference:(BOOL)shouldRetainReference; +- (instancetype)initWithObject:(Object *)object retainingReference:(BOOL)shouldRetainReference; + ++ (BOOL)isCompatibleObject:(Object *)object; ++ (String)godotNameForSelector:(SEL)selector; + +@end + +#endif // __GDOOBJECT_HPP__ diff --git a/src/GDObject.mm b/src/GDObject.mm new file mode 100644 index 0000000..745af51 --- /dev/null +++ b/src/GDObject.mm @@ -0,0 +1,150 @@ +/** + * Copyright (C) 2023 Gil Barbosa Reis. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the “Software”), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is furnished to do + * so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "GDObject.hpp" + +#import "NSInvocation+Godot.hpp" +#import "objc_conversions.hpp" +#import "objc_marshalling.hpp" + +#include +#include +#include + +using namespace objcgdextension; + +@implementation GDObject { + Object *_obj; + BOOL _shouldRetainReference; +} + ++ (instancetype)objectWithObject:(Object *)object retainingReference:(BOOL)shouldRetainReference { + return [[[self alloc] initWithObject:object retainingReference:shouldRetainReference] autorelease]; +} + +- (instancetype)initWithObject:(Object *)object retainingReference:(BOOL)shouldRetainReference { + if (self = [super init]) { + _obj = object; + _shouldRetainReference = shouldRetainReference; + if (shouldRetainReference) { + if (RefCounted *ref = Object::cast_to(object)) { + ref->reference(); + } + } + } + return self; +} + +- (void)dealloc { + if (_shouldRetainReference) { + if (RefCounted *ref = Object::cast_to(_obj)) { + ref->unreference(); + } + } + [super dealloc]; +} + +- (instancetype)copyWithZone:(NSZone *)zone { + return [self retain]; +} + +- (BOOL)respondsToSelector:(SEL)aSelector { + return [self methodSignatureForSelector:aSelector] != nil; +} + +- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector { + @try { + if (UtilityFunctions::is_instance_valid(_obj)) { + String ctypes = _obj->call("methodSignatureForSelector", to_string(aSelector)); + if (!ctypes.is_empty()) { + NSMethodSignature *signature = [NSMethodSignature signatureWithObjCTypes:ctypes.ascii().get_data()]; + if (signature.numberOfArguments >= 2 + && [signature getArgumentTypeAtIndex:0][0] == '@' + && [signature getArgumentTypeAtIndex:1][0] == ':' + ) { + return signature; + } + else { + ERR_PRINT(String("Invalid method signature '%s': expected at least target and selector arguments (e.g.: 'v@:')") % ctypes); + } + } + } + } + @catch (NSException *ex) { + ERR_PRINT(ex.description.UTF8String); + } + return [super methodSignatureForSelector:aSelector]; +} + +- (void)forwardInvocation:(NSInvocation *)anInvocation { + if (UtilityFunctions::is_instance_valid(_obj)) { + String methodName = [GDObject godotNameForSelector:anInvocation.selector]; + if (_obj->has_method(methodName)) { + Array args = anInvocation.argumentArray; + Variant result = _obj->callv(methodName, args); + set_result_variant(anInvocation, result, args); + return; + } + else if (anInvocation.methodSignature.numberOfArguments == 2) { + Variant property = _obj->get(methodName); + Array string_holder; + set_result_variant(anInvocation, property, string_holder); + return; + } + } + [super forwardInvocation:anInvocation]; +} + +- (id)valueForUndefinedKey:(NSString *)key { + if (UtilityFunctions::is_instance_valid(_obj)) { + Variant value = _obj->get(key.UTF8String); + return to_nsobject(value); + } + else { + return [super valueForUndefinedKey:key]; + } +} + +- (Variant)variant { + return Variant(_obj); +} + ++ (BOOL)isCompatibleObject:(Object *)object { + if (object) { + return object->has_method("methodSignatureForSelector"); + } + else { + return NO; + } +} + ++ (String)godotNameForSelector:(SEL)selector { + if (selector) { + return String(sel_getName(selector)) + .trim_suffix(":") + .replace(":", "_"); + } + else { + return ""; + } +} + +@end diff --git a/src/NSInvocation+Godot.hpp b/src/NSInvocation+Godot.hpp new file mode 100644 index 0000000..4e48f5b --- /dev/null +++ b/src/NSInvocation+Godot.hpp @@ -0,0 +1,36 @@ +/** + * Copyright (C) 2023 Gil Barbosa Reis. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the “Software”), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is furnished to do + * so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef __NSINVOCATION_GODOT_HPP__ +#define __NSINVOCATION_GODOT_HPP__ + +#include +#include + +using namespace godot; + +@interface NSInvocation (Godot) + +@property(readonly) Array argumentArray; + +@end + +#endif // __NSINVOCATION_GODOT_HPP__ diff --git a/src/NSInvocation+Godot.mm b/src/NSInvocation+Godot.mm new file mode 100644 index 0000000..790e5f8 --- /dev/null +++ b/src/NSInvocation+Godot.mm @@ -0,0 +1,38 @@ +/** + * Copyright (C) 2023 Gil Barbosa Reis. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the “Software”), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is furnished to do + * so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "NSInvocation+Godot.hpp" + +#include "objc_marshalling.hpp" + +using namespace objcgdextension; + +@implementation NSInvocation (Godot) + +- (Array)argumentArray { + Array args; + for (int i = 2; i < self.methodSignature.numberOfArguments; i++) { + args.append(get_argument_variant(self, i)); + } + return args; +} + +@end diff --git a/src/NSMethodSignature+ArgumentsFromData.hpp b/src/NSMethodSignature+Godot.hpp similarity index 83% rename from src/NSMethodSignature+ArgumentsFromData.hpp rename to src/NSMethodSignature+Godot.hpp index 79690ef..55e64af 100644 --- a/src/NSMethodSignature+ArgumentsFromData.hpp +++ b/src/NSMethodSignature+Godot.hpp @@ -19,19 +19,21 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ -#ifndef __NSMETHOD_SIGNATURE_ARGUMENTS_FROM_DATA_HPP__ -#define __NSMETHOD_SIGNATURE_ARGUMENTS_FROM_DATA_HPP__ +#ifndef __NSMETHOD_SIGNATURE_GODOT_HPP__ +#define __NSMETHOD_SIGNATURE_GODOT_HPP__ #include #include using namespace godot; -@interface NSMethodSignature (ArgumentsFromData) +@interface NSMethodSignature (Godot) + +@property(readonly) NSUInteger totalArgumentSize; +@property(readonly) String completeSignature; -- (NSUInteger)totalArgumentSize; - (Array)arrayFromArgumentData:(const void *)data; @end -#endif // __NSMETHOD_SIGNATURE_ARGUMENTS_FROM_DATA_HPP__ +#endif // __NSMETHOD_SIGNATURE_GODOT_HPP__ diff --git a/src/NSMethodSignature+ArgumentsFromData.mm b/src/NSMethodSignature+Godot.mm similarity index 87% rename from src/NSMethodSignature+ArgumentsFromData.mm rename to src/NSMethodSignature+Godot.mm index ea913f0..6048c9d 100644 --- a/src/NSMethodSignature+ArgumentsFromData.mm +++ b/src/NSMethodSignature+Godot.mm @@ -19,13 +19,13 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ -#import "NSMethodSignature+ArgumentsFromData.hpp" +#import "NSMethodSignature+Godot.hpp" #include "objc_marshalling.hpp" using namespace objcgdextension; -@implementation NSMethodSignature (ArgumentsFromData) +@implementation NSMethodSignature (Godot) - (NSUInteger)totalArgumentSize { NSUInteger totalSize = 0; @@ -38,6 +38,14 @@ - (NSUInteger)totalArgumentSize { return totalSize; } +- (String)completeSignature { + String signature(self.methodReturnType); + for (int i = 0; i < self.numberOfArguments; i++) { + signature += [self getArgumentTypeAtIndex:i]; + } + return signature; +} + - (Array)arrayFromArgumentData:(const void *)data { Array args; const uint8_t *ptr = (const uint8_t *) data; diff --git a/src/ObjectiveCAPI.hpp b/src/ObjectiveCAPI.hpp index 371630a..a74dbce 100644 --- a/src/ObjectiveCAPI.hpp +++ b/src/ObjectiveCAPI.hpp @@ -39,6 +39,7 @@ class ObjectiveCAPI : public RefCounted { ObjectiveCClass *find_class(const String& name) const; ObjectiveCObject *create_block(const String& objCTypes, const Callable& implementation) const; + ObjectiveCObject *wrap_object(Object *godot_object, bool retaining_reference = false) const; static ObjectiveCAPI *get_singleton(); diff --git a/src/ObjectiveCAPI.mm b/src/ObjectiveCAPI.mm index 1acbcf0..9463385 100644 --- a/src/ObjectiveCAPI.mm +++ b/src/ObjectiveCAPI.mm @@ -21,8 +21,10 @@ */ #include "ObjectiveCAPI.hpp" +#include "GDObject.hpp" #include "GDCallableBlock.hpp" #include "ObjectiveCClass.hpp" +#include "ObjectiveCObject.hpp" #include "objc_conversions.hpp" #include @@ -58,6 +60,11 @@ } } +ObjectiveCObject *ObjectiveCAPI::wrap_object(Object *godot_object, bool retaining_reference) const { + ERR_FAIL_COND_V_MSG(![GDObject isCompatibleObject:godot_object], nullptr, "Object must implement 'func methodSignatureForSelector(String) -> String'"); + return memnew(ObjectiveCObject([[GDObject alloc] initWithObject:godot_object retainingReference:retaining_reference], false)); +} + ObjectiveCAPI *ObjectiveCAPI::get_singleton() { return instance; } @@ -65,6 +72,7 @@ void ObjectiveCAPI::_bind_methods() { ClassDB::bind_method(D_METHOD("find_class", "name"), &ObjectiveCAPI::find_class); ClassDB::bind_method(D_METHOD("create_block", "objc_types", "implementation"), &ObjectiveCAPI::create_block); + ClassDB::bind_method(D_METHOD("wrap_object", "object", "retaining_reference"), &ObjectiveCAPI::wrap_object, DEFVAL(false)); } bool ObjectiveCAPI::_get(const StringName& name, Variant& r_value) { diff --git a/src/objc_conversions.hpp b/src/objc_conversions.hpp index 3f4ea63..671c0a4 100644 --- a/src/objc_conversions.hpp +++ b/src/objc_conversions.hpp @@ -33,6 +33,7 @@ namespace objcgdextension { Class class_from_string(const String& string); Protocol *protocol_from_string(const String& string); SEL to_selector(const String& string); +String to_string(SEL selector); String format_selector_call(id obj, const String& selector); Variant to_variant(NSObject *obj); diff --git a/src/objc_conversions.mm b/src/objc_conversions.mm index 53c6c68..4096ff6 100644 --- a/src/objc_conversions.mm +++ b/src/objc_conversions.mm @@ -21,6 +21,7 @@ */ #include "objc_conversions.hpp" +#include "GDObject.hpp" #include "ObjectiveCClass.hpp" #include "ObjectiveCObject.hpp" @@ -46,6 +47,10 @@ SEL to_selector(const godot::String& string) { return sel_registerName(chars.get_data()); } +String to_string(SEL selector) { + return String(sel_getName(selector)); +} + String format_selector_call(id obj, const String& selector) { return String("%s[%s %s]") % Array::make( object_isClass(obj) ? "+" : "-", @@ -69,6 +74,10 @@ Variant to_variant(NSObject *obj) { NSNumber *number = (NSNumber *) obj; return to_variant(number); } + else if ([obj isKindOfClass:GDObject.class]) { + GDObject *gdobj = (GDObject *) obj; + return gdobj.variant; + } else { return memnew(ObjectiveCObject(obj)); } @@ -173,8 +182,11 @@ Variant to_variant(NSData *data) { else if (auto objc_obj = Object::cast_to(obj)) { return objc_obj->get_obj(); } + else if ([GDObject isCompatibleObject:obj]) { + return [GDObject objectWithObject:obj retainingReference:NO]; + } else { - ERR_FAIL_V_MSG(nil, String("Conversion from '%s' to NSObject* is not supported yet.") % obj->get_class()); + ERR_FAIL_V_MSG(nil, String("Conversion from '%s' to NSObject* is not supported.") % obj->get_class()); } } diff --git a/src/objc_invocation.mm b/src/objc_invocation.mm index 42a31ba..1a647e7 100644 --- a/src/objc_invocation.mm +++ b/src/objc_invocation.mm @@ -21,6 +21,7 @@ */ #include "objc_invocation.hpp" +#include "NSMethodSignature+Godot.hpp" #include "ObjectiveCClass.hpp" #include "ObjectiveCObject.hpp" #include "objc_conversions.hpp" @@ -35,13 +36,16 @@ NSInvocation *prepare_and_invoke(id target, const String& selector, const godot::Variant **argv, GDExtensionInt argc) { SEL sel = to_selector(selector); - ERR_FAIL_COND_V_EDMSG( - ![target respondsToSelector:sel], + + NSMethodSignature *signature = [target methodSignatureForSelector:sel]; + ERR_FAIL_COND_V_MSG( + signature.numberOfArguments < 2 + || [signature getArgumentTypeAtIndex:0][0] != '@' + || [signature getArgumentTypeAtIndex:1][0] != ':', nil, - String("Invalid call to %s: selector not supported") % format_selector_call(target, selector) + String("Invalid method signature '%s': expected at least 2 arguments, target and selector") % signature.completeSignature ); - NSMethodSignature *signature = [target methodSignatureForSelector:sel]; int expected_argc = signature.numberOfArguments - 2; ERR_FAIL_COND_V_MSG( expected_argc != argc, diff --git a/src/objc_marshalling.hpp b/src/objc_marshalling.hpp index 2910b75..ef664fe 100644 --- a/src/objc_marshalling.hpp +++ b/src/objc_marshalling.hpp @@ -31,7 +31,9 @@ namespace objcgdextension { Variant get_variant(const char *objc_type, const void *buffer); Variant get_result_variant(NSInvocation *invocation); +Variant get_argument_variant(NSInvocation *invocation, NSUInteger index); bool set_variant(const char *objc_type, void *buffer, const Variant& value, Array& string_holder); +bool set_result_variant(NSInvocation *invocation, const Variant& value, Array& string_holder); } diff --git a/src/objc_marshalling.mm b/src/objc_marshalling.mm index e2822d0..74b8949 100644 --- a/src/objc_marshalling.mm +++ b/src/objc_marshalling.mm @@ -79,6 +79,11 @@ inline BOOL is_method_encoding(char c) { return type; } +struct InvocationArgument { + NSInvocation *invocation; + NSUInteger index; +}; + template T deref_as(const void *buffer) { return *static_cast(buffer); @@ -91,6 +96,13 @@ T deref_as(NSInvocation *invocation) { return value; } +template +T deref_as(InvocationArgument& arg) { + T value; + [arg.invocation getArgument:&value atIndex:arg.index]; + return value; +} + template Variant get_variant(const char *objc_type, TDeref buffer) { objc_type = skip_method_encodings(objc_type); @@ -167,13 +179,25 @@ Variant get_result_variant(NSInvocation *invocation) { return get_variant(invocation.methodSignature.methodReturnType, invocation); } +Variant get_argument_variant(NSInvocation *invocation, NSUInteger index) { + InvocationArgument arg = { invocation, index }; + return get_variant([invocation.methodSignature getArgumentTypeAtIndex:index], arg); +} + template bool set_value(void *buffer, const T& value) { *static_cast(buffer) = value; return true; } -bool set_variant(const char *objc_type, void *buffer, const Variant& value, Array& string_holder) { +template +bool set_value(NSInvocation *invocation, const T& value) { + [invocation setReturnValue:(void *) &value]; + return true; +} + +template +bool set_variant(const char *objc_type, TDeref buffer, const Variant& value, Array& string_holder) { objc_type = skip_method_encodings(objc_type); switch (objc_type[0]) { case 'B': @@ -213,6 +237,9 @@ bool set_variant(const char *objc_type, void *buffer, const Variant& value, Arra } } + case 'v': + return true; + case '*': { PackedByteArray chars; if (value.get_type() == Variant::PACKED_BYTE_ARRAY) { @@ -250,4 +277,12 @@ bool set_variant(const char *objc_type, void *buffer, const Variant& value, Arra } } +bool set_variant(const char *objc_type, void *buffer, const Variant& value, Array& string_holder) { + return set_variant(objc_type, buffer, value, string_holder); +} + +bool set_result_variant(NSInvocation *invocation, const Variant& value, Array& string_holder) { + return set_variant(invocation.methodSignature.methodReturnType, invocation, value, string_holder); +} + } diff --git a/test/unit_tests/GDObject.gd b/test/unit_tests/GDObject.gd new file mode 100644 index 0000000..47bb0cf --- /dev/null +++ b/test/unit_tests/GDObject.gd @@ -0,0 +1,54 @@ +extends RefCounted + +static var ObjectiveC = Engine.get_singleton("ObjectiveC") +const NOTIFICATION_NAME = "my cool notification" +const NOTIFICATION_USER_INFO = { hello = "world" } + +var my_property = 5 +var notification_called = false +var self_objc = ObjectiveC.wrap_object(self) + + +func test_property() -> bool: + assert(self_objc.my_property == my_property) + assert(self_objc.responds_to_selector("my_property")) + assert(self_objc.invoke("my_property") == my_property) + return true + + +func test_method() -> bool: + assert(self_objc.responds_to_selector("intToBool:")) + assert(self_objc.invoke("intToBool:", 1) == true) + assert(self_objc.invoke("intToBool:", 0) == false) + return true + + +func test_notification() -> bool: + var NSNotificationCenter = ObjectiveC.NSNotificationCenter.defaultCenter + NSNotificationCenter.invoke("addObserver:selector:name:object:", self_objc, "handleNotification:", NOTIFICATION_NAME, null) + NSNotificationCenter.invoke("postNotificationName:object:userInfo:", NOTIFICATION_NAME, null, NOTIFICATION_USER_INFO) + NSNotificationCenter.invoke("removeObserver:", self_objc) + assert(notification_called, "Notification was not called") + return true + + +func intToBool(value: int) -> bool: + return bool(value) + + +func handleNotification(notification): + assert(notification.name == NOTIFICATION_NAME) + assert(notification.userInfo.to_dictionary() == NOTIFICATION_USER_INFO) + notification_called = true + + +func methodSignatureForSelector(sel: String) -> String: + match sel: + "my_property": + return "i@:" + "intToBool:": + return "B@:i" + "handleNotification:": + return "v@:@" + _: + return ""