How to JSExport a Objective-C Method with Object - ios

I want to write a method by the Objective-C that can be exported to JavaScript, so that I can get the JavaScript Object into the native code. For example in the following code: someObject is a native implemented object, and fun is its method.
someObject.fun({ k : 'value1', k2 : 'value2'})
I know that using JSExport can export the native method to JavaScript. and successfully pass the JavaScript String to the native code (NSString*). But when I want to pass the JavaScript Object to native, it fails.
How to solve this?
Thanks a lot

In addition to the conversion of javascript strings to NSString* as you have already observed, Javascript objects are copied to a KVC-compliant objective-C object (essentially, an NSDictionary instance) before being passed to your objective-c method.
For example, with the following definitions:
#protocol MyJSAPI
- (void)fun:(id)param;
#end
#interface MyObject: NSObject<MyJSAPI>
- (void)fun:(id)param;
#end
and the initialization of your JSContext as follows:
myjscontext[#"someObject"] = [[MyObject alloc] init];
[myjscontext evaluateScript:#"someObject.fun({ k : 'value1', k2 : 'value2'});
then your fun:(id)param method can access the k and k2 fields of the objects like this:
#implementation MyObject
- (void)fun:(id)param {
NSString* k = [param valueForKey:#"k"];
NSString* k2 = [param valueForKey:#"k2"];
NSLog(#"k = %#, k2 = %#", k, k2);
}
#end

In addition to ste3veh answer.
You can use JSExportAs macro in following way to export Objective-C method as JavaScript one:
#interface MyObject: NSObject <JSExport>
JSExportAs(fun,
- (void)fun:(id)param
);
#end
#implementation MyObject
- (void)fun:(id)param {
NSString* k = [param objectForKey:#"k"];
NSString* k2 = [param objectForKey:#"k2"];
NSLog(#"k = %#, k2 = %#", k, k2);
}
#end
In this case param would be implicitly converted to corresponding Objective-C oblect, using -[JSValue toObject]. In your case it will be converted in NSDictionary. To avoid implicit conversion, you may specify parameter as JSValue:
#interface MyObject: NSObject <JSExport>
JSExportAs(fun,
- (void)fun:(JSValue*)param
);
#end
#implementation MyObject
- (void)fun:(JSValue*)param {
NSString* k = [[param valueForProperty:#"k"] toString];
NSString* k2 = [[param valueForProperty:#"k2"] toString];
NSLog(#"k = %#, k2 = %#", k, k2);
}
#end
Note that big JavaScript objects, i.e. window or document, are very expensive to convert into corresponding Objective-C ones, so second way is preferred. Also, you can use JSValue parameter to call back JavaScript methods of this object from Objective-C, using callWithArguments:.

I don't think you can. See the JSExport.h header: It defines what the arguments can be, and it doesn't appear that generic JavaScript objects can be passed.

Related

Accessing a variable list through a class instance object

I currently develop an SDK and I want to know how to access a list of variables through a class instance object as follow:
MyClass * myObject = [[MyClass alloc] init];
[myObject changeShape : myObject.FORM_SQUARE];
[myObject changeShape : myObject.FORM_CIRCLE];
[myObject changeShape : myObject.FORM_RECTANGLE];
...
These variables ( three dozen ) are static and return just an integer to identify the form.
Do I have to set a #property for each variable or may be there are a more optimized way?
If they are not properties then you will have to implement a getter method to access them.
-(String*)getiVar{
return iVar;
}
Finally, the solution is to use NS_ENUM.
Like that:
typedef NS_ENUM(NSUInteger, shape) {
rectangle = 0,
triangle = 1,
square = 1,
...
};
-(void)changeShape:(shape)newShape;

Is there a way to Make NSInvocation surport variable parmas function line [NSstring stringWithFormat:..]

Apple doc says "NSInvocation does not support invocations of methods with either variable numbers of arguments or union arguments. "
i searched for hours ,some people says var_list works, but i tryed ,it does Not
but I think there may be a way to do the same thing (reflection) on variable params function,as i metioned ,[stringWithFormat:],
so , I found a way ,please readt the code bellow .
#interface INTObj : NSObject
#property (nonatomic, assign) int realvalue
#end
#interface FloatObj : NSObject
#property (nonatomic, assign) float realvalue
#end
// here ,the selectorName is only know in runtime
NSString *selectorName = #"stringWithFormat:";
SEL selector = NSSelectorFromString(selectorName);
typedef id (*Obj_Imp)(id,SEL,...);
Method md = class_getClassMethod(obj, selector); // here, obj means NSString
Obj_Imp fun = (Obj_Imp )method_getImplementation(md); // stringWithFormat:...
NSString *arg1 = #"hello %f %d";
FloatObj *fo = [[FloatObj alloc] init];
fo.realvalue = 11.3;
INTObj *io = [[INTObj alloc] init];
io.realvalue = 5;
NSObject *arr[3] = {arg1, fo,io};
// it cracks . exc_bad_Access code = 1
NSString *result = fun(obj , selector, arr[0], [arr[1] realvalue],[arr[2] realvalue]) ;
// it works but i cant do this ,because i don't know the type of the 4th parameters at Runtime
NSString *result = fun(obj , selector, arr[0],[(FloatObj *)arr[1] realvalue],[arr[2] realvalue])
why does the second calling of the function "fun" works while the first one cracks?
is there a better way to to do this?
This has nothing to do with NSInvocation or calling the method implementation directly. You should get the same undefined behavior if you called the method directly:
NSString *result = [NSString stringWithFormat:arr[0], [arr[1] realvalue], [arr[2] realvalue]];
or even a regular function:
NSLog(arr[0], [arr[1] realvalue], [arr[2] realvalue]);
In C (and Objective-C) every expression must have a compile-time type. The compiler needs to be know this compile-time type be able to compile the code correctly.
So let me ask you, what should be the compile-time type of [arr[1] realvalue]? Is it int? Is it float? The compiler will do different things depending on what type it is. If it is float for example, the C standard says that float passed to varargs will be promoted to double. The calling conventions for passing an int and a double in varargs are different (in fact, these two types have different sizes). And +[NSString stringWithFormat:] expects the compile-time type of the thing you pass to match the format specifier you give it in your format string, or there will be undefined behavior.
From your format string, it seems like you wanted the [arr[1] realvalue] argument to be float or double since you used %f. Since FloatObj is the class whose realvalue method returns float, it seems that casting arr[1] to FloatObj * is the right thing to do.

Comparing in objective C - Implicit conversion of 'int' to 'id' is disallowed with ARC

I i'm getting the error "Implicit conversion of 'int' to 'id' is disallowed with ARC" at the line marked with "faulty line". I guess it have something to do with that i'm checking for an integer in an array, that contains objects instead of integers.
#import "RandomGenerator.h"
#implementation RandomGenerator
NSMutableArray *drawnNumbers;
-(int) randomNumber:(int)upperNumber {
return arc4random_uniform(upperNumber);
}
-(NSMutableArray*) lotteryNumbers :(int)withMaximumDrawnNumbers :(int)andHighestNumber {
for (int i = 1; i <= withMaximumDrawnNumbers; i++)
{
int drawnNumber = [self randomNumber:andHighestNumber];
if ([drawnNumbers containsObject:drawnNumber]) { //faulty line
//foo
}
}
return drawnNumbers;
}
#end
NSArrays can only contain objective-c objects. So actually the method containsObject: is expecting an object, not an int or any other primitive type.
If you want to store number inside an NSArray you should pack them into NSNumber objects.
NSNumber *someNumber = [NSNumber numberWithInt:3];
In your case, if we assume that drawnNumbers is already an array of NSNumbers, you should change the randomNumber: generation to:
-(NSNumber*) randomNumber:(int)upperNumber {
return [NSNumber numberWithInt:arc4random_uniform(upperNumber)];
}
And then when picking it up on the lotteryNumbers method, you should:
NSNumber *drawnNumber = [self randomNumber:andHighestNumber];
Another note would go for the method you defined for lotteryNumbers. You used a really strange name for it, I think you misunderstood how the method naming works in objective-c. You were probably looking for something more like:
-(NSMutableArray*) lotteryNumbersWithMaximumDrawnNumbers:(int)maximumDrawnNumbers andHighestNumber:(int)highestNumber;
Late edit:
Objective-C now allows a way more compact syntax for creating NSNumbers. You can do it like:
NSNumber *someNumber = #(3);
And your method could be rewritten as:
-(NSNumber*) randomNumber:(int)upperNumber {
return #(arc4random_uniform(upperNumber));
}
You are using an int where an object (presumably NSNumber) is expected. So convert before use:
if ([drawnNumbers containsObject:#( drawnNumber )])

Objective-C how to define static parameters

I'm new at Objective-C. I want to use global parameters like in C/C++
#define PARAM_1 1
How could I do this in Objective-C
The same way.
Objective-C is a superset of C, so your define is perfectly valid.
edited following comment
Rather than a define, which just performs textual substitution, you could use a static variable instead:
static NSNumber const * retrieveFriendRequestNumber = nil;
Which you can initialise in the class's initialiser method
+ (void)initialize {
retrieveFriendRequestNumber = #(2);
}
(Yes a bit long-winded, but an example of how to initialise a literal).
and then you can use it as:
[parameters setObject:retrieveFriendRequestNumber forKey:#"fcode"];
Alternatively, declare it as a static NSUInteger and convert it to an object when you use it:
static NSUInteger retrieveFriendRequest = 2;
And use it as:
[parameters setObject:#(retrieveFriendRequest) forKey:#"fcode"];

What is the Objective-C equivalent of Java BeanUtils.copyProperties? [duplicate]

I would like to know if they have an equivalent in Objective C of the JAVA's methode "BeanUtils.CopyProperties(bean1, Bean2);" ?
Or other solution, i would like to cast motherObject to childObject :
#interface motherBean : NSObject{ ...}
#interface childBean : motherBean { ...}
motherBean m = [motherBean new];
childBean f = m;
With the first tests it's work but I have a warning : "incompatible pointer types returning ...";
I use WSDL2Objc and it generate bean, and the name of it can change between 2 generation :-/
I prefere to work with the child and just change the name in her definition
Thanks
Anthony
Take a look at commons-beanutils package. It has lots of property method for you to copy stuff. In particular:
PropertyUtils.copyProperties(bean1, bean2);
However, as to your second question, you're trying to downcast an instance of a parent class to a child class?
I'm not sure how that would be legal in any OO language. Sure you can forcibly cast:
// This is not legal because you can't case from one class to anther
// unless the actual instance type (not the declared type of the variable,
// but the constructed type) is either the casted class or a subclass.
Parent p = new Parent();
Child c = (Child) p;
But you'd get a ClassCastException , since you can't treat an instance of a parent class as if it were a child class (only the other way around). Either of these would be legal however:
// This is legal because you're upcasting, which is fine
Child c = new Child();
Parent p = c;
// This is legal because the instance "p" is actually an
// instance of the "Child" class, so the downcast is legal.
Parent p = new Child();
Child c = (Child) p;
To answer your first question, you could easily write the code to copy property values between instances. It is easiest if you restrict properties to proper Objective-C properties (items declared using #property()) which is probably the best practice anyway. You can use the Objective-C runtime functions to get the list of all properties on a class (class_getPropertyList) and call property_getName() to get the property's name and call property_getAttributes() to make sure it is writeable. Then you can use NSObject's Key Value Coding to get and set the property values using valueForKeyPath: and setValueForKeyPath: respectively.
Some problems with you code example are that instances should be pointers. Second, you need an explicit cast since you are assigning an instance of a class to its super class. The reverse would not require a cast. That is probably why you are getting the warning.
The method BeanUtils.copyProperties
//.h
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#interface BeanUtils : NSObject
+(void)copyProperties:(id)src dest:(id)dest;
#end
//.m
#import "BeanUtils.h"
#implementation BeanUtils
+(void)copyProperties:(id)src dest:(id)dest {
NSLog(#"classeSrc=%# dst=%#", [src class], [dest class]);
if(src == NULL || dest == NULL) {
return;
}
Class clazz = [src class];
u_int count;
objc_property_t* properties = class_copyPropertyList(clazz, &count);
NSMutableArray* propertyArray = [NSMutableArray arrayWithCapacity:count];
for (int i = 0; i < count ; i++)
{
NSString *propertyName = [NSString stringWithUTF8String:property_getName(properties[i])];
[propertyArray addObject:propertyName];
//on verifie que la prop existe dans la classe dest
objc_property_t prop = class_getProperty([dest class], [propertyName UTF8String]);
if(prop != NULL) {
id result = [src valueForKey:propertyName];
[dest setValue:result forKey: propertyName];
}
else {
[NSException raise:NSInternalInconsistencyException format:#"La propriété %# n'existe pas dans la classe %#",propertyName, [dest class] ];
}
}
free(properties);
}
#end
call :
EleveBean *eleveBean = [EleveBean new];
eleveBean.nom = #"bob";
eleveBean.prenom = #"john";
tns1_EleveBean *tnsEleve = [tns1_EleveBean new];
[BeanUtils copyProperties:eleveBean dest:tnsEleve];
STAssertEquals(eleveBean.nom, tnsEleve.nom, #"");
STAssertEquals(eleveBean.prenom, tnsEleve.prenom, #"");

Resources