Coredata working with data (Multithreading) - ios

I need to place hotspots to the Map. All of them are saved with CoreData (it's about 25000).
So I need 25000 annotations. I also implement MKAnnotation protocol for HotSpot Entity.
Selected solution for this situation was multithreading. But after pins are on the map some of them has no data (data ).
here is code that populate data in array
- (void)addAnnotationsForCurrentLocation {
NSInteger hotSpotsCount = [HotSpot MR_countOfEntities];
self.testSpotsArray = [[NSMutableArray alloc] initWithCapacity:hotSpotsCount];
NSInteger lastThread;
NSInteger threads = 5;
//calculate how much spots will be in the
NSInteger spotsInThread = hotSpotsCount/threads;
//calclulate how mush threads will be in one spot
lastThread = hotSpotsCount - spotsInThread*(threads-1);
dispatch_queue_t firstThreadQueue = dispatch_queue_create("com.thecloud.firstThreadQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t secondThreadQueue = dispatch_queue_create("com.thecloud.secondThreadQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t thirdThreadQueue = dispatch_queue_create("com.thecloud.thirdThreadQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t forthThreadQueue = dispatch_queue_create("com.thecloud.forthThreadQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t fifthThreadQueue = dispatch_queue_create("com.thecloud.fifthThreadQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t fillArrayGroup = dispatch_group_create();
NSLock *arrayLock = [[NSLock alloc] init];
dispatch_group_async(fillArrayGroup, firstThreadQueue, ^{
[self fetchWithOffest:0 andLimit:spotsInThread andLock:arrayLock];
DDLogInfo(#"com.thecloud.firstThreadQueue and self.testSpotsArray objects - %i", [self.testSpotsArray count]);
});
//Other Queue
dispatch_group_notify(fillArrayGroup, dispatch_get_main_queue(), ^{
[self.treeController setAnnotations:self.testSpotsArray];
});
dispatch_release(fillArrayGroup);
}
- (void)fetchWithOffest:(NSInteger)offset andLimit:(NSInteger)limit andLock:(NSLock *)arrayLock {
NSFetchRequest *request = [HotSpot MR_requestAll];
[request setFetchOffset:offset];
[request setFetchLimit:limit];
[request setReturnsObjectsAsFaults:NO];
NSArray *array = [HotSpot MR_executeFetchRequest:request];
for (int i=0; i < [array count]; i++) {
HotSpot *spot = (HotSpot *)[array objectAtIndex:i];
[spot convertLogitudeAndLattitudeToLocationCoordinate];
[arrayLock lock];
[self.testSpotsArray addObject:spot];
[arrayLock unlock];
}
}
After this when I tap on some of pins no popup with description appears.
Any solution to have full data in each object in the array of annotations?
UPD
Problem solved. I did change in my entity model. Now when I add the entity to annotations array instead of
-(NSString *)title {
return self.title;
}
-(NSString *)subtitle {
return self.spotToAddress.addressLine1;
}
-(CLLocationCoordinate2D) coordinate {
return CLLocationCoordinate2DMake([self.latitude doubleValue], [self.longitude doubleValue]);
}
I use direct assignment
- (void)prepareAnnotation {
_title = self.name;
_subtitle = self.spotToAddress.addressLine1;
_coordinate = CLLocationCoordinate2DMake([self.latitude doubleValue], [self.longitude doubleValue]);
}

Problem solved. I did change in my entity model. Now when I add the entity to annotations array instead of
-(NSString *)title {
return self.title;
}
-(NSString *)subtitle {
return self.spotToAddress.addressLine1;
}
-(CLLocationCoordinate2D) coordinate {
return CLLocationCoordinate2DMake([self.latitude doubleValue], [self.longitude doubleValue]);
}
I use direct assignment
- (void)prepareAnnotation {
_title = self.name;
_subtitle = self.spotToAddress.addressLine1;
_coordinate = CLLocationCoordinate2DMake([self.latitude doubleValue], [self.longitude doubleValue]);
}

Related

Loading data taking too much time CoreData

I am facing problems when i tries to save 40,000 records into CoreData Entity.
I am getting 40,000 records by consuming the webservice using AFNetworking, the response is in JSON. Than i divide the data into 4 , 10000 record chunks and then assign these 4 chunks to separate NSOperation objects (i have created subclass of NSOperation) and add these NSOperation Objects to NSOperationQueue.
The problem is that this way it is taking too much time to save the data into CoreData. And i want to find a solution where i can load the data very quickly.
This is the code in which i am creating NSOperation objects and adding them to NSOperationQueue.
- (void)casesResponseReceived:(NSArray*)array
{
id responseObject = [array objectAtIndex:0];
NSManagedObjectContext *moc = [array objectAtIndex:1];
NSString *responseString = [[NSString alloc] initWithData:responseObject encoding:NSUTF8StringEncoding];
NSArray *response = [responseString JSONValue];
NSString *responseStr = [response JSONRepresentation];
NSRange range = [responseStr rangeOfString:#"["];
int index = 0;
int objectsCount = 5000;
if (range.location == 0) {
NSInteger count = objectsCount;
totalOperationsCount = 0;
completedOperationsCount = 0;
self.myQueue = [[NSOperationQueue alloc] init];
while (count == objectsCount) {
if ((index+count) > [response count]) {
count = [response count] - index;
}
NSArray *subArray = [response subarrayWithRange:NSMakeRange(index, count)];
index += objectsCount;
CaseParseOperation *operation = [[CaseParseOperation alloc] initWithData:subArray MOC:moc];
operation.delegate = self;
totalOperationsCount++;
[self.myQueue addOperation:operation];
}
/*
if (self.delegate && [self.delegate respondsToSelector:#selector(serviceHelperDidCasesReceivedSuccessful:)]) {
[self.delegate serviceHelperDidCasesReceivedSuccessful:self];
}*/
}
else {
if (self.delegate && [self.delegate respondsToSelector:#selector(serviceHelperDidCasesReceivedFailed:)]) {
[self.delegate serviceHelperDidCasesReceivedFailed:self];
}
}}
CaseOperation.h
#class CaseParseOperation;
#protocol CaseParseOperationProtocol <NSObject>
-(void)caseParseOperationDidOperationComplete: (CaseParseOperation*)caseParseOperation;
#end
#interface CaseParseOperation : NSOperation
#property (nonatomic, weak) id<CaseParseOperationProtocol> delegate;
-(id)initWithData:(NSArray*)parseData MOC:(NSManagedObjectContext*)moc;
#end
CaseOperation.m
#interface CaseParseOperation()
#property (nonatomic, copy) NSArray *casesData;
#property (nonatomic, strong) NSManagedObjectContext *mainMOC;
#property (nonatomic, strong) NSManagedObjectContext *localMOC;
#end
#implementation CaseParseOperation
- (id)initWithData:(NSArray*)parseData MOC:(NSManagedObjectContext*)moc
{
self = [super init];
if (self) {
self.casesData = [parseData copy];
self.mainMOC = moc;
}
return self;
}
- (void)main
{
#autoreleasepool {
self.localMOC = [[NSManagedObjectContext alloc] init];
self.localMOC.persistentStoreCoordinator = self.mainMOC.persistentStoreCoordinator;
[[NSNotificationCenter defaultCenter] addObserver: self
selector: #selector(mergeChanges:)
name: NSManagedObjectContextDidSaveNotification
object: self.localMOC];
[self parseData];
}
}
-(void) mergeChanges: (NSNotification*) saveNotification {
dispatch_async(dispatch_get_main_queue(), ^{
[self.mainMOC mergeChangesFromContextDidSaveNotification:saveNotification];
});
if (self.delegate && [self.delegate respondsToSelector:#selector(caseParseOperationDidOperationComplete:)]) {
[self.delegate caseParseOperationDidOperationComplete:self];
}
}
- (void)parseData
{
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *ent = [NSEntityDescription entityForName:#"Case" inManagedObjectContext:self.localMOC];
fetchRequest.entity = ent;
NSString *predicateString = [NSString stringWithFormat:#"caseNumber == $caseNumber"];
NSPredicate *predicate = [NSPredicate predicateWithFormat:predicateString];
//NSMutableArray *insertedObjects = [[NSMutableArray alloc] init];
for (NSMutableDictionary *dic in self.casesData) {
if (self.isCancelled) {
break;
}
NSString *desc = [dic valueForKey:#"description"];
BOOL enabled = [[dic valueForKey:#"enabled"] boolValue];
NSString *billToCustomerNo = [dic valueForKey:#"billToCustomerNo"];
NSString *caseNo = [dic valueForKey:#"caseNo"];
NSString *billToName = [dic valueForKey:#"billToName"];
NSString *personResponsible = [dic valueForKey:#"personResponsible"];
NSDictionary *variables = #{ #"caseNumber" : caseNo };
fetchRequest.predicate = [predicate predicateWithSubstitutionVariables:variables];
NSArray *matchedObj = [self.localMOC executeFetchRequest:fetchRequest error:nil];
if ([matchedObj count] > 0) {
Case *caseObj = [matchedObj objectAtIndex:0];
caseObj.isEnabled = [NSNumber numberWithBool:enabled];
caseObj.caseDescription = desc;
caseObj.customerNumber = billToCustomerNo;
caseObj.customerName = billToName;
caseObj.personResponsible = personResponsible;
}
else {
/*
Case *caseObj = [[Case alloc] initWithEntity:[NSEntityDescription entityForName:#"Case"
inManagedObjectContext:self.localMOC] insertIntoManagedObjectContext:nil];
caseObj.caseNumber = caseNo;
caseObj.customerName = billToName;
caseObj.customerAddress = #"";
caseObj.customerPhone = #"";
caseObj.caseDescription = desc;
caseObj.customerNumber = billToCustomerNo;
caseObj.isEnabled = [NSNumber numberWithBool:enabled];
caseObj.personResponsible = personResponsible;
[insertedObjects addObject:caseObj];
*/
[Case createObjectWithCaseNumber:caseNo customerName:billToName customerAddress:#"" customerPhone:#"" caseDescription:desc customerNumber:billToCustomerNo isEnabled:enabled personResponsible:personResponsible MOC:self.localMOC];
}
}
/*
if ([insertedObjects count] > 0) {
NSError *error = nil;
BOOL isInserted = [self.localMOC obtainPermanentIDsForObjects:insertedObjects error:&error];
if (error || !isInserted) {
NSLog(#"Error occured");
}
}
*/
if ([self.localMOC hasChanges]) {
[self.localMOC save:nil];
}
}
#end
The first thing to do is run Instruments and find the bottlenecks, as #jrturton recommends.
But there's one huge glaring bottleneck that's apparent from reading the code. To avoid duplicates, you're doing a fetch-- for every incoming instance. With 40k records you'll have to do 40k fetches during the import process, and that's going to be slow no matter what.
You can improve that by processing the data in batches:
Get a bunch of caseNumber values into an array
Do a fetch with a predicate of caseNumber IN %#, with the array as the argument.
Use that array to check for duplicates.
You'll need to experiment a little to see how many "a bunch" is in step 1. Higher numbers mean fewer fetches, which is good for speed. But higher numbers also mean more memory use.
For a more detailed discussion, see Apple's Efficiently Importing Data guide, especially the section named "Implementing Find-or-Create Efficiently".
Thanks guys valuable suggestions. But i have solved that issue by just altering some technique in the parseData function.
-(void)parseData
{
NSString *predicateString = [NSString stringWithFormat:#"caseNumber == $caseNumber"];
NSPredicate *predicate = [NSPredicate predicateWithFormat:predicateString];
NSArray *allCases = [Case getAllCaseObjectsWithMOC:self.localMOC];
for (NSMutableDictionary *dic in self.casesData) {
if (self.isCancelled) {
break;
}
NSString *caseNo = [dic valueForKey:#"caseNo"];
NSDictionary *variables = #{ #"caseNumber" : caseNo };
predicate = [predicate predicateWithSubstitutionVariables:variables];
NSArray *matchedObj = [allCases filteredArrayUsingPredicate:predicate];
if ([matchedObj count] == 0) {
NSString *desc = [dic valueForKey:#"description"];
BOOL enabled = [[dic valueForKey:#"enabled"] boolValue];
NSString *billToCustomerNo = [dic valueForKey:#"billToCustomerNo"];
NSString *billToName = [dic valueForKey:#"billToName"];
NSString *personResponsible = [dic valueForKey:#"personResponsible"];
[Case createObjectWithCaseNumber:caseNo customerName:billToName customerAddress:#"" customerPhone:#"" caseDescription:desc customerNumber:billToCustomerNo isEnabled:enabled personResponsible:personResponsible MOC:self.localMOC];
}
}
if ([self.localMOC hasChanges]) {
[self.localMOC save:nil];
}
}

Sort UITableView by distance

I am trying to sort my tableview in ascending order by distance that I calculate from coordinates. Everything works like a charm except I can't get it in ascending order, I have been mucking around with NSSortDescriptor etc., but getting unlucky, any help would be appreciated, here is my code:
- (void) retrieveData
{
NSURL *url = [NSURL URLWithString:jsonFile];
NSData *data = [NSData dataWithContentsOfURL:url];
_jsonArray = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil];
_salesArray = [[NSMutableArray alloc]init];
for (int i = 0; i < _jsonArray.count; i++) {
NSString *sID = [[_jsonArray objectAtIndex:i] objectForKey:#"id"];
NSString *sName = [[_jsonArray objectAtIndex:i] objectForKey:#"name"];
NSString *sAddress = [[_jsonArray objectAtIndex:i] objectForKey:#"address"];
NSString *sPostcode = [[_jsonArray objectAtIndex:i] objectForKey:#"postcode"];
__block NSString *distance;
CLGeocoder *geocoder = [[CLGeocoder alloc]init];
[geocoder geocodeAddressString:sPostcode completionHandler:^(NSArray *placemarks, NSError *error) {
if (error == nil && placemarks.count > 0) {
CLPlacemark *placemark = [placemarks objectAtIndex:0];
CLLocation *location = placemark.location;
CLLocation *myLocation = self.manager.location;
CLLocationDistance miles = [location distanceFromLocation:myLocation];
//this is the variable i want in my convenience init.
distance = [NSString stringWithFormat:#"%.1f m", (miles/1609.344)];
}
}];
[_salesArray addObject:[[sales alloc] initWithSales:sID andName:sName andAddress:sAddress andPostcode:distance]];
}
[_salesArray sortUsingComparator:
^NSComparisonResult(id obj1, id obj2){
sales *p1 = (sales *)obj1;
sales *p2 = (sales *)obj2;
if (p1.postcode > p2.postcode) {
return (NSComparisonResult)NSOrderedDescending;
}
if (p1.postcode < p2.postcode) {
return (NSComparisonResult)NSOrderedAscending;
}
return (NSComparisonResult)NSOrderedSame;
}
];
[self.tableView reloadData];
}
There are a few of issues here:
The geocodeAddressString imposes a few limitations, as outlined in the documentation:
This method submits the specified location data to the geocoding server asynchronously and returns. Your completion handler block will be executed on the main thread. After initiating a forward-geocoding request, do not attempt to initiate another forward- or reverse-geocoding request.
Geocoding requests are rate-limited for each app, so making too many requests in a short period of time may cause some of the requests to fail. When the maximum rate is exceeded, the geocoder passes an error object with the value kCLErrorNetwork to your completion handler.
Several key observations here:
This runs asynchronously (so you cannot call geocodeAddressString and use its results immediately afterwards). You have do invoke the work contingent on the geocoding inside the completion block.
You should not be starting the next geocode request until the prior one completes.
This means that you have to geocode the first postal code, let it complete asynchronously (i.e. later), geocode the next one, let it complete, etc., and only then do your sort and reload the table. A simple for loop is not an appropriate way to do this. You can either write a method that does a single geocode and invokes the next geocode in the completion block, or you can use NSOperation subclass as I have below.
I would advise storing the distance as a NSNumber. In MVC, the one decimal place string representation is a "view" behavior, and should probably not be part of the "model".
The advantage of this is that when you want to sort the objects, you can simply invoke the compare method for the NSNumber. For example, if salesPersonnel was a NSMutableArray of objects which each SalesPerson object has the NSNumber property called distance, you could then do:
[self.salesPersonnel sortUsingComparator:^NSComparisonResult(SalesPerson *obj1, SalesPerson *obj2) {
return [obj1.distance compare:obj2.distance];
}];
I wasn't sure if your sales entries per actual sales transactions or sales personnel, so I apologize if I misinterpreted the object types, but hopefully this illustrates the idea.
You can do this any way you want, but for me, when I want to run a number of asynchronous tasks, but do so sequentially, I gravitate to concurrent NSOperation subclass which I'll add to a serial NSOperationQueue.
NSError *error;
NSArray *addressEntries = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
NSAssert(addressEntries, #"unable to parse: %#", error);
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.maxConcurrentOperationCount = 1;
self.salesPersonnel = [NSMutableArray array];
// define sort operation that will be called when all of the geocode attempts are done
NSOperation *sortAndReloadTableOperation = [NSBlockOperation blockOperationWithBlock:^{
[self.salesPersonnel sortUsingComparator:^NSComparisonResult(SalesPerson *obj1, SalesPerson *obj2) {
return [obj1.distance compare:obj2.distance];
}];
[self.tableView reloadData];
}];
// create the geocode operations
for (NSDictionary *addressEntry in addressEntries) {
SalesPerson *salesPerson = [[SalesPerson alloc] initWithSalesId:addressEntry[#"id"]
name:addressEntry[#"name"]
address:addressEntry[#"address"]
postalCode:addressEntry[#"postcode"]];
[self.salesPersonnel addObject:salesPerson];
NSOperation *geocodeOperation = [[GeocodeOperation alloc] initWithPostalCode:salesPerson.postalCode completionHandler:^(NSArray *placemarks, NSError *error) {
CLPlacemark *placemark = [placemarks firstObject];
CLLocation *location = placemark.location;
CLLocationDistance meters = [location distanceFromLocation:self.currentLocation];
salesPerson.distance = #(meters / 1609.344);
}];
[sortAndReloadTableOperation addDependency:geocodeOperation]; // note, the final sort is dependent upon this finishing
[queue addOperation:geocodeOperation]; // go ahead and queue up the operation
}
// now we can queue the sort and reload operation, which won't start until the geocode operations are done
[[NSOperationQueue mainQueue] addOperation:sortAndReloadTableOperation];
And the GeocodeOperation is a basic concurrent NSOperation subclass:
// GeocodeOperation.h
#import <Foundation/Foundation.h>
typedef void(^GeocodeCompletionHandler)(NSArray *placemarks, NSError *error);
#interface GeocodeOperation : NSOperation
#property (nonatomic, copy) GeocodeCompletionHandler geocodeCompletionHandler;
- (instancetype)initWithPostalCode:(NSString *)postalCode completionHandler:(GeocodeCompletionHandler)geocodeCompletionHandler;
#end
and the implementation (note, the main method is the only interesting bit here ... all the rest is routine concurrent NSOperation subclass code; personally, I move all of the concurrent NSOperation stuff into a base class, which cleans up this GeocodeOperation code, but I didn't want to confuse this further, so I've kept this simple):
// GeocodeOperation.m
#import "GeocodeOperation.h"
#import CoreLocation;
#interface GeocodeOperation ()
#property (nonatomic, readwrite, getter = isFinished) BOOL finished;
#property (nonatomic, readwrite, getter = isExecuting) BOOL executing;
#property (nonatomic, copy) NSString *postalCode;
#end
#implementation GeocodeOperation
#synthesize finished = _finished;
#synthesize executing = _executing;
- (CLGeocoder *)sharedGeocoder
{
static CLGeocoder *geocoder = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
geocoder = [[CLGeocoder alloc]init];
});
return geocoder;
}
- (instancetype)initWithPostalCode:(NSString *)postalCode completionHandler:(GeocodeCompletionHandler)geocodeCompletionHandler
{
self = [super init];
if (self) {
_postalCode = [postalCode copy];
_geocodeCompletionHandler = geocodeCompletionHandler;
}
return self;
}
- (void)main
{
[[self sharedGeocoder] geocodeAddressString:self.postalCode completionHandler:^(NSArray *placemarks, NSError *error) {
if (self.geocodeCompletionHandler) {
self.geocodeCompletionHandler(placemarks, error);
}
[self completeOperation];
}];
}
#pragma mark - NSOperation methods
- (void)start
{
if ([self isCancelled]) {
self.finished = YES;
return;
}
self.executing = YES;
[self main];
}
- (void)completeOperation
{
self.executing = NO;
self.finished = YES;
}
- (BOOL)isConcurrent
{
return YES;
}
- (void)setExecuting:(BOOL)executing
{
if (_executing != executing) {
[self willChangeValueForKey:#"isExecuting"];
_executing = executing;
[self didChangeValueForKey:#"isExecuting"];
}
}
- (void)setFinished:(BOOL)finished
{
if (_finished != finished) {
[self willChangeValueForKey:#"isFinished"];
_finished = finished;
[self didChangeValueForKey:#"isFinished"];
}
}
#end
I think the problem is postcode is an NSString. So in your block (p1.postcode > p2.postcode) is comparing the ADDRESS LOCATIONS, not the string values themselves.
You want to use the NSString function compare: instead of doing it yourself.
Try this:
[_salesArray sortUsingComparator:
^NSComparisonResult(id obj1, id obj2){
sales *p1 = (sales *)obj1;
sales *p2 = (sales *)obj2;
NSString *postcode1 = p1.postcode;
NSString *postcode2 = p2.postcode;
return [postcode1 compare:posecode2];
];

Adding mapview annotations within parse query returns null randomly

I am creating an iOS app using Parse database(asynchronously) to store information that will be used when populating a mapview. I have been trying to figure out what is wrong for a long time and have done plenty of research without any luck. I have, however, found the source of the issue.
In my code, I am querying the parse database in hopes of getting the information I want and then storing the information in a custom pointAnnotation class, which is of type MkPointAnnotation. Each item is stored in an array of pointAnnotations, and once all items in the database have been stored in the array, the annotations are added to MyMapView. --I have tried adding the annotations as they are created, which does not change anything.
The issue I have been having is that randomly, the query will iterate under the for(PFObject *vendor in Vendors) and reach an error, calling NSLog(#"%#", error.debugDescription); which shows (null) in the output log. The amount of objects that return null seems to change each time I run the application, and occasionally it will work as expected. After adding a do while(pointArray.count < query.countObjects), the function will iterate roughly 20-30 times and then will add the correct number of annotations, however, it is extremely inefficient.
Is this an inefficiency within Parse or is there a better way to achieve the expected results?
PFQuery *query = [PFQuery queryWithClassName:#"Vendors"];
[query orderByDescending:#"updatedAt"];
[query findObjectsInBackgroundWithBlock:^(NSArray *vendors, NSError *error){
NSMutableArray *pointArray = [[NSMutableArray alloc] init];
if (!error) {
// The find succeeded.
// Do something with the found objects
do {
pointArray = [[NSMutableArray alloc] init];
for (PFObject *vendor in vendors) {
NSDate *lastUpdated = vendor.updatedAt;
NSDate *today = [NSDate date];
NSDate *newDate = [lastUpdated dateByAddingTimeInterval:86400];
if (today <= newDate) {
PFGeoPoint *point = vendor[#"Location"];
NSString *vendor_ID = vendor[#"Vendor_ID"];
NSMutableArray *FruitList = vendor[#"Fruits"];
NSMutableArray *VeggieList = vendor[#"Veggies"];
NSMutableArray *addressArray = vendor[#"Address"];
NSString *startHr = vendor[#"Start_Time"];
NSString *endHr = vendor[#"End_Time"];
Boolean more = false;
NSString *moreString = vendor[#"And_More"];
if ([moreString isEqual: #"true"]) {
more = true;
}
CLLocationCoordinate2D location;
location.latitude = point.latitude;
location.longitude = point.longitude;
pointAnnotation *newAnnotation = [[pointAnnotation alloc] init];
if ([[[NSUserDefaults standardUserDefaults] objectForKey:#"language"] isEqual:#"ENGLISH"]){
FindCartsLabel.text = #"Find Carts within:";
MilesTextField.text = #"Show All";
milesArray=[[NSArray alloc]initWithObjects:#"Show All", #"1 Mile", #"5 Miles", #"10 Miles", #"20 Miles", nil];
AddressBar.placeholder = ENGLISH_Address;
newAnnotation.title = #"Good. To. Go. Vendor";
newAnnotation.fruits = FruitList;
newAnnotation.veggies = VeggieList;
}else if ([[[NSUserDefaults standardUserDefaults] objectForKey:#"language"] isEqual:#"SPANISH"]){
FindCartsLabel.text = #"Encuentra Carros Dentro:";
newAnnotation.title = #"Good. To. Go. Vendedor";
AddressBar.placeholder = SPANISH_Address;
NSMutableArray *spanishFruitList = [[NSMutableArray alloc]init];
for (NSString *current in FruitList) {
MilesTextField.text = #"Mostrar Todo";
milesArray=[[NSArray alloc]initWithObjects:#"Mostrar Todo", #"1 Milla", #"5 Millas", #"10 Millas", #"20 Millas", nil];
if ([current isEqual:#"Apples"]) {
[spanishFruitList addObject:SPANISH_Apples];
}
if ([current isEqual:#"Bananas"]) {
[spanishFruitList addObject:SPANISH_Bananas];
}
if ([current isEqual:#"Citrus"]) {
[spanishFruitList addObject:SPANISH_Citrus];
}
if ([current isEqual:#"Mangos"]) {
[spanishFruitList addObject:SPANISH_Mangos];
}
if ([current isEqual:#"Strawberries"]) {
[spanishFruitList addObject:SPANISH_Strawberries];
}
if ([current isEqual:#"And More"]) {
[spanishFruitList addObject:SPANISH_More];
}
}
NSMutableArray *spanishVeggieList = [[NSMutableArray alloc]init];
for (NSString *current in VeggieList) {
if ([current isEqual:#"Avocados"]) {
[spanishVeggieList addObject:SPANISH_Avocados];
}
if ([current isEqual:#"Broccoli"]) {
[spanishVeggieList addObject:SPANISH_Broccoli];
}
if ([current isEqual:#"Carrots"]) {
[spanishVeggieList addObject:SPANISH_Carrots];
}
if ([current isEqual:#"Squash"]) {
[spanishVeggieList addObject:SPANISH_Squash];
}
if ([current isEqual:#"Onions"]) {
[spanishVeggieList addObject:SPANISH_Onions];
}
if ([current isEqual:#"Tomatoes"]) {
[spanishVeggieList addObject:SPANISH_Tomatoes];
}
if ([current isEqual:#"And More"]) {
[spanishVeggieList addObject:SPANISH_More];
}
}
newAnnotation.fruits = spanishFruitList;
newAnnotation.veggies = spanishVeggieList;
}
newAnnotation.coordinate = location;
newAnnotation.vendorID = vendor_ID;
newAnnotation.startHour = startHr;
newAnnotation.endHour = endHr;
newAnnotation.loc = point;
newAnnotation.isCustomAddress = false;
//newAnnotation.subtitle = address;
__block NSString *address = [NSString stringWithFormat:#"%# %#, %#, %#, %#",
addressArray[0], addressArray[1],
addressArray[2], addressArray[3],
addressArray[4]];
__block NSString *currAddress = [NSString stringWithFormat:#"%# %#\n"
"%#, %#, %#\n"
"%#\n",
addressArray[0], addressArray[1],
addressArray[2], addressArray[3],
addressArray[4], addressArray[5]];
newAnnotation.subtitle = address;
newAnnotation.addressFormatted = currAddress;
static NSString *identifier = #"MyLocation";
MKPinAnnotationView *currentView = [[MKPinAnnotationView alloc] initWithAnnotation:newAnnotation reuseIdentifier:identifier];
[pointArray addObject:currentView];
} else {
//[self viewDidLoad];
NSLog(#"%#", error.debugDescription);
}
//} ];
}
} while (pointArray.count < query.countObjects);
}
if (pointArray.count == query.countObjects) {
for (MKPinAnnotationView *currentPoint in pointArray) {
[self.MyMapView addAnnotation:currentPoint.annotation];
}
}
}];
Thanks in advance for the help. I do not really understand why this code would not complete after only one iteration.
The NSLog(#"%#", error.debugDescription); doesn't look like it's in the right place. It's in an else block that is associated with the if (today <= newDate) which is inside a block of code that is only executed if error is null which is why it says null in the log (when what it really means is "today > newDate"). – Anna

Background computation on iPad blocks user interface

I'm trying to use background threds to do some computations on an iPad.
The thing is even thou the computationa are running. The UI is blocked while they run...
What am I doing wrong.
[mc evaluateFormula:adapted runNo:10000];
This is called from an IBAction.
This is the code that is called:
-(void)evaluateFormula:(NSDictionary *)frm runNo:(NSUInteger)runCount
{
self.runCount = runCount;
self.frm = frm;
[self performSelectorInBackground:#selector(backgroundEvalFrm) withObject:nil];
// for (int i = 0; i < runCount; i++) {
// [self runFormula:frm];
// }
//
}
-(void)backgroundEvalFrm
{
percentVal = self.runCount / 100;
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue setMaxConcurrentOperationCount:20];
for (int i = 0; i<self.runCount; i++) {
NSInvocationOperation *op =[[NSInvocationOperation alloc] initWithTarget:self selector:#selector(runFormula:) object:self.frm];
[queue addOperation:op];
}
}
So why is the UI blocked?
Here is the thread return code... it's all in the same class
-(void)runFormula:(NSDictionary *)frm
{
NSMutableString *formula = [[frm objectForKey:kFormulaExpresion] mutableCopy];
NSArray *variables = [frm objectForKey:kVariableArray];
NSArray *evals = [self evaluateVariables:variables];
for (NSDictionary *var in evals) {
NSString *sym = [var objectForKey:kVariableSymbol];
[formula replaceOccurrencesOfString:sym withString:[[var objectForKey:#"numVal"] stringValue] options:NSCaseInsensitiveSearch range:NSMakeRange(0, [formula length])];
}
//parse formula
//NSLog(#"formula to parse:%#",formula);
NSNumber *resNo = [formula numberByEvaluatingString];
// NSLog(#"formula %# the result : %f",formula,[resNo doubleValue]);
//NSNumber *resNo = [NSNumber numberWithDouble:result];
[self performSelectorOnMainThread:#selector(addNewResult:) withObject:resNo waitUntilDone:NO];
}
#pragma mark -- data aggregation delegate
-(void)addNewResult:(NSNumber *)nr
{
NSLog(#"index : %i result: %f",currentIndex,[nr doubleValue]);
[[self delegate] didReceiveResult:nr];
resultsArray[currentIndex]=[nr doubleValue];
currentIndex ++;
if ( (currentIndex % percentVal) == 0) {
[[self delegate] percentCompleted];
}
}
if your calculations are all competing for the same resource (i.e. CPU or I/O) in an uncoordinated manner (highly probable), then you should significantly lower the maximum concurrent operation count -- try 2. chances are, they will complete using less time/energy. furthermore, the main thread will not be reduced to less than 5% of the CPU time during the period that the calculations are executing (result: more responsive UI).

dispatch_queue and return data

I'm trying to write this method that returns an NSArray. My NSMutableArray (friendUsers) adds the objects right, but outside the dispatch_async the array is empty.
I try to add the users in the main queue ( as ashowed) but the array is empty to. Any ideas ? Thanks for all your help.
- (NSArray *)checkUsersInGroup {
NSMutableArray *friendUsers = [[NSMutableArray alloc] init];
dispatch_queue_t checkUSers = dispatch_queue_create("CheckUsers", NULL);
dispatch_async(checkUSers, ^{
NSArray *totalUsers = [VVDataRead lecturaDades];
NSArray *usersToSearch = [_grup objectForKey:#"groupFriends"];
for (NSString *tempUserId in usersToSearch){
for (NSDictionary *user in totalUsers){
NSString *id = [user objectForKey:#"id"];
if ([tempUserId isEqualToString:id])
dispatch_async(dispatch_get_main_queue(), ^{
[friendUsers addObject:user];
});
}
}
});
NSLog(#"people:%#",friendUsers);
return [friendUsers copy];
}
you can use blocks, it can make your life easier in this case.
- (void)checkUsersInGroupWithCompleteBlock:(void(^)(NSMutableArray * resultArray))completeBlock {
NSMutableArray *friendUsers = [[NSMutableArray alloc] init];
dispatch_queue_t checkUSers = dispatch_queue_create("CheckUsers", NULL);
dispatch_async(checkUSers, ^{
NSArray *totalUsers = [VVDataRead lecturaDades];
NSArray *usersToSearch = [_grup objectForKey:#"groupFriends"];
for (NSString *tempUserId in usersToSearch){
for (NSDictionary *user in totalUsers){
NSString *id = [user objectForKey:#"id"];
if ([tempUserId isEqualToString:id])
dispatch_async(dispatch_get_main_queue(), ^{
[friendUsers addObject:user];
});
}
}
// call the complete block with the result when you finished
if (completeBlock) completeBlock(friendUsers);
});
}
...and here is how you can call the method:
- (void)anyMethod {
// ... do whetever you want here before
[self checkUsersInGroupWithCompleteBlock:^(NSMutableArray *resultArray) {
NSLog(#"%#", resultArray);
}];
// ... or after
}
EDITED:
NOTE: here is another possible solution, but in your case it just suspends the main thread (which is definitely bad), so you won't gain anything with this solution but pain on the main thread, but if you are on two background threads, this solution can give a very nice example of synchronisation between the threads.
- (NSArray *)checkUsersInGroup {
NSMutableArray *friendUsers = [[NSMutableArray alloc] init];
// our semaphore is here
dispatch_semaphore_t _semaphore = dispatch_semaphore_create(0);
dispatch_queue_t checkUSers = dispatch_queue_create("CheckUsers", NULL);
dispatch_async(checkUSers, ^{
NSArray *totalUsers = [VVDataRead lecturaDades];
NSArray *usersToSearch = [_grup objectForKey:#"groupFriends"];
for (NSString *tempUserId in usersToSearch){
for (NSDictionary *user in totalUsers){
NSString *id = [user objectForKey:#"id"];
if ([tempUserId isEqualToString:id])
dispatch_async(dispatch_get_main_queue(), ^{
[friendUsers addObject:user];
});
}
}
// the process finished
dispatch_semaphore_signal(_semaphore);
});
// ... we are wainitng for the semaphore's signal
dispatch_semaphore_wait(_semaphore, DISPATCH_TIME_FOREVER);
dispatch_release(_semaphore);
NSLog(#"people:%#",friendUsers);
return [friendUsers copy];
}
There are number of strategies to solve this, but as your operation happens on a background thread, returning the array isn't one of them. You could use NSNotificationCenter to signal that the task as finished and read the array. i.e.
- (void)checkUsersInGroup {
NSMutableArray *friendUsers = [[NSMutableArray alloc] init];
dispatch_queue_t checkUSers = dispatch_queue_create("CheckUsers", NULL);
dispatch_async(checkUSers, ^{
NSArray *totalUsers = [VVDataRead lecturaDades];
NSArray *usersToSearch = [_grup objectForKey:#"groupFriends"];
for (NSString *tempUserId in usersToSearch){
for (NSDictionary *user in totalUsers){
NSString *id = [user objectForKey:#"id"];
if ([tempUserId isEqualToString:id])
dispatch_async(dispatch_get_main_queue(), ^{
[friendUsers addObject:user];
});
}
}
// Signal background task is finished
// Make sure to add an observer to this notification
[[NSNotificationCenter defaultCenter] postNotificationName:#"friendsAddLiteral"
object:nil];
});
}
//this method will respond to the notification
- (void) onFriendsAdded:(NSNotification*)notif {
//do something on the main thread
}

Resources