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]
Related
I've spent days trying every possible solution I can think of to this problem, but nothing seems to be working.
I run a background thread like this:
[MagicalRecord saveInBackgroundWithBlock:^(NSManagedObjectContext *localContext) {
Media *localMedia = [media inContext:localContext];
UIImage *image = localMedia.thumbnail;
dispatch_async(dispatch_get_main_queue(), ^{
[thumbnails setObject:image forKey:[NSNumber numberWithInt:i]];
[contentDict setObject:thumbnails forKey:#"MediaItems"];
[cell.entryView setNeedsDisplay];
});
}];
Or like this:
dispatch_queue_t cellSetupQueue = dispatch_queue_create("com.Journalized.SetupTimelineThumbnails", NULL);
dispatch_async(cellSetupQueue, ^{
NSManagedObjectContext *newMoc = [[NSManagedObjectContext alloc] init];
NSPersistentStoreCoordinator *coordinator = [NSManagedObjectContext contextForCurrentThread].persistentStoreCoordinator;
[newMoc setPersistentStoreCoordinator:coordinator];
NSNotificationCenter *notify = [NSNotificationCenter defaultCenter];
[notify addObserver:self
selector:#selector(mergeChanges:)
name:NSManagedObjectContextDidSaveNotification
object:newMoc];
Media *localMedia = [media inContext:[NSManagedObjectContext contextForCurrentThread];
UIImage *image = localMedia.thumbnail;
dispatch_async(dispatch_get_main_queue(), ^{
[thumbnails setObject:image forKey:[NSNumber numberWithInt:i]];
[contentDict setObject:thumbnails forKey:#"MediaItems"];
[cell.entryView setNeedsDisplay];
});
}];
Both of these give me a crash with UIImage returning as nil object, and a Cocoa Error 133000.
I've removed every other piece of background threading code, and have saved on the main thread directly before this just to make sure. Running the code above on the main thread also works, but freezes up my UI. Despite all of these efforts, the above code crashes every time.
I'm not sure what to do to make this work.
Edit:
My question, specifically, is how do I make this work without crashing? It seems to be a problem with objects from 1 context not existing in another, but how do i make them exist in another context?
Remember, the MR_inContext: method is using [NSManagedObjectContext objectWithID: ] method under the covers. You should look in there to make sure your object has:
1) Been saved prior to entering into the background context/block in your first code block
2) This method is returning something useful
I'm also not sure how you set up your thumbnail attribute. Ideally it shouldn't matter as long as you have the NSTransformable code write (there are samples on the internets that show you how to save a UIImage in core data using the transformable attribute)
Also, your code should look like this:
UIImage *image = nil;
[MagicalRecord saveInBackgroundWithBlock:^(NSManagedObjectContext *localContext) {
Media *localMedia = [media inContext:localContext]; //remember, this looks up an existing object. If your context is a child of a parent context that has contains this, the localContext should refresh the object from the parent.
//image processing/etc
image = localMedia.thumbnail;
} completion:^{
[thumbnails setObject:image forKey:#(i)]; //new literals are teh awesome
contentInfo[#"MediaItems"] = thumbnails; //as is the new indexer syntax (on the latest compiler)
[cell.entryView setNeedsDisplay];
}];
Fast answer:
NSManagedObjectReferentialIntegrityError = 133000,
// attempt to fire a fault pointing to an object that does not exist (we can see the store, we can't see the object)
EDIT:
It's pretty difficult to see something from the code. What is a managed object there?
I suppose the problem is that you are using temporary objects from one context in another context.
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();
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
I'm calling a selector on background thread,
The selector has NSAutorelasePool around it.
I guess the arguments I pass to the selector is causing the problem.
How should I deal with it?
SEL theSelector;
NSMethodSignature *aSignature;
NSInvocation *anInvocation;
theSelector = #selector(changeColor:forColorString:);
aSignature = [[animationData class] instanceMethodSignatureForSelector:theSelector];
anInvocation = [NSInvocation invocationWithMethodSignature:aSignature];
[anInvocation setSelector:theSelector];
[anInvocation setTarget:animationData];
// indexes for arguments start at 2, 0 = self, 1 = _cmd
[anInvocation setArgument:¤tColor atIndex:2];
[anInvocation setArgument:&nsColorString atIndex:3];
[anInvocation performSelectorInBackground:#selector(invoke) withObject:NULL];
When you tell the invocation to perform invoke in the background, the new thread is created with invoke being the first method called. Invoke does not create an autorelease pool, so anything autoreleased during that method will be leaked.
To fix this, use a wrapper method to perform the invocation.
- (void)performInvocation:(NSInvocation *)anInvocation {
NSAutoreleasePool *pool = [NSAutoreleasePool new];
[anInvocation invoke];
[pool release];
}
//where you were performing the invoke before:
[self performSelectorInBackground:#selector(performInvocation:) withObject:anInvocation];
In addition to what ughoavgfhw said, you also need to call [anInvocation retainArguments] if you intend to set objects as arguments and pass to a background thread.
I've come across an issue with using a third party library, and am not sure what the common pattern to solve it is.
I'm using the asi-http-request class, which fetches http objects asynchronously using a thread.
In my objects dealloc() method, I do
[request setDelegate:nil];
[request release];
However the delegate is sometimes still called after this has happened. (I can see when this happens the delegate field of the request object is nil.) This sometimes causes a crash if the delegate has been destroyed already.
I believe this is a race condition. The code from ASIHTTPRequest that calls the delegate looks like this:
// Let the delegate know we are done
if ([self didFinishSelector] && [[self delegate] respondsToSelector:[self didFinishSelector]]) {
[[self delegate] performSelectorOnMainThread:[self didFinishSelector] withObject:self waitUntilDone:[NSThread isMainThread]];
}
The problem happens if the performerSelectorOnMainThread has been called (but not completed) when the setDelegate call happens on the main thread.
One solution would be to add a wrapper around 'didFinishSelector' that checks (on the main thread) that the delegate is still non-nil before calling the selector, but this would result in a lot of wrappers.
There is some background here:
http://groups.google.com/group/asihttprequest/browse_thread/thread/721220b9645f4a42
All suggestions on the "normal" solution for this appreciated!
Thanks
Joseph
My original thoughts (wrapper around 'didFinishSelector' that checks on the main thread that the delegate is still non-nil before calling the selector) turned out to be the correct solution, as confirmed by helpful folks over on the apple dev forums:
https://devforums.apple.com/message/255935#255935
To avoid my worry of ending up with lots of wrappers, I managed to create only a single wrapper:
- (void)callSelectorCallback:(SEL *)selectorPtr withTarget:(id *)targetPtr
{
id target = *targetPtr;
SEL selector = *selectorPtr;
if (!selector || !target)
return;
if ([target respondsToSelector:selector])
{
[target performSelector:selector withObject:self];
}
}
- (void)callSelector:(SEL *)selector withDelegate:(id *)target
{
if (!*selector || !*target)
return;
SEL callback = #selector(callSelectorCallback:withTarget:);
NSMethodSignature *signature = [ASIHTTPRequest instanceMethodSignatureForSelector:callback];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
[invocation setSelector:callback];
[invocation setTarget:self];
[invocation setArgument:&selector atIndex:2];
[invocation setArgument:&target atIndex:3];
[invocation performSelectorOnMainThread:#selector(invoke) withObject:nil waitUntilDone:[NSThread isMainThread]];
}
then when I want to call the delegate, the code just looks something like this:
[self callSelector:&didFinishSelector withDelegate:&delegate];
As best I can tell from experiments & code analysis (and assuming setDelegate is only called from the main thread), this is 100% safe. It could be made safe for the non-main thread calls to setDelegate by taking the object lock inside callSelectorCallback.
For dealing with objects across threads, you should almost always retain them. Basically,
id delegate = [[self delegate] retain];
if ([self didFinishSelector] && [delegate respondsToSelector:[self didFinishSelector]]) {
[delegate performSelectorOnMainThread:[self didFinishSelector]
withObject:self
waitUntilDone:[NSThread isMainThread]];
}
[delegate release];
Technically, the delegate could be dealloc-ed in between [self delegate] and the subsequent retain, and I'm not at all sure if Apple's #synthesized atomic accessors protect against this or not, but I believe the only way to solve this is in the accessor,
[[delegate retain] autorelease];
best of luck, race conditions get the best of us!