Objective C Properties memory issue - ios

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 {
…
}

Related

NSString : leak when assigning value to a property

Assuming we don't use ARC.
Suppose we have a very simple class in which we declare 2 NSString properties, like this :
#interface Foo : UIView {}
-(id)initWithArguments:(NSString*)mess title:(NSString*)tit;
#property(nonatomic, retain) NSString *message;
#property(nonatomic, retain) NSString *title;
#end
and in implementation :
#implementation Foo
#synthesize message, title;
-(id)initWithArguments:(NSString*)mess title:(NSString*)tit{
if((self = [super init])){
message = mess; // (1)
self.title = tit; // (2)
(...)
}
return self;
}
-(void)dealloc{
message = nil;
title = nil;
[super dealloc];
}
#end
Now if I call a method from another class, in which I create 2 NSString and an instance of Foo , like this :
-(void)someMethod{
NSString *string1 = [NSString stringWithFormat:#"some text with %d things", 5];
NSString *string2 = [NSString stringWithFormat:#"other text with %d things", 5];
Foo *foo = [[Foo alloc] initWithArguments:string1 title:string2];
}
The whole code works fine and doesn't crash, but, if I profile it with instruments,
it doesn't cause a leak when calling (1)("message = mess;")
it cause a leak when calling (2)("self.title = tit;")
It's very confusing, because stringWithFormat is an autoreleased object, isn't it ?
So, how an autoreleased object can cause a leak when assigning to a property ???
I read somewhere that it's almost always better to use the "self.text = value;" form instead of the "text = value;" form, because the second one may cause a leak.
Actually, in this code it's the contrary.
And... If I use a constant NSString like #"some text", instead of the values returned by [NSString stringWithFormat], there is no leak, of course.
Any idea ?
You have forgotten to invoke the (compiler-generated) setter methods in a few cases:
self.message = mess; // in init method
self.message = nil; // in dealloc method
self.title = nil; // ditto
It's crucial that you use the setter/getter methods in non-ARC code.

Using NSMutableArray to keep Objects

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(#"----------------------------");
}

Parsing Json Location Data

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.

iOS parser alloc breakpoint

.h
#class HtmlParser
#interface ClassName : NSObject <UITableViewDataSource>
{
NSString *img;
HtmlParser *htmlParser;
}
: )
.M
- (NSString*)img
{
if (img!=nil) return img;
if (_description!=nil)
{
// NSString* description = [NSString stringWithString:_description];
htmlParser = [[HtmlParser alloc] loadHtmlByString:(NSString*) _description];
}
return img;
}
I am trying to initialize HtmlParser with the contents of description. "description" is RSS html loaded asynchronously, started in the tableViewController.
I get a breakpoint with or without the NSString* description. '-[HtmlParser loadHtmlbyString:]: unrecognized selector sent to instance 0x75aa9b0'... That's all the debugging I know how to do. Breakpoints are enabled for all exceptions.
-the method in .m is called in the viewController's cellForRowAtIndexPath:
ClassName *object = _objects[indexPath.row];
NSString *i = object.img;
UIImage* iG = [UIImage imageWithData:
[NSData dataWithContentsOfURL:[NSURL URLWithString:i]]];
cell.imageView.image = iG;
Its messy so let me know if further clarification is needed.
.h
#interface HtmlParser: NSObject <NSXMLParserDelegate>
{
ET Cetera
}
- (id) loadHtmlByString:(NSString *)string;
When you call the method in question:
htmlParser = [[HtmlParser alloc] loadHtmlbyString:(NSString*) _description];
It shouldn't have the (NSString *) in there. It should be:
htmlParser = [[HtmlParser alloc] loadHtmlbyString: _description];
But, is loadHtmlbyString an init method? If so, then you should start the name with init, and you should also adhere to the naming conventions by capitalizing all the words in the name (including By).
The 'loadHtmlbyStringmethod is not a method of theHtmlParserclass, it is a method of yourClassName` class.
Don't you get a compiler warning on this line:
htmlParser = [[HtmlParser alloc] loadHtmlbyString:(NSString*) _description];
Look at the .h for the HtmlParser class and see what methods are defined for that class.

TableViewController crashes when calling a retained property?

I have a Table View Controller and during its initialisation I set an NSArray property which is then used in the cellForRowAtIndexPath method to display the data on the table.
But, when I touch a row, once I call this retained NSArray property it says EXC_BAD_ACCESS!
FYI the property is defined as shown below, and uses a custom getter function:
#property (nonatomic,retain) NSArray *dataList;
and in the .m file:
#synthesize dataList;
- (NSArray *)dataList
{
if (!dataList)
{
NSString *p = [kind lowercaseString];
NSString *s = [[NSBundle mainBundle] pathForResource:p ofType:#"txt"];
NSLog(#"%#",s);
NSData *dataRep = [NSData dataWithContentsOfFile:s];
NSPropertyListFormat format;
dataList = [NSPropertyListSerialization propertyListFromData: dataRep
mutabilityOption: NSPropertyListImmutable
format: &format
errorDescription: nil];
if (dataList.count == 0)
NSLog(#"Fetch failed!");
}
return dataList;
}
Any suggestions?
This is the problem:
dataList = [NSPropertyListSerialization propertyListFromData ...
This function does not begin with alloc, copy, or retain, therefore it returns an autoreleased object. However, you need it to be retained so that it stays around.
You have two options:
self.dataList = [NSPropertyListSerialization propertyListFromData ...
or,
dataList = [[NSPropertyListSerialization propertyListFromData ...] retain];

Resources