Xcode retrieve userInfo from NSNotification - xcode

I've an aNotification with a userInfo I'd like to call in another method with a different format.
Is there a way to retrieve a string from the [aNotification userInfo] and modify it?
userInfo is something formatted like this:
{
action = "the string I'd like to use";
}
I did like this (and it's almost working) but I feel there is a better way to do the same.
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(playClicked:) name:kNotificationActionTapped object:nil];
..................
-(void)playClicked:(NSNotification *)aNotification {
NSLog(#"Notification=%#", [aNotification userInfo]);
SBJsonWriter *jsonWriter = [[SBJsonWriter alloc] init];
NSString *jsonString = [jsonWriter stringWithObject:[aNotification userInfo]];
[jsonWriter release];
NSString * string1 = [jsonString substringFromIndex:11];
NSString * string2 = [string1 substringToIndex:[watch length] -2];
NSLog(#"string=%#", string1);
NSLog(#"string=%#", string2);
}

userInfo is an NSDictionary. Use standard NSDictionary methods.
NSString *aString = [aNotification.userInfo objectForKey:#"action"];

Related

Query data from Parse.com, iterate through, add certain parts to an NSObject, and add the object to an array of objects

I'm using iOS7 Xcode 5 with Parse.com's SDK. While querying data via parse, I'm trying to construct a Person (NSObject) for each returned object and create an NSArray of defaultPeople.
Here is the code for the Person:
Person.h
// Person.h
#import <Foundation/Foundation.h>
#interface Person : NSObject
#property (nonatomic, strong) NSString *name;
#property (nonatomic, strong) UIImage *image;
#property (nonatomic, assign) NSUInteger age;
#property (nonatomic, strong) NSString *gender;
#property (nonatomic, strong) NSString *location;
#property (nonatomic, strong) NSString *tagline;
#property (nonatomic, strong) NSString *objectId;
- (instancetype)initWithName:(NSString *)name
image:(UIImage *)image
age:(NSUInteger)age
gender:(NSString*)gender
location:(NSString*)location
tagline:(NSString*)tagline
objectId:(NSString*)objectId;
#end
Person.m:
// Person.m
#import "Person.h"
#implementation Person
#pragma mark - Object Lifecycle
- (instancetype)initWithName:(NSString *)name
image:(UIImage *)image
age:(NSUInteger)age
gender:(NSString*)gender
location:(NSString *)location
tagline:(NSString*)tagline
objectId:(NSString *)objectId {
self = [super init];
if (self) {
_name = name;
_image = image;
_age = age;
_gender = gender;
_location = location;
_tagline = tagline;
_objectId = objectId;
}
return self;
}
#end
Now here's the code I am using to try and create the array in my view controller .m file :
- (NSArray *)defaultPeople {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSLog(#"Current City for Querying: %#", [defaults objectForKey:#"CurrentCity"]);
if ([defaults objectForKey:#"CurrentCity"]) {
PFQuery *query = [PFQuery queryWithClassName:#"_User"];
[query whereKey:#"CurrentCity" equalTo:[defaults objectForKey:#"CurrentCity"]];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
// The find succeeded.
NSLog(#"Successfully retrieved %d scores.", objects.count);
// Do something with the found objects
for (PFObject *object in objects) {
NSString *userID = object.objectId;
NSString *first = [object objectForKey:#"FirstName"];
NSString *city = [object objectForKey:#"CurrentCity"];
NSUInteger age = (int)[object objectForKey:#"Age"];
NSString *gender = [object objectForKey:#"Gender"];
NSString *tagline = [object objectForKey:#"Tagline"];
Person *p = [[Person alloc]
initWithName:first
image:[UIImage imageWithData:
[NSData dataWithContentsOfURL:
[NSURL URLWithString:
[object objectForKey:#"PictureURL"]]]]
age:age
gender:gender
location:city
tagline:tagline
objectId:userID];
[self.people addObject:p]
}
} else {
NSLog(#"Error: %# %#", error, [error userInfo]);
}
}];
}
return self.people; //people was defined in the interface as:
//#property (nonatomic, strong) NSMutableArray *people;
}
I know that the querying is fine because I've NSLogged each NSString/NSUInteger in the for loop and it always returns the right value. My problem is creating a new Person object from those values and adding it to the defaultPeople array after each iteration. The result of this code is that my defaultPeople array always returns (null). PLEASE HELP!!! Thanks :)
Clayton
Ok guys FINALLY I figured out how do do this in a block that actually works:
- (void)queryForAllPostsNearLocation:(CLLocation *)currentLocation withNearbyDistance:(CLLocationAccuracy)nearbyDistance {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setBool:1 forKey:#"Users"];
PFQuery *query = [PFQuery queryWithClassName:#"_User"];
// If no objects are loaded in memory, we look to the cache first to fill the table
// and then subsequently do a query against the network.
if (query.countObjects == 0) {
query.cachePolicy = kPFCachePolicyCacheThenNetwork;
}
// Create a PFGeoPoint using the current location (to use in our query)
PFGeoPoint *userLocation =
[PFGeoPoint geoPointWithLatitude:[Global shared].LastLocation.latitude longitude:[Global shared].LastLocation.longitude];
// Create a PFQuery asking for all wall posts 1km of the user
[query whereKey:#"CurrentCityCoordinates" nearGeoPoint:userLocation withinKilometers:10];
// Include the associated PFUser objects in the returned data
[query includeKey:#"objectId"];
//Run the query in background with completion block
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (error) { // The query failed
NSLog(#"Error in geo query!");
} else { // The query is successful
defaultPeople = [[NSMutableArray alloc] init];
// 1. Find new posts (those that we did not already have)
// In this array we'll store the posts returned by the query
NSMutableArray *people = [[NSMutableArray alloc] initWithCapacity:100];
// Loop through all returned PFObjects
for (PFObject *object in objects) {
// Create an object of type Person with the PFObject
Person *p = [[Person alloc] init];
NSString *userID = object.objectId;
p.objectId = userID;
NSString *first = [object objectForKey:#"FirstName"];
p.name = first;
NSString *city = [object objectForKey:#"CurrentCity"];
p.location = city;
NSString *age = [object objectForKey:#"Age"];
p.age = age;
NSString *gender = [object objectForKey:#"Gender"];
p.gender = gender;
NSString *tagline = [object objectForKey:#"Tagline"];
p.tagline = tagline;
UIImage *img = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:[NSString stringWithFormat:#"%#",[object objectForKey:#"PictureURL"]]]]];
p.image = img;
if (![p.objectId isEqualToString:myID] && ![p.gender isEqualToString:myGender] && ![people containsObject:p]) {
[people addObject:p];
NSLog(#"Person: %#",p);
}
}
[defaultPeople addObjectsFromArray:people];
[[Global shared] setDefaultPeople:defaultPeople];
NSLog(#"Default People: %#",[Global shared].defaultPeople);
NSLog(#"Success. Retrieved %lu objects.", (unsigned long)[Global shared].defaultPeople.count);
if (defaultPeople.count == 0) {
[defaults setBool:0 forKey:#"Users"];
} else {
[defaults setBool:1 forKey:#"Users"];
}
}
}];
}
The BOOL returns on the bottom are to let the controller know whether or not to switch view controllers when prompted. If the switch controller toggle is hit, it only switches if the BOOL = 1, i.e. there are people in the area.
Thanks for all your help guys. Seriously.
[self.people addObject:p] is happening in the background thread so "return self.people" happens before self.people is updated. Thats why it is always returns nil.
instead of [query findObjectsInBackground] you can do
NSArray *objects = [query findObjects]
You need to return people inside the block, otherwise it will hit the return statement before it finishes finding the objects. It's finding them asynchronously with the block.
Another alternative is to get rid of the block and do:
NSArray *array = [query findObjects];
for (PFObject *object in array) {
NSString *userID = object.objectId;
NSString *first = [object objectForKey:#"FirstName"];
NSString *city = [object objectForKey:#"CurrentCity"];
NSUInteger age = (int)[object objectForKey:#"Age"];
NSString *gender = [object objectForKey:#"Gender"];
NSString *tagline = [object objectForKey:#"Tagline"];
Person *p = [[Person alloc]
initWithName:first
image:[UIImage imageWithData:
[NSData dataWithContentsOfURL:
[NSURL URLWithString:
[object objectForKey:#"PictureURL"]]]]
age:age
gender:gender
location:city
tagline:tagline
objectId:userID];
[self.people addObject:p];
}
return self.people;

How can I run a NSTask in the background and show the results in a modal NSWindow while running

I would like to execute a command with NSTask, and be able to see the progress in a modal window. For example if I execute 'ls -R /' i would like to see the chunks appearing in a NSTextView.
I came up with the following, and everything works fine, except the update part. The task get executed (with the spinning beachbal) and when it is finished i see the result appear in the textview.
#interface ICA_RunWindowController ()
#property (strong) IBOutlet NSTextView* textResult;
#property (strong) IBOutlet NSButton* buttonAbort;
#property (strong) IBOutlet NSButton* buttonOK;
- (IBAction) doOK:(id) sender;
- (IBAction) doAbort:(id) sender;
#end
#implementation ICA_RunWindowController {
NSTask * executionTask;
id taskObserver;
NSFileHandle * errorFile;
id errorObserver;
NSFileHandle * outputFile;
id outputObserver;
}
#synthesize textResult,buttonAbort,buttonOK;
- (IBAction)doOK:(id)sender {
[[self window] close];
[NSApp stopModal];
}
- (IBAction)doAbort:(id)sender {
[executionTask terminate];
}
- (void) taskCompleted {
NSLog(#"Task completed");
[[NSNotificationCenter defaultCenter] removeObserver:taskObserver];
[[NSNotificationCenter defaultCenter] removeObserver:errorObserver];
[[NSNotificationCenter defaultCenter] removeObserver:outputObserver];
[self outputAvailable];
[self errorAvailable];
executionTask = nil;
[buttonAbort setEnabled:NO];
[buttonOK setEnabled:YES];
}
- (void) appendText:(NSString *) text inColor:(NSColor *) textColor {
NSDictionary * makeUp = [NSDictionary dictionaryWithObject:textColor forKey:NSForegroundColorAttributeName];
NSAttributedString * extraText = [[NSAttributedString alloc] initWithString:text attributes:makeUp];
[textResult setEditable:YES];
[textResult setSelectedRange:NSMakeRange([[textResult textStorage] length], 0)];
[textResult insertText:extraText];
[textResult setEditable:NO];
[textResult display];
}
- (void) outputAvailable {
NSData * someData = [outputFile readDataToEndOfFile];
if ([someData length] > 0) {
NSLog(#"output Available");
NSString * someText = [[NSString alloc] initWithData:someData encoding:NSUTF8StringEncoding];
[self appendText:someText inColor:[NSColor blackColor]];
}
}
- (void) errorAvailable {
NSData * someData = [errorFile readDataToEndOfFile];
if ([someData length] > 0) {
NSLog(#"Error Available");
NSString * someText = [[NSString alloc] initWithData:someData encoding:NSUTF8StringEncoding];
[self appendText:someText inColor:[NSColor redColor]];
}
}
- (void) runCommand:(NSString *) command {
// make sure all views are initialized
[self showWindow:[self window]];
// some convience vars
NSArray * runLoopModes = #[NSDefaultRunLoopMode, NSRunLoopCommonModes];
NSNotificationCenter * defCenter = [NSNotificationCenter defaultCenter];
// create an task
executionTask = [[NSTask alloc] init];
// fill the parameters for the task
[executionTask setLaunchPath:#"/bin/sh"];
[executionTask setArguments:#[#"-c",command]];
// create an observer for Termination
taskObserver = [defCenter addObserverForName:NSTaskDidTerminateNotification
object:executionTask
queue:[NSOperationQueue mainQueue]
usingBlock:^(NSNotification *note)
{
[self taskCompleted];
}
];
// Create a pipe and a filehandle for reading errors
NSPipe * error = [[NSPipe alloc] init];
[executionTask setStandardError:error];
errorFile = [error fileHandleForReading];
errorObserver = [defCenter addObserverForName:NSFileHandleDataAvailableNotification
object:errorFile
queue:[NSOperationQueue mainQueue]
usingBlock:^(NSNotification *note)
{
[self errorAvailable];
[errorFile waitForDataInBackgroundAndNotifyForModes:runLoopModes];
}
];
[errorFile waitForDataInBackgroundAndNotifyForModes:runLoopModes];
// Create a pipe and a filehandle for reading output
NSPipe * output = [[NSPipe alloc] init];
[executionTask setStandardOutput:output];
outputFile = [output fileHandleForReading];
outputObserver = [defCenter addObserverForName:NSFileHandleDataAvailableNotification
object:outputFile
queue:[NSOperationQueue mainQueue]
usingBlock:^(NSNotification *note)
{
[self outputAvailable];
[outputFile waitForDataInBackgroundAndNotifyForModes:runLoopModes];
}
];
[outputFile waitForDataInBackgroundAndNotifyForModes:runLoopModes];
// start task
[executionTask launch];
// show our window as modal
[NSApp runModalForWindow:[self window]];
}
My question: Is it possible to update the output while the task is running? And, if yes, how could I achieve that?
A modal window runs the run loop in NSModalPanelRunLoopMode, so you need to add that to your runLoopModes.
You should not be getting the spinning beach ball. The cause is that you're calling -readDataToEndOfFile in your -outputAvailable and -errorAvailable methods. Given that you're using -waitForDataInBackgroundAndNotifyForModes:, you would use the -availableData method to get what data is available without blocking.
Alternatively, you could use -readInBackgroundAndNotifyForModes:, monitor the NSFileHandleReadCompletionNotification notification, and, in your handler, obtain the data from the notification object using [[note userInfo] objectForKey:NSFileHandleNotificationDataItem]. In other words, let NSFileHandle do the work of reading the data for you.
Either way, though, once you get the end-of-file indicator (an empty NSData), you should not re-issue the ...InBackgroundAndNotifyForModes: call. If you do, you'll busy-spin as it keeps feeding you the same end-of-file indicator over and over.
It shouldn't be necessary to manually -display your text view. Once you fix the blocking calls that were causing the spinning color wheel cursor, that will also allow the normal window updating to happen automatically.

Setting up NSMetadataQueryDidUpdateNotification for a simple response

I'm trying to get up and running with an NSMetadataQueryDidUpdateNotification on an OS X app, to alert me when a file in my iCloud ubiquity container is updated. I've been doing a lot of research (including reading other Stack answers like this, this, this, and this), but I still don't have it quite right, it seems.
I've got a "CloudDocument" object subclassed from NSDocument, which includes this code in the H:
#property (nonatomic, strong) NSMetadataQuery *alertQuery;
and this is the M file:
#synthesize alertQuery;
-(id)init {
self = [super init];
if (self) {
if (alertQuery) {
[alertQuery stopQuery];
} else {
alertQuery = [[NSMetadataQuery alloc]init];
}
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(queryDidUpdate:) name:NSMetadataQueryDidUpdateNotification object:nil];
NSLog(#"Notification created");
[alertQuery startQuery];
}
return self;
}
-(void)queryDidUpdate:(NSNotification *)notification {
NSLog(#"Something changed!!!");
}
According to my best understanding, that should stop a pre-existing query if one is running, set up a notification for changes to the ubiquity container, and then start the query so it will monitor changes from here on out.
Except, clearly that's not the case because I get Notification created in the log on launch but never Something changed!!! when I change the iCloud document.
Can anyone tell me what I'm missing? And if you're extra-super-sauce awesome, you'll help me out with some code samples and/or tutorials?
Edit: If it matters/helps, there is only one file in my ubiquity container being synced around. It's called "notes", so I access it using the URL result from:
+(NSURL *)notesURL {
NSURL *url = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil];
return [url URLByAppendingPathComponent:kAllNotes];
}
where "kAllNotes" is set with #define kAllNotes #"notes".
EDIT #2: There have been a lot of updates to my code through my conversation with Daij-Djan, so here is my updated code:
#synthesize alertQuery;
-(id)init {
self = [super init];
if (self) {
alertQuery = [[NSMetadataQuery alloc] init];
if (alertQuery) {
[alertQuery setSearchScopes:[NSArray arrayWithObject:NSMetadataQueryUbiquitousDocumentsScope]];
NSString *STEDocFilenameExtension = #"*";
NSString* filePattern = [NSString stringWithFormat:#"*.%#", STEDocFilenameExtension];
[alertQuery setPredicate:[NSPredicate predicateWithFormat:#"%K LIKE %#", NSMetadataItemFSNameKey, filePattern]];
}
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(queryDidUpdate:) name:NSMetadataQueryDidFinishGatheringNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(queryDidUpdate:) name:NSMetadataQueryDidUpdateNotification object:nil];
NSLog(#"Notification created");
[alertQuery startQuery];
}
return self;
}
-(void)queryDidUpdate:(NSNotification *)notification {
[alertQuery disableUpdates];
NSLog(#"Something changed!!!");
[alertQuery enableUpdates];
}
How do you save your document - what url do you give it? Unless you give it an extension yourself, it won't automatically be given one - so your *.* pattern will never match a file that does not have an extension. Try * as the pattern and see what happens.
Also, it helps to log what is happening within queryDidUpdate, until you've worked out exactly what's going on :
Try something like:
-(void)queryDidUpdate:(NSNotification *)notification {
[alertQuery disableUpdates];
NSLog(#"Something changed!!!");
// Look at each element returned by the search
// - note it returns the entire list each time this method is called, NOT just the changes
int resultCount = [alertQuery resultCount];
for (int i = 0; i < resultCount; i++) {
NSMetadataItem *item = [alertQuery resultAtIndex:i];
[self logAllCloudStorageKeysForMetadataItem:item];
}
[alertQuery enableUpdates];
}
- (void)logAllCloudStorageKeysForMetadataItem:(NSMetadataItem *)item
{
NSNumber *isUbiquitous = [item valueForAttribute:NSMetadataItemIsUbiquitousKey];
NSNumber *hasUnresolvedConflicts = [item valueForAttribute:NSMetadataUbiquitousItemHasUnresolvedConflictsKey];
NSNumber *isDownloaded = [item valueForAttribute:NSMetadataUbiquitousItemIsDownloadedKey];
NSNumber *isDownloading = [item valueForAttribute:NSMetadataUbiquitousItemIsDownloadingKey];
NSNumber *isUploaded = [item valueForAttribute:NSMetadataUbiquitousItemIsUploadedKey];
NSNumber *isUploading = [item valueForAttribute:NSMetadataUbiquitousItemIsUploadingKey];
NSNumber *percentDownloaded = [item valueForAttribute:NSMetadataUbiquitousItemPercentDownloadedKey];
NSNumber *percentUploaded = [item valueForAttribute:NSMetadataUbiquitousItemPercentUploadedKey];
NSURL *url = [item valueForAttribute:NSMetadataItemURLKey];
BOOL documentExists = [[NSFileManager defaultManager] fileExistsAtPath:[url path]];
NSLog(#"isUbiquitous:%# hasUnresolvedConflicts:%# isDownloaded:%# isDownloading:%# isUploaded:%# isUploading:%# %%downloaded:%# %%uploaded:%# documentExists:%i - %#", isUbiquitous, hasUnresolvedConflicts, isDownloaded, isDownloading, isUploaded, isUploading, percentDownloaded, percentUploaded, documentExists, url);
}
you never allocate your alertQuery....
somewhere you need to alloc,init a NSMetaDataQuery for it
example
NSMetadataQuery* aQuery = [[NSMetadataQuery alloc] init];
if (aQuery) {
// Search the Documents subdirectory only.
[aQuery setSearchScopes:[NSArray
arrayWithObject:NSMetadataQueryUbiquitousDocumentsScope]];
// Add a predicate for finding the documents.
NSString* filePattern = [NSString stringWithFormat:#"*"];
[aQuery setPredicate:[NSPredicate predicateWithFormat:#"%K LIKE %#",
NSMetadataItemFSNameKey, filePattern]];
// Register for the metadata query notifications.
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(processFiles:)
name:NSMetadataQueryDidFinishGatheringNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(processFiles:)
name:NSMetadataQueryDidUpdateNotification
object:nil];
// Start the query and let it run.
[aQuery startQuery];
}
processFiles method
== your queryDidUpdate
- processFiles(NSNotification*)note {
[aQuery disableUpdates];
.....
[aQuery enableUpdates];
}
NOTE: disable and reenable search there!
see:
http://developer.apple.com/library/ios/#documentation/General/Conceptual/iCloud101/SearchingforiCloudDocuments/SearchingforiCloudDocuments.html

xcode global nsmutablearray that keeps values

Did a search but cant seem to find exactly what I'm looking for
Basically I load values into a nsmutablearray in one method and then I want to access these values in another method to print them to a table
I declared the array in the app.h
NSMutableArray *clients;
Then in the app.m
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
[connection release];
NSString *responseString = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
[responseData release];
NSArray *results = [responseString JSONValue];
clients = [[NSMutableArray alloc]init];
// Loop through each entry and add clients to array
for (NSDictionary *entry in results)
{
if (![clients containsObject:[entry objectForKey:#"client"]])
{
[clients addObject:[entry objectForKey:#"client"]];
}
}
}
Now Im try to acces the clients array in another method
I have seen some suggestions to use extern in the app.h? Some sort of global variable?
Any help would be appreciated
Thanks
Take the clients array in app delegate class.declare the property,synthesizes in the app delegate class.Then in the below method write like this.
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
[connection release];
YourApplicationDelegate *delegate = [UIApplication sharedApplication]delegate];
NSString *responseString = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
[responseData release];
NSArray *results = [responseString JSONValue];
clients = [[NSMutableArray alloc]init];
// Loop through each entry and add clients to array
for (NSDictionary *entry in results)
{
if (![clients containsObject:[entry objectForKey:#"client"]])
{
[delegate.clients addObject:[entry objectForKey:#"client"]];
}
}
}
after that suppose you if you want to use the clients array in another class do like this.
YourApplicationDelegate *delegate = [UIApplication sharedApplication]delegate];
NSLog(#"client array is %#",delegate.clients);

Memory problems with NSMutableDictionary, causing NSCFDictionary memory leaks

Help me please with the following problem:
- (NSDictionary *)getGamesList
{
NSMutableDictionary *gamesDictionary = [[NSMutableDictionary dictionary] retain];
// I was trying to change this on the commented code below, but did have no effect
// NSMutableDictionary *gamesDictionary = [[NSMutableDictionary alloc] init];
// [gamesDictionary retain];
while (sqlite3_step(statement) == SQLITE_ROW)
{
NSString *key = [NSString stringWithUTF8String:(char *)sqlite3_column_text(statement, 1)];
NSArray *gameDate = [key componentsSeparatedByString:#" "];
NSNumber *_id = [[NSNumber alloc] initWithInt:sqlite3_column_int(statement, 0)];
NSString *date_time = [NSString stringWithFormat:#"%#, %#",[gameDate objectAtIndex:0],[gameDate objectAtIndex:2]];
if (![gamesDictionary valueForKey:date_time]) [gamesDictionary setValue:[NSMutableArray array] forKey:date_time];
[[gamesDictionary valueForKey:date_time] addObject:[[_id copy] autorelease]];
[_id release];
}
sqlite3_reset(statement);
return gamesDictionary;
}
The leak starts in another method of another class, there the getGamesList method is called, like this:
NSMutableDictionary *gamesDictionary;
gamesDictionary = [[NSMutableDictionary dictionaryWithDictionary:[appDelegate getGamesList]] retain];
After that there are a lot of leaks that points to NSCFArray in the string:
NSArray *keys = [[NSArray arrayWithArray:[gamesDictionary allKeys]] retain];
in this method:
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
NSArray *keys = [[NSArray arrayWithArray:[gamesDictionary allKeys]] retain];
if ([keys count] != 0) return [[keys objectAtIndex:section] uppercaseString];
return #"";
}
I assume these things are connected to each other, but I still can not understand all of the memory management tips.
Thanks a lot!
Didn't use Cocoa for years (that's why I can't tell you an exact answer :/). But I guess your problem is that you systematically use retain on your objects.
Since the object reference count never get to 0, all dictionaries are keep in memory and not freed.
Try to remove the retain on [NSArray arrayWithArray] and [NSMutableDictionary dictionaryWithDictionary
http://en.wikibooks.org/wiki/Programming_Mac_OS_X_with_Cocoa_for_beginners/Some_Cocoa_essential_principles#Retain_and_Release
It does look like you are over-retaining your array.
When you create the gamesDictionary it is created with an retain count of +1. You then retain it (count becomes +2). When you get the value outside this function you retain again (count becomes +3).
You are correct that if you create an object you are responsible for it's memory management. Also, when you get an object from a method, you should retain it if you want to keep it around for longer than the span of the function. In your case, you just want to get at some of the properties of the object, so you don't need to retain it.
Here is a suggestion:
- (NSDictionary *)getGamesList
{
NSMutableDictionary *gamesDictionary = [NSMutableDictionary dictionary]; // Remove the retain.
while (sqlite3_step(statement) == SQLITE_ROW)
{
NSString *key = [NSString stringWithUTF8String:(char *)sqlite3_column_text(statement, 1)];
NSArray *gameDate = [key componentsSeparatedByString:#" "];
NSNumber *_id = [[NSNumber alloc] initWithInt:sqlite3_column_int(statement, 0)];
NSString *date_time = [NSString stringWithFormat:#"%#, %#",[gameDate objectAtIndex:0],[gameDate objectAtIndex:2]];
if (![gamesDictionary valueForKey:date_time]) [gamesDictionary setValue:[NSMutableArray array] forKey:date_time];
[[gamesDictionary valueForKey:date_time] addObject:[[_id copy] autorelease]];
[_id release];
}
sqlite3_reset(statement);
return gamesDictionary;
}
This next bit is messy. you create a new dictionary and retain it. The original dictionary is not autoreleased, so the count isn't decremented and it always hangs around. Just assign the dictionary rather than create a new one.
NSMutableDictionary *gamesDictionary = [[appDelegate getGamesList] retain];
// Retaining it, becuase it looks like it's used elsewhere.
Now, in this method:
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
NSString *returnString;
// Don't need to retain the keys because you are only using it within the function
// and since you didn't alloc, copy or retain the array it contains, you aren't responsible for it's memory management.
NSArray *keys = [NSArray arrayWithArray:[gamesDictionary allKeys]];
if ([keys count] != 0) {
returnString = [[NSString alloc] initWithString:[[keys objectAtIndex:section] uppercaseString]];
return [returnString autorelease];
}
return #"";
}

Resources