CloudKit Query Time - objective-c

I've just started trialling CloudKit and am having some pretty slow query times. Here is some sample code I am using:
//CLOUDKIT
CKContainer *container = [CKContainer defaultContainer];
CKDatabase *privateDatabase = [container privateCloudDatabase];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"TRUEPREDICATE"];
CKQuery *query = [[CKQuery alloc] initWithRecordType:#"FlightLog" predicate:predicate];
[privateDatabase performQuery:query inZoneWithID:nil completionHandler:^(NSArray *results, NSError *error) {
//SUCCESS
if (!error)
{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"SUCCESS" message:#"IT WORKED" delegate:self cancelButtonTitle:#"dismiss" otherButtonTitles:nil];
[alert show];
NSLog(#"%#", #"fetchFlights success!");
NSLog(#"%#", self.fetchedRecords);
self.fetchedRecords = results;
[self.tableView reloadData];
}
//ERROR
else
{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"error" message:error.localizedDescription delegate:self cancelButtonTitle:#"dismiss" otherButtonTitles:nil];
[alert show];
NSLog(#"%#", error);
}
}];
I get the private database, and query for all records. There is just four simple ones I added in the dashboard.
Upon calling this code, I can see from my console log that the success message gets called almost immediately, with a null results array. Then moments later, the results are returned, as seen in the log. However, the alert view isn't shown and results displayed in my table for about 3-4 more seconds.
What's going on?

This is resolved. As Edwin mentions, I didn't know that the callback is on a background thread. So when I call [self.tableView reloadData] in the completion block, it is also running on the background thread.
By putting it back on the main thread, the table view reloads within about a second. Vs taking about 4-5 seconds if running on the same thread as the callback.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
});
});
Let me know if I have misunderstood, but I think that's what has happened.

Related

NSJSONSerialization handle returning array or dictionary

I am making a call to twitters API to load some tweets for a specific section of my app.
A small chunk of users are reporting a crash when loading the tweets view, while the rest have no problem at all.
I have submitted the code to Apple Tech Support and they responded letting me know that NSJSONSerialization can sometimes return a NSArray or NSDictionary.
Obviously it will throw an error is objectAtIndex: is called on an NSDictionary object, which I believe is the culprit for all of my users.
The partial solution is to detect if it is an Array or NSDictionary.
Here is where I am at now:
id feedData = [NSJSONSerialization JSONObjectWithData:responseData options:0 error:&jsonError];
if ([feedData isKindOfClass:[NSArray class]]) {
//Is array
} else if ([feedData isKindOfClass:[NSDictionary class]]) {
//is dictionary
}
I basically need an NSArray every single time. So in the is array block, I basically just use the feedData, but in NSDictionary, how can I convert it to an NSArray that will match the structure I need.
Honestly the biggest issue is that I cannot see what the NSDictionary structure looks like because none of my testing devices or simulator return the NSDictionary data, they all return an NSArray.
Here is what the entire getUserFeed method that sends the request to twitter looks like:
// Get the twitter feed
NSURL *requestURL = [NSURL URLWithString:TW_API_TIMELINE];
// Set up proper parameters
NSMutableDictionary *timelineParameters = [[NSMutableDictionary alloc] init];
[timelineParameters setObject:kNumTweets forKey:#"count"];
[timelineParameters setObject:#"1" forKey:#"include_entities"];
// Create the Social Request
SLRequest *postRequest = [SLRequest requestForServiceType:SLServiceTypeTwitter requestMethod:SLRequestMethodGET URL:requestURL parameters:timelineParameters];
postRequest.account = self.delegate.userAccount;
// Perform the request
[postRequest performRequestWithHandler:^(NSData *responseData, NSHTTPURLResponse *urlResponse, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
// Check if we reached the reate limit
if ([urlResponse statusCode] == 429) {
// Rate limit reached
// Display an alert letting the user know we have hit the rate limit
UIAlertView *twitterAlert = [[UIAlertView alloc] initWithTitle:kRateLimitTitle
message:kRateLimitMessage
delegate:nil
cancelButtonTitle:#"Ok"
otherButtonTitles:nil];
[twitterAlert show];
// Stop animating the pull to refresh if it is animating
[self.feedTableView.pullToRefreshView stopAnimating];
return;
}
// Check if there was an error
if (error) {
NSLog(#"Error: %#", error.localizedDescription);
// Stop animating the pull to refresh if it is animating
[self.feedTableView.pullToRefreshView stopAnimating];
return;
}
// Check if there is some response data
if (responseData) {
NSError *jsonError = nil;
id feedData = [NSJSONSerialization JSONObjectWithData:responseData options:NSJSONReadingMutableContainers error:&jsonError];
if ([feedData isKindOfClass:[NSArray class]]) {
//Is array
NSLog(#"It's an Array");
} else if ([feedData isKindOfClass:[NSDictionary class]]) {
//Is dictionary
NSLog(#"It's a Dictionary");
} else {
//is something else
}
if (!jsonError) {
[self gatherTweetsFromArray:feedData];
} else {
// Stop animating the pull to refresh if it is animating
[self.feedTableView.pullToRefreshView stopAnimating];
// Alert the user with the error
UIAlertView *twitterAlert = [[UIAlertView alloc] initWithTitle:kErrorTitle
message:kErrorMessage
delegate:nil
cancelButtonTitle:#"Ok"
otherButtonTitles:nil];
[twitterAlert show];
}
} else {
// Stop animating the pull to refresh if it is animating
[self.feedTableView.pullToRefreshView stopAnimating];
// Alert the user with the error
UIAlertView *twitterAlert = [[UIAlertView alloc] initWithTitle:kErrorTitle
message:kErrorMessage
delegate:nil
cancelButtonTitle:#"Ok"
otherButtonTitles:nil];
[twitterAlert show];
}
});
}];
This is a MAJOR bug and I need to squash it, so any ideas or information will be greatly appreciated! Thank you!

UIAlertView does not appear if requestAccessToEntityType fails

i'm making an app that adds events to the default calendar but i found a problem. This is the code used to make the app access to the calendar:
// create eventStore object.
EKEventStore *eventStore = [[EKEventStore alloc] init];
if([eventStore respondsToSelector:#selector(requestAccessToEntityType:completion:)])
{
[eventStore requestAccessToEntityType:EKEntityTypeEvent completion:^(BOOL granted, NSError *error)
{
if(granted)
{
// create an instance of event with the help of event-store object.
EKEvent *event = [EKEvent eventWithEventStore:eventStore];
// set the title of the event.
event.title = #"Event";
event.startDate = [[NSDate date] dateByAddingTimeInterval:86400];
event.endDate = [[NSDate date] dateByAddingTimeInterval:90000];
// set the calendar of the event. - here default calendar
[event setCalendar:[eventStore defaultCalendarForNewEvents]];
// store the event using EventStore.
NSError *err;
[eventStore saveEvent:event span:EKSpanThisEvent error:&err];
}
else {
UIAlertView *warningAlert = [[UIAlertView alloc] initWithTitle:#"Error" message:#"No permission to access!" delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil];
[warningAlert show];
}
}];
}
I supposed that if i got to general>privacy and set the access to calendar to "NO", whenever i click on the button that makes the action listed before, the program should skip in the "else" (founding the bool as false) that creates the alertView. But when i try this the program crashes not letting me do anything and if i press the home-button and then re-enter the app an empty alertView will appear (no title or message).
How can i solve this? i put the alertView in the wrong place?
Added info: this function is on a button that i click every time i mean to an event.
Have you tried showing the alert in main thread..
if (granted)
{
....
}
else {
dispatch_async(dispatch_get_main_queue(), ^{
UIAlertView *warningAlert = [[UIAlertView alloc] initWithTitle:#"Error" message:#"No permission to access!" delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil];
[warningAlert show];
});
}
Requesting permissions from user won't happen in main thread. You can do your UI stuff (Alert) by getting to the main thread. Hope it helps..

Allocating/showing a UIAlertView in a Block statement

I'm pretty new to blocks in objective C. I've read the docs and I have a pretty basic understanding of them.
Why won't this work? This is a framework callback for requesting Calendar access. It takes a block as an argument. All I want to do is allocate and show the UIAlertView in the block, but it will crash when it tries to show.
I hope this isn't a silly question... all the intro examples on the net using blocks just show trivial examples with counters.
//Request access
[eventStore requestAccessToEntityType:EKEntityTypeEvent completion:^(BOOL granted, NSError *error) {
if (granted == FALSE) {
UIAlertView *myAlert = [[[UIAlertView alloc]initWithTitle:#"Calendar Access Denied"
message:#"<InfoText>"
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil] autorelease];
[myAlert show];
}
else {
[self addToCalendar];
}
}];
have you tried?
if (granted == FALSE)
{
dispatch_async(dispatch_get_main_queue(), ^{
UIAlertView *myAlert = [[[UIAlertView alloc]initWithTitle:#"Calendar Access Denied"
message:# <InfoText>"
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil] autorelease];
[myAlert show];
});
}
this makes calls back in the main thread, useful for mixing blocks and UIKit

Setting variable with Grand Central Dispatch not retrievable

I'm trying to set and a NSURL using grand central dispatch, however it appears that the variable is set and accessible until you try to access it outside of the grand central dispatch block.
-(void)viewDidLoad {
[super viewDidLoad];
dispatch_queue_t backgroundQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(backgroundQueue,^{
self.ubiquitousURL = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil];
NSLog(#"ubiq inside: %#", self.ubiquitousURL);
if (self.ubiquitousURL) {
self.iCloudDocURL = [NSURL URLWithString:[NSString stringWithFormat:#"%#Documents", self.ubiquitousURL]];
self.iCloudDocString = [self.iCloudDocURL absoluteString];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(loadFiles) name: UIApplicationDidBecomeActiveNotification object:nil];
} else {
/* change to the main queue if you want to do something with the UI. For example: */
dispatch_async(dispatch_get_main_queue(),^{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Please enable iCloud" message:nil delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil];
[alert show];
});
}
});
NSLog(#"ubiq outside: %#", self.ubiquitousURL);
}
The first NSLog which, starts with ubiq inside returns the correct URL, while ubiq outside returns NULL. I'm using ARC, so no need to mention memory or anything similar... this is a GCD problem.
Do you know why self.ubiquitousURL is not accessible outside of the GCD block? Thanks.
You are making async call. So this line NSLog(#"ubiq outside: %#", self.ubiquitousURL); will get executed whether or not your code inside backgroundQueue is done.
You would see the outside log first then inside log.
dispatch_async means "run this later". Thus, the code inside the block doesn't run immediately; it runs at some later time, after the "outside" NSLog call has already been run. If you were to, for instance, put sleep(5) before the NSLog call, you would probably see the value. (You shouldn't really do that in the actual code, though; it would basically freeze the app for five seconds.)
If you want to run more code on the main queue after you've set that property, do something like this:
dispatch_async(backgroundQueue,^{
self.ubiquitousURL = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil];
NSLog(#"ubiq inside: %#", self.ubiquitousURL);
if (self.ubiquitousURL) {
self.iCloudDocURL = [NSURL URLWithString:[NSString stringWithFormat:#"%#Documents", self.ubiquitousURL]];
self.iCloudDocString = [self.iCloudDocURL absoluteString];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(loadFiles) name: UIApplicationDidBecomeActiveNotification object:nil];
// ************** NEW HOTNESS HERE **************
dispatch_async(dispatch_get_main_queue(),^{
NSLog(#"ubiq outside: %#", self.ubiquitousURL);
});
} else {
/* change to the main queue if you want to do something with the UI. For example: */
dispatch_async(dispatch_get_main_queue(),^{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Please enable iCloud" message:nil delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil];
[alert show];
});
}
});
Replace that NSLog line with a method call to do actual work if that's what you want to do once you've retrieved the iCloud URL.

GameKit Server/Client

I have been trying to implement the GameKit Framework for bluetooth connection and want to use a Server/Client relationship to reduce lag and be able to distinguish between to connected devices. I found this thread and it is similar to what I am trying to do, but the code doesn't work for me. Here is what I have:
Connect Method:
-(IBAction) btnConnect:(id) sender {
if(sender == server){
[self.currentSession initWithSessionID:#"BT" displayName:nil sessionMode:GKSessionModeServer];
currentSession.available == YES;
NSLog(#"Setup Server");
}else{
[self.currentSession initWithSessionID:#"BT" displayName:nil sessionMode:GKSessionModeClient];
currentSession.available == YES;
NSLog(#"Setup Client");
}
currentSession.delegate = self;
currentSession.disconnectTimeout = 0;
[currentSession setDataReceiveHandler:self withContext:nil];
[client setHidden:YES];
[server setHidden:YES];
[disconnect setHidden:NO];
}
didChangeState:
- (void)session:(GKSession *)session peer:(NSString *)peerID didChangeState:(GKPeerConnectionState)state {
NSLog(#"didChangeState was called with status: %#.", state);
switch (state)
{
case GKPeerStateConnected:
NSLog(#"connected");
break;
case GKPeerStateDisconnected:
NSLog(#"disconnected");
[self.currentSession release];
currentSession = nil;
[connect setHidden:NO];
[disconnect setHidden:YES];
break;
case GKPeerStateAvailable:
NSLog(#"Server is Available, Presenting UIALert...");
NSLog(#"%#", peerID);
peerName = [session displayNameForPeer:peerID];
NSLog(#"%#", peerName);
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Server Available!" message:[NSString stringWithFormat:#"The Server %# is Available, Would you like to Connect?", peerName] delegate:self cancelButtonTitle:#"Decline" otherButtonTitles:#"Accept", nil];
[alert show];
[alert release];
if(selection == #"accept"){
[session connectToPeer:peerID withTimeout:15];
session.available = NO;
}else{
}
break;
}
}
didReceiveConnectionRequest:
- (void)session:(GKSession *)session didReceiveConnectionRequestFromPeer:(NSString *)peerID{
NSLog(#"Recieved Connection Request");
NSLog(#"%#", peerID);
peerName = [session displayNameForPeer:peerID];
NSLog(#"%#", peerName);
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Connection Request" message:[NSString stringWithFormat:#"The Client %# is trying to connect.", peerName] delegate:self cancelButtonTitle:#"Decline" otherButtonTitles:#"Accept", nil];
[alert show];
[alert release];
if(selection == #"accept"){
[session acceptConnectionFromPeer:peerID error:nil];
}else{
[session denyConnectionFromPeer:peerID];
}
}
I think I have this all setup right, but the didChangeState isn't getting called to inform the user that another device is available. Am I missing something or should I try to use a different method. Thanks for any help
currentSession.disconnectTimeout = 0;
The disconnect timeout is a time in seconds that peers should wait before disconnecting unresponsive peers. You don't want this to be 0. The default is 20 seconds, you should leave it there or say like 10 seconds. I actually don't set this in my GameKit code and it works well.
Also, it might help to post your entire implementation class somewhere. We'll need to make sure you are implementing GKSessionDelegate, e.g.:
#interface SomeObject : NSObject <GKSessionDelegate>
Also, you're setting up a Peer-2-Peer above. You said you were trying to do client/server. If so you should start the client session with a mode of GKSessionModeClient and server as GKSessionModePeer.
Lastly...are you testing this on actual devices or with a device and simulator? Don't forget that simulator and first gen iPhones and touches do not support bluetooth. So you'll have to have everyone involved connected to the same wireless network for anything to happen.
What are you seeing in the console when you start up your debug session?