Is it something wrong with the requestPanoramaNearCoordinate Google maps SDK method? cause it get´s stuck in the while loop. I´v written the loop cause I want to wait with executing the rest of the method until the asynchronous callback method has completed. But the the while loop loops infinitely. Is it my code that´s simply wrong?
__block GMSPanorama *panPhoto = nil;
__block BOOL finished = NO;
[self.panoService requestPanoramaNearCoordinate:ranLatLng callback:^(GMSPanorama *panorama, NSError *error) {
NSLog(#"panorama: %# error: %#", panorama, error);
panPhoto = panorama;
finished = YES;
}];
while (!finished) {
// Do nothing);
}
if (!panPhoto) return [self randomLatitudeLongitude];
return ranLatLng;
}
Why you launch async method and then doing loop? You must add block (with GMSPanorama argument) as parameter to your method and call this block inside callback:^(GMSPanorama *panorama, NSError *error){
Smth like that:
- (void) methodNameWithBlock:(BlockName)block;
__block GMSPanorama *panPhoto = nil;
__block BOOL finished = NO;
[self.panoService requestPanoramaNearCoordinate:ranLatLng callback:^(GMSPanorama *panorama, NSError *error) {
NSLog(#"panorama: %# error: %#", panorama, error);
panPhoto = panorama;
finished = YES;
BlockName handler = [block copy];
if (!ranLatLng){
handler([self randomLatitudeLongitude])
} else {
handler(ranLatLng)
}
}];
}
Related
I understand that this function first return "images" then "findObjectsInBackgroundWithBlock" retrieve data that's why results is nil.
1 - how to return array from block?
2 - how to put this block not in main thread?
+(NSMutableArray *)fetchAllImages{
__block NSMutableArray *images = [NSMutableArray array];
PFQuery *query = [PFQuery queryWithClassName:#"Photo"];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
for (PFObject *object in objects) {
PFFile *applicantResume = object[#"imageFile"];
NSData *imageData = [applicantResume getData];
NSString *imageName = [ImageFetcher saveImageLocalyWithData:imageData FileName:object.objectId AndExtention:#"png"];
[images addObject:imageName];
// here images is not empty
}
} else {
NSLog(#"Error: %# %#", error, [error userInfo]);
}
}];
// here images is empty
return images;
}
The method performs its work asynchronously, and the caller needs to know that. So,
Do not:
+(NSMutableArray *)fetchAllImages{
return an array, because the array is not ready at the time of return.
Do:
+ (void)fetchAllImages {
return nothing, because that's what you have when the method finishes execution.
But how to give the images to the caller? The same way that findObjectsInBackgroundWithBlock does, with a block of code that runs later....
Do:
+ (void)fetchAllImagesWithBlock:(void (^)(NSArray *, NSError *)block {
Then, using your code from within the findBlock:
[images addObject:imageName];
// here images is not empty
// good, so give the images to our caller
block(images, nil);
// and from your code, if there's an error, let the caller know that too
NSLog(#"Error: %# %#", error, [error userInfo]);
block(nil, error);
Now your internal caller calls this method just like your fetch code calls parse:
[MyClassThatFetches fetchAllImagesWithBlock:^(NSArray *images, NSError *error) {
// you can update your UI here
}];
Regarding your question about the main thread: you want the network request to run off the main, and it does. You want the code that runs after it finishes to run ON the main, so you can safely update the UI.
It doesn't work that way.
You are calling an asynchronous method. You can't wait for the result of an asynchronous method and return the result (well, you can, but not if you are asking how to do it on stackoverflow). With an asynchronous block, you trigger an action, and it is up to the completion block to deliver the results where they are needed.
There are gazillions of examples how to do this on stackoverflow. Looking for them is your job.
I have 2 performance test methods in test class. If i run them separately they pass. If i run hole class methods they fail with message:
**** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'API violation - multiple calls made to -[XCTestExpectation fulfill].'*
Is there any way to include couple performance tests in 1 class?
here is sample code:
- (void)testPerformanceAuthenticateWithLogin {
XCTestExpectation *authenticateExpectation = [self expectationWithDescription:#"Authenticate With Login"];
__block int userID = 0;
[self measureBlock:^{
[AuthenticationService authenticateWithLogin:email password:password success:^(AuthenticationResponse *result) {
XCTAssert(result.isAuthenticated);
userID = result.userID;
[authenticateExpectation fulfill];
} failure:^(NSError *error) {
XCTAssertNil(error);
}];
}];
[self waitForExpectationsWithTimeout:3 handler:^(NSError *error) {
XCTAssertNil(error);
[AuthenticationService logoutWithServicePersonID:userID success:nil failure:nil];
}];
}
- (void)testPerformanceGetServicePersonByID {
XCTestExpectation *getServicePersonExpectation = [self expectationWithDescription:#"get Service Person By ID"];
__block int userID = 0;
[AuthenticationService authenticateWithLogin:email password:password success:^(AuthenticationResponse *result) {
userID = result.userID;
[self loginSuccess:result];
[self measureBlock:^{
[ServicePersonService getServicePersonByIDWithServicePersonID:userID success:^(ServicePersonDTO *result) {
XCTAssertNotNil(result);
[getServicePersonExpectation fulfill];
} failure:^(NSError *error) {
XCTAssertNil(error);
}];
}];
} failure:^(NSError *error) {
XCTAssertNil(error);
}];
[self waitForExpectationsWithTimeout:3 handler:^(NSError *error) {
XCTAssertNil(error);
[AuthenticationService logoutWithServicePersonID:userID success:nil failure:nil];
}];
}
The measureBlock actually runs the code inside the block multiple times to determine an average value.
If I understand correctly you want to measure how long does the authentication take. If that's the case:
you could put the expectation inside the measure block so every time a measure iteration is done, the test iteration will wait for the expectation to be fulfilled before executing the block again
you could bump up the expectation timeout a bit just in case.
I've done something like this (which worked for me):
- (void)testPerformanceAuthenticateWithLogin {
[self measureBlock:^{
__block int userID = 0;
XCTestExpectation *authenticateExpectation = [self expectationWithDescription:#"Authenticate With Login"];
[AuthenticationService authenticateWithLogin:email password:password success:^(AuthenticationResponse *result) {
XCTAssert(result.isAuthenticated);
userID = result.userID;
[authenticateExpectation fulfill];
} failure:^(NSError *error) {
XCTAssertNil(error);
}];
[self waitForExpectationsWithTimeout:10 handler:^(NSError *error) {
XCTAssertNil(error);
[AuthenticationService logoutWithServicePersonID:userID success:nil failure:nil];
}];
}];
}
I have the following snippet of code below that fetches data from Parse using PFQueues in the background and returns data and a status. This structure is based off of waiting for the dispatch_group_t to notify that's it's completed all entered groups. Unfortunately dispatch_group_notify(downloadGroup, dispatch_get_main_queue(), ^{
is called before the completion blocks call dispatch_group_leave. By the time the dispatch_group_leave() is called on any of the completion blocks, an EXC_BAD_INSTRUCTION is thrown. I've attached an image below for the instruction error. Does anyone know if I'm doing something wrong or if Parse has some annoyances that prevent me from using this method?
- (void)downloadAndCacheObjectsWithCompletion:(void (^)(NSError *))callback
{
__block NSError *downloadError1;
__block NSError *downloadError2;
__block NSError *downloadError3;
__block NSError *downloadError4;
NSLog(#"%#", NSStringFromSelector(_cmd));
dispatch_group_t downloadGroup = dispatch_group_create();
dispatch_group_enter(downloadGroup);
[self fetchDataWithCompletion:^(NSArray *artwork, NSError *error) {
downloadError1 = error;
dispatch_group_leave(downloadGroup);
}];
dispatch_group_enter(downloadGroup);
[self fetchDataWithCompletion:^(NSArray *artworkPhotos, NSError *error) {
downloadError2 = error;
dispatch_group_leave(downloadGroup);
}];
dispatch_group_enter(downloadGroup);
[self fetchDataWithCompletion:^(NSArray *artists, NSError *error) {
downloadError3 = error;
dispatch_group_leave(downloadGroup);
}];
dispatch_group_enter(downloadGroup);
[self fetchDataWithCompletion:^(NSArray *badges, NSError *error) {
downloadError4 = error;
dispatch_group_leave(downloadGroup);
}];
dispatch_group_notify(downloadGroup, dispatch_get_main_queue(), ^{
NSError *returnError;
if (downloadError1 || downloadError2 || downloadError3 || downloadError4) {
returnError = [[NSError alloc] initWithDomain:#"ParseFactory" code:-1 userInfo:#{NSLocalizedDescriptionKey: #"There was an error retrieving the content"}];
}
if (callback) {
callback(returnError);
}
});
}
- (void)fetchDataWithCompletion:(void(^)(NSArray *data, NSError *error))callback
{
NSLog(#"Fetching Data");
if ([self.cachedData objectForKey:kDataClassName]) {
if (callback) {
callback([self.cachedData objectForKey:kDataClassName], nil);
}
return;
}
PFQuery *dataQueue = [PFQuery queryWithClassName:kDataClassName];
dataQueue.cachePolicy = kPFCachePolicyCacheThenNetwork;
[dataQueue findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
[self.cachedData setObject:objects forKey:kDataClassName];
} else {
NSLog(#"Fetching Data Error: %#", error);
}
if (callback) {
callback(objects, error);
}
}];
}
The download process listed above is called from AppDelegate as such
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
//Register PFObject subclasses
[Data registerSubclass];
[Parse setApplicationId:#"appkey" clientKey:#"clientkey"];
[[ParseFactory sharedInstance] downloadAndCacheObjectsWithCompletion:^(NSError *error) {
}];
return YES;
}
Stack trace:
The error you're seeing indicates that your program calls dispatch_group_leave too many times. It's trivial to reproduce. I reproduced it with this program:
int main(int argc, const char * argv[])
{
#autoreleasepool {
dispatch_group_t group = dispatch_group_create();
dispatch_group_leave(group);
}
return 0;
}
Therefore I deduce that your fetchDataWithCompletion: method calls its completion block more than once. If you can't figure out why, edit your question to include the source code of that method (and any related methods or declarations).
I come in late, but it seems clear your issue comes from the kPFCachePolicyCacheThenNetwork.
Parse will call the completion block twice, one with cached data (even the 1st time), one with downloaded data... so your dispatch_group_leave will be called twice as much as your dispatch_group_enter.
I am trying to create a method that will return me a ALAsset for a given asset url. (I need upload the asset later and want to do it outside the result block with the result.)
+ (ALAsset*) assetForPhoto:(Photo*)photo
{
ALAssetsLibrary* library = [[[ALAssetsLibrary alloc] init] autorelease];
__block ALAsset* assetToReturn = nil;
NSURL* url = [NSURL URLWithString:photo.assetUrl];
NSLog(#"assetForPhoto: %#[", url);
[library assetForURL:url resultBlock:^(ALAsset *asset)
{
NSLog(#"asset: %#", asset);
assetToReturn = asset;
NSLog(#"asset: %# %d", assetToReturn, [assetToReturn retainCount]);
} failureBlock:^(NSError *error)
{
assetToReturn = nil;
}];
NSLog(#"assetForPhoto: %#]", url);
NSLog(#"assetToReturn: %#", assetToReturn); // Invalid access exception coming here.
return assetToReturn;
}
The problem is assetToReturn gives an EXC_BAD_ACCESS.
Is there some problem if I try to assign pointers from inside the block? I saw some examples of blocks but they are always with simple types like integers etc.
A few things:
You must keep the ALAssetsLibrary instance around that created the ALAsset for as long as you use the asset.
You must register an observer for the ALAssetsLibraryChangedNotification, when that is received any ALAssets you have and any other AssetsLibrary objects will need to be refetched as they will no longer be valid. This can happen at any time.
You shouldn't expect the -assetForURL:resultBlock:failureBlock:, or any of the AssetsLibrary methods with a failureBlock: to be synchronous. They may need to prompt the user for access to the library and will not always have their blocks executed immediately. It's better to put actions that need to happen on success in the success block itself.
Only if you absolutely must make this method synchronous in your app (which I'd advise you to not do), you'll need to wait on a semaphore after calling assetForURL:resultBlock:failureBlock: and optionally spin the runloop if you end up blocking the main thread.
The following implementation should satisfy as a synchronous call under all situations, but really, you should try very hard to make your code asynchronous instead.
- (ALAsset *)assetForURL:(NSURL *)url {
__block ALAsset *result = nil;
__block NSError *assetError = nil;
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
[[self assetsLibrary] assetForURL:url resultBlock:^(ALAsset *asset) {
result = [asset retain];
dispatch_semaphore_signal(sema);
} failureBlock:^(NSError *error) {
assetError = [error retain];
dispatch_semaphore_signal(sema);
}];
if ([NSThread isMainThread]) {
while (!result && !assetError) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
}
else {
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
}
dispatch_release(sema);
[assetError release];
return [result autorelease];
}
You should retain and autorelease the asset:
// ...
assetToReturn = [asset retain];
// ...
return [assetToReturn autorelease];
I have a set of asynchronous calls being spawned using NSInvocationOperation:
- (void)listRequestQueue:(StoreDataListRequest *)request {
[openListRequests addObject:request];
NSInvocationOperation *requestOp = [[NSInvocationOperation alloc]
initWithTarget:self
selector:#selector(listRequestStart:)
object:request];
[opQueue addOperation:requestOp];
[requestOp release];
}
- (void)listRequestStart:(StoreDataListRequest *)request {
if(self.resourceData == nil) {
//TODO fail appropriately...
return;
}
StoreDataListResponse *response = [self newListResponseForProductID:request.productID];
[self performSelectorOnMainThread:#selector(listRequestFinish:)
withObject:response waitUntilDone:NO];
[response release];
[self performSelectorOnMainThread:#selector(cleanUpListRequest:)
withObject:request waitUntilDone:NO];
}
- (void)listRequestFinish:(StoreDataListResponse *)response {
[self.delegate storeData:self didReceiveListResponse:response];
}
- (StoreDataListResponse *)newListResponseForProductID:(NSString *)productID {
CollectionData *data = [self.resourceData dataForProduct:productID];
if(data == nil) {
//TODO do something
}
StoreDataListResponse *response = [[StoreDataListResponse alloc] init];
response.productID = productID;
if(productID != data.productID) {
//TODO fail; remove product from list
}
response.name = NSLocalizedString(#"Loading...", #"Loading message");
response.blurb = NSLocalizedString(#"Waiting for response from server", #"Waiting for website to respond");
return response;
}
For each of the TODOs in the above code, I should resolve the issue and let any handlers know that things have failed and why. Looking at the NSError class and documentation, it appears to be the appropriate answer but I am having trouble figuring out how to get to work with NSInvocationOperation and performSelectorOnMainThread:withObject:waitUntilDone:. I can certainly get the NSErrors out of the newListResponseForProductID: method by changing it to something like this:
- (StoreDataListResponse *)newListResponseForProductID:(NSString *)productID error:(NSError **)error;
How do I get the error generated back into the main thread so I can deal with the failed request?
The easiest way to run any code on the main thread is to use GCD and blocks on iOS 4 and above. Like this:
dispatch_async(dispatch_get_main_queue(), ^(void) {
// Your code to run on the main thread here,
// any variables in scope like NSError is captured.
});
Or use dispatch_sync() if you know you are on a background thread and want the block to complete on the main thread before you continue.