Why would a property essentially disappear? Obj-C, Cocoa - objective-c

I am quite stumped. I have an app with a class for storing item details. Called LEItem. Those items are stored in a store with a class labeled LEItemStore. I have a view with a table of all items. This works fine. If you tap on a row, it sends this message to LogbookFirstViewController.
LogbookFirstViewController *logController = [[LogbookFirstViewController alloc] initForNewItem:NO];
NSArray *items = [[LEItemStore sharedStore] allItems];
LEItem *selectedItem = [items objectAtIndex:[indexPath row]];
NSString *description = [selectedItem description];
NSLog(#"%#", description);
[logController setItem:selectedItem];
[self dismissViewControllerAnimated:YES completion:nil];
That is in a TableView class. In the LogbookFirstViewController.m I have
-(void)setItem:(LEItem *)i{
item = i;
NSString *t = [item description];
NSLog(#"In LogbookFirstViewController, returning %#", t);
}
This is where it gets odd. That works. It outputs the correct item, therefore I would think everything would be okay. But it's not. item is a class-level property, so it should stay, but it doesn't. In the same class, I have overrode this method.
-(void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
//NSString *string = [item description];
//NSLog(#"Item = %#", string);
NSLog(#"View did Appear:animated");
int glucoseValue = [item glucose];
NSString *glucoseString = [NSString stringWithFormat:#"%d", glucoseValue];
[glucoseField setText:glucoseString];
int proteinValue = [item protein];
NSString *proteinString = [NSString stringWithFormat:#"%d", proteinValue];
[proteinField setText:proteinString];
int carbsValuue = [item carbs];
NSString *carbsString = [NSString stringWithFormat:#"%d", carbsValuue];
[carbsField setText:carbsString];
int insulinValue1 = [item insulin];
NSString *insulin1String = [NSString stringWithFormat:#"%d", insulinValue1];
[insulinField1 setText:insulin1String];
int insulinValue2 = [item insulin2];
NSString *insulinString2 = [NSString stringWithFormat:#"%d", insulinValue2];
[insulinField2 setText:insulinString2];
//NSDateFormatter *dateFormatter = [[NSDateFormatter alloc]init];
//[dateFormatter setDateStyle:NSDateFormatterShortStyle];
//[dateFormatter setTimeStyle:NSDateFormatterShortStyle];
//NSLog(#" The item was created on %#", [dateFormatter stringFromDate:[item dateCreated]]);
//[dateButton setTitle:[dateFormatter stringFromDate:[item dateCreated]] forState:UIControlStateNormal];
NSString *t = [item description];
NSLog(#"Loading view... Returns: %#", t);
}
I know that it isn't the cleanest code, but the idea is the same. It uses exactly the same code as the setItem: method. However, this always returns (null). Why? The property appears to go missing at viewWillAppear.
Thanks.
EDIT
I solved the problem. As you can see, the checked answer below did give the right idea, here is what I did to solve it. The problem was that when I sent setItem: I used this code to get LogbookFirstViewController
LogbookFirstViewController *logController = [[LogbookFirstViewController alloc] initForNewItem:NO];
As I know see, that created a new instance of LogbookFirstViewController, so therefore, the existing one did not change it's Item property, as properties are assigned to one instance. Therefore, I was only changing the value of Item for this "invisible" property.
To solve this, one must get the existing instance of the viewController. To do this I did the following:
In LogbookFirstViewController.h I added this property
#property (assign) LogbookFirstViewController *instance;
Then, synthesize instance in your .m and in the same placed I added this to viewDidLoad
- (void)viewDidLoad
{
instance = self;
...
Then, in the other viewController, entriesViewController, I added this too the .h
#property (nonatomic, strong) LogbookFirstViewController *logController;
Synthesize it. Then, I just used my didSelectRowAtIndexPath the same way, just using the existing logController
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSArray *items = [[LEItemStore sharedStore] allItems];
LEItem *selectedItem = [items objectAtIndex:[indexPath row]];
NSString *description = [selectedItem description];
NSLog(#"%#", description);
NSLog(#"Setting controller: %#", logController);
[logController setItem:selectedItem];
[self dismissViewControllerAnimated:YES completion:nil];
}
Then it works!

You have a line where you create the LogbookFirstViewController but you don't actually cause it to display anything (push or present). Since it's a local variable, it would appear that whatever instance of that controller is loading its view is not the same one that you initialize in the code you've shown.
You can verify this by adding a couple of NSLog lines, such as:
NSLog(#"Setting controller: %#", logController); // Insert before existing line
[logController setItem:selectedItem];
...and...
[super viewWillAppear:animated];
NSLog(#"Viewing controller: %#", self); // Insert after existing line
For things to work the way you want, those have to print the same address.

You should retain when assigning object to property without ARC:
-(void)setItem:(LEItem *)i{
_item = [i retain];
...
}
If you use property with ARC, then write _item = i;:
-(void)setItem:(LEItem *)i{
_item = i;
...
}

Related

Populate UITableView in ViewController class from separate DataController class that is using grand central dispatch

I have a UITableView in a ViewController class. The ViewController class uses a custom dataController (specified in the AppDelegate). In the dataController class I'm fetching some JSON from the web, parsing it to an NSMutableArray, then using that data to populate the UITableView in the ViewController.
This all works great, except there is a noticeable lag when the app starts up since it takes time to get the JSON and work with it. I'd like to show an empty UITableView with an activity indicator while this data is loading. Unfortunately whenever I put the code in the dataController class into a dispatch queue, the UITableView is never populated with data (the data is loaded according to the log). All I see is a blank table.
I guess my main issue is I don't know how to set up a queue in the dataController class and then update the UI with the data in that queue but in another class.
Relevant code:
from dataController class:
- (void)initializeDefaultDataList {
NSMutableArray *dataList = [[NSMutableArray alloc] init];
self.masterDataList = dataList;
dispatch_queue_t myQueue = dispatch_queue_create("name.queue.my", NULL);
dispatch_async(myQueue, ^{
NSString *jsonString = [JSONHelper JSONpostString:#"http://webservice/getData"];
NSError *jsonError = nil;
//convert string to dictionary using NSJSONSerialization
NSDictionary *jsonResults = [NSJSONSerialization JSONObjectWithData: [jsonString dataUsingEncoding:NSUTF8StringEncoding]
options: NSJSONReadingMutableContainers
error: &jsonError];
if (jsonError) NSLog(#"[%# %#] JSON error: %#", NSStringFromClass([self class]), NSStringFromSelector(_cmd), jsonError.localizedDescription);
NSArray *dataArray = [jsonResults objectForKey:#"d"];
for (NSString *dataItem in dataArray) {
[self addDataWithItem:dataItem];
}
});
}
from AppDelegate:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
UINavigationController *navigationController = (UINavigationController *)self.window.rootViewController;
MyMasterViewController *firstViewController = (MyMasterViewController *)[[navigationController viewControllers] objectAtIndex:0];
MyDataController *aDataController = [[MyDataController alloc] init];
firstViewController.dataController = aDataController;
return YES;
}
from ViewController:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
//would this go here?
dispatch_async(dispatch_get_main_queue(), ^{
MyObject *objectAtIndex = [self.dataController objectInListAtIndex:indexPath.row];
[[cell textLabel] setText:objectAtIndex.name];
});
return cell;
}
In case you couldn't tell I'm really new to iOS and Objective C. Any help or hints you can give would be greatly appreciated. I'm not even sure if I'm expressing my question properly - it just seems that what I want to do shouldn't be this difficult. Thanks!
EDIT
Ok, so maybe this is a life cycle issue. Just realized that anything I set within the async block is nil outside the block, at least it is until it's too late to make a difference. That's why cellForRowAtIndexPath is never called - because the masterDataList being passed to the UITableView is empty. Tested this by initializing
__block NSString *s = [[NSString alloc] init];
outside the block, then setting a value inside the block:
s = #"Testing...";
and finally NSLogging the value of s after the block has supposedly run. But obviously the block hadn't run yet because s was nil.
It looks like you're doing the right thing to get back on the main thread after your work is done, but you haven't told the table view it needs to show the new data. [self.tableView reloadData] ought to help.
As I discovered in posts such as this one, data set within the async dispatch cannot be used outside the queue. As I understand it, the whole idea of GCD is that it determines when it's best to run and dispose of data.
As a result, I ended up splitting up my code so I was only using the DataController class to, well, control data (I know, revolutionary) and moved all the GCD parts to my ViewController. Amended code:
DataController class:
- (void)initializeDefaultDataList {
NSMutableArray *dataList = [[NSMutableArray alloc] init];
self.masterDataList = dataList;
}
ViewController class:
#interface ObjectMasterViewController () {
__block NSString *jsonString;
}
#end
...
- (void)getJSONString
{
jsonString = [JSONHelper JSONpostString:#"http://webservice/getData"];
}
...
- (void)initData {
NSError *jsonError = nil;
//convert string to dictionary using NSJSONSerialization
NSDictionary *jsonResults = [NSJSONSerialization JSONObjectWithData: [jsonString dataUsingEncoding:NSUTF8StringEncoding]
options: NSJSONReadingMutableContainers
error: &jsonError];
if (jsonError) NSLog(#"[%# %#] JSON error: %#", NSStringFromClass([self class]), NSStringFromSelector(_cmd), jsonError.localizedDescription);
NSArray *dataArray = [jsonResults objectForKey:#"d"];
//loop through array and add items to list
for (NSString *dataItem in dataArray) {
[self addDataWithItem:dataItem];
}
}
...
- (void)viewDidLoad
{
[super viewDidLoad];
dispatch_queue_t myQueue = dispatch_queue_create("name.queue.my", NULL);
dispatch_async(myQueue, ^{
//initalize service url string
[self getJSONString];
dispatch_async(dispatch_get_main_queue(), ^{
//retrieve data
[self initData];
//reload tableView with new data
[self.tableView reloadData];
});
});
}
Hope this can help someone who might be in the same boat I was in.

MKMapView setSelected: animated: not working first time

When I call MKMapView method setSelected:animated: on an annotation, it doesn't work. But if I call it next time with a different annotation, it starts working.
Anyone have any ideas what could be possibly wrong?
Thanks
Code (2 relevant methods):
- (void)viewDidLoad {
[super viewDidLoad];
annotations = [[NSMutableArray arrayWithCapacity:30] retain];
for (NSDictionary *entry in entries) {
double lat = [[entry objectForKey:#"lat"] doubleValue];
double lon = [[entry objectForKey:#"lon"] doubleValue];
NSString *PLZ = [entry objectForKey:#"PLZ"];
NSString *name = [NSString stringWithFormat:#"%#%#", PLZ.length != 0? [NSString stringWithFormat:#"%#, ", PLZ] : #"", [entry objectForKey:#"Ort"]];
NSString *address = [NSString stringWithFormat:#"%# - %#", [entry objectForKey:#"Grund"], [entry objectForKey:#"Zeit"]];
CLLocationCoordinate2D coordinate;
coordinate.latitude = lat;
coordinate.longitude = lon;
MyLocation *annotation = [[[MyLocation alloc] initWithName:name address:address coordinate:coordinate] autorelease];
[mapView addAnnotation:annotation];
[annotations addObject:annotation];
}
NSLog(#"LOaded");
[self zoomToFitMapAnnotations];
}
- (void)showAnnotation:(int)i {
if (i <= [annotations count]) {
[mapView setSelectedAnnotations:[NSArray arrayWithObject:[annotations objectAtIndex:i]]];
}
}
The last method, showAnnotation is the one that gets called and an annotation is shown. Once again, it doesn't work with the annotation I call first time. No matter how many times i call it. But if I change the annotation, then it starts working, even with the annotation I called first (hope that makes sense).
Also, this works even the first time: [mapView setSelectedAnnotations:[NSArray arrayWithObject:[annotations objectAtIndex:i]]];
You shouldn't be calling that method youself.
According to the MKAnnotationView documentation:
You should not call this method directly. An MKMapView object calls
this method in response to user interactions with the annotation.
Instead try Dipen's suggested method (setSelectedAnnotations).
Use this may be help you
[mapView1 setSelectedAnnotations:[NSArray arrayWithObjects:addAnnotation,nil]];

How to Fix EXC_BAD_ACCESS on NSArray Property?

This is yet another EXC_BAD_ACCESS question. Although I've done my homework and am certain that I am not over-releasing my NSArray.
So here is a snippet of my code:
tableData = [NSDictionary dictionaryWithJSONString:JSONstring error:&error];
//Collect Information from JSON String into Dictionary. Value returns a mutli
dimensional NSDictionary. Eg: { value => { value => "null"}, etc }
NSMutableArray *t_info = [[NSMutableArray alloc] init];
for(id theKey in tableData)
{
NSDictionary *get = [tableData objectForKey:theKey];
[t_info addObject:get];
[get release];
} // converting into an NSArray for use in a UITableView
NSLog(#"%#", t_info);
//This returns an Array with the NSDictionary's as an Object in each row. Returns fine
if (tvc == nil)
{
tvc = [[tableViewController alloc] init]; //Create Table Controller
tableView.delegate = tvc;
tableView.dataSource = tvc;
tvc.tableView = self.tableView;
tvc.tableData = t_info; //pass our data to the tvc class
[tvc.tableView reloadData];
}
...
Now in my TableViewController Class:
#implementation tableViewController
#synthesize tableData, tableView;
- (NSInteger) tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [tableData count]; //Returns X Amount Fine.
}
- (UITableViewCell *)tableView:(UITableView *)the_tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
NSString *MyIdentifier = [NSString stringWithFormat:#"MyIdentifier"];
UITableViewCell *cell = [the_tableView dequeueReusableCellWithIdentifier:MyIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:MyIdentifier] autorelease];
}
NSLog(#"%#", tableData); //** CRASHES!!**
cell.textLabel.text = #"This is a test";
return cell;
}
If I were to comment out that NSLog, it'll work fine and return "this is a test" on each table row.
This one has really got me stumped, all the articles I have around about this problem is generally related to retain/memory issues.
Also, another important point.
If I were to pass through my original (NSDictionary) tableData from my first class code and run the same script in my tableViewController - I can NSLog the object perfectly fine.
The only time you need to release an object is if you have explicitly allocated it by way of new, alloc, or copy.
NSMutableArray *t_info = [[NSMutableArray alloc] init];
for(id theKey in tableData)
{
NSDictionary *get = [tableData objectForKey:theKey];
[t_info addObject:get];
[get release];
}
You shouldn't be releasing get here. By doing this, you're releasing the reference that the tableData dictionary is holding onto, which is bad. My guess is that this is what is causing the problem that you're encountering.
If I'm not mistaken, the reason why [tableData count] returns the expected value is because the array is still holding onto the references that have been released.

Get & Edit NSMutableArray from different class file

I am trying to access and change a array from a different class file. When using a NSLog, I get a result of (null). Below is my code:
RootViewController.h
NSMutableArray *listOfItems;
#property (nonatomic, retain) NSMutableArray *listOfItems;
RootViewController.m
#synthesize listOfItems;
listOfItems = [[NSMutableArray alloc] init];
[listOfItems addObject:#"One"];
[listOfItems addObject:#"Two"];
[listOfItems addObject:#"Three"];
SecondViewController.m
RootViewController *test = [[RootViewController alloc] init];
NSLog(#"Results: %#", test.listOfItems);
I get the following results in my console: Results: (null)
Thanks in advance,
Coulton
P.S. Obviously I have left out a bunch of code. I just tried to make it easier to read. If you need to see anything else, I would be more than happy to post more. Just ask
EDIT #1:
I am getting hundreds of NSLog Messages that look something like this:
*** __NSAutoreleaseNoPool(): Object 0x4e39020 of class __NSArrayI autoreleased with no pool in place - just leaking
And here's my init code:
- (id) init {
//NSLog(#"%#", theUserID);
// Set up database connection
NSString *myDB = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:#"database.db"];
database = [[Sqlite alloc] init];
[database open:myDB];
//Initialize the array.
listOfItems = [[NSMutableArray alloc] init];
// Add to array to display in the tableView
NSArray *listOfItemsTwo = [database executeQuery:#"SELECT * FROM albums"];
for (NSDictionary *rowone in listOfItemsTwo) {
NSString *getName = [rowone valueForKey:#"name"];
if (getName != NULL) {
[listOfItems addObject:getName];
[getName release];
}
}
return self;
}
I guess you reversed RootViewController.m and RootViewController.h snippets right?
Are you sure that the
listOfItems = [[NSMutableArray alloc] init];
gets called? Maybe you can put a breakpoint there.
EDIT: Order of RootViewController.m and RootViewController.h has been fixed in the question. It's not clear from the question where the above line is in the code. That's a important piece of information.
EDIT2: Example of init method.
#implementation RootViewController
- (id) init
{
listOfItems = [[NSMutableArray alloc] init];
[listOfItems addObject:#"One"];
return self;
}
#end

Objective C - UITableView after calling reloadData my object properties are null/nil

I have a ViewController defined as follows:
#interface SectionController : UITableViewController {
NSMutableArray *sections;
}
- (void) LoadSections;
When LoadSection is call it makes a call to NSURLConnection to load a url which in turn calls
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
NSString *responseString = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
[connection release];
[responseData release];
NSDictionary *results = [responseString JSONValue];
NSMutableArray *jSections = [results objectForKey:#"Items"];
sections = [NSMutableArray array];
for (NSArray* jSection in jSections)
{
Section* section = [Section alloc];
section.Id = [jSection objectForKey:#"Id"];
section.Description = [jSection objectForKey:#"Description"];
section.Image = [jSection objectForKey:#"Image"];
section.Parent = [jSection objectForKey:#"Parent"];
section.ProductCount = [jSection objectForKey:#"ProductCount"];
[sections addObject:section];
[section release];
}
[jSections release];
[results release];
[delegate sectionsLoaded];
[self.view reloadData];
}
The data parses correctly and I now have sections filled with many items.
Calling [self.view reloadData] forces a callback to the delegate method cellForRowAtIndexPath which should then present the data into the cell however its at this point that sections is now nil again.
Can someone please point out my mistake? I must admit I am a newbie to objective c and it probably a pointer issue. What is need to do is retain the value of sections after calling reloadData.
Many thanks.
Seeing the new code the problem is obvious:
sections = [NSMutableArray array];
should become
[sections release];
sections = [[NSMutableArray alloc] init];
note that the array does not become again "nil", is instead deallocated and you get an invalid reference, which might (should) generate a crash on dereferencing.
I suggest you to read some articles on reference counted memory management as it might be not obvious if you are new to Objective-C, and often leads to mistake (i.e: autorelease is not magic at all)
best way to avoid all memory leaks here is just simply use #property (nonatomic, retain) NSMutableArray *sections; by using property you can be sure that all men management works will be correctly managed by system. Just don't forget that property retains value when you doing setSections:, so that you need to pass autoreleased object here.
self.sections = [NSMutableArray array];
...
[self.sections addObject:section];
Also to avoid all problem try to make all objects which should live only in this method autorelease. Like this:
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
NSString *responseString = [[[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding] autorelease];
NSDictionary *results = [responseString JSONValue];
NSMutableArray *jSections = [results objectForKey:#"Items"];
self.sections = [NSMutableArray array];
for (NSArray* jSection in jSections) {
Section* section = [[[Section alloc] init] autorelease];
section.Id = [jSection objectForKey:#"Id"];
section.Description = [jSection objectForKey:#"Description"];
section.Image = [jSection objectForKey:#"Image"];
section.Parent = [jSection objectForKey:#"Parent"];
section.ProductCount = [jSection objectForKey:#"ProductCount"];
[self.sections addObject:section];
}
[delegate sectionsLoaded];
[self.view reloadData];
}
And also most of object you trying to release already autoreleased:
all params passed into your method shouldn't be released manually, check I think JSONValue also should returns autoreleased object and anything you getting by enumerating or by call objectForKey: