Stub Methods with Block Arguments - objective-c-blocks

I'm trying to stub a method which has got an block argument.
[[[myMock stub] andCall:#selector(performBlock:) onObject:self] performBlock:[OCMArg any]];
The block is of a simple type.
typedef void (^MyBlock)(void);
Upon execution I get the following error:
unexpected method invoked: performBlock:<__NSStackBlock__: 0xbffff418>
stubbed: performBlock:<OCMAnyConstraint: 0x1c1ff70>
It seems as a block argument is not compatible with [OCMArg any]. Any tipps how to stub this method then?

Ok, I have cropped too much from my example.
I had a second parameter of type bool which is not compatible with [OCMArg any].
Even worse: You don't get a compiler warning for it unless you use BOOL instead...

Related

Objective C adding methods to classes argument mismatch

I am working on this piece of code, basically adding a block to NSObject:
class_addMethod(object_getClass([NSObject class]), #selector(toUpper2:), imp_implementationWithBlock(^NSString*(id self, SEL _cmd, NSString* s) {
NSLog(#"self: %#", self);
NSLog(#"_cmd: %#", _cmd); // I know %# is not SEL, but look for yourself
NSLog(#"s: %#", s);
return [s uppercaseString];
}), "##:#"); // the type signature was created using #encode
To me, this looks fairly innocent, but if I do this: (I've defined a +toUpper2 in a different class too, so that the compiler does not complain):
[(id)[NSObject class] toUpper2:#"hallo"];
This happens:
2018-07-06 16:45:52.302676+0200 xctest[43736:32056962] self: NSObject
2018-07-06 16:45:52.302706+0200 xctest[43736:32056962] _cmd: hallo
2018-07-06 16:45:52.302721+0200 xctest[43736:32056962] s: (null)
As you can see, the arguments got messed up. What's more, if i enact the same method using performSelector, like that:
[NSObject performSelector:#selector(toUpper2:) withObject:#"hallo"];
Then, things get even more astray:
2018-07-06 16:45:52.302737+0200 xctest[43736:32056962] self: NSObject
2018-07-06 16:45:52.302751+0200 xctest[43736:32056962] _cmd: hallo
2018-07-06 16:45:52.302763+0200 xctest[43736:32056962] s: hallo
Can anyone explain this behaviour?
Best regards,
thejack
The docs are wrong, bug filed (41908695). The objc header file is correct:
/**
* Creates a pointer to a function that will call the block
* when the method is called.
*
* #param block The block that implements this method. Its signature should
* be: method_return_type ^(id self, method_args...).
* The selector is not available as a parameter to this block.
* The block is copied with \c Block_copy().
*
* #return The IMP that calls this block. Must be disposed of with
* \c imp_removeBlock.
*/
OBJC_EXPORT IMP _Nonnull
imp_implementationWithBlock(id _Nonnull block)
OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0, 2.0);
The reason why it was implemented this way was speed speed speed. And simplicity.
Specifically, a method decomposes into a C function that always takes at least two arguments; self and _cmd, both of which happen to pointers. That is followed by 0...N arbitrary arguments which will be packed into registers or onto the stack arbitrarily as defined by the targeted architectures ABI.
A block call site, on the other hand, always decomposes into a C function call where the function has one guaranteed argument; a reference to the block. It is this reference through which the compiler can emit code to reference captured state, if any. Like a method, the block's args are followed by an arbitrary list of arguments.
Now, re-encoding argument lists on any architecture is a nightmare. Slow, prone to error, and extremely complex.
To avoid that, imp_implementationWithBlock() does some behind the scenes magic that returns a function pointer that, when invoked, treats the first argument like a pointer (should be self) into the second argument's slot (overwrites _cmd), shoves the block reference into the first argument's slot, and then tail calls the block's code.
The block doesn't know that it was invoked as a method. And the objc runtime doesn't know that a method invocation trampolined thru to a block.
I haven't used this method myself, but it looks to me like you've misunderstood the API here.
The docs describe the structure of the block parameter.
block
The block that implements this method. The signature of block should be method_return_type ^(id self, self, method_args …). The selector of the method is not available to block. block is copied with Block_copy().
The emphasis here is mine, but should make it quite clear that your SEL argument is not appropriate here. I'm not entirely sure why they've duplicated self in the description, especially given that you're typically seeing your method_args begin at that parameter index.

How to extract an argument of 'function' type from NSInvocation

I am writing a unit test for the function that receives a protocol as input argument.
This function that I am testing calls some method of that protocol inside.
I want to mock this protocol and that method.
To mock the protocol using OCMock I wrote the following:
id<MyProtocol> myProtocol = OCMProtocolMock(#protocol(MyProtocol));
Now to mock the function I am using OCMStub.
The interesting part is that the function doesn't return any value but rather gets the callback as input argument and invokes it.
Here is its signature:
- (void)myFunction:(void (^ _Nonnull)(NSDictionary<NSString *, NSString *> * _Nonnull))completion;
I am writing the following code to mock this function:
OCMStub([myProtocol myFunction:[OCMArg any]])._andDo(^(NSInvocation *invocation){
void (^ _Nonnull)(NSDictionary<NSString *, NSString *> * _Nonnull) completion;
[invocation getArgument:&completion atIndex:0];
// Here I will invoke the completion callback with some dictionary and invoke the invocation
});
However I am getting the following error: "Expected identifer or '('". The error points to the line that defines completion variable.
How can I define the function variable of signature void (^ _Nonnull)(NSDictionary<NSString *, NSString *> * _Nonnull)?
That ain't a function. It is a block!
Regardless, both functions and blocks can be treated as void * in declarations. You'll need to cast them to the appropriate type after.
But that's probably the easiest way to deal with it; extract from the invocation as a void*, cast to a block, call it.
Actually I was able to extract a first argument by doing the following:
OCMStub([myProtocol myFunction:[OCMArg any]])._andDo(^(NSInvocation *invocation){
void (^ completion)(NSDictionary<NSString *, NSString *> * _Nonnull);
[invocation getArgument:&completion atIndex:2];
// Do other stuff
});
I was just declaring a variable of 'block' type incorrectly.
And I have also realized that the first argument should be accessed by index = 2;

Checking the value of a Swift variable inside the completion block of the method that initializes it

Take the creation of NSURLSessionDownloadTask in Objective-C:
NSURLSessionDownloadTask *task = [[NSURLSession sharedSession] downloadTaskWithURL:[NSURL URLWithString:#"google.com"] completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) {
if (task.state == NSURLSessionTaskStateCompleted) {
// Do things
}
}];
[task resume];
I can access the very task I'm creating, task, within the completion block as well without issue.
However, in Swift, if I try the same thing:
let URL = NSURL(string: "google.com")
let task = NSURLSession.sharedSession().downloadTaskWithURL(URL, completionHandler: { location, response, error in
if task.state == .Completed {
// Do things
}
}
task.resume()
I can an error that "variable is being used within its own initial value".
How do I circumvent this?
Updated answer
I realize now that I didn't read your code carefully. You are not passing the closure to an initializer, but to a method. What I originally wrote is still valid when passing a closure to an initializer, but not in your case.
Your problem is similar though.
You have a task variable, which is initialized with the return value of a function. You pass a closure to the function, and inside the closure you are referencing the task variable.
The compiler doesn't know when the closure is executed (at least I don't think it checks for that, it's a downloadTaskWithURL() internal implementation detail) - and it's possible that it is executed in the function body (as opposed to have it stored in a property and executed at a later time). If the closure is executed in the function body, then it would access to the task variable when it has not been assigned a value yet (because the function is still executing).
If there were a way to let the compiler know the closure is not executed in the function body, then it would be possible for the compiler to handle that case. But swift doesn't implement anything like that.
Conclusion: I appreciate the compiler throwing an error for that, because otherwise I'd expect a runtime exception - although maybe not in your specific case (because the closure is executed later).
Original answer
As you probably know, in swift self is unavailable until all class/struct properties have been properly initialized, and a base initializer has been called (for inherited classes)
You are passing the closure to the class initializer - the compiler can't determine when the closure will be executed, so the closure itself cannot contain any (direct or indirect) reference to self.
In your case, task is the variable being instantiated, so when you are using it in the closure, it's like you are using self. That's not allowed in swift, so you have that error.
The same doesn't happen in Objective C because there's no such constraint in initializers.
Note however that conceptually what you are doing doesn't look a good practice. You are reading properties of a class instance before it's been properly initialized. To determine the status of the call, you should rely on the parameters passed to the closure, which ideally should provide all info you need.
Your task.state has not been initiazed yet whith some value. You are trying to read properties of a class instance before it's been initialized.
Right now the only way I have been able to get around this is to declare the variable before the initializer and then access it inside the closure:
let URL = NSURL(string: "google.com")
var task: NSURLSessionTask?
task = NSURLSession.sharedSession().downloadTaskWithURL(URL, completionHandler: { location, response, error in
if task?.state == .Completed {
// Do things
}
}
task?.resume()

Obj-C: Difference between calling a method (with no input) on an object vs calling a method with input

I am an absolute beginner in objective-c and just read an overview of the language on cocoadevcentral.
The first section briefly discusses syntax for calling methods and gives two examples; one for calling a method on an object and the second is a method with input being called on an object,
[object method];
[object methodWithInput: input];
Can anyone explain the difference to me, possibly with a simple example?
There is no huge difference between the two and all depends on what you are doing.
Method 1
[object method];
There are two parts to this method.
object this is either an instance of a class or is a class itself all depends on the type of method you are calling whether it be an instance method or a class method. Each are declared differently.
A Class method is declared like + (void)myClassMethod; and would be called like [NSString myClassMethod];
An Instance method would be declared like - (void)myInstanceMethod; and would be called like [myStr myInstanceMethod]; (Where myStr is an instace of NSString)
method The second part is the actual method that you are calling this all that this will do when you call something like [myStr myInstanceMethod]; it will call the implementation of that method so it would call
- (void)myInstanceMethod
{
NSLog(#"We called our instance method");
}
Method 2
[object methodWithInput: input];
The only difference here is that we are passing in an argument. So here we have three parts the same first two from method 1 and the argument
input All this is, is the value that you are passing into the method to be used within it.
This type of method would be declared something like - (void)myInstanceMethodWithArgument:(NSString *)str;. Here are just saying that we have an argument of type NSString so when we call this like [str myInstanceMethod:#"Some Random String I want to pass in"]; it will run the following implementation code
- (void)myInstanceMethod:(NSString *)str
{
NSLog(#"My str value is : %#", str);
}
Method 3
[object methodWithInput1:input1 andInput2:input2];
Just throwing this in because you my get a little confused later when dealing with multiple arguments. This is exactly the same as method 2 except it has two arguments and not one. This would be declared like - (void)myInstanceMethodWithInput1:(NSString *)str1 andInput2:(NSString *)str2;. Does exactly the same is method 2 except it has multiple arguments that's it nothing to be scared of.
I would recommend that you have a read of the Apple Coding Guidelines for Cocoa. Best of look with learning as it's probably not the easiest language to learn.
Try substituting 'input' for 'argument'..
[object someMethod:(CGFloat )floatArgument];
The type should be there in the brackets, with a dereference operator (*) eg (NSObject *)theArgument if that argument is a pointer.
So basically some methods supply one or more arguments, and some do not, just as with C
When you call method without input data it means that method will work with already existing class's properties.
- (void)someMethod {
self.var_1 = self.var_2 + self.var_3; //or any other implementation
}
You will call this method like this
[self someMethod];
When you call method with some input data it means that this data will be used in method's implementation
- (void)someMethodWithInputData:(NSInteger)inputData {
self.var_1 = self.var_2 * inputData;
}
You will call it like this
[self someMethodWithInputData:10];
It's just the difference between saying "I wait" and "I eat an omelette". In some cases you can say what you mean with just a verb. In some cases a sentence needs an object in order to communicate its meaning.
The same thing applies in programming. Sonetimes you're going to need to specify more than just the action. But not always.

Verify method call with a handle argument on an OCMockito mock

I have an OCMockito mock of a class QuestionBuilder with the method questionsFromJSON:error:. This method accepts a handle (NSError **)error as an argument. How do I verify the method was called?
I’ve tried:
[verify(builder) questionsFromJSON:#"Fake JSON"
error:nil];
and:
NSError *err;
[verify(builder) questionsFromJSON:#"Fake JSON"
error:&err];
Both issue the error:
testQuestionJSONIsPassedToQuestionBuilder
(QuestionCreationTests) failed:
*** -[NSProxy doesNotRecognizeSelector:questionsFromJSON:error:] called!
I don't think OCMockito supports this yet; when I do it using given instead of verify, i get a weird error when the code under test calls the method with the ** argument. If possible you may have to modify your method signature to not take in an NSError** (if you have control over that code).