I've a NSMutableDictionary , that i add to some custom NSObjects which i receive asynchronously. Each time i receive new data i clear the dictionary then add the new objects. Then push data to another class "UIView" to render the content in the dictionary. I have been getting SIGABRT exception which as far as i know means the object trying to access has been released. I have tried synchronization blocks , creating a mututablecopy and getting allvalues but nothing worked now My question how i can achieve synchronization
here is a code snippet ,
#interface MyAltViewController : UIViewController
{
__block NSMutableDictionary *currentDataList;
TestUIVIEW *myUIVIEW
}
#implementation MyAltViewController
......
- (void)viewDidLoad
{
currentDataList = [[NSMutableDictionary alloc] initWithCapacity:10];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(processDataMessag:)
name:view_Data object:nil];
}
.....
-(void)processDataMessag:(NSNotification *) notification
{
[currentDataList removeAllObjects];
NSArray tmpAr= (NSArray*) [notification object]
dispatch_async(dbQueue, ^(void)
{
/// Loop through the array and process the data then add to NSDictionary
[self pushtoMUViewLayer:currentDataList];
});
}
.........
-(void)pushtoMUViewLayer:(NSMutableDictionary *)ina
{
/// Even here if i try to access and object within
/// currentDataList and just a specific NSString i get the SIGABRT
[myUIVIEW updateWithData:ina];
}
//////////////////////////////////////////////////
#interface TestUIVIEW : UIView
{
NSMutableDictionary *mdata;
UIImage *outputImage ;
}
#property (assign) NSMutableDictionary *mapdata;
#property (retain) UIImage *outputImage ;
#implementation TestUIVIEW
.......
-(void)updateWithData:(NSMutableDictionary *)data
{
mdata = [data retain]; // ??? not sure how to set it correctly
dispatch_async(dispatch_get_main_queue(), ^(void)
{
[self CreateImage];
[self setNeedsDisplay];
});
}
-(void) CreateImage
{
NSString *key;
for(key in mapdata)
{
DataMessage *tmpData= (DataMessage*)[mapdata objectForKey:key];
NSString *aID = [ tmpData alt] ;
double aLat = [ tmpData lat] ;
double aLong = [ tmpData lon] ;
/*
Drawing code I can Get aLat & A Long
but it fails with SiABRT when trying to render
aID
/*
}
}
- (void)drawRect:(CGRect)rect
{
// Drawing code
CGPoint imagePoint = CGPointMake(0, 0);
[outputImage drawAtPoint:imagePoint];
}
/// if that synchronization didnt work . i can use NSNotification to see my currentDataList
// to my currentDataList
Adding #synchronised block around all the places where i access the NSDictionary did solove the problem . Thanks all for your input
Related
i have to show the user details from NSUserDefaults in more than 5 view controllers. So i have created a NSObject subclass, which will load the user details from server when the first view controllers viewDidLoad is called.
Here is my First view controller viewDidLoad
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
// Getting the Current User Details
CurrentUserDetails *userDetails = [[CurrentUserDetails alloc]init];
[userDetails initializeTheCurrentUserData];
//CurrentUserDetails is my NSObject class
}
And
#import <Foundation/Foundation.h>
#interface CurrentUserDetails : NSObject
#property(strong,nonatomic) NSString *memberName;
#property(strong,nonatomic) NSString *designation;
#property(strong,nonatomic) NSString *memberType;
#property(strong,nonatomic) NSString *entreprenuer;
#property(strong,nonatomic) NSDate *expiryDate;
#property(strong,nonatomic) NSData *imageData;
- (void) initializeTheCurrentUserData;
#end
and implementation
#implementation CurrentUserDetails
- (void) initializeTheCurrentUserData{
NSData *data = [[NSUserDefaults standardUserDefaults] valueForKey:#"userDictionary"];
NSDictionary *retrievedDictionary = [NSKeyedUnarchiver unarchiveObjectWithData:data];
self.memberName = [retrievedDictionary valueForKey:#"Name"];
self.designation = [retrievedDictionary valueForKey:#"Designation"];
self.memberType = [[retrievedDictionary valueForKey:#"Member_type"] stringValue];
self.expiryDate = [retrievedDictionary valueForKey:#"Expiry"];
self.kanaraEntreprenuer = [retrievedDictionary valueForKey:#"CityName"];
NSString *imageUrl = [retrievedDictionary valueForKey:#"Member_image"];
self.imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:[NSString stringWithFormat:#"%#%#",[GlobalVariables getBaseURLForMemberImage],imageUrl]]];
}
And when iam trying to take the details from other class like this..
CurrentUserDetails *userDetails = [[CurrentUserDetails alloc]init];
memberName = userDetails.memberName;
designation = userDetails.designation;
memberType = userDetails.memberType;
dateFromServer = userDetails.expiryDate;
entreprenuer = userDetails.entreprenuer;
imageDataFromServer = userDetails.imageData;
I am getting nil values.
But if call initializeTheCurrentUserData method each time, i am getting the exact values. I though once a property is assigned with a value , we can use the property for entire program. I'm getting confusion.. Can anyone please tell me about this????. Do i need to call initializeTheCurrentUserData everytime when i want to use the values?
Once you set a property of an instance, that property remains for that instance. You, however, are creating new instances with [[CurrentUserDetails alloc] init]. Each new instance will be initialized with default values (nil for NSString).
Call -initializeTheCurrentUserData in -init so each instance will be initialized with the values from user defaults.
#implementation CurrentUserDetails
- (instancetype)init {
self = [super init];
if (self != nil) {
[self initializeTheCurrentUserData];
}
return self;
}
- (void)initializeTheCurrentUserData {
…
}
I think that I'm missing some fundamental knowledge on Xcode Objective C programming standards. Unfortunately I couldn't find the appropriate solution to my problem.
The problem is that when I try to keep data in an array of objects it becomes impossible to keep them separately. Adding new objects overwrites the previous objects in array. Here is some code about that:
CustomObject.m file:
#import "CustomObject.h"
NSString * title;
NSString * detail;
#implementation CustomObject
- (void) initCustomObjectWithValues : (NSString *) iTitle : (NSString *) iDetail {
title = [NSString stringWithString:iTitle];
detail = [NSString stringWithString:iDetail];
}
- (NSString *) getTitle {
return title;
}
- (NSString *) getDetail {
return detail;
}
#end
viewDidLoad function in ViewController.m file:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
myMutableArray = [[NSMutableArray alloc] init];
for (int i=0; i<10; i++) {
NSString * tempTitle = [#"title " stringByAppendingString:[NSString stringWithFormat:#"%d",i]];
CustomObject * myCustomObject = [[CustomObject alloc] init];
[myCustomObject initCustomObjectWithValues :[NSString stringWithFormat:#"%#",tempTitle]
:[#"detail " stringByAppendingString:[NSString stringWithFormat:#"%d",i]]];
[myMutableArray addObject:myCustomObject];
}
for (int i=0; i<10; i++) {
NSLog(#"%#",[[myMutableArray objectAtIndex:i] getTitle]);
NSLog(#"%#",[[myMutableArray objectAtIndex:i] getDetail]);
NSLog(#"----------------------------");
}
}
Here, myMutableArray is defined at the top of the ViewController.m file. (To make it global and can be used in other functions in future)
Here what I've got in the logs:
title 9
detail 9
----------------------------
title 9
detail 9
----------------------------
title 9
detail 9
----------------------------
title 9
detail 9
----------------------------
title 9
detail 9
----------------------------
title 9
detail 9
----------------------------
title 9
detail 9
----------------------------
title 9
detail 9
----------------------------
title 9
detail 9
----------------------------
title 9
detail 9
----------------------------
As far as I understand each new added object overwrites the olds. First I thought that they are referring to same allocated memory but in debug tool myMutableArray seems like this:
Printing description of myMutableArray:
<__NSArrayM 0x8d8cb60>(
<CustomObject: 0x8d8e990>,
<CustomObject: 0x8d8dd40>,
<CustomObject: 0x8d8d2e0>,
<CustomObject: 0x8d8d470>,
<CustomObject: 0x8d8d350>,
<CustomObject: 0x8d8ddf0>,
<CustomObject: 0x8d8df00>,
<CustomObject: 0x8d8df40>,
<CustomObject: 0x8d8dff0>,
<CustomObject: 0x8d8e0c0>
)
Does anyone have an idea about the solution. It should be something very basic but I can't catch the problem.
Thank you all in advance
using
NSString * title;
NSString * detail;
outside of the #interface part creates global variables. When you assign a variable to title or detail you don't set an instance variable of your object, you change those global variables. And since they are global, they are the same for all objects that reference them.
Turn those global variables into instance variables, or even better use #property.
Your code is bad objective-c overall.
You should not use get in getters that return variables. You should not have methods that start with init and don't return self. You should only call init in [[Foo alloc] init...] situations. You should avoid unnamed parameters in your methods.
And there is no need to create strings from strings from strings.
Here is how I would write it:
// CustomObject.h
#interface CustomObject : NSObject
#property (copy, nonatomic) NSString * title;
#property (copy, nonatomic) NSString * detail;
- (id)initWithTitle:(NSString *)title detail:(NSString *)detail
#end
// CustomObject.m
#import "CustomObject.h"
#implementation CustomObject
- (id)initWithTitle:(NSString *)title detail:(NSString *)detail {
self = [super init];
if (self) {
// use stringWithString: to create #"" strings when title is nil
// if nil is a valid value for those variables you should use
// _title = [title copy];
_title = [NSString stringWithString:title];
_detail = [NSString stringWithString:detail];
}
return self;
}
#end
for (int i=0; i<10; i++) {
NSString *tempTitle = [NSString stringWithFormat:#"title %d",i];
NSString *tempDetail = [NSString stringWithFormat:#"detail %d",i];
CustomObject * myCustomObject = [[CustomObject alloc] initWithTitle:tempTitle detail:tempDetail];
[myMutableArray addObject:myCustomObject];
}
for (int i=0; i<10; i++) {
CustomObject *object = myMutableArray[i];
NSLog(#"%#", object.title);
// or NSLog(#"%#", [object title]); if you don't like dot-notation.
NSLog(#"%#", object.detail);
NSLog(#"----------------------------");
}
Im still trying to get on to the ios development but im hoping you can help me.
Currently I have a WCF that returns some json data in the format of
"Address": "3453453",
"Category": "CONCRETE",
"Closest_Property_Number": 2,
"ID": 42,
"Image1": 324,
"Image2": 0,
"Image3": 0,
"Latitude": 2,
"Longitude": "-6.541902",
"Notes": "GHTFHRG",
"User_ID": 2
I then created a class called Location here is the Location.m
#import "Location.h"
#implementation Location {
NSString* _address;
NSString* _category;
NSString* _closest_Property_Number;
NSString* _iD;
NSString* _image1;
NSString* _latitude;
NSString* _longitude;
NSString* _notes;
NSString* _user_ID;
}
#synthesize address = _address;
#synthesize category = _category;
#synthesize closest_Property_Number = _closest_Property_Number;
#synthesize iD = _iD;
#synthesize image1 = _image1;
#synthesize latitude = _latitude;
#synthesize longitude = _longitude;
#synthesize notes = _notes;
#synthesize user_ID = _user_ID;
#end
I think this is right so far? Here is my class where all the importing happens
#import "Location.h"
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
NSString *urlAsString = #"http://crm.fpmccann.co.uk/TemperatureWebService/iphonewebservice.svc/retrievelocations";
NSURL *url = [NSURL URLWithString:urlAsString];
NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url];
[NSURLConnection
sendAsynchronousRequest:urlRequest
queue:[[NSOperationQueue alloc] init]
completionHandler:^(NSURLResponse *response,
NSData *data,
NSError *error)
{
if ([data length] >0 && error == nil)
{
NSMutableArray* tmpLocations = [[NSMutableArray alloc] init];
for (NSDictionary* loc in locations) {
Location* location = [[Location alloc] initWithParameters:loc];
[tmpLocations addObject:location];
}
NSMutableArray* tmpAnnotations;
for (NSDictionary* location in tmpLocations)
{
// retrieve latitude and longitude from location
MKPointAnnotation* annotation = [[MKPointAnnotation alloc] init];
annotation.title = location.address;
newAnnotation.coordinate = location;
[tmpAnnotations addObject:annotation];
}
dispatch_async(dispatch_get_main_queue(), ^{
self.locations = tmpLocations;
self.annotations = tmpAnnotations;
[self.mapView reloadInputViews];
});
}
else if ([data length] == 0 && error == nil)
{
NSLog(#"Nothing was downloaded.");
}
else if (error != nil){
NSLog(#"Error = %#", error);
}
}];
}
Here is where i am having problems, I want to show an annotation on a UImapview using the information from the json data. Please see the errors i am having in this part of the code below, commented on the line that they are happening
if ([data length] >0 && error == nil)
{
NSMutableArray* tmpLocations = [[NSMutableArray alloc] init];
for (NSDictionary* loc in locations) { //receiving error use of undeclared identifier 'locations', did you mean 'Location'
Location* location = [[Location alloc] initWithParameters:loc];
[tmpLocations addObject:location];
}
NSMutableArray* tmpAnnotations;
for (NSDictionary* location in tmpLocations)
{
// retrieve latitude and longitude from location
MKPointAnnotation* annotation = [[MKPointAnnotation alloc] init];
annotation.title = location.address; // receiving error Property 'address' not found on object of type 'NSDictionary'
newAnnotation.coordinate = location; // receiving error use of undeclared identifier 'newAnnotation'
[tmpAnnotations addObject:annotation];
}
dispatch_async(dispatch_get_main_queue(), ^{
self.locations = tmpLocations; /// receiving error Property 'locations' not found on object of type 'MapViewController'
self.annotations = tmpAnnotations; /// receiving error Property 'annotations' not found on object of type 'MapViewController'
[self.mapView reloadInputViews];
});
}
And here is my MapViewController.h
#import <UIKit/UIKit.h>
#import <MapKit/MapKit.h>
#interface MapViewController : UIViewController <MKMapViewDelegate>
#property (nonatomic, retain) IBOutlet MKMapView *mapView;
- (IBAction)refreshTapped:(id)sender;
#end
You should make a few improvements to your code:
First, it's crucial to conform to the "naming conventions" in Objective-C. Properties, should start with a lowercase letter. For example:
#property (nonatomic, copy) NSString* address;
Properties of type NSString should have a "copy" attribute (the exception is managed objects).
Almost always the name of a class should be in singular form, that is instead of
#class LocationResults;
I would suggest to name it
#class Location;
The preferred way to declare ivars is in the implementation. So, instead of declaring ivars in the interface
In file Location.h
#interface Location : NSObject{
NSString* address;
}
declare them as shown below:
#interface Location : NSObject
... // public properties and methods
#end
In file Location.m:
#implementation Location {
NSString* _address;
}
#synthesize address = _address;
Note:
clang supports "auto-synthesized" properties, which let you omit the ivar declaration and the #synthesize directive.
Now, regarding your code in viewDidLoad:
You seem to load a resource from a remote server:
NSData *data = [NSData dataWithContentsOfURL:url];
This is not an appropriate way to load resources from a remote server: it's an synchronous method which merely uses the thread to wait for something happen in the future (a response from the underlying network code).
The underlying network code internally dispatches its work onto internal private threads.
The effect is, you are wasting system resources when you just use a thread which gets blocked anyway for doing nothing. And - even more importantly - since you are calling this method in your main thread you are blocking the main thread and thus blocking UIKit display updates and other UIKit tasks.
Furthermore, networks request may fail in countless ways. The method dataWithContentsOfURL: cannot return reasonable error information.
These are just the most obvious caveats - but rest assured, there are more!
So, when accessing remote resources, generally use NSURLConnection or NSURLSession (or a third party library which utilizes these under the hood). In a first viable approach use the asynchronous class method:
+ (void)sendAsynchronousRequest:(NSURLRequest *)request
queue:(NSOperationQueue *)queue
completionHandler:(void (^)(NSURLResponse*, NSData*, NSError*))handler;
While this method is better, it sill has a number of caveats: no way to cancel, no way to tailor authentication, no way to customize anything.
There a bunch of questions and answers how to use sendAsynchronousRequest:queue:completionHandler: on SO. Here are a few related questions and answers:
How to return an UIImage type from a GCD
NSData dataWithContentsOfURL: not returning data for URL that shows in browser
ERROR happened while deserializing the JSON data
As a rule of thumb, always check return values and if an error output parameter is given, provide an NSError object.
In case of sendAsynchronousRequest:queue:completionHandler: you should also check for the status code of the HTTP response and the Content-Type and confirm that you actually get what you requested and what you expect.
Having that said, you would populate your array of Locations as follows:
In the completion handler of sendAsynchronousRequest:queue:completionHandler: you would, first check the error and status code if that matches your expectations. IFF this is true, you have obtained a NSData object containing your JSON, then within the completion handler you implement this code:
NSError* error;
NSArray* locations = [NSJSONSerialization JSONObjectWithData:data
options:0
error:&error];
if (locations != nil)
{
NSMutableArray* tmpLocations = [[NSMutableArray alloc] init];
for (NSDictionary* loc in locations) {
Location* location = [[Location alloc] initWithParameters:loc];
[tmpLocations addObject:location];
}
NSMutableArray* tmpAnnotations;
for (NSDictionary* location in tmpLocations)
{
// retrieve latitude and longitude from location
MKPointAnnotation* annotation = [[MKPointAnnotation alloc] init];
annotation.title = location.address;
annotation.coordinate = ...
[tmpAnnotations addObject:annotation];
}
dispatch_async(dispatch_get_main_queue(), ^{
self.locations = tmpLocations;
self.annotations = tmpAnnotations;
[self.tableView reloadData];
});
}
else {
// handle error
....
}
Note: The actual implementation depends on you more specific requirements. This implementation is merely an example how to solve a such a problem.
The method initWithParameters: should be straight forward.
I'm learning iOS development through Stanford's iTunesU program. I am stuck on an unexpected problem I am having.
I have added a clear method, but I am getting this error
//Use of undeclared identifier 'operandStack'; did you mean '_operandStack'?
I know I can fix the problem by using [self.operandStack ...etc instead of [operandStack
Why do I need self? Isn't it implied? Why do I not need to use self when referencing _operandStack?
#import "CalculatorBrain.h"
#interface CalculatorBrain()
//string because we are the only ones interested
#property (nonatomic, strong) NSMutableArray *operandStack;
#end
#implementation CalculatorBrain
#synthesize operandStack = _operandStack;
- (void) setOperandStack:(NSMutableArray *)operandStack
{
_operandStack = operandStack;
}
- (NSMutableArray *) operandStack
{
if(_operandStack==nil) _operandStack = [[NSMutableArray alloc] init];
return _operandStack;
}
- (void) pushOperand:(double)operand
{
NSNumber *operandObject = [NSNumber numberWithDouble:operand];
[self.operandStack addObject:operandObject];
}
- (double) popOperand
{
NSNumber *operandObject = [self.operandStack lastObject];
if (operandObject !=nil)
{
[self.operandStack removeLastObject];
}
return [operandObject doubleValue];
}
- (void) clear
{
//clear everything
[operandStack removeAllObjects];
//***************************
//Use of undeclared identifier 'operandStack'; did you mean '_operandStack'?
}
- (double) performOperation:(NSString *)operation
{
double result =0;
//calculate result
if ([operation isEqualToString:#"+"]) {
result = [self popOperand] + [self popOperand];
} else if ([operation isEqualToString:#"*"]) {
result = [self popOperand] * [self popOperand];
} else if ([operation isEqualToString:#"π"]) {
[self pushOperand:3.14159];
NSNumber *operandObject = [self.operandStack lastObject];
return [operandObject doubleValue];
}
[self pushOperand:result];
return result;
}
#end
Because you have synthesized it (note: in newer Objective-C version the synthesis is automatic) :
#synthesize operandStack = _operandStack;
It means that you generated getter and setter, and that you access the property by calling it _operandStack. If you want to call it operantStack change it to that:
#synthesize operandStack;
If instead you use self.operandStack, you are using the getter/setter generated by the property, not the synthesized one.
Using synthesized and not synthesized properties is different, there isn't a "recommended way" of accessing properties like many people think, they just have different meaning. For example here:
- (void) setOperandStack:(NSMutableArray *)operandStack
{
_operandStack = operandStack;
}
You must use the synthesized property, otherwise you go into an infinite loop. The synthesized property is automatically generated, the non synthesized property is also automatically generated but it can be overridden, like you did in that case, and it can be also accessed externally.
I've got a layered NSMutableDictionary object and i'd like to be able to remove dictionaries deeper down in the hierarchy. Is there a quick and easy way to do this, for example, a removeObjectAtKeyPath-like method? Can't seem to find one.
Thanks!
Nothing built in, but your basic category method will do just fine:
#implementation NSMutableDictionary (WSSNestedMutableDictionaries)
- (void)WSSRemoveObjectForKeyPath: (NSString *)keyPath
{
// Separate the key path
NSArray * keyPathElements = [keyPath componentsSeparatedByString:#"."];
// Drop the last element and rejoin the path
NSUInteger numElements = [keyPathElements count];
NSString * keyPathHead = [[keyPathElements subarrayWithRange:(NSRange){0, numElements - 1}] componentsJoinedByString:#"."];
// Get the mutable dictionary represented by the path minus that last element
NSMutableDictionary * tailContainer = [self valueForKeyPath:keyPathHead];
// Remove the object represented by the last element
[tailContainer removeObjectForKey:[keyPathElements lastObject]];
}
#end
N.B. That this requires that the second-to-last element of the path -- the tailContainer be something that responds to removeObjectForKey:, probably another NSMutableDictionary. If it's not, boom!
You can create a category :
This is upto 1 level down:
#import "NSMutableDictionary+RemoveAtKeyPath.h"
#implementation NSMutableDictionary (RemoveAtKeyPath)
-(void)removeObjectAtKeyPath:(NSString *)keyPath{
NSArray *paths=[keyPath componentsSeparatedByString:#"."];
[[self objectForKey:paths[0]] removeObjectForKey:paths[1]];
}
#end
It is called as :
NSMutableDictionary *adict=[[NSMutableDictionary alloc]initWithDictionary:#{#"key1" : #"obj1", #"key11":#"obj11"}];
NSMutableDictionary *bdict=[[NSMutableDictionary alloc]initWithDictionary:#{#"key2" : adict}];
NSLog(#"%#",bdict);
NSLog(#"%#",[bdict valueForKeyPath:#"key2.key1"]);
[bdict removeObjectAtKeyPath:#"key2.key1"];
NSLog(#"After category : %#",bdict);
Minor improvement to Josh's answer, in order to handle keypaths which don't contain a period (i.e. keypaths which are actually keys):
- (void)removeObjectAtKeyPath:(NSString *)keyPath
{
NSArray *keyPathElements = [keyPath componentsSeparatedByString:#"."];
NSUInteger numElements = [keyPathElements count];
if (numElements == 1) {
[self removeObjectForKey:keyPath];
} else {
NSString *keyPathHead = [[keyPathElements subarrayWithRange:(NSRange){0, numElements - 1}] componentsJoinedByString:#"."];
NSMutableDictionary *tailContainer = [self valueForKeyPath:keyPathHead];
[tailContainer removeObjectForKey:[keyPathElements lastObject]];
}
}
I know this is an older post, but I needed to find the same solution in Swift 2.0, unable to find a simple answer I came up with this solution:
public extension NSMutableDictionary {
public func removeObjectAtKeyPath(keyPath:String) {
let elements = keyPath.componentsSeparatedByString(".")
let head = elements.first!
if elements.count > 1 {
let tail = elements[1...elements.count-1].joinWithSeparator(".")
if let d = valueForKeyPath(head) as? NSMutableDictionary {
d.removeObjectAtKeyPath(tail)
}
}else{
removeObjectForKey(keyPath)
}
}
}
I've added an extension to NSMutableDictionary using recursion to step thru the keyPath
I just iterate on jscs's accepted answer with minor improvement - working on the KVC key-path using NSString's built-in pathUtlities category.
#implementation NSMutableDictionary (NestedMutableDictionaries)
- (void)removeObjectForKeyPath: (NSString *)keyPath
{
NSArray *key = [keyPath pathExtension];
NSString *keyPathHead = [keyPath stringByDeletingPathExtension];
[[self valueForKeyPath:keyPathHead] removeObjectForKey:key];
}
#end
Just a little nicer, I think.