Assume a method signature such as the following:
- (void)theMethod:(void(^)(BOOL completed))completionBlock;
I would like to mock this method signature to ensure the method is called, and just call the completion block. I see from other posts like this one that I can mock the method call and accept any block, but not run the block. I also know there is a andDo method that I might be able to use, but I can't figure out how to pass a block in and run it.
Any ideas?
Thanks.
You can use [[mock stub] andDo:] like this to pass another block that gets called when your mocked method is called:
void (^proxyBlock)(NSInvocation *) = ^(NSInvocation *invocation) {
void (^passedBlock)( BOOL );
[invocation getArgument: &passedBlock atIndex: 2];
};
[[[mock stub] andDo: proxyBlock] theMethod:[OCMArg any]];
The block gets a NSInvocation instance from which you can query all the used arguments. Note that the first argument is at index 2 since you have self and _cmd at the indices 0 and 1.
EDIT 2:
Use https://stackoverflow.com/a/32945785/637641 instead.
Using andDo: is perfectly fine, but personally I prefer [OCMArg checkWithBlock:].
[[mock expect] theMethod:[OCMArg checkWithBlock:^BOOL(id param)
{
void (^passedBlock)( BOOL ) = param;
// Normally I set some expectations here and then call the block.
return YES;
}]];
// Code to test
[mock verify];
You can use also [mock stub] but I prefer to verify that theMethod is called.
EDIT 1
OCMock 3 version:
OCMExpect([mock theMethod:[OCMArg checkWithBlock:^BOOL(void (^passedBlock)(BOOL))
{
// call the block...
return YES;
}]]);
// Code to test
OCMVerify(mock);
This is now supported in OCMock 3.2. You can use [OCMArg invokeBlock] and [OCMArg invokeBlockWithArgs:...] to invoke the block passed as an argument to a stubbed method.
Using andDo: blocks is sometimes required but for most cases you can use [OCMArg invokeBlock] or [OCMArg invokeBlockWithArgs:].
In your example you can do the following
If you don't care about the arguments:
// Call block with default arguments.
OCMStub([mock theMethod:[OCMArg invokeBlock]];
If you want to send specific arguments:
// Call block with YES.
OCMStub([mock theMethod:([OCMArg invokeBlockWithArgs:#YES, nil])];
Note the nil termination since you can pass multiple arguments to this method.
In addition the entire expression must be wrapped in parentheses.
You can read more about it in the OCMock documentation.
This is Sven's answer updated for OCMock 3.
OCMStub([myMock myMethodWithMyBlock:[OCMArg any]]).andDo(^(NSInvocation *invocation) {
void (^passedBlock)(BOOL myFirstArgument, NSError *mySecondArgument);
[invocation getArgument: &passedBlock atIndex: 2];
passedBlock(YES, nil);
});
Related
#import <objc/runtime.h>
#implementation Foo
+ (void)test
{
[Foo performSelector:#selector(foo)];
[Foo performSelector:#selector(foo) withObject:#"works"];
[Foo performSelector:#selector(foo1)];
[Foo performSelector:#selector(foo2)];
}
+ (BOOL)resolveClassMethod:(SEL)sel
{
NSLog(#"resolveClassMethod: %#", NSStringFromSelector(sel));
if (sel == #selector(foo)) {
class_addMethod(objc_getMetaClass("Foo"), sel, (IMP)fooIMP_withoutArgument, "s#####");
return YES;
} else if (sel == #selector(foo1)) {
class_addMethod(objc_getMetaClass("Foo"), sel, (IMP)fooIMP_with1Argument, "Works");
return YES;
} else if (sel == #selector(foo2)) {
class_addMethod(objc_getMetaClass("Foo"), sel, (IMP)fooIMP_with2Arguments, "Not Works");
return YES;
}
return [super resolveClassMethod:sel];
}
void fooIMP_withoutArgument(id self, SEL _cmd)
{
NSLog(#"fooIMP_withoutArgument");
}
void fooIMP_with1Argument(id self, SEL _cmd, id arg)
{
NSLog(#"fooIMP_with1Argument");
}
void fooIMP_with2Arguments(id self, SEL _cmd, id arg1, id arg2)
{
NSLog(#"fooIMP_with2Argument");
}
#end
Output is:
2018-02-27 16:41:31.638693+0800 Test[74840:2194481] resolveInstanceMethod: foo
2018-02-27 16:41:31.639201+0800 Test[74840:2194481] fooIMP_withoutArgument
2018-02-27 16:41:31.639228+0800 Test[74840:2194481] fooIMP_withoutArgument
2018-02-27 16:41:31.639490+0800 Test[74840:2194481] resolveInstanceMethod: foo1
2018-02-27 16:41:31.639519+0800 Test[74840:2194481] fooIMP_with1Argument
2018-02-27 16:41:31.639548+0800 Test[74840:2194481] resolveInstanceMethod: foo2
(lldb)
class_addMethod function does not check argument types?
Is the selector just a key point to the implementation without other role?
Why IMP with 1 argument work, but 2 arguments not?
How it works when call a method with params (or not) but IMP without params (or has)
Can someone help me explain the reason?
class_addMethod function does not check argument types?
It does not. Being a dynamic-typed language, Objective-C are all essentially just id at runtime—the type checking for objects occurs at compile-time, and that information is not preserved in the binary. class_addMethod is a low-level runtime function, and as such it does not include any type checking. This is okay, since these runtime functions are meant to be very rarely used, and only in cases where you know exactly what you're doing, and therefore, what types to expect.
SEL is just a key point to IMP without other role?
SEL is a selector, which in the typical implementations of Objective-C is a string, under the hood. objc_msgSend resolves this selector to an IMP, which is just a function whose body contains the method's implementation.
Why IMP with 1 argument work, but 2 arguments not?
Since you don't show us the signature of foo2, nor describe what's exactly going wrong, it's difficult to say. But since it appears that you don't try to call foo2 with any arguments, it's not too surprising that the effect would end up being something other than what you want.
Also, you're not returning YES after you add the methods, but calling super's implementation of resolveInstanceMethod:. If that method happens to return NO, the runtime is going to think that adding the method didn't work. You should return YES instead after successfully adding a method. (And in the case where you don't, you should call super's implementation of resolveClassMethod: rather than resolveInstanceMethod:.)
No, there is generally no argument type checking during run-time.
A selector is just the name for a method. It is represented by just a constant C string. If you add a method to a class the selector acts like a key in a dictionary with implementations as values.
Adding methods with arbitrary number of arguments work fine. Your example might fail, because the types parameter is wrong, and the argument number in the selector doesn't match. Fix these issues first.
I am trying to use an array of strings dynamically access methods at runtime within my class. For now the methods are already there, eventually I want to create them.
Is this possible?
For example:
bool nextLevel=NO;
for(NSString * match in gameLevels)
{
if([match isEqualToString:self.level])
{
nextLevel=YES;
}
else if(nextLevel==YES)
{
self.level=match;
nextLevel=NO;
}
}
//access method named self.level
Thank you in advance!
I use:
NSSelectorFromString(selectorString)
In your case, the selectorString would be:
NSString * selectorString = #"setLevel:";
This is 'setLevel' instead of 'level' because the Objective-C runtime will automatically expand dot properties to these selector names when assignment occurs.
To access a method based on a string, check the other answer.
To add a method in the runtime you need to create a IMP function or block.
If using a function, could be something like:
void myMethodIMP(id self, SEL _cmd)
{
// implementation ....
}
You could also use a block like this:
IMP blockImplementation=imp_implementationWithBlock(^(id _self, ...){
//Your Code here
}
Then you need to add the method, like this:
class_addMethod(yourClass, #selector(selectorName), (IMP) blockImplementation, encoding);
The encoding part is a special runtime encoding to describe the type of parameters your method receives. You can find that on the Objective-C runtime reference.
If you receive dynamic arguments on your generated methods, you need to use the va_list to read the values.
I am going around blocks and try to discover the ways that they can be used.
So I am wondering is it possible to pass block to block like parameter?
Here is some sample code:
//declaration
static id (^someBlock)(id) = ^(id someClass) {
// do some stuff to obtain class some class instance
// check if class instance respond to #selector
// if yes - perform selector
}
//usage
+ (instancetype)someMethod {
someBlock(SomeClass.class);
// do additional work and return some instance type
}
This works fine, but is not good enough, because we obligate caller to respond to selector if caller want to do some additional stuff when someBlock is completed.
So my question is how I can invoke someBlock block with parameter block which I want to be executed when someBlock is completed.
Some like:
//declaration
static id (^someBlock)(id, <b>^otherBlock</b>) = ^(id someClass, <b>????</b>) {
// do some stuff to obtain class some class instance
otherBlock();
}
Any advice?
PS: Please note that the question is not about passing block to method as parameter.
Thanks,
Venelin
Is this what you are looking for?
static id (^someBlock)(id, void (^otherBlock)()) = ^id (id someClass, void (^otherBlock)()) {
otherBlock();
return nil; // just because you declares a `id` return type
};
And call it like
someBlock(someClass, ^() {
NSLog(#"other stuff");
});
typedef void (^RequestProductsCompletionHandler)(BOOL success, NSArray * products);
I am having difficulty understanding what this line of code is doing in .h file.
Please explain in detail
typedef.
void (I know what void do, but whats the purpose here?).
(^RequestProductsCompletionHandler)(BOOL success, NSArray * products);
How to call it?
This is definition of objective-c block type with name RequestProductsCompletionHandler that takes 2 parameters (BOOL and NSArray) and does not have return value. You can call it the same way you would call c function, e.g.:
RequestProductsCompletionHandler handler = ^(BOOL success, NSArray * products){
if (success){
// Process results
}
else{
// Handle error
}
}
...
handler(YES, array);
Vladimir described it well. It defines a variable type which will represent a block that will pass two parameters, a boolean success and an array of products, but the block itself returns void. While you don't need to use the typedef, it makes the method declaration a tad more elegant (and avoids your having to engage in the complicated syntax of block variables).
To give you a practical example, one might infer from the name of the block type and its parameters that this defines a completion block (e.g. a block of code to be performed when some asynchronous operation, like a slow network request, completes). See Using a Block as a Method Argument.
For example, imagine that you had some InventoryManager class from which you could request product information, with a method with an interface defined like so, using your typedef:
- (void)requestProductsWithName:(NSString *)name completion:(RequestProductsCompletionHandler)completion;
And you might use the method like so:
[inventoryManager requestProductsWithName:name completion:^(BOOL success, NSArray * products) {
// when the request completes asynchronously (likely taking a bit of time), this is
// how we want to handle the response when it eventually comes in.
for (Product *product in products) {
NSLog(#"name = %#; qty = %#", product.name, product.quantity);
}
}];
// but this method carries on here while requestProductsWithName runs asynchronously
And, if you looked at the implementation of requestProductsWithName, it could conceivably look something like:
- (void)requestProductsWithName:(NSString *)name completion:(RequestProductsCompletionHandler)completion
{
// initiate some asynchronous request, e.g.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// now do some time consuming network request to get the products here
// when all done, we'll dispatch this back to the caller
dispatch_async(dispatch_get_main_queue(), {
if (products)
completion(YES, products); // success = YES, and return the array of products
else
completion(NO, nil); // success = NO, but no products to pass back
});
});
}
Clearly, this is unlikely to be precisely what your particular completion handler block is doing, but hopefully it illustrates the concept.
Mike Walker created a nice one page site that shows all possibilities to declare a block in Objective-C. This can be helpful to understand your problem as well:
http://fuckingblocksyntax.com
To quote his site, this is how you can define blocks:
As a local variable:
returnType (^blockName)(parameterTypes) = ^returnType(parameters) {...};
As a property:
#property (nonatomic, copy) returnType (^blockName)(parameterTypes);
As a method parameter:
- (void)someMethodThatTakesABlock:(returnType (^)(parameterTypes))blockName {...}
As an argument to a method call:
[someObject someMethodThatTakesABlock: ^returnType (parameters) {...}];
As a typedef:
typedef returnType (^TypeName)(parameterTypes);
TypeName blockName = ^(parameters) {...}
I have a method which i need to call within another method. in a UIViewController
//This is how i call methoed
tmpUITextView.text= [self ssidValue:1 arrayOf:ssidArray];
//my Method
+ (NSString *)ssidValue:(NSInteger)selectedValue arrayOf:(NSMutableArray *)tmpArray {
NSString *result=[tmpArray objectAtIndex:selectedValue];
NSLog(#"%#",result);
return result;
}
but i am getting waring ( warning: 'UIViewController' may not respond to '-ssidValue:arrayOf:')and crash it.
Please tell me what i am doing wrong here.
Thanks in Advance
You are declaring the method as a class method (note the "+") but you are calling it as an instance method.
To solve this, either declare it as an instance method (replace "+" with "-") or call it as a class method:
[[self class] ssidValue:1 arrayOf:ssidArray];
use the instance method type declaration i.e.,. use "-" instead of "+" for method declaration
and you can call it using the class name
tmpUITextView.text= [class_Name ssidValue:1 arrayOf:ssidArray];
class_name is the name of the class Wherethe method initialized
You are declaring the method as a class method (note the "+") but you are calling it as an instance method.
To solve this, either declare it as an instance method (replace "+" with "-") or call it as a class method:
[[self class] ssidValue:1 arrayOf:ssidArray];