Multiple contexts , create only if not exists - ios

I have a view controller that is performing an afnetworking request , and upon the response , I am adding that response to my main MOC , and saving it.
My app is a realtime app , and so , performing certain kinds of requests will send messages from server to client via a long lived web socket. What happens is ,when a client sends a single message , he will receive the same message via socket (and I am going to add apns functionality later on, so he will receive the same functionality via apns as well)
I want the web socket to use a background context, so my UI is taxed less.
Right now I am performing a fetch to see if data already exists. If it does not, then I am adding to the context. This check is being done on the socket side as well as the api side.
My concern is , since they are both going to be checked at such close time intervals to each other, is it possible for them to both say that data doesn't exist, and then both will add to the db, causing an unwanted duplicate?
This is my code for clarification:
if ([Chat fetchById:[jsonBody valueForKey:#"id"] withContext:self.managedObjectContext]==nil) {
NSLog(#"GOT NIL FROM SOCKET");
Chat *chat = [Chat initWithContext:self.managedObjectContext];
chat.id = [jsonBody valueForKey:#"id"];
chat.body = [jsonBody valueForKey:#"body"];
chat.user = [User fetchByID:[jsonBody valueForKey:#"user_id"] inContext:self.managedObjectContext];
NSString *date = [jsonBody valueForKey:#"created_at"];
chat.created_at = [TimeFormatter NSDateFromServerString:date];
chat.group = [Group fetchByID:[jsonBody valueForKey:#"grp_id"] inContext:self.managedObjectContext];
[self.managedObjectContext save:nil];
}

Related

How to sync contacts with server contacts in shortest iteration and faster?

I have created one application the same as WhatsApp to chat with the same industry people and my basic concept is to sync user contact and find a user who is using this app and users can chat with each other.
Contact sync I have done in my application and its working fine till 100 to 500 contacts but if any user has 2000 to 3000 contacts in his contact book its taking time to sync with the server.
I am using the below code to get user contacts and sent them to the server.
ABAddressBookRef addressBookRef = ABAddressBookCreateWithOptions(NULL, nil);
NSArray *allContacts = (__bridge NSArray *)ABAddressBookCopyArrayOfAllPeople(addressBookRef);
NSMutableArray *contacListAddIntoDatabase = [NSMutableArray new];
for (id record in allContacts) {
ABRecordRef thisContact = (__bridge ABRecordRef)record;
//save Contact in DataBase.
}
After saving all records in the local database i am sending that record to the server to check if that contact is a user of my app and get backlist of users who are using my app. I have implemented paging in contactsync service.
Here is my contact sync to server code :
if ([[APP_DELEGATE contactArrayForSync] count] > 0) {
int long pageSize = ([[APP_DELEGATE contactArrayForSync] count] > CONTACT_SYNC_PAGE_SIZE ) ? CONTACT_SYNC_PAGE_SIZE : [[APP_DELEGATE contactArrayForSync] count];
NSError *error;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:contactList
options:0
error:&error];
NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
[[APP_DELEGATE apiManager] syncUserContacts:[APP_DELEGATE getEncryptedUserID] andJSONString:jsonString withCallback:^(BOOL success, id responseObject) {
//Update DataBase as per server response
}];
}
to sync 2000 contacts it's taking around 2min to sync and its very large amount of time to sync contacts.
I want to reduce this time so is there any way to get sync data in sorter time.
I am stuck here and the client asking a faster solution to sync my contacts.
Note: In the server, I have around 50000 records in my Contact table and it may increase in the future.
any suggestions will be appreciated.
UPDATE: At the first time I am sending all contacts to a server in paging that's fine but after first sync every second sync I need to send only updated or newly added records right now I am sending all record to the server every time.
so how can I get an updated record or newly added record and faster next time sync also?
I think you can speed up the syncing by changing some implementation to your code as well on server side code. you send all phone numbers in on array of dictionary (name, all phone number comma separated) you need to create this structure while you are fetching the records from address book, now change your logic accordingly on the server side. both of your task storing in database and syncing can be done on different threads.
Hope this helps for you.
You can use realm for local storage with observe function. Then you can listen changes from realm for remote sync.

The right approach to keep users sessions

I’m developing an app that uses external API and requires authentication through oauth2. In the response from api I get access token, refresh token and time in which token will expire. Everything is just fine until the time expire. So far before sending a request i check whether access token is valid:
if ([[AppCredentials sharedCredentials]tokenIsValid]) {
BackEnd *backendPUT = [[BackEnd alloc]init];
[backendPUT setDelegate:self];
[backendPUT updateMenuInDiary:menuDietyDoUpdate forDate:[[DziennikDietaModel sharedDziennikDietaModel]getwybranaDate] mealID:[menuDietyDoUpdate objectForKey:#"id"]];
monitor = [[UICustomLoadingMonitor alloc]initWithDefaultOptionsInView:[self view]];
[monitor start];
}else{
[[AppCredentials sharedCredentials] getAppTokenFromRefreshedToken];
}
when getAppTokenFromRefreshedToken is finished, method userTokenDownloaded is called using delegate.
The question is: how can I go back to that certain code which could not be executed in the first part of the if statement? Everything is asynchronous that is why i’ve got a problem.
In my view controller there are couple of methods that sends different requests to api and I need to differentiate which one needs to be called again.
What would be the right approach to this? closures ?
ok. solved it with blocks :) in the case someone was looking for an answer that is how i've done it:
BackEnd *backEnd = [[BackEnd alloc]init];
[backEnd setDelegate:self];
if ([[AppCredentials sharedCredentials]tokenIsValid]) {
NSLog(#"TOKEN VALID NO BLOCKS");
[backEnd updateMenuInDiary:menuDietyDoUpdate];
}else{
NSLog(#"USING BLOCKS");
[backEnd getAppTokenFromRefreshToken:refreshedToken withCompletionBlock:^{
NSLog(#"ponowna proba z bloku");
[backEnd updateMenuInDiary:menuDietyDoUpdate];
}];
}
works great :)

RestKit: How to reject an entire mapping when a value is not valid

So I'm trying to support offline usage of an iOS application I'm making that uses a REST API. Here's what I have so far:
A server running with a REST interface to manipulate my data model.
An iOS application that uses RestKit to retrieve the data stored on my server.
RestKit stores server responses locally in Core Data.
When the server is unavailable I still want users to be able to update the data model and then when the server becomes available again I want those updates to be pushed to the server.
The issue I've run into is that when a value that has been updated locally (but not pushed to the server yet) is received from the server it is overwritten with the contents of the server's response. To prevent this I am trying to cancel the save to my local storage if the new 'updatedAt' date is before the current 'updatedAt' date.
This is my current validation function:
- (BOOL)validateUpdatedAt:(id *)ioValue error:(NSError **)outError
{
if(self.updatedAt && [self.updatedAt compare:((NSDate *)*ioValue)] == NSOrderedDescending) {
*outError = [NSError errorWithDomain:RKErrorDomain code:RKMappingErrorMappingDeclined userInfo:nil];
return NO;
}
return YES;
}
This works, but only prevents this individual value from being changed. I want the entire update of that object to be canceled if this one field is invalid. How do I do this?
The support available from RestKit is to set discardsInvalidObjectsOnInsert for the whole object to be discarded when validation fails. But, this won't work for you as it only works for NSManagedObject instances that are being inserted, because it uses validateForInsert:.
You could look at using validateForUpdate: to perform a similar check and then reverting the changes, but RestKit isn't really offering you anything in terms of the abort in your case.

How to combine multiple notifications of the same kind

I have an iOS app that shows the user a set of different news feeds in a PageViewController. Everytime the app starts, it requests the news data from the backend for every single feed. In case it worked fine, a notification via for every single news feed is sent via NSNotificationCenter so the data can be displayed.
In case of an error, a notification for every single feed is sent as well, triggering a popup message that tells the user something went wrong. But if this happens, a popup will be shown for every news feed, up to the amount of added news feeds.
My question is, how can I combine all those error case notifications to a single one and therefore avoid having many useless and annyoing popups?
if (self.isShowingErrorDialog) {
return; // Or possibly cache to show after current one is dismissed.
} else {
[[UIAlertView ...] show];
self.showingErrorDialog = YES;
}
When you send a notification using NSNotificationCentre, you can include user info. This is basically an NSDictionary with additional information.
Why not just include the timestamp of the failed request. You can test this with some fuzziness to see if you've already put up an alert for this batch of requests.
- (void) notificationListener: (NSNotification*) notification {
static NSDate* lastAlerted = nil;
NSDate* sentDate = notification.userInfo[#"RequestDate"];
if ( lastAlerted != nil && [lastAlerted timeIntervalSince:sentDate] > FUZZY_INTERVAL) {
// post alert
// And update last Alerted
lastAlerted = sentDate;
}
}
The method you need is postNotificationWithName:Object:UserInfo:.
Gordon
I don't think you can.
Just to confirm, the notifications you're sending are Apple remote notifications and the alerts are the system alerts popped up by the message centre.
The alerts occur before you get control, as the user has to have the opportunity to ignore them, or else people would use this as a cheat to make apps run in the background and kill user's batteries.
All you can do is send a batch token in your request, and check on the back end.
Good luck

Survey application with offline capabilities and how to capture the data from multiple ipads to one?

I am trying to build a Survey application where the surveys will be taken offline on multiple ipads and when these ipads are online they are going to upload the data(survey answers) to our servers? I am really struggling how to send the survey to multiple ipads and more importantly to capture from different ipads to one source ?
I need help to clear my architecture part and I need some examples to do the coding part. Do you know anything similar?
What are you ideas?
Many Thanks in advance,
Arda
Create a web server to accept and send survey questions and answers.
I would envision an app that goes like this:
1) Slave iPads makes a HTTP POST request to server asking for the survey
This is usually done using a networking library for iOS like MKNetworkKit or AFNetworking. The general process is to:
create a NSDictionary of key-value pairs to form the HTTP POST request
submit the data through a block construct with completion handler
So something like:
MKNetworkOperation *op = [engine operationWithURLString:#"http://www.mywebserver.com/api/fetchQuestions"
params:nil
httpMethod:#"POST"];
2) Server receives request, grabs all survey questions in database and return JSON encoded questions to slave ipads.
I'm not sure what platform your web server is on but in the past, I used Symfony 2.0 which is a PHP web framework.
It provides very helpful tools like Doctrine (an Object Relational Mapper or ORM) to let me work with my MySQL data as if they're programming objects.
So my general process for fetching data would be something like:
// pseudo php function codes
public function sendSurveyQuestionAction()
{
$repository = $this->getDoctrine()->getRepository('MyAppBundle:Survey');
$query = $repository->createQueryBuilder('query')->getQuery();
$arrObjs = $query->getResult();
$arrObjDatas = NULL;
foreach($arrObjs as $obj)
{
$arrObjDatas[] = $obj->toArray();
}
$response = new Response(json_encode(array('data' => $arrObjDatas)));
$response->headers->set('Content-Type', 'application/json');
$return $response;
}
This would return all survey in JSON format, ready to be parsed by your master iPad app.
3) Users on slave iPads fill in the questions through the app UI and submits. The app saves
the data to disk, checks for a working internet connection before sending data back to server.
Submitting the answer is very similar grabbing the questions, so your iOS code should be something like:
// ------------------------------------------------------------------------------------
// store all question-answers into a dictionary to be submitted as HTTP POST variables
// obviously, you wouldn't create it here, this is just example code, you would likely
// have stored your questions and answers when user presses 'finish' button
// ------------------------------------------------------------------------------------
NSMutableDictionary *paramDictionary = [[NSMutableDictionary alloc] init];
[paramDictionary setObject:#"5" forKey:#"q1"];
[paramDictionary setObject:#"10" forKey:#"q2"];
[paramDictionary setObject:#"15" forKey:#"q3"];
// this helps your web server know how many question-answers to expect, or you could hard code it into your business logic
[paramDictionary setObject:[NSNumber numberWithInteger:3] forKey:#"numberOfQA"];
MKNetworkOperation *op = [engine operationWithURLString:#"http://www.mywebserver.com/api/submitAnswers"
params:paramDictionary
httpMethod:#"POST"];
This will submit your answers for each of your question. You may have noticed I used q1, q2, q3.
These are for your web server code to identify each questions and extract the respective answers from them.
4) Server receives finished answers and commit them to database
So if you were using Symfony 2.0 PHP code, then something like:
// pseudo php function
public function saveAnswersAction()
{
$numOfQA = $_REQUEST['numberOfQA'];
for($i = 0; $i < $numOfQA; $i++)
{
// ----------------------------------------------------------------------
// looping through all the questions such as q1, q2, q3, q4, q5....
// by appending the counter variable to the question identifier
// ----------------------------------------------------------------------
$currentAnswer = $_REQUEST['q'.$i];
// use Doctrine to create new answer entities, and fill in their data
$answerEntity.answer = $currentAnswer;
$surveyEntity->addAnswerEntity($answerEntity);
// mark survey as complete so we can fetch all 'completed' surveys later
$surveyEntity.complete = true;
}
// tell Doctrine to commit changes to MySQL Database
// return HTTP OK status message
}
5) Now all that's left is for your master iPad app to make a HTTP POST request to get all surveys.
The process is the same with your iOS code making a HTTP POST requesting for all 'completed' survey entities from your web server.
The web server grabs them and return them as JSON encoded data.
Your app then receives the completed surveys with question answer like this:
surveys
{
{
questionNumber: 1,
questionAnswer: "5"
},
{
questionNumber: 2,
questionAnswer: "10"
},
{
questionNumber: 3,
questionAnswer: "15"
}
}
Now you use JSONKit to parse this JSON data. You should end up with a NSDictionary from JSONKit.
You can then go something like:
// pseudo code
-(void)displayCompletedSurveys
{
[MKNetworkOperationEngine doRequest:
...
^completionBlock {
// parse JSON data
NSDictionary *surveyData = [JSONKit dictionaryFromJSONData:data)
NSEnumerator *enumerator = [surveyData enumerator];
NSDictionary *currentQuestion = nil;
while([enumerator nextObject] != nil)
{
// do something with each of your question-answer e.g. show it on screen
}
}];
}
Points To Consider
Most of the code above are pseudo-codes. Your final real code would probably be much more in depth.
You'll need to build some master login into your app to prevent everyone from seeing the completed surveys.
Some Extra Information You Should Know
Here are some extra information to help you
JSONKit for fast JSON data decoding from your web server
MKNetworking or AFNetworking to submit your data to your web server
You need to know how to write web services to handle accepting the survey answers. I recommend learning a web framework like Symfony 2.0
Hope that helps.

Resources