Search code examples
javascriptiosobjective-cjavascriptcore

Use a NSBlock as a property in JavaScriptCore


I need to use a NSBlock as a property that can be changed in JavaScript.

@protocol ExportingUser <NSObject, JSExport>

@property (nonatomic, copy) void (^changedName) (void);

@property (nonatomic) NSString* name;

@end

I basically want to call changedName from Obj-C, a function whose definition comes from JavaScript. Like this:

user.name = "Peyman"; // this will change the name property in the Obj-C as well.

user.changedName = function() { console.log("yay"); } // but this doesn't

I know that this is possible through JSValue's callWithArguments method but I prefer this method if possible.

Edit: actually, I don't think the callWithArguments method would work. It's possible since objects that are bound to Objective-C classes are not extensible in JavaScript so introducing new fields would simply be ignored.


Solution

  • As listed in JSValue.h :

    //   Objective-C type  |   JavaScript type
    // --------------------+---------------------
    //         nil         |     undefined
    //        NSNull       |        null
    //       NSString      |       string
    //       NSNumber      |   number, boolean
    //     NSDictionary    |   Object object
    //       NSArray       |    Array object
    //        NSDate       |     Date object
    //       NSBlock *     |   Function object *
    //          id **      |   Wrapper object **
    //        Class ***    | Constructor object ***
    //
    // * Instances of NSBlock with supported arguments types will be presented to
    // JavaScript as a callable Function object. For more information on supported
    // argument types see JSExport.h. If a JavaScript Function originating from an
    // Objective-C block is converted back to an Objective-C object the block will
    // be returned. All other JavaScript functions will be converted in the same
    // manner as a JavaScript object of type Object.
    

    Apparently, you can get away with :

    @property (nonatomic, copy) JSValue* changedName;
    

    Then, when you want to call it:

    typedef void (^nameChangedBlock)(void);
    
    nameChangedObject = [self.changedName toObject];
    
    if ([nameChangedObject isKindOfClass: NSClassFromString(@"NSBlock")]){
       ((nameChangedBlock)nameChangedObject)();
    }