I'm developing an app in iOS
this application captures signatures and also redraws them back from a database...
The problem appeared when I added threads, for printing on the labels the biometric data on real time while the signature is being drawn, randomly it crashes, I mean sometimes it draws the signature complete, sometimes just 3/4 of the signature or not even a half of it....
Used a tool called Instruments trying to detect the NSZombies and this is what I got...
and looking in the code, this is the portion of code I found...
this is the piece of code where it crashes:
-(void)imprimeValoresFirmaEnTabla:(NSMutableArray *)valores{
if ([valores count] == 3) {
if ([valores objectAtIndex:0] != nil) self.pressureStrokeLabel.text = [valores objectAtIndex:0];
if ([valores objectAtIndex:1] != nil) self.speedStrokeLabel.text = [valores objectAtIndex:1];
if ([valores objectAtIndex:2] != nil) self.locationStrokeLabel.text = [valores objectAtIndex:2];
}
}
it says that -[NSConcreteMutableAtributedString lenght] is the responsible of the Zombie
this is my code:
- (IBAction)drawSignature{
[self clear]; // clear the canvas
NSData *datos = [self desSerializarFirma:[self traerFirmadeBD]]; //unpack the registered signature and store it on the NSData object
if (datos.length <= 1) // if there's no data stored on database
[[[UIAlertView alloc]initWithTitle:#"Notification" message:#"For showing your signature, it is required to register it first, draw your signature on the canvas and tap the Save button" delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil] show];
if (datos.length > 1) { // if is data stored on database
//store the signature on the object
self.firmaCompleta = [NSKeyedUnarchiver unarchiveObjectWithData:datos];
//this loop draws each point on the canvas
for (int i = 0; i < [self.firmaCompleta.location count]; i++) {
SmoothStroke *currentStroke = [canvasView getStrokeForTouchHash:[[self.firmaCompleta.touchHash objectAtIndex:i] intValue]];
//this draws each point using the biometric info
[canvasView addLineToAndRenderStroke:currentStroke
toPoint:CGPointFromString([self.firmaCompleta.location objectAtIndex:i])
toWidth:[canvasView widthForPressure:[[self.firmaCompleta.pressure objectAtIndex:i] intValue]]
toColor:[canvasView colorForPressure:[[self.firmaCompleta.pressure objectAtIndex:i] intValue]]];
//this stores the biometric data in an array for sending to background thread
if (!valoresFirma) valoresFirma = [[NSMutableArray alloc]init];
[valoresFirma removeAllObjects];
[valoresFirma addObject:[NSString stringWithFormat: #"%f", [canvasView widthForPressure:[[self.firmaCompleta.pressure objectAtIndex:i] intValue]]]];
[valoresFirma addObject:[NSString stringWithFormat:#"%f",velocidadTrazo]];
[valoresFirma addObject:[self.firmaCompleta.location objectAtIndex:i]];
//this triggers the tread for printing the values on labels (real time)
[NSThread detachNewThreadSelector:#selector(imprimeValoresFirmaEnTabla:) toTarget:self withObject:valoresFirma];
}
}
}
and the code called by the thread:
-(void)imprimeValoresFirmaEnTabla:(NSMutableArray *)valores{
if ([valores count] == 3) {
if ([valores objectAtIndex:0] != nil) self.pressureStrokeLabel.text = [valores objectAtIndex:0];
if ([valores objectAtIndex:1] != nil) self.speedStrokeLabel.text = [valores objectAtIndex:1];
if ([valores objectAtIndex:2] != nil) self.locationStrokeLabel.text = [valores objectAtIndex:2];
}
}
thanks in advance for your support
Don't do threading like that, it's too easy to shoot yourself in the foot. Create an NSOperation subclass to do the drawing. Make a custom initializer that takes an array. Store the array with the operation object so you know it won't be released on you. It's a few extra lines of code compared to detachNewThreadSelector but it's much safer. Plus you can cancel the operation if the user navigates away from the view that you're showing.
Read up on operations and operation queues here:
http://developer.apple.com/library/mac/#documentation/General/Conceptual/ConcurrencyProgrammingGuide/Introduction/Introduction.html#//apple_ref/doc/uid/TP40008091
Related
I'm working on an application for the iPhone and I'm keeping track of the users current location. When the didupdateLocations delegate method actually executes i would like to test if the location in the NSArray is in a predefined array the contains other locations, this array mind you could grow over time.
I'm running a for loop within this method to test against my own array of locations but i would like to move that to a separate thread. So in case my own array with multiple locations grows to a large number the for loop does not freeze my UI.
I have tried it like this but I'm getting undesirable results. I understand that the location tracking definitely happens in a separate thread. However those didupdateLocations execute on a separate thread. The Apple doc's are not very clear on the matter. My end goal again is to compare against my array and not lock the UI.
- (void)locationManager:(CLLocationManager *)manager
didUpdateLocations:(NSArray *)thisLocation {
dispatch_queue_t queue= dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// get the last object in the array of locations
CLLocation* location = [thisLocation lastObject];
dispatch_async(queue, ^{
[self checkmyArray:location];
});
}
-(void)checkmyArray:(CLLocation *)workingLocation{
NSLog(#"SoundTheAlarm");
int alarm_on_c = 0;
NSUInteger tmp_count = [theData.LocationsObjectArray count];
BOOL alarm;
NSMutableDictionary * tempObject;
CLLocationDistance distance = 0.0;
for (int i = 0; i < tmp_count; i++) {
tempObject= [theData.LocationsObjectArray objectAtIndex:i];
thisLoc = [[tempObject objectForKey:#"onoff"] isEqual:#YES];
if (thisLoc) {
//check if we are near that location
double lat = [[tempObject objectForKey:#"latitude"] doubleValue];
double lon = [[tempObject objectForKey:#"longitude"] doubleValue];
// goal = [[CLLocation alloc] initWithLatitude:40.097771 longitude:-74.941399];
goal = [[CLLocation alloc] initWithLatitude:lat longitude:lon];
// check the destination between current location and goal location - in meters
distance = [goal distanceFromLocation:workingLocation];
NSLog(#"distance %f\n", distance);
}
// if distance from goal is less than 350 meters
if (distance <= 350){
[self scheduleNotification:[tempObject objectForKey:#"name"]];
// turn off tracking for this location
[tempObject setObject:#NO forKey:#"onoff"];
[theData.LocationsObjectArray replaceObjectAtIndex:i withObject:tempObject];
NSIndexPath *path = [NSIndexPath indexPathForRow:i inSection:0];
ExtendedSavedCellTableViewCell *cell = (ExtendedSavedCellTableViewCell *)[self.tableView cellForRowAtIndexPath:path];
cell.switchView.on = NO;
// save the update to the switch to the database as well
NSString *lat = [tempObject objectForKey:#"latitude"];
/*check to determine if the uiswitch is turned off or on.*/
[self fetchedResultsController:#NO lat:lat index:path];
[self displayAlertViewForAlarm:[tempObject objectForKey:#"name"]];
}
-(void)displayAlertViewForAlarm:(NSString *)nameOfLocation{
dispatch_async(dispatch_get_main_queue(), ^(void) {
UIAlertView *myAlert = [[UIAlertView alloc] initWithTitle:#"Destination reached"
message:nameOfLocation
delegate:self
cancelButtonTitle:#"Go Away"
otherButtonTitles:#"Notify", nil];
[myAlert show];
});
}
It's generally a bad idea to use threads in iOS if you can at all avoid it. In your case, I'd implement the function which does the looping to automatically pop out of the loop after too many iterations and then schedule the next chunk of iterating to happen in another pass through the event loop. In other words, something like this:
- (void) checkLocationsStartingAt:(NSNumber)start
{
NSInteger s = (start) ? [start intValue] : 0;
for (i = s; i < list.length; i++) {
if (i > TOO_MANY) {
[self performSelector:#selector(checkLocationsStartingAt:)
withObject:#(i)
afterDelay:0.001];
return;
} else {
// check element at i
}
}
}
see: NSObject Reference
You are checking one location against an array of other locations.
You display an alert for time you are closer than 350m from a location.
You could be within 350m of many items in the list.
You have no code prevent multiple alerts.
So you will sometimes have many alerts.
You have a couple options. One will probably suit your needs better than the others. Possibly one I didn't list.
You could re-use a single UIAlertView instance and check the
visible property on it. If it's already visible, do nothing.
You could break out of the loop once you get a single "hit" on being
<350m.
You could stick in a BOOL which records if you've shown the alert and
check the BOOL every time before you show the alert again.
I have 2 tableViews at the moment.
One is a basic tableView which shows cells and if selected, it goes into a detailed viewController.
I created a new viewController to create a filtered page view. I populate my page view with a plist. I managed to create a filter but don't know how to proceed from here.
This is what I have:
- (IBAction)ingredientsAddButton:(UIButton *)sender
{
int j=0;
onemsiz.text=ingredientTextField.text;
ingredientText=onemsiz.text;
NSLog(ingredientText);
NSString *path = [[NSBundle mainBundle] pathForResource:#"recipes" ofType:#"plist"];
NSArray *arrayOfPlist = [[NSArray alloc] initWithContentsOfFile:path];
if (arrayOfPlist==NULL) {
}else {
for (int i=0; i<4; i++) {
// i currently have 4 element is plist.
NSString *strCurrentRecipeIngredients = [[arrayOfPlist objectAtIndex:i] objectForKey:#"recipeIngredients"];
NSString *strCurrentRecipeName = [[arrayOfPlist objectAtIndex:i] objectForKey:#"recipeName"];
//NSLog(#"%d. Loop \n", i+1);
if([strCurrentRecipeIngredients rangeOfString:(#"%#",ingredientText)].location!=NSNotFound) {
NSLog(#"%# contains %# ",strCurrentRecipeName, ingredientText);
NSLog(ingredientText);
NSLog(strCurrentRecipeIngredients);
j++;
}else {
NSLog(#"Not found");
NSLog(ingredientText);
NSLog(strCurrentRecipeIngredients);
}
if (ingredientText==NULL) {
NSLog(#"empty input");
}else {
}
}
}
NSLog(#"%d",j);
}
My first problem is how can I show the results in a tableView?
You create a table view and then create and assign its data source and delegate. If data in one of the table view needs to be changed when it is already displayed, the easy way to force it to update is call [tableView reloadData];
for (int i = 0; i< [delarsInfoArray count] ; i++)
{
NSString *lattitudeValue;
NSString *longitudeValue;
if ([[delarsInfoArray objectAtIndex:i]count]>1) {
lattitudeValue = [[[delarsInfoArray objectAtIndex:i]valueForKey:#"LATITUDE"]objectAtIndex:1];
longitudeValue = [[[delarsInfoArray objectAtIndex:i]valueForKey:#"LONGITUDE"]objectAtIndex:0];
}
else
{
lattitudeValue = #"";
longitudeValue = #"";
}
CLLocationCoordinate2D pinLocation;
if(([lattitudeValue floatValue] != 0) && ([longitudeValue floatValue] != 0) ) {
mapRegion.center.latitude = [lattitudeValue floatValue];
mapRegion.center.longitude = [longitudeValue floatValue];
if(pinLocation.latitude !=0 && pinLocation.longitude !=0) {
myAnnotation1 = [[MyAnnotation alloc] init];
if ([[delarsInfoArray objectAtIndex:i] count] == 0) {
myAnnotation1.title = #"";
myAnnotation1.subtitle = #"";
}
else
{
// NSLog(#"====== delears array is===%#",delarsInfoArray);
NSLog(#"===== delears array count is %d",[delarsInfoArray count]);
if ([[[delarsInfoArray objectAtIndex:i]valueForKey:#"Address"]objectAtIndex:2] !=nil)
{
myAnnotation1.title = [[[delarsInfoArray objectAtIndex:i]valueForKey:#"Address"]objectAtIndex:2];
}
if ([[[delarsInfoArray objectAtIndex:i]valueForKey:#"City"]objectAtIndex:3]!= nil) {
myAnnotation1.subtitle = [[[delarsInfoArray objectAtIndex:i]valueForKey:#"City"]objectAtIndex:3];
}
NSLog(#"%#",[[[delarsInfoArray objectAtIndex:i]valueForKey:#"City"]objectAtIndex:3]);
}
[dealerMapView setRegion:mapRegion animated:YES];
[dealerMapView addAnnotation:myAnnotation1];
myAnnotation1.coordinate = mapRegion.center;
[myAnnotation1 release];
}
}
}
The above code is written in the viewWillAppear.After loading the map in to the view,when i clicked on the map.app gets crashed.How can solve this crash?
There are a lot of issues here, but the one that leaps out to the top of the list are the lines that read:
if ([[[delarsInfoArray objectAtIndex:i]valueForKey:#"Address"]objectAtIndex:2] !=nil)
...
and
if ([[[delarsInfoArray objectAtIndex:i]valueForKey:#"City"]objectAtIndex:3]!= nil) {
...
The problem is that objectAtIndex of a valueForKey of an array will never be nil. You can't store a nil in an array, so what valueForKey does, if it doesn't find a value, is it uses a NSNull object, [NSNull null]. That designates that there was no value found, but uses NSNull (which can be added to the array) instead of nil (which can't).
The problem is likely that there is some subsequent code (for example, the code that tries to figure out the size of the callout bubble) which tries to get the length of the string, but since you stored a NSNull, it's trying to call the length method and it's failing.
You could fix this a number of ways, such as:
if ([[[delarsInfoArray objectAtIndex:i]valueForKey:#"Address"]objectAtIndex:2] != [NSNull null])
...
My app is MapKit based, where multiple users can be tracked. Now using our web services, I am displaying my location on the map plus other users' last, let's say 10 locations. If a user updates their location, it is sent through the web service and displayed on the maps via call back. I am able to track other users in real time but don't know how to use Threading here. My UI is blocking at times and also crashing sometimes due to memory issue.
In my connectionDidFinishLoading method, I am parsing JSON data and then creating annotations and overlay:
-(void) connectionDidFinishLoading: (NSURLConnection *) connection
{
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
NSArray *trackingDict = [NSJSONSerialization JSONObjectWithData:empJsonData options:kNilOptions error:nil];
NSLog(#"Json Dictionary = %#", trackingDict);
NSLog(#"COUNT = %i",trackingDict.count);
if ([trackingDict count] >= 2) {
for (trackUsersCount = 0; trackUsersCount< trackingDict.count; trackUsersCount++) {
NSLog(#"trackUsersCount %i", trackUsersCount);
NSMutableArray *latlongArray = [[NSMutableArray alloc]init];
latlongArray = [[trackingDict objectAtIndex:trackUsersCount]objectForKey:#"latlong"];
[userLongitudeArray removeAllObjects];
[userLatitudeArray removeAllObjects];
for (int i = 0; i<latlongArray.count; i++) {
NSLog(#"COunt - > %#", [[latlongArray objectAtIndex:i]objectForKey:#"lat"]);
NSLog(#"COunt - > %#", [[latlongArray objectAtIndex:i]objectForKey:#"long"]);
[userLatitudeArray addObject:[[latlongArray objectAtIndex:i]objectForKey:#"lat"]];
[userLongitudeArray addObject:[[latlongArray objectAtIndex:i]objectForKey:#"long"]];
}
// ProfilePIC URL
profilePicURLString = [[trackingDict objectAtIndex:trackUsersCount]objectForKey:#"user_profilePicture"];
NSString *name = [[trackingDict objectAtIndex:trackUsersCount]objectForKey:#"user_firstName"];
[userNameArray addObject:name];
[profilePicURLStringArray addObject:profilePicURLString];
for (int i = 0; i<userLatitudeArray.count; i++) {
CLLocationCoordinate2D userLocation;
userLocation.latitude = [[userLatitudeArray objectAtIndex:i]doubleValue];
userLocation.longitude = [[userLongitudeArray objectAtIndex:i] doubleValue];
Annotation *Anno = [[Annotation alloc]init];
Anno.coordinate = userLocation;
Anno.title = name;
Anno.userProfileImageString = profilePicURLString;
[mapView addAnnotation:Anno];
}
NSLog(#"ARRAY for longitude %#", userLongitudeArray);
NSLog(#"ARRAY for latitude %#", userLatitudeArray);
int i;
for (i = 0; i<userLatitudeArray.count; i++) {
CLLocationCoordinate2D userLocation;
userLocation.latitude = [[userLatitudeArray objectAtIndex:i]doubleValue];
userLocation.longitude = [[userLongitudeArray objectAtIndex:i] doubleValue];
MKMapPoint * pointsArray = malloc(sizeof(CLLocationCoordinate2D)*userLongitudeArray.count);
pointsArray[i] = MKMapPointForCoordinate(userLocation);
polyline = [MKPolyline polylineWithPoints:pointsArray count:i];
free(pointsArray);
}
[mapView addOverlay:polyline];
}
}
[mapView reloadInputViews];
}
}
The web service is called after every 20 seconds, I know I can user GCD here or other threading approach but at the time-interval when web service is called via background thread, the annotations and overlays are not displayed not the map.
Any help is much appreciated!
I don't think there's an quick & easy fix for this. One approach would be to separate the code that collects (modifies) the data from the code that updates the view.
Set the code that is dealing with UI into a separate method, updateUI for example.
From here on you have couple of choices. You could try this for example:
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
//perform the data collection, calculations...
//here is where the model in MVC gets modified
//
[self performSelectorOnMainThread: #selector(updateUI) withObject:nil waitUntilDone: NO];
}
-(void)updateUI
{
//do the UI updates (like adding overlays etc...) here
}
You could also store all the data needed to update the UI to a kind of object and pass it as withObject: parameter.
After cleaning up my code and making it ready for deployment I encountered a weird problem. When I try to add a contact to my tableview it always crashes when the empty is array. After that its not problem to add as many as you want. And also when I delete a contact and the array is empty, it also crashes with the same error message:
Terminating app due to uncaught exception 'NSRangeException', reason:
' -[__NSArrayM objectAtIndex:]: index 0 beyond bounds for empty array'
Here is what I do: I add a contact via php to my database, read it out with xml and give it back to the app so the user can instantly see what happened (adding a contact that is). There is a lot of code involved so I stick to the basics for now.
When the view loads the array gets allocated. User can add a contact. While adding the contact a php file is called that creates the xml file where the contacts are saved.
Here is where I get the object back and paste it into my array and display it
self.filteredListContent = [NSMutableArray arrayWithCapacity:[self.tabelle count]];
self.tableView.scrollEnabled = YES;
numbers = [[NSMutableArray alloc] init];
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
NSString *fileName = [prefs stringForKey:#"number"];
NSString *cc = [prefs stringForKey:#"Code"];
NSString *urlString = [NSString stringWithFormat: #"http://myserver/%#%#.xml", cc, fileName];
NSURL *url = [NSURL URLWithString: urlString];
DataFileToObjectParser *myParser = [[DataFileToObjectParser alloc] parseXMLAtUrl:url toObject:#"contacts" parseError:nil];
for(int i = 0; i < [[myParser items] count]; i++) {
contacts *new = [[Telefonbuch alloc] init];
new = (contacts *) [[myParser items] objectAtIndex:i];
[numbers addObject:new];
[self.tableView reloadData];
And here is how it gets displayed, nothing special:
NSString *TableText = [[NSString alloc] initWithFormat:#"%#", [[numbers objectAtIndex:indexPath.row] fname]];
NSString *TableText2 = [[NSString alloc] initWithFormat:#"%#", [[numbers objectAtIndex:indexPath.row] lname]];
NSString *cellValue = [NSString stringWithFormat:#"%# %#", TableText, TableText2];
cell.textLabel.text = cellValue;
Your loop sounds strange.
for(int i = 0; i < [[myParser items] count]; i++) {
contacts *new = [[Telefonbuch alloc] init];
new = (contacts *) [[myParser items] objectAtIndex:i];
[numbers addObject:new];
[self.tableView reloadData];
First avoid to use new keyword (as user1118321 already suggested).
Then, what is contacts? Is contacts a superclass of type Telefonbuch?
What do these two lines mean?
contacts *new = [[Telefonbuch alloc] init];
new = (contacts *) [[myParser items] objectAtIndex:i];
You alloc-init an instance of Telefonbuch class to a new (avoid this) variable but then you assign that variable to another object. Why?
The right code could be like the following.
for(int i = 0; i < [[myParser items] count]; i++) {
Telefonbuch* newContact = (Telefonbuch*) [[myParser items] objectAtIndex:i]; // or contacts* newContact = (contacts*) [[myParser items] objectAtIndex:i];
// maybe it could be better to rename contacts with a capital letter
[numbers addObject:newContact];
}
[self.tableView reloadData];
Some notes
If you want to add or delete item to a table view, you need to do it in 2 stages:
1) Deal with your model
[yourModel removeObjectAtIndex:row];
2) Deal with your table’s animation
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
If you need to download content, maybe you could consider to do it asynchrously without blocking the main thread. In particular, this is could be the synchronous call that could be a blocking one (since I don't have any details I'm only supposing it).
DataFileToObjectParser *myParser = [[DataFileToObjectParser alloc] parseXMLAtUrl:url toObject:#"contacts" parseError:nil];
If you don't use ARC, pay attention to memory management.
Hope it helps.