Can I pass a function name as an argument ? - iphone

I want to make this class dynamically perform functions on itself by passing different names to it. Is that possible ? Or rather: How is it possible ?
-(id)initWithMethod:(NSString*)method{
if ((self = [super init])){
[self method];
}
return self;
}
-(void) lowHealth {
CCSprite *blackScreen = [CCSprite spriteWithFile:#"blackscreen.png"];
blackScreen.anchorPoint = ccp(0,0);
[self addChild:blackScreen];
id fadeIn = [CCFadeIn actionWithDuration:1];
id fadeOut = [CCFadeOut actionWithDuration:1];
id fadeInAndOut = [CCRepeatForever actionWithAction:[CCSequence actions:fadeIn, fadeOut, nil]];
[blackScreen runAction:fadeInAndOut];
}

You should use performSelector and get the selector from your NSString using NSSelectorFromString:
[self performSelector:NSSelectorFromString(method)];
instead of [self method];

The standard way is using Selectors as mentioned in Matteo's answer.
You can also look at Objective-C Blocks. They are becoming very common in the CocoaTouch APIs and you can do some very slick things with them. The resulting architecture of your class is often easier to understand IMO.
For example this method from UIView
+ (void)animateWithDuration:(NSTimeInterval)duration
animations:(void (^)(void))animations
completion:(void (^)(BOOL finished))completion
Takes two block, one that is runs the code for the actual animation, and one to for the code after the animation is complete. You could call this with block variables or by writing code inline:
...animations:^{
// animation code
}
completion:^(BOOL finished) {
// completion code
}
The receiving method (in this case animateWithDuration:...) would simply call these blocks at some point like so:
animations();

Related

EXC_BAD_ACCESS when accessing parameters in andDo of OCMock

I am trying to write an block of code using OCMock's stub andDo method.
In this case UIImageView extension class is being tested. I want to check that the extension calls [self setImage:] with parameter that is non-nil (later other image comparison will be used).
When using OCMock's andDo method, the test crashes with EXC_BAD_ACCESS after the block completes.
id mockView = [OCMockObject mockForClass:[UIImageView class]];
[[[mockView stub] andDo:^(NSInvocation *invocation)
{
UIImage *img;
[invocation getArgument:&img atIndex:2]; <---- line causing the exception
somebodySetImage |= (img != nil);
}] setImage:OCMOCK_ANY];
[mockView do_something_that_calls_setImage];
The only solution that I've found for now is using andCall instead of andDo, but this complicates the test.
Can I avoid the crash with andDo?
UPDATE
Well, I will try to give a better example here:
Here is the new piece of the test code:
- (void)testDownloadingThumbnail
{
PInfo *_sut = [[PInfo alloc] init];
__block id target = nil;
id mock = [OCMockObject mockForClass:[NSOperationQueue class]];
[[[mock expect] andDo:^(NSInvocation *inv)
{
NSInvocationOperation *op;
[inv getArgument:&op atIndex:2];
target = [[op invocation] target]; /* replacing this line with STAssert does not help either */
}] addOperation:OCMOCK_ANY];
[_sut setDownloadQueue:mock];
[_sut startDownloadingImagesAsync:YES];
[mock verify];
STAssertEqualObjects(target, _sut, #"invalid op target");
}
Here is the tested code (single method from PInfo):
- (void)startDownloadingImagesAsync:(bool)isThumbnailImg
{
NSInvocationOperation *inv;
inv = [[NSInvocationOperation alloc] initWithTarget:self
selector:#selector(loadThumbnailWorker:)
object:nil];
[[self downloadQueue] addOperation:inv];
}
The code still crashes upon exit from startDownloadingImagesAsync with EXC_BAD_ACCESS.
If I add a breakpoint inside the andDo block, I see that the control reaches this point and retrieves correct objects via getArgument.
Yet, if I use getArgument inside the block, it crashes whatever I try to do.
P.S. Thanks for help.
I ran into a similar problem when using NSProxy's forwardInvocation: method.
Can you try the below?
NSInvocationOperation *op; // Change this line
__unsafe_unretained NSInvocationOperation *op; // to this line
Or another approach could be to retain NSInvocation's arguments:
[invocation retainArguments];
http://developer.apple.com/library/ios/documentation/Cocoa/Reference/Foundation/Classes/NSInvocation_Class/Reference/Reference.html#//apple_ref/occ/instm/NSInvocation/retainArguments
I'll try to add a more detailed explanation later.
I think the problem is that you're trying to invoke a mock object directly. For what you're trying to do, you shouldn't need a mock object. Just call the method and verify that the image was set:
expect(myObject.imageView.image).to.beNil();
[myObject do_something_that_calls_setImage];
expect(myObject.imageView.image).not.to.beNil();
If you really want to use a mock for some reason, you could do it with a real UIImageView and a partial mock:
UIImageView *imageView = myObject.imageView;
id mockView = [OCMockObject partialMockForObject:imageView];
__block BOOL imageSet = NO;
[[[mockView stub] andDo:^(NSInvocation *invocation) {
UIImage *img;
[invocation getArgument:&img atIndex:2];
imageSet = (img != nil);
}] setImage:OCMOCK_ANY];
[myObject do_something_that_calls_setImage];
expect(imageSet).to.beTruthy();
In my case this was happening because I introduced another parameter to this method, so the block parameter got shifted by one.
I fixed it by changing [inv getArgument:&op atIndex:2] to [inv getArgument:&op atIndex:3]

animationDidStop for group animation

I have a group animation but I can not detect when hits animationDidStop. example of my code:
[group setDelegate:self];
[view.layer addAnimation:group forKey:#"groupAnimation"];
any of you knows how I know when the group animation is done?
You need to also set the animationName property to match, and ensure that your delegate function is properly defined:
CAAnimationGroup *group = [CAAnimationGroup animation];
group.duration = 2.0f;
group.delegate = self;
[group setValue:#"groupAnimation" forKey:#"animationName"];
[group setAnimations:[NSArray arrayWithObjects:myAnimation, myOtherAnimation, nil]];
[view.layer addAnimation:group forKey:#"groupAnimation"];
.
.
.
- (void)animationDidStop:(CAAnimation *)animation finished:(BOOL)finished
{
if (finished)
{
NSString *animationName = [animation valueForKey:#"animationName"];
if ([animationName isEqualToString:#"groupAnimation"])
{
// your groupAnimation has ended
}
}
}
Please note that with group animations, the delegates set on your component animations will be ignored.
I came up with a way to organize animation completion code that works really well. First, I defined a custom type for a block of code to run when an animation completes:
typedef void (^animationCompletionBlock)(void);
I define a constant that I use to add a custom key-value pair to my animation:
#define kAnimationCompletionBlock #"animationCompletionBlock"
Then, when I create an animation, if I want it to execute a block of code when it finishes, I add a custom property to my animation that contains the block of code I want to execute:
animationCompletionBlock theBlock;
theBlock = ^void(void)
{
someButton.enabled = TRUE;
NSLog(#"Animation complete");
//whatever other code you want to do on completion
}
[myAnimation setValue: theBlock forKey: kAnimationCompletionBlock];
I set the view controller to be the animation's delegate:
myAnimation.delegate = self
Finally, I write a general-purpose animation completion method that looks for an animation completion block attached to the animation, and executes it if it finds it:
/*
This method looks for a value added to the animation that just completed
with the key kAnimationCompletionBlock.
If it exists, it assumes it is a code block of type animationCompletionBlock,
and executes the code block.
This allows you to add a custom block of completion code to any animation or
animation group, rather than Having a big complicated switch statement in your
animationDidStop:finished: method with global animation ompletion code.
(Note that the system won't call the animationDidStop:finished method for
individual animations in an animation group - it will only call the
completion method for the entire group. Thus, if you want to run code after
part of an animation group completes, you have to set up a manual timer.)
*/
- (void)animationDidStop:(CAAnimation *)theAnimation
finished:(BOOL)flag
{
animationCompletionBlock theBlock =
[theAnimation valueForKey: kAnimationCompletionBlock];
if (theBlock)
theBlock();
}
In addition to being very clean, this approach also lets your animation completion code have access to local variables that are inside the scope where you define the block. That solves the problem of passing information to your completion method, which can be difficult.
You can see this technique in a working example program I posted to Github:
Core Animation demo on Github, including completion block code
I'm using this category for setting the completion like this:
[group setCompletionBlock:^{
}];
First CAAnimationGroup+Blocks.h:
#import <QuartzCore/QuartzCore.h>
#import <objc/runtime.h>
typedef void (^TIFAnimationGroupCompletionBlock)();
#interface CAAnimationGroup (Blocks)
- (void)setCompletionBlock:(TIFAnimationGroupCompletionBlock)handler;
#end
And CAAnimationGroup+Blocks.m:
#import "CAAnimationGroup+Blocks.h"
static char CAAnimationGroupBlockKey;
#implementation CAAnimationGroup (Blocks)
- (void)setCompletionBlock:(TIFAnimationGroupCompletionBlock)handler {
objc_setAssociatedObject(self, &CAAnimationGroupBlockKey, handler, OBJC_ASSOCIATION_COPY_NONATOMIC);
self.delegate = self;
}
- (void)animationDidStop:(CAAnimation *)animation finished:(BOOL)finished
{
if (finished)
{
TIFAnimationGroupCompletionBlock handler = (TIFAnimationGroupCompletionBlock)objc_getAssociatedObject(self, &CAAnimationGroupBlockKey);
if (handler) {
handler();
}
}
}
#end

Why the Cocos2D programming guide suggests to use initWithTexture?

I found that in the cocos2d Best Practices there is a suggestion to use initWithTexture to initialize sprite subclasses, instead in several books and tutorials I found that you can also use initWithSpriteFrameName. Why is that?
All of the initialization methods in CCSprite use initWithTexture:rect to create the sprite. For example,
-(id) initWithFile:(NSString*)filename
{
NSAssert(filename!=nil, #"Invalid filename for sprite");
CCTexture2D *texture = [[CCTextureCache sharedTextureCache] addImage: filename];
if( texture ) {
CGRect rect = CGRectZero;
rect.size = texture.contentSize;
return [self initWithTexture:texture rect:rect];
}
[self release];
return nil;
}
Other functions (initWithFile, initWithSpriteFrame, initWithSpriteFrameName, et cetera) also call initWithTexture:rect either directly or indirectly. If your CCSprite subclass has any special initialization that needs to take place (as it ostensibly will, since you're subclassing another class), doing it in initWithTexture:rect guarantees that it will be run.

iOS - How to implement a performSelector with multiple arguments and with afterDelay?

I am an iOS newbie. I have a selector method as follows -
- (void) fooFirstInput:(NSString*) first secondInput:(NSString*) second
{
}
I am trying to implement something like this -
[self performSelector:#selector(fooFirstInput:secondInput:) withObject:#"first" withObject:#"second" afterDelay:15.0];
But that gives me an error saying -
Instance method -performSelector:withObject:withObject:afterDelay: not found
Any ideas as to what I am missing?
Personally, I think that a closer solution to your needs is the use of NSInvocation.
Something like the following will do the work:
indexPath and dataSource are two instance variables defined in the same method.
SEL aSelector = NSSelectorFromString(#"dropDownSelectedRow:withDataSource:");
if([dropDownDelegate respondsToSelector:aSelector]) {
NSInvocation *inv = [NSInvocation invocationWithMethodSignature:[dropDownDelegate methodSignatureForSelector:aSelector]];
[inv setSelector:aSelector];
[inv setTarget:dropDownDelegate];
[inv setArgument:&(indexPath) atIndex:2]; //arguments 0 and 1 are self and _cmd respectively, automatically set by NSInvocation
[inv setArgument:&(dataSource) atIndex:3]; //arguments 0 and 1 are self and _cmd respectively, automatically set by NSInvocation
[inv invoke];
}
Because there is no such thing as a [NSObject performSelector:withObject:withObject:afterDelay:] method.
You need to encapsulate the data you want to send along into some single Objective C object (e.g. a NSArray, a NSDictionary, some custom Objective C type) and then pass it through the[NSObject performSelector:withObject:afterDelay:] method that is well known and loved.
For example:
NSArray * arrayOfThingsIWantToPassAlong =
[NSArray arrayWithObjects: #"first", #"second", nil];
[self performSelector:#selector(fooFirstInput:)
withObject:arrayOfThingsIWantToPassAlong
afterDelay:15.0];
You can package your parameters into one object and use a helper method to call your original method as Michael, and others now, have suggested.
Another option is dispatch_after, which will take a block and enqueue it at a certain time.
double delayInSeconds = 15.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
[self fooFirstInput:first secondInput:second];
});
Or, as you've already discovered, if you don't require the delay you can just use - performSelector:withObject:withObject:
The simplest option is to modify your method to take a single parameter containing both arguments, such as an NSArray or NSDictionary (or add a second method that takes a single parameter, unpacks it, and calls the first method, and then call the second method on a delay).
For instance, you could have something like:
- (void) fooOneInput:(NSDictionary*) params {
NSString* param1 = [params objectForKey:#"firstParam"];
NSString* param2 = [params objectForKey:#"secondParam"];
[self fooFirstInput:param1 secondInput:param2];
}
And then to call it, you can do:
[self performSelector:#selector(fooOneInput:)
withObject:[NSDictionary dictionaryWithObjectsAndKeys: #"first", #"firstParam", #"second", #"secondParam", nil]
afterDelay:15.0];
- (void) callFooWithArray: (NSArray *) inputArray
{
[self fooFirstInput: [inputArray objectAtIndex:0] secondInput: [inputArray objectAtIndex:1]];
}
- (void) fooFirstInput:(NSString*) first secondInput:(NSString*) second
{
}
and call it with:
[self performSelector:#selector(callFooWithArray) withObject:[NSArray arrayWithObjects:#"first", #"second", nil] afterDelay:15.0];
You can find all the types of provided performSelector: methods here:
http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/nsobject_Class/Reference/Reference.html
There are a bunch of variations but there isn't a version that takes multiple objects as well as a delay. You'll need to wrap up your arguments in an NSArray or NSDictionary instead.
- performSelector:
- performSelector:withObject:
- performSelector:withObject:withObject:
– performSelector:withObject:afterDelay:
– performSelector:withObject:afterDelay:inModes:
– performSelectorOnMainThread:withObject:waitUntilDone:
– performSelectorOnMainThread:withObject:waitUntilDone:modes:
– performSelector:onThread:withObject:waitUntilDone:
– performSelector:onThread:withObject:waitUntilDone:modes:
– performSelectorInBackground:withObject:
I dislike the NSInvocation way, too complex. Let’s keep it simple and clean:
// Assume we have these variables
id target, SEL aSelector, id parameter1, id parameter2;
// Get the method IMP, method is a function pointer here.
id (*method)(id, SEL, id, id) = (void *)[target methodForSelector:aSelector];
// IMP is just a C function, so we can call it directly.
id returnValue = method(target, aSelector, parameter1, parameter2);
I just did some swizzling and needed to call the original method. What I did was making a protocol and cast my object to it.
Another way is to define the method in a category, but would need suppression of a warning (#pragma clang diagnostic ignored "-Wincomplete-implementation").
A simple and reusable way is to extend NSObject and implement
- (void)performSelector:(SEL)aSelector withObjects:(NSArray *)arguments;
something like:
- (void)performSelector:(SEL)aSelector withObjects:(NSArray *)arguments
{
NSMethodSignature *signature = [self methodSignatureForSelector: aSelector];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature: signature];
[invocation setSelector: aSelector];
int index = 2; //0 and 1 reserved
for (NSObject *argument in arguments) {
[invocation setArgument: &argument atIndex: index];
index ++;
}
[invocation invokeWithTarget: self];
}
I would just create a custom object holding all my parameters as properties, and then use that single object as the parameter

drawLayer Method

I have drawLayer method, which i'm calling from another method using setNeedsDisplay method, but the flow of code seems to be incorrect.
I have this method
-(IBAction)makeSinglePageMode:(UIButton *)sender
from where i'm calling another method
-(void)loadSinglePageWithWidth:(float)width andHeight:(float)height
which in turn calls `drawLayer` method.
I'm attaching the methods.I'm attaching only a part of code in these methods just to have more clarity while understanding the flow.
After calling [tiledLayer1 setNeedsDisplay]; the flow comes back to makeSinglePageMode: method without executing drawLayer method.
The next statement in makeSinglePageMode: method gets executed i.e. [self loadMarkUpFromPageDataSource:pd WithPageSize:pageSize inFrame:markupFrame];
I want this line of code to be executed only after drawLayer method execution is completed.
Any sort of help in this regard will be appreciated.
`
-(IBAction)makeSinglePageMode:(UIButton *)sender{
GLogDebug(#"setting size of image to %#", NSStringFromCGSize(aspectFitSize));
[scrollView setContentSize:CGSizeMake(self.view.bounds.size.width, self.view.bounds.size.height - __TOP_BRANDING_BAR_HEIGHT)];
[markupView removeAllSubViews];
tiledLayer1.frame=CGRectMake(0,0,768,1024);
[self loadSinglePageWithWidth:(aspectFitSize.width) andHeight:aspectFitSize.height];
GLogDebug(#"markupFrame-%#", NSStringFromCGRect(markupFrame));
[self loadMarkUpFromPageDataSource:pd WithPageSize:pageSize inFrame:markupFrame];
}`
-(void)loadSinglePageWithWidth:(float)width andHeight:(float)height{
[tiledLayer1 setNeedsDisplay];
}
`- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx
{
int c=[currentPage intValue]+1;
if(c==[kbDataSource numberOfPagesInBook]){
tiledLayer2.hidden=YES;
}
else tiledLayer2.hidden=NO;
}
CGContextSetInterpolationQuality(ctx, kCGInterpolationHigh);
CGContextSetRenderingIntent(ctx, kCGRenderingIntentDefault);
CGContextDrawPDFPage(ctx, myPageRef);
CGContextRestoreGState(ctx);
}
}