When running this code...
-(IBAction)loginWithTwitter:(id)sender {
NSLog(#"Logging in with twitter");
ACAccountStore *accountStore = [[ACAccountStore alloc]init];
ACAccountType *accountType = [accountStore accountTypeWithAccountTypeIdentifier:ACAccountTypeIdentifierTwitter];
[accountStore requestAccessToAccountsWithType:accountType options:nil completion:^(BOOL granted, NSError *error) {
if (error) {
[self showError:error];
return;
}
if (granted) {
NSArray *accountsArray = [accountStore accountsWithAccountType:accountType];
if ([accountsArray count] > 1) {
NSLog(#"Multiple twitter accounts");
}
ACAccount *twitterAccount = [accountsArray objectAtIndex:0];
NSLog(#"%#", twitterAccount);
[self pushMainController];
}
}];
}
There is a 5-10 second delay before the pushMainControlleris actually called, even though the account information is logged almost immediately (after pre-authorization). If I move the pushMainController call after the block however, it happens immediately, the only problem being that a user isn't necessarily logged in at that point. I understand that it can take a second for a block to have a response due to variables such as network connectivity, but can someone help me understand this?
The completion block is not being done on the main queue. You need to ensure your UI code gets done on the main thread:
[accountStore requestAccessToAccountsWithType:accountType options:nil completion:^(BOOL granted, NSError *error) {
if (error) {
[self showError:error];
return;
}
if (granted) {
NSArray *accountsArray = [accountStore accountsWithAccountType:accountType];
if ([accountsArray count] > 1) {
NSLog(#"Multiple twitter accounts");
}
ACAccount *twitterAccount = [accountsArray objectAtIndex:0];
NSLog(#"%#", twitterAccount);
dispatch_async(dispatch_get_main_queue(), ^{
[self pushMainController];
});
}
}];
You may have to wrap the showError call as well.
Related
I'm working on an app for iPad iOS 8, and I need to make my app wait for an answer from:
[directions calculateETAWithCompletionHandler:^(MKETAResponse *response, NSError *error) {}]
This method is inside 3 loops. I tried dispatch_semaphore_t, but the app can't continue after this line:
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
and gets stuck. I tried with dispatch_group_t and got the same result.
I guess that I'm doing something wrong, but I don't know what. I tried to search SO for similar problems and but found nothing. Can someone explain how I could accomplish this?
-(void)setTimesMissions {
for (Driver *d in self.dataList) {
for (Period *p in d.periods) {
for (Mission *m in p.missions) {
MKDirections *directions = ....
// HERE i want the for loop stop until this completionHandler finish
[directions calculateETAWithCompletionHandler:^(MKETAResponse *response, NSError *error) {
//and when he finish here continue
}];
}
}
}
}
Call your method in dispatch_async block.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[youClassInstance setTimesMissions];
});
And use dispatch_semaphore_wait in your loop
- (void)setTimesMissions {
Mission *home = [[Mission alloc]init];
Mission *next = [[Mission alloc]init];
for (Driver *d in self.dataList) {
home.clientLat = d.centralPointLat;
home.clientLon = d.centralPointLon;
home.clientPaddres = d.centralAddress;
for (Period *p in d.periods) {
home.times = [[NSMutableArray alloc]init];
if ([p.periodIx isEqualToString:self.thisPeriodIX]) {
for (Mission *m in p.missions) {
Mission *source = home;
Mission *destination = m ;
MKPlacemark *placemarkSource = [[MKPlacemark alloc] initWithCoordinate:CLLocationCoordinate2DMake([source.clientLat doubleValue], [source.clientLon doubleValue]) addressDictionary:nil] ;
MKMapItem *mapItemSource = [[MKMapItem alloc] initWithPlacemark:placemarkSource];
MKPlacemark *placemarkDestination = [[MKPlacemark alloc] initWithCoordinate:CLLocationCoordinate2DMake([destination.clientLat doubleValue], [destination.clientLon doubleValue])addressDictionary:nil] ;
MKMapItem *mapItemDestination = [[MKMapItem alloc] initWithPlacemark:placemarkDestination];
MKDirectionsRequest *directionsRequest = [[MKDirectionsRequest alloc] init];
[directionsRequest setSource:mapItemSource];
[directionsRequest setDestination:mapItemDestination];
directionsRequest.transportType = MKDirectionsTransportTypeAutomobile;
[directionsRequest setRequestsAlternateRoutes:NO];
MKDirections *directions = [[MKDirections alloc] initWithRequest:directionsRequest];
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
__block double timeTo;
[directions calculateETAWithCompletionHandler:^(MKETAResponse *response, NSError *error) {
if ( response.expectedTravelTime) {
timeTo = response.expectedTravelTime;
double ans = timeTo;
Time *t = [[Time alloc]init];
t.ix = m.serviceIX;
t.time = ans;
[home.times addObject:t];
}
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
}
dispatch_async(dispatch_get_main_queue(), ^{
// code that should be executed on main queue
});
if (next.clientPaddres) {
home = next;
}
}
}
}
}
Whenever run the below code in my IOS app, it just magically stops running when I make a NSMutableSet a value for a PFFobject Key (line marked with "TROUBLE" below). If I put a break point, the computer just doesn't get to the next break point. I even added a column in my Parse dashboard, where "peopleWhoFavorited" is an Object type, the problem still occurs. Why? Code:
BOOL POSTING; //prevents repitious saving when user presses button multiple times.
-(void)post {
if (!(self.data==nil) && ![self.postField.text isEqualToString:#""] && !POSTING && ![self.postField.textColor isEqual:[UIColor grayColor]]) { //hackey with that grey color, but oh well.
POSTING=YES;
PFFile *file = [PFFile fileWithName:#"data" data:self.data];
[file saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if (succeeded) {
PFObject *post = [PFObject objectWithClassName:#"post"];
[post setObject:#(0) forKey:#"voteScore"];
[post setObject:#(0) forKey:#"numTimesReported"];
[post setObject:#[] forKey:#"whoReported"];
[post setObject:#[] forKey:#"comments"];
//And now, the favorited sets and upvote and downvote sets...
NSMutableSet * favoritedSet=[[NSMutableSet alloc] init];
[post setObject:favoritedSet forKey:#"peopleWhoFavorited"]; "TROUBLE" //magical stopping point
NSMutableSet * peopleWhoUpVoted=[[NSMutableSet alloc] init];
//[post setObject:peopleWhoUpVoted forKey:#"peopleWhoUpVoted"];
NSMutableSet * peopleWhoDownVoted=[[NSMutableSet alloc] init];
//[post setObject:peopleWhoDownVoted forKey:#"peopleWhoDownVoted"];
//
//And a set for the people who report the post.
NSMutableSet * peopleWhoReported=[[NSMutableSet alloc] init];
//[post setObject:peopleWhoReported forKey:#"peopleWhoReported"];
//
[post setObject:self.postField.text forKey:#"postName"];
[post setObject:self.dataTypeString forKey:#"postType"]; //either a movie or an image
[post setObject:[NSDate date] forKey:#"dateMade"]; //hackey, equivalent of createdAt to avoid weird behaivor
[post setObject:file forKey:#"data"];
NSString * deviceIDString=[[[UIDevice currentDevice] identifierForVendor] UUIDString];
[post setObject:deviceIDString forKey:#"deviceThatPosted"];
[PFGeoPoint geoPointForCurrentLocationInBackground:^(PFGeoPoint *geoPoint, NSError *error) {
if (!error) {
// set the location of the post.
[post setObject:geoPoint forKey:#"location"];
//save the post in the background
[post saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if (succeeded) {
NSLog(#"Saved.");
[self dismissViewControllerAnimated:YES completion:^{
NSLog(#"Posted and Dismiss completed");
}];
POSTING=NO;
} else {
NSLog(#"%#", error);
POSTING=NO;
}
}];
} else{
NSLog(#"%#", error);
POSTING=NO;
}
}];
} else {
NSLog(#"%#", error);
POSTING=NO;
}
}];
}
}
I understand why, but the ambiguity of the errors that HealthKit puts out is a total black box. Why am I getting the error:
An error occurred while adding a sample to the workout: The operation couldn’t be completed.
I've been scouring the web for examples, but most of them are in swift. :(
Here's my code:
- (NSSet *)dataTypesToRead {
HKQuantityType *heartRate = [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierHeartRate];
return [NSSet setWithObjects:heartRate, nil];
}
- (NSSet *)dataTypesToWrite {
HKWorkoutType* workout = [HKWorkoutType workoutType];
HKQuantityType *energyBurnedType = [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierActiveEnergyBurned];
return [NSSet setWithObjects:workout, energyBurnedType, nil];
}
- (void)saveWorkout {
HKHealthStore* healthStore = [[HKHealthStore alloc] init];
NSDate* timeOfWorkout = [NSDate date];
HKWorkoutType* type = [HKWorkoutType workoutType];
[healthStore requestAuthorizationToShareTypes:[self dataTypesToWrite]
readTypes:[self dataTypesToRead]
completion:^(BOOL success, NSError *error) {
if(success == YES)
{
// This sample uses hard-coded values and performs all the operations inline
// for simplicity's sake. A real-world app would calculate these values
// from sensor data and break the operation up using helper methods.
HKQuantity *energyBurned =
[HKQuantity quantityWithUnit:[HKUnit kilocalorieUnit]
doubleValue:333.0];
HKQuantity *distance =
[HKQuantity quantityWithUnit:[HKUnit mileUnit]
doubleValue:0.0];
// Provide summary information when creating the workout.
HKWorkout *workout = [HKWorkout workoutWithActivityType:HKWorkoutActivityTypeTraditionalStrengthTraining
startDate:timeOfWorkout
endDate:timeOfWorkout
duration:0
totalEnergyBurned:energyBurned
totalDistance:distance
metadata:nil];
// Save the workout before adding detailed samples.
[healthStore saveObject:workout withCompletion:^(BOOL success, NSError *error) {
if (!success) {
// Perform proper error handling here...
NSLog(#"*** An error occurred while saving the "
#"workout: %# ***", error.localizedDescription);
abort();
}
}];
// Add optional, detailed information for each time interval
NSMutableArray *samples = [NSMutableArray array];
HKQuantityType *energyBurnedType =
[HKObjectType quantityTypeForIdentifier:
HKQuantityTypeIdentifierActiveEnergyBurned];
[samples addObject:energyBurnedType];
// Add all the samples to the workout.
[healthStore
addSamples:samples
toWorkout:workout
completion:^(BOOL success, NSError *error) {
if (!success) {
// Perform proper error handling here...
NSLog(#"*** An error occurred while adding a "
#"sample to the workout: %# ***",
error.localizedDescription);
abort();
}
}];
}
else
{
// Determine if it was an error or if the
// user just canceld the authorization request
}
}];
}
Here I find 2 issues,
Your trying to add samples to a workout before saving the workout in HealthKit.
Samples array should contain objects of type HKQuantitySample or HKCategorySample.
This is working fine...
- (NSSet *)dataTypesToRead {
HKQuantityType *heartRate = [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierHeartRate];
return [NSSet setWithObjects:heartRate, nil];
}
- (NSSet *)dataTypesToWrite {
HKWorkoutType* workout = [HKWorkoutType workoutType];
HKQuantityType *energyBurnedType = [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierActiveEnergyBurned];
return [NSSet setWithObjects:workout, energyBurnedType, nil];
}
- (void)saveWorkout {
HKHealthStore* healthStore = [[HKHealthStore alloc] init];
NSDate* timeOfWorkout = [NSDate date];
[healthStore requestAuthorizationToShareTypes:[self dataTypesToWrite]
readTypes:[self dataTypesToRead]
completion:^(BOOL success, NSError *error) {
if(success == YES)
{
// This sample uses hard-coded values and performs all the operations inline
// for simplicity's sake. A real-world app would calculate these values
// from sensor data and break the operation up using helper methods.
HKQuantity *energyBurned =
[HKQuantity quantityWithUnit:[HKUnit kilocalorieUnit]
doubleValue:333.0];
HKQuantity *distance =
[HKQuantity quantityWithUnit:[HKUnit mileUnit]
doubleValue:0.0];
// Provide summary information when creating the workout.
HKWorkout *workout = [HKWorkout workoutWithActivityType:HKWorkoutActivityTypeTraditionalStrengthTraining
startDate:timeOfWorkout
endDate:timeOfWorkout
duration:0
totalEnergyBurned:energyBurned
totalDistance:distance
metadata:nil];
// Save the workout before adding detailed samples.
[healthStore saveObject:workout withCompletion:^(BOOL success, NSError *error) {
if (!success) {
// Perform proper error handling here...
NSLog(#"*** An error occurred while saving the "
#"workout: %# ***", error.localizedDescription);
// abort();
}
else
{
// Add optional, detailed information for each time interval
NSMutableArray *samples = [NSMutableArray array];
HKQuantityType *energyBurnedType =
[HKObjectType quantityTypeForIdentifier:
HKQuantityTypeIdentifierActiveEnergyBurned];
HKQuantity *energyBurnedQuantity = [HKQuantity quantityWithUnit:[HKUnit kilocalorieUnit] doubleValue:334];
HKQuantitySample *energyBurnedSample = [HKQuantitySample quantitySampleWithType:energyBurnedType quantity:energyBurnedQuantity startDate:[NSDate date] endDate:[NSDate date]];
[samples addObject:energyBurnedSample];
// Add all the samples to the workout.
[healthStore
addSamples:samples
toWorkout:workout
completion:^(BOOL success, NSError *error) {
if (!success) {
// Perform proper error handling here...
NSLog(#"*** An error occurred while adding a "
#"sample to the workout: %# ***",
error.localizedDescription);
// abort();
}
}];
}
}];
}
else
{
// Determine if it was an error or if the
// user just canceld the authorization request
}
}];
}
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.
When I run [_accountStore requestAccessToAccountsWithType: ...] I am getting no response. My completion handler isn't being called. However I do have access to twitter.
// Step 0: Check that the user has local Twitter accounts
if ([SLComposeViewController
isAvailableForServiceType:SLServiceTypeTwitter]) {
NSLog(#"OH YES!!!");
// Step 1: Obtain access to the user's Twitter accounts
ACAccountType *twitterAccountType = [_accountStore accountTypeWithAccountTypeIdentifier: ACAccountTypeIdentifierTwitter];
NSLog(#"OH YES!!!");
[_accountStore requestAccessToAccountsWithType:twitterAccountType options:nil
completion:^(BOOL granted, NSError *error)
{
NSLog(#"Im in!");
if (granted == YES)
{
NSArray *arrayOfAccounts = [_accountStore
accountsWithAccountType:twitterAccountType];
if ([arrayOfAccounts count] > 0)
{
ACAccount *twitterAccount = [arrayOfAccounts lastObject];
NSLog(#"%#", twitterAccount.userFullName);
NSLog(#"%#", twitterAccount.username);
NSLog(#"%#", twitterAccount.credential);
NSLog(#"%#", twitterAccount.identifier);
}
}else{
NSLog(#"Failed muahahaha");
}
}];
}
Why is this happening?
Make shure _accountStore is initialized for example with
_accountStore = [[ACAccountStore alloc] init];