XCTest exception when using keyValueObservingExpectationForObject:keyPath:handler: - ios

In my unit tests, I am using the -[XCTestCase keyValueObservingExpectationForObject:keyPath:handler:] method in order to ensure that my NSOperation finishes, here is the code from my XCDYouTubeKit project:
- (void) testStartingOnBackgroundThread
{
XCDYouTubeVideoOperation *operation = [[XCDYouTubeVideoOperation alloc] initWithVideoIdentifier:nil languageIdentifier:nil];
[self keyValueObservingExpectationForObject:operation keyPath:#"isFinished" handler:^BOOL(id observedObject, NSDictionary *change)
{
XCTAssertNil([observedObject video]);
XCTAssertNotNil([observedObject error]);
return YES;
}];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
XCTAssertFalse([NSThread isMainThread]);
[operation start];
});
[self waitForExpectationsWithTimeout:5 handler:nil];
}
This test always passes when I run it locally on my Mac but sometimes it fails on Travis with this error:
failed: caught "NSRangeException", "Cannot remove an observer <_XCKVOExpectation 0x1001846c0> for the key path "isFinished" from <XCDYouTubeVideoOperation 0x1001b9510> because it is not registered as an observer."
Am I doing something wrong?

Your code is correct, you have found a bug in the XCTest framework. Here is an in depth explanation, you can skip to the end of this answer if you are just looking for a workaround.
When you call keyValueObservingExpectationForObject:keyPath:handler:, an _XCKVOExpectation object is created under the hood. It is responsible for observing the object/keyPath you passed. Once the KVO notification has fired, the _safelyUnregister method is called, this is where the observer is removed. Here is the (reverse engineered) implementation of the _safelyUnregister method.
#implementation _XCKVOExpectation
- (void) _safelyUnregister
{
if (!self.hasUnregistered)
{
[self.observedObject removeObserver:self forKeyPath:self.keyPath];
self.hasUnregistered = YES;
}
}
#end
This method is called once again at the end of waitForExpectationsWithTimeout:handler: and when the _XCKVOExpectation object is deallocated. Note that the operation terminates on a background thread but the test is run on the main thread. So you have a race condition: if _safelyUnregister is called on the main thread before the hasUnregistered property is set to YES on the background thread, the observer is removed twice, causing the Cannot remove an observer exception.
So in order to workaround this issue, you have to protect the _safelyUnregister method with a lock. Here is a code snippet for you to compile in your test target that will take care of fixing this bug.
#import <objc/runtime.h>
__attribute__((constructor)) void WorkaroundXCKVOExpectationUnregistrationRaceCondition(void);
__attribute__((constructor)) void WorkaroundXCKVOExpectationUnregistrationRaceCondition(void)
{
SEL _safelyUnregisterSEL = sel_getUid("_safelyUnregister");
Method safelyUnregister = class_getInstanceMethod(objc_lookUpClass("_XCKVOExpectation"), _safelyUnregisterSEL);
void (*_safelyUnregisterIMP)(id, SEL) = (__typeof__(_safelyUnregisterIMP))method_getImplementation(safelyUnregister);
method_setImplementation(safelyUnregister, imp_implementationWithBlock(^(id self) {
#synchronized(self)
{
_safelyUnregisterIMP(self, _safelyUnregisterSEL);
}
}));
}
EDIT
This bug has been fixed in Xcode 7 beta 4.

Related

How do I wait for a method to finish before calling it again?

I am building a simple messaging app using Parse's framework. I have a method called displayMessages. This is called each time the phone receives a push.
However, as this message is doing work in the Parse database I don't want to call it again if it's already running. I want to wait until it is finished and then call it.
I am using the following code:
-(void)receivedPush
{
[self displayMessages];
}
and:
-(void)displayMessages
{
//code here
}
If received push is called I want it to wait until displayMessages is finished before calling it. Could someone please point me in the right direction with this?
UPDATE
I tried using the NSOperationQueue method and realised that although this does work for waiting for displayMessages it doesn't result in the required behavior.
In displayMessages I have: [PFObject deleteAllInBackground:toDelete]; it's actually this I need to wait for completion before calling displayMessages again.
Create a NSOperationQueue and set the maxConcurrentOperationCount to 1. Implement your data access method as an operation (possibly block-type operation) and submit it to the queue. (I like this better than gcd since you can do cancellation or test the number of items already in the queue.)
Note that if the method actually displays things, you'll need to dispatch back to the main queue for UI work.
You could use a NSOperationQueue with maxConcurrentOperationCount set to 1.
Declare the NSOperationQueue as an iVar of your class, initialize it in the init method and set
[_opQueue setMaxConcurrentOperationCount:1];
and then when you receive the push:
- (void)receivedPush {
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:#selector(displayMessages) object:nil];
[_opQueue addOperation:op];
}
Shortest and simples would be creating BOOL isExecuting and checking if you can call method based on that (changing values before execution but after check and after execution)
How about this for a fairly lightweight solution:
#property (nonatomic, assign) BOOL needsToDisplayMessages;
#property (nonatomic, assign) BOOL displayingMessages;
Then
-(void)receivedPush
{
if (!self.displayingMessages) {
[self displayMessages];
} else {
self.needsToDisplayMessages = YES;
}
}
-(void)displayMessages
{
self.needsToDisplayMessages = NO;
self.displayingMessages = YES;
//long-running code here
self.displayingMessages = NO;
if (self.needsToDisplayMessages) {
[self displayMessages]
}
(ignoring concurrency issues ... for which you could use GCD in displayMessages or NSOperationQueue as per a couple of the other answers)
With your new updated requirement, you can use deleteAllInBackground:block:. According to document:
"Deletes a collection of objects all at once asynchronously and executes the block when done."
Why not schedule each message handling using:
-(void)receivedPush
{
dispatch_async(dispatch_get_main_queue(), ^{
/* Show the update on the display */
NSLog(#"Handling new messages");
NSArray *newMessages=<populate with new messages>;
[handler displayMessages:newMessages];
});
}
This will queue up your handling of each set as they come in. Only one displayMessages will run at a time.

are iOS block parameters somehow allowed to be specific subclasses of defined type

I've got a callback with a the following class hierarchy:
JSONCustomerObj : JSONObj
Here is the definition of the method with the generic JSONObj block parameter:
-(void) _getRemote:(NSString*) url callback:(void (^)(JSONObj *))callback{
[[NSOperationQueue mainQueue] addOperation:/*do operation extension of NSOperation*/];
}
Here are specific invocations of _getRemote:callback: with 2 failed attempts.
OPTION # 1
-(void) getCustomer:(void (^)(JSONCustomerObj *))cb{
// I get a compile error
[self _getRemote:#"www.a.com/json" callback:cb];
}
If I do the above^ I get a compile error. If I wrap and cast like below, I get a zombie crash (probably because my insitchu function wasn't retained).
OPTION # 2
-(void) getCustomer:(void (^)(JSONCustomerObj *))cb{
// I get a runtime crash (non retained anonymous block?)
[self _getRemote:#"www.a.com/json" callback:^(JSONObj* rsp){
cb((JSONCustomerObj*)rsp);
}];
}
Either option is invoked like the following
#implementation MyAppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
[[Services sharedInstance] getCustomer:^(JSONCustomerObj* obj){
NSLog(#"name = %#",obj.name);
}];
return YES;
}
Here is a screenshot of how the crash manifests (note that it actually DOES NSLog out but crashes shortly after:
Any help would be greatly appreciated!
Use the exact definition of your method _getRemote:callback: and then cast your object like so :
JSONCustomerObj *customer = (JSONCustomerObj *)rsp
Thanks for the help everyone. Turns out (I think) my NSOperation subclass was garbage collected when the main method returned all while my callback block was being called asynchronously.
I fixed it like this; instead of calling completionCallbackBlock (which was in a property in my NSOperation subclass) at the end of my [NSOperation main] override, I used the setCompletionBlock function on NSOperation. Suddenly the crashing stopped. Here is what I ended up with.
-(void) _runRequest:(NSString*) url complete:(void(^)(JSONObj *response)) complete{
RequestOperation * operation = [[RequestOperation alloc] initWithURL:url];
__weak RequestOperation * weakOperation = operation;
[operation setCompletionBlock:^{
complete(weakOperation.responseObject);
}];
[[NSOperationQueue mainQueue] addOperation:operation];
}

setDelegate self and using delegates in ASINetworkQueue. Selector not found when request finished

I have the following class:
File_Downloadmanager.h:
#import "ASINetworkQueue.h"
#interface File_Downloadmanager : NSObject {
}
-(void)addRequestToDownloadQueue:(NSString*)objectID :(NSString*)userID :(NSString*)filename;
-(void)initDownloadQueue; // creates a new download queue and sets delegates
-(void)startDownload; // starts the download queue
-(void)requestFinished;
-(void)requestFailed;
-(void)queueFinished;
#property(retain) ASINetworkQueue *downloadQueue;
#end
File_Downloadmanager.m:
#implementation File_Downloadmanager
#synthesize downloadQueue;
-(void)initDownloadQueue{
NSLog(#"Init DownloadQueue");
// Stop anything already in the queue before removing it
[[self downloadQueue] cancelAllOperations];
[self setDownloadQueue:[ASINetworkQueue queue]];
[[self downloadQueue] setDelegate:self];
[[self downloadQueue] setRequestDidFinishSelector:#selector(requestFinished:)];
[[self downloadQueue] setRequestDidFailSelector:#selector(requestFailed:)];
[[self downloadQueue] setQueueDidFinishSelector:#selector(queueFinished:)];
[self downloadQueue].shouldCancelAllRequestsOnFailure = NO;
}
-(void)startDownload{
NSLog(#"DownloadQueue Go");
[downloadQueue go];
}
- (void)requestFinished:(ASIHTTPRequest *)request
{
// If no more elements are queued, release the queue
if ([[self downloadQueue] requestsCount] == 0) {
[self setDownloadQueue:nil];
}
NSLog(#"Request finished");
}
- (void)requestFailed:(ASIHTTPRequest *)request
{
// You could release the queue here if you wanted
if ([[self downloadQueue] requestsCount] == 0) {
[self setDownloadQueue:nil];
}
//... Handle failure
NSLog(#"Request failed");
}
- (void)queueFinished:(ASINetworkQueue *)queue
{
// You could release the queue here if you wanted
if ([[self downloadQueue] requestsCount] == 0) {
[self setDownloadQueue:nil];
}
NSLog(#"Queue finished");
}
-(void)addRequestToDownloadQueue:(NSString*)objectID :(NSString*)userID :(NSString*)filename{
...SourceCode for creating the request...
// add operation to queue
[[self downloadQueue] addOperation:request];
}
In another class a function is called an inside that function I'm doing the following:
-(void)downloadFiles{
File_Downloadmanager * downloadhandler = [[File_Downloadmanager alloc]init];
// initialize download queue
[downloadhandler initDownloadQueue];
for (int i = 0; i < [meetingObjects count]; i++) {
....some other code to get the objectID, userID, etc.
[downloadhandler addRequestToDownloadQueue:ID :[loginData stringForKey:#"userId"] :[NSString stringWithFormat:#"%#%#",currentObject.id,currentObject.name]]
}
[downloadhandler startDownload];
}
Everything works fine and the download begins. But when the first file is downloaded, I get an error in the ASINetworkQueue class that my selector "requestFinished" can't be called (I don't have the exact message, can't start the app at the moment, but the failure code was exc_bad_access code=1).
Is the time of declaration / initialization of my File_Downloadmanager object the problem? Because the function "downloadFiles" is called, the DownloadManager object created, the requests added and then the "downloadFiles" method returns because the queue works async?
I haven't used the ASI networking stuff before, but have seen lots of references to it on the net.
It sounds to me like the ASINetworkQueue class expects it's delegate to conform to a specific protocol. If it's set up correctly, you should get a warning when you try to assign yourself as the delegate of the ASINetworkQueue object but have not declared that your class conforms to the appropriate protocol. If you DO include a protocol declaration, then you should get a warning that you have not implemented required methods from that protocol.
Try cleaning your project and rebuilding, and then look carefully for warnings, specifically on your line:
[[self downloadQueue] setDelegate:self];
EDIT: I just downloaded one of the ASIHTTPRequest projects, and to my dismay, the delegate property of the ASINetworkQueue class does not have to conform to a specific protocol. This is bad programming style. If you set up a delegate, you should make the delegate pointer conform to a specific protocol.
Also, be aware that the ASI networking classes have not been maintained for several years now and are getting badly out of date. There are better alternatives out there, and you should look at moving to a different networking framework.
It looks like the downloadhandler object that ASINetworkQueue is attempting to send the requestFinished message to no longer exists at the time that the message is sent to it, as it's probably being deallocated when the downloadFiles method finishes executing. Instead of making the downloadhandler object local to the downloadFiles method, instead make it a (strong, nonatomic) property within the class that contains the downloadFiles method. That way, you can ensure that it will still exist when requestFinished is called.

block callback not being called from a unit test program in IOS

I have the following setup. I have a object called "View" in which I want to unit test a method which contains two dispatch_async calls with in it.
view.m
typedef void (^onTaskCompletion)(); //defining the block
-(void) viewdidLoad
{
onTaskCompletion block = ^{
// callback into the block };
[self test1:block];
}
-(void) test1:(onTaskCompletion) block
{
//do something
dispatch_async(queue, ^{
// dispatch async into serial queue & do something
dispatch_async(dispatch_get_main_queue){
// calling the block
block();
};
};
}
When I run the IOS APP , the block in -(void) viewdidLoad gets called. Works perfectly fine. But the problem I have is this:
in Tests : XCTestCase (.m fie)
#property (retain) View *view;
-(void) testMyCode
{
onTaskCompletion block = ^{
// Never gets called.
};
[view test1:block];
}
When I try to Unit test this method test1(), The block never gets called.
Note: The break point within test1() method inside the dispatch_get_main_queue() never gets hit when running in test mode but does get hit when I just run the app. Any thoughts as to why it works when the app is run normally but not when running unit tests?
The problem you are facing is that the tests continue onwards even though they are not finished. The solution is to stall the runloop until the async test if finished.
You can use this dead-simple open source macro WAIT_WHILE(<expression>, <time_limit>) found here https://github.com/hfossli/AGAsyncTestHelper
- (void)testAsyncBlockCallback
{
__block BOOL jobDone = NO;
[Manager doSomeOperationOnDone:^(id data) {
jobDone = YES;
}];
WAIT_WHILE(!jobDone, 2.0);
}
If you want to be able to unit test asynchronous code, you will need to wrap the dispatch_async in a class that you can mock. This class would have for example:
- (void)executeInBackground:(void(^)())task;
- (void)executeInForeground:(void(^)())task;
Then, during your tests you can mock this class. Instead of actually calling the tasks, collect the tasks when they are called, and manually have them executed in your test (not actually calling asynchronously):
- (void)executeNextBackgroundTask;
- (void)executeNextForegroundTask;
Then you can explicitly test each order of execution.

Objective-C concurrency processing

I've got class:
ClassX.m
#property (assign) BOOL wasProcessed;
-(void) methodA { //<- this can be called many times in short period of time
dispatch_async(dispatch_get_main_queue(), ^{
[self methodB];
});
}
- (void) methodB {
if (!self.wasProcessed) {
self.wasProcessed = YES;
//... some code
}
}
Since dispatch_async is used so a few calls to methodB can be processed concurrently at the same time and following code needs to be atomic:
if (!self.wasProcessed) {
self.wasProcessed = YES; //e.g two calls can enter here before setting YES and it would be bad because I want to process it only one time
How can those 2 lines be made atomic (checking and setting variable)? I dont want to make atomic code that is after "self.wasProcessed = YES;" so moving whole if to #synchronize(self) won't be good solution. If there is anything wrong with my thinking please point it out as I'm not very experienced in those topics, Thank you.
Try #synchronized. While the enclosed code is being executed on a thread, it will block other threads from executing it.
- (void) methodB {
#synchronized(self) {
if (!self.wasProcessed) {
self.wasProcessed = YES;
//... some code
}
}
}
-(void) methodA {
dispatch_async(dispatch_get_main_queue(), ^{
[[NSOperationQueue mainQueue] addOperationWithBlock:^(){
[self methodB];
}];
});
}
Your's methodB will be only called in main thread, so it will be never performed simultaneously.

Resources