I created an objective-c method which will invoke a method via NSInvocation:
typedef void (^ScriptingEmptyBlock)();
typedef void (^ScriptingErrorBlock)(NSError *error);
- (void)scripting_execute:(NSString *)operation withParams:(nullable NSArray *)args {
SEL selector = [self scripting_selectorForOperation:operation];
Class class = [self class];
NSMethodSignature *signature = [class instanceMethodSignatureForSelector:selector];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
[invocation setSelector:selector];
[invocation setTarget:self];
for (int idx = 0; idx < args.count; idx ++) {
id arg = args[idx];
[invocation setArgument:&arg atIndex:idx + 2];
}
ScriptingEmptyBlock success = args[1];
// Breakpoint added on next line to test for nil
success(); // this is nil and would crash!
// (lldb) po args.count
// 3
// (lldb) po success
// Printing description of success:
// (ScriptingEmptyBlock) success = 0x0000000000000000
// (lldb) po args[1]
// (Function)
//[invocation getArgument:&success atIndex:2]; // also tried this and got nil as well
[invocation invoke];
}
The method takes an "operation" which is translated into a selector by overriding scripting_selectorForOperation: in subclasses and then performs the invocation.
All of that works, except when the invoked method has block arguments they are nil, I added the test for nil I describe with comments, when attempting to read the closure from the array it will be nil.
Called like:
let successClosure: ScriptingEmptyBlock = {
print("Renamed product")
}
let errorClosure: ScriptingErrorBlock = { error in
print("Failed to rename product: \(error)")
}
let params:[Any] = [ "testName", successClosure, errorClosure]
object.scripting_execute (ScriptOperation.updateProductName.rawValue, withParams: params)
Why is closure becoming nil?
success is not nil (in fact, NSArray cannot contain nils). If you print it like NSLog(#"%#", success);, it will say (Function), not (null). And if you print its class like NSLog(#"%#", [success class]);, it will say _SwiftValue. Basically, it is a Swift value that is bridged into Objective-C.
The problem is that the object success points to is not an Objective-C block. It is a Swift closure, and Swift closures are not the same as Objective-C blocks. Trying to use invoke it as if it were an Objective-C block causes undefined behavior. po in the debugger prints it wrong probably because it is printing it assuming it were type ScriptingEmptyBlock (a block type). If you do po (id) success, it will print (Function).
As to how you can explicitly put an Objective-C block into the array from Swift, the only way I figured out to do it something like:
let params:[Any] = [ "testName",
successClosure as (#convention(block) () -> Void)!,
errorClosure as (#convention(block) (NSError) -> Void)!]
object.scripting_execute (ScriptOperation.updateProductName.rawValue,
withParams: params)
I am not sure why it's necessary to put the function type inside a !, but it doesn't seem to work otherwise. Maybe someone else can find a better way.
I must admit that I don't fully understand why this is happening, but as far as I can tell this has nothing to do with using NSInvocation and would happen even if we just passed a Swift closure to an Objective-C function via a parameter of type id. Passing an Objective-C block via id works just fine, not sure why: Swift closures are supposed to be compatible with Objective-C blocks. As you know, elements of NSArray are of type id, so any Objective-C object can be an array element.
To work around this problem of accessing a Swift closure passed via id in Objective-C one can introduce a wrapper class.
// In a header:
#interface EmptyBlockWrapper : NSObject
#property EmptyBlock _blk;
#end
// In an implementation file (just an empty implementation):
#implementation EmptyBlockWrapper
#end
Then we can use a wrapper instance instead of a block as an array element in Swift:
let myBlock : EmptyBlock = {
print("In Swift EmptyBlock...")
}
let myBlockWrapper = EmptyBlockWrapper()
myBlockWrapper._blk = myBlock
In an Objective-C method we can call it as follows, assuming args is NSArray *:
EmptyBlockWrapper * emptyBlockWrapper = args[1];
emptyBlockWrapper._blk();
Hopefully this is helpful. Of course, this is just a simplified example to give you an idea; this could be made much fancier.
Related
I am designing a new application by modernizing code I wrote in the past. This old code uses the class/delegate model and I am trying to transform them to use blocks as callbacks, not the delegate stuff.
What I do is to create a property like
#property (nonatomic, copy) void (^onTouch)(NSInteger index);
That would pass to the object using that class a block where code can be inserted and in this case executed on touch.
But my problem is this. When you use delegates and you have a method on the delegate protocol, Xcode will warn if you use that class and forget to implement the delegate protocols. Is that a way to do that with blocks? Or in other words: is there a way to make Xcode complain if a callback block is not defined by the caller?
I mean this would be the correct:
MyClass *obj = [[MyClass alloc] init];
obj.onTouch = ^(NSInteger *index){ //call back code to be executed };
This would be OK too
MyClass *obj = [[MyClass alloc] init];
obj.onTouch = nil;
but this would generate a message
MyClass *obj = [[MyClass alloc] init];
// no callback block defined.
Is this possible?
If you want to enforce setting a certain parameter, I would include it in the initializer.
MyClass *obj = [[MyClass alloc] initWithBlock:^(NSInteger *index) { /* code*/ }];
Then, in MyClass:
- (id)init {
// This will result in a runtime error if you use the wrong initializer.
NSAssert(NO, #"Use initWithBlock instead.");
}
- (id)initWithBlock(initWithBlock:^(NSInteger *)block) {
self = [super init];
if (self) {
self.onTouch = block;
}
return self;
}
Also note, attempting to execute a NULL block results in a crash, so make sure to do:
if (self.onTouch) { self.onTouch(); }
Wherever you run the block.
First, I strongly recommend defining types to represent your blocks - makes them a lot easier to work with, especially if you need to refactor the parameters.
You can't write code that distinguishes between "I set this property to nil" or "the runtime initialized this property to nil", at least not without some crazy runtime code to check the stack. Only option I can think of would be to use the null object pattern. Before I elaborate, bear in mind that I haven't actually tried to test this, but it should work. Define a block that means 'has no value' and set your property to point to that block on init. Then you can compare to that NullBlock at runtime to identify if someone explicitly set the property to nil (because it would be nil at that point) or gave it a real non-nil value.
Alternatively, if you don't mind manually writing your set accessors, you could have a BOOL that tracks if someone set the property explicitly. Then when you call the block just check if someone actually set the value or not.
#synthesize onTouchBlock=_onTouchBlock;
MyBlock _onTouchBlock;
BOOL _onTouchBlockWasSet;
- (void)setOnTouchBlock:(MyBlock)block {
_onTouchBlockWasSet = YES;
_onTouchBlock = block;
}
I would not recommend passing the value in the initializer because that makes it tied to the creation of that object type. If you wanted to change the block in code based on some condition, you'd be back to square one. Also, it prevents you from using storyboards which create that object.
I'm trying to understend construction like this:
- (void)someMethodWithArgs:(type?) param, ...
{
???
}
[self someMethodWithArgs:arg1, arg2, arg3];
How to get acces to arguments list?
Should 'type' be a pointer, or it can be 'int', for example?
The way Objective-C functions work.
Declaration
- (void) someMethodWithArgA:(type)paramName argB:(typeB)paramNameB
{
// do something with paramName and paramNameB
}
Calling
[self someMethodWithArgA:val argB:valB];
The C-equivalent would be :
void someMethodWithArgs(type paramName, typeB paramNameB)
{
// do something with paramName and paramNameB
}
someMethodWithArgs(val,valB);
And of course, as with C, variable types can be anything (why should they be just pointers?).
A simple example :
- (int)addNum:(int)a withNum:(int)b
{
int c = a+b;
return c;
}
int k = [self addNum:2 withNum:3];
// k = 5
Reference
The Objective-C model of object-oriented programming is based on
message passing to object instances. In Objective-C one does not
simply call a method; one sends a message.
http://en.wikipedia.org/wiki/Objective-C#Messages
UPDATE
Implementing method with variable number of arguments
#import <Cocoa/Cocoa.h>
#interface NSMutableArray (variadicMethodExample)
- (void) appendObjects:(id) firstObject, ...; // This method takes a nil-terminated list of objects.
#end
#implementation NSMutableArray (variadicMethodExample)
- (void) appendObjects:(id) firstObject, ...
{
id eachObject;
va_list argumentList;
if (firstObject) // The first argument isn't part of the varargs list,
{ // so we'll handle it separately.
[self addObject: firstObject];
va_start(argumentList, firstObject);
// Start scanning for arguments after firstObject.
// As many times as we can get an argument of type "id"
while (eachObject = va_arg(argumentList, id))
[self addObject: eachObject];
va_end(argumentList);
}
}
#end
From : http://developer.apple.com/library/mac/#qa/qa1405/_index.html
In ObjC argument list syntax originates C argument list syntax.
- (void) appendObjects:(id) firstObject, ...
{
id eachObject;
va_list argumentList;
va_start(argumentList, firstObject); // Start scanning for arguments after firstObject.
while (eachObject = va_arg(argumentList, id)) // As many times as we can get an argument of type "id"
[self addObject: eachObject]; // that isn't nil, add it to self's contents.
va_end(argumentList);
}
You can find more information from here: developer.apple.com
Update: ooops, i'm a little late :)
Matt Gallagher provides a nice tutorial on Variable Arguments lists:
Variable argument lists in Cocoa
In Apple's docs there's also a short Technical Q&A QA1405 on this topic:
Variable arguments in Objective-C methods
I have a sample app which i downloaded from net
In this i was unable to understand following code
UILocalNotification *localNotif = [[UILocalNotification alloc] init];
if (localNotif == nil)
return;
also
if (!array1)
return;
does this code means if object does not exists then return.....
HELP
In Cocoa, an initialiser will either return an object pointer if the call was successful, or a nil if it was unable to create the object.
Both cases are checking for the existence of the object. Actually, checking for the existence of a pointer to the object and simply returning if the object does not exist. As an example, here is a common form of initialiser for an object.
- (id)init {
// Call the superclass initialiser first and check that it was successful.
if (!(self = [super init])) {
// If the superclass initialiser failed then self will be nil.
// return a nil because we cannot create this object.
return nil; // Bail!
}
// Do more initialising
// If we can initialise the superclass and ourself, return a pointer to ourself
return self;
}
However, the snippets you have provided are not enough to tell whether the code is correct. For example, the first example is incorrect if it is part of an initialiser method because it is not returning any kind of object.
Edit
From your further examples both of these print hiiiiiiii
NSArray *arr;
if(arr) { NSLog(#"hiiiiii");
and
NSArray *arr = [[NSArray alloc]init];
if(arr) { NSLog(#"hiiiiii");
In the first case you are declaring arr to be a pointer to an NSArray, but because it hasn't been initialised this pointer is just a garbage value of random numbers. But it isn't nil So your if-statement evaluates as true. That doesn't mean that it is a valid pointer to an NSArray.
In your second example you declare an NSArray pointer and initialise it. This was successfully initialised so the pointer is not nil and the if-statement evaluates as true. In this case you do have a valid NSArray pointer.
Declaration is not initialisation!
Maybe if you explain what it is that you are trying to do we'll be able to better answer your questions.
They are both checking if the object is nil. In the first case it seems a bit silly though :)
Yes, except in the first case localNotif will not be nil because it has been set
Alright, so I think I'm doing this the right way. I'm new to objective-C, so I'm not sure about the syntax... I have a set of code that I need to call multiple times, from different files. So I made a new class that has a method in it that I'll call and pass it the values that it needs.
Because I am passing different values I've put them in a dictionary and decided to just pass the dictionary. Here is that code:
NSNumber *testNum = [NSNumber numberWithInt:varMoney];
NSMutableDictionary *dictionary = [[NSMutableDictionary alloc] init];
[dictionary setObject:#"OMG, Object 1!!!!" forKey:#"1"];
[dictionary setObject:#"Number two!" forKey:#"2"];
[dictionary setObject:testNum forKey:#"3"];
This code creates a test variable, and then puts it into the dictionary "dictionary." That all works, I have my nice little dictionary. However, now I need to create the class and it's method that will recieve the dictionary, and do something with it.
This is my class header file:
#import <UIKit/UIKit.h>
#interface EndOfTurnObjC : UIView {
}
#end
And this is the implementation file:
#import "EndOfTurnObjC.h"
#implementation EndOfTurnObjC
- (id)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
// Initialization code
}
return self;
}
- (void)dealloc {
[super dealloc];
}
#end
I haven't created any of the real code, because I'm not sure how to do the passing. I need to create a function (Method?) in the class that will take a Dictionary has a parameter, and then return the dictionary.
I also have no idea how to call such a function because it's in the class. So, the questions are:
1: How do I define the method in the class to accept the dictionary as a parameter (and then perhaps some example code to pull out one of the objects in a dictionary, so I can be sure it works)
2: How do I return the dictionary at the end of the method?
3: How do I call this method, in the class, from another class? (I know it involves making an object of thing class and calling the method of the object... I think, but I'm not sure about the syntax.)
Please include relavent code for the 3 files (header, implementation, and the other class that I call from). Thank you so much, I've been working on this particular problem for a while now.
Apple's The Objective-C Programming Language is a good and pretty concise reference for Objective-C syntax. What you want is just a normal method that takes an NSDictionary as a parameter. So as given in that document:
A message with a single argument affixes a colon (:) to the selector name and puts the argument right after the colon. This construct is called a keyword; a keyword ends with a colon, and an argument follows the colon, as shown in this example:
[myRectangle setWidth:20.0];
So a method call to pass dictionary would look like:
[someObject setAttributes:dictionary];
In the header:
-(NSMutableDictionary *) doSomethingWithDictionary:(NSMutableDictionary *) aDict;
in the implementation:
-(NSMutableDictionary *) doSomethingWithDictionary:(NSMutableDictionary *) aDict{
//do something with the dictionary
return aDict;
}
To call the method:
NSMutableDictionary *returnDict=[EndOfTurnObjC doSomethingWithDictionary:dictionary];
Note that as a matter of good design you wouldn't want to pass a mutable dictionary around like a token. That is asking for trouble. Instead pass static dictionaries and get another dictionary back.
You also shouldn't be passing data to a UIView. Instead, your UIViewController should process the data and then populate the view's UI elements as needed.
if you just want to do stuff to your dictionary u just
-(void) changeMyDictionary:(NSMutableDictionary * ) dictionary_
{
[dictionary_ doStuff];
....
...
}
no need to return the dictionary.
In my iPhone application, I have the following line in a constructor:
self.myVar = myVar_in;
where myVar is a property and myVar_in is a parameter passed to the constructor.
When I run the code, I get an EXC_BAD_ACCESS error on this line. However, when I replace the line with:
[myVar release];
[myVar_in retain];
myVar = myVar_in;
the code runs fine. My property is declared like this:
NSNumber *myVar;
...
#property (retain) NSNumber *myVar;
The error is consistent and I'm positive it's not a variable scope issue. Can someone explain this behavior?
EDIT: I've confirmed that myVar_in is valid right before the line(s) are executed. Here's the actual code, although it won't help much:
-(GetAddressRequestHelper*)initWithRequest:(ClientRequest*)request delegate:(id<ServerResponseDelegate>)delegate number:(NSNumber*)myVar_in location:(CLLocation*)location {
self = [super initWithRequest:request delegate:delegate];
if( self ) {
// same behavior even if this line is uncommented!!!
myVar_in = [NSNumber numberWithInt:123];
// prints "myVar_in is 123"
NSLog(#"myVar_in is %#",myVar_in);
// doesn't throw exception
/*[myVar release];
[myVar_in retain];
myVar = myVar_in;*/
// throws exception
self.myVar = myVar_in;
self.location=location;
}
return self;
}
EDIT2: I've found I still get the behavior when I explicitly initialize the param with myVar_in = [NSNumber numberWithInt:123];!
Thanks
One critical difference between this code:
[myVar release];
[myVar_in retain];
myVar = myVar_in;
and this code:
self.myVar = myVar_in;
is the use of self to call the method (setMyVar).
Almost certainly your object has been incorrectly created/allocated and self is a random value (in which case the assignment of myVar = myVar_in is scribling over random memory).
Post the code showing your object creation/init call and for good how measure how myVar_in gets its value. Also post your init code (you can (very carefully) delete extraneous code, but since this is a weird case, any extraneous code might well be relevent...
Try using the auto-generated setter method if you have #property and #synthesize statements for your variable. That will make sure the value of myVar_in is retained when assigned to myVar ([self setMyVar:myVar_in])
The error that you see is probably because myVar_in is released by the time you use it.
Make sure myVar_in is actually initialized. Would you mind posting the code where you initialize myVar_in and call the initializer method?