When I pass a NSMutableArray from a controller class to a NSWindowController class using #property and #synthesize I am able to use the objects of the array in the windowDidLoad method.
However, after the method is done and I click a button on the window triggerig an IBAction, the passed value is nil.
Can anyone explain me why this is happening and how I can preserve the NSMutableArray?
Here is the code:
passClass.h
#import <Foundation/Foundation.h>
#class ResultWindowController;
#interface passClass : NSObject {
#private
IBOutlet NSTextField *searchField;
ResultWindowController *resultWindowController;
}
- (IBAction)passIt:(id)sender;
#end
passClass.m
#import "passClass.h"
#import "ResultWindowController.h"
#implementation passClass
- (IBAction)passIt:(id)sender {
NSString *searchString = searchField.stringValue;
NSMutableArray array = [[NSMutableArray alloc]init];
[array addObject:searchString];
[array addObject:searchString];
if(!resultWindowController) {
resultWindowController = [[ResultWindowController alloc] initWithWindowNibName:#"ResultWindow"];
resultWindowController.array =[[NSMutableArray alloc]initWithArray:array copyItems:YES];
[resultWindowController showWindow:self];
}
}
#end
ResultWindowController.h
#import <Cocoa/Cocoa.h>
#interface ResultWindowController : NSWindowController <NSTableViewDataSource> {
IBOutlet NSTableView *resultView;
NSMutableArray *resultList;
//NSMutableArray *array;
}
- (IBAction)returnValue:(id)sender;
#property (nonatomic,strong) NSMutableArray *array;
#end
ResultWindowController.m
#import "Results.h"
#interface ResultWindowController ()
#end
#implementation ResultWindowController
//#synthesize array;
- (id)initWithWindow:(NSWindow *)window
{
self = [super initWithWindow:window];
if (self) {
// Initialization code here.
resultList = [[NSMutableArray alloc] init];
}
return self;
}
- (void)windowDidLoad
{
[super windowDidLoad];
for (NSInteger i = 0; i< [array count];i++)
{
Results *result = [[Results alloc]init];
result.resultName = [self.array objectAtIndex:i];
[resultList addObject:result];
[resultView reloadData];
NSLog (#"self.array: %#", self.array);
// works fine, tableview gets populated, array is correct
}
}
- (NSInteger) numberOfRowsInTableView:(NSTableView *)resultView{
return [resultList count];
}
- (id)tableView:(NSTableView *)resultView objectValueForTableColumn:(NSTableColumn *)resultColumn row:(NSInteger)row{
Results *result = [resultList objectAtIndex:row];
NSString *identifier = [resultColumn identifier];
return [result valueForKey:identifier];
}
- (IBAction)selectedSeries:(id)sender {
NSLog (#"self.array: %#", self.array);
//when I break here the array is nil
}
#end
Here is the NSLog result:
2013-12-26 10:36:49.487 MyProgram[545:303] self.array: (
"test",
"test"
)
2013-12-26 10:37:24.044 MyProgram[545:303] self.array: (null)
Try to remove NSMutableArray *array; from ResultWindowController class declaration and leave only the property declaration for it.
Or you could try initiating the array property in your ResultVWindowsContorller init class and in the - (IBAction)passIt:(id)sender just add objects to array.
I honestly can't see how this works at all unless, in -windowDidLoadNib you are expecting array to be empty.
When you synthesise a property, the default name of the instance variable that is used is prefixed by an underscore. Thus the class in your code has two instance variables, array and _array.
There are several ways to fix this. Here's what I think what you should do is delete the instance variable in your interface definition. Then you'll start getting compilation errors every for each time you use it. Fix them by using the property instead, so for example, the line
result.resultName = [array objectAtIndex:i];
in -windowDidLoadNib becomes
result.resultName = [self.array objectAtIndex:i];
Related
Put simply, is there a way to receive a general notification when any property in an Objective-C class is changed? I know I can use KVO to monitor particular property changes, but I have the need to call a particular method whenever any setProperty: message is sent to my class. I want to be able to receive a generic notification without any concern about which property in particular was modified.
If it helps to clarify why I want to do this, I am making use of some fast table scrolling code found here: http://blog.atebits.com/2008/12/fast-scrolling-in-tweetie-with-uitableview/
Part of the process of accomplishing this is that whenever a property in a table view cell is modified, [ self setNeedsDisplay ] needs to be called. I'd rather not have to override the setter methods for every property in my class just to make this call.
As Chuck notes, you can create a dependent key, or of course you can directly observe all the properties (which is less work than overloading the setters).
Using the Objective-C runtime, if you exclusively use properties, you can automate this process using class_copyPropertyList(). But I'd probably only do this if this problem comes up a bit for you. If you only have one instance of this problem, it's probably easier and safer and more maintainable just to directly observe the list of properties unless you feel like working in the ObjC runtime.
Here's an example built off of Chuck and Rob's suggestions:
DrakeObject.h
#interface DrakeObject : NSObject
#property (nonatomic, strong) NSNumber *age;
#property (nonatomic, strong) NSNumber *money;
#property (nonatomic, strong) NSString *startPosition;
#property (nonatomic, strong) NSString *currentPosition;
#property (nonatomic, strong, readonly) id propertiesChanged;
#end
DrakeObject.m
#implementation DrakeObject
- (instancetype)init {
self = [super init];
if (self) {
self.age = #25;
self.money = #25000000;
self.startPosition = #"bottom";
self.currentPosition = #"here";
}
return self;
}
- (id)propertiesChanged {
return nil;
}
+(NSSet *)keyPathsForValuesAffectingPropertiesChanged {
return [NSSet setWithObjects:#"age", #"money", #"startPosition", #"currentPosition", nil];
}
observing propertiesChanged will let us know anytime a property has changed.
[self.drakeObject addObserver:self
forKeyPath:#"propertiesChanged"
options:NSKeyValueObservingOptionNew
context:nil];
Not exactly. You can create a dependent key that depends on every property you wish to expose and then observe that. That's about as close as you'll get, I think.
Here an example of code. I have a general object and dother object. Dother object has to save his state on change each property.
#import <Foundation/Foundation.h>
#interface GeneralObject : NSObject
+ (instancetype)instanceWithDictionary:(NSDictionary *)aDictionary;
- (instancetype)initWithDictionary:(NSDictionary *)aDictionary;
- (NSDictionary *)dictionaryValue;
- (NSArray *)allPropertyNames;
#end
implementation
#import "GeneralObject.h"
#import <objc/runtime.h>
#implementation GeneralObject
#pragma mark - Public
+ (instancetype)instanceWithDictionary:(NSDictionary *)aDictionary {
return [[self alloc] initWithDictionary:aDictionary];
}
- (instancetype)initWithDictionary:(NSDictionary *)aDictionary {
aDictionary = [aDictionary clean];
for (NSString* propName in [self allPropertyNames]) {
[self setValue:aDictionary[propName] forKey:propName];
}
return self;
}
- (NSDictionary *)dictionaryValue {
NSMutableDictionary *result = [NSMutableDictionary dictionary];
NSArray *propertyNames = [self allPropertyNames];
id object;
for (NSString *key in propertyNames) {
object = [self valueForKey:key];
if (object) {
[result setObject:object forKey:key];
}
}
return result;
}
- (NSArray *)allPropertyNames {
unsigned count;
objc_property_t *properties = class_copyPropertyList([self class], &count);
NSMutableArray *array = [NSMutableArray array];
unsigned i;
for (i = 0; i < count; i++) {
objc_property_t property = properties[i];
NSString *name = [NSString stringWithUTF8String:property_getName(property)];
[array addObject:name];
}
free(properties);
return array;
}
#end
and after all we have dother class, which should save his state on each change of any property
#import "GeneralObject.h"
extern NSString *const kUserDefaultsUserKey;
#interface DotherObject : GeneralObject
#property (strong, nonatomic) NSString *firstName;
#property (strong, nonatomic) NSString *lastName;
#property (strong, nonatomic) NSString *email;
#end
and implementation
#import "DotherObject.h"
NSString *const kUserDefaultsUserKey = #"CurrentUserKey";
#implementation DotherObject
- (instancetype)initWithDictionary:(NSDictionary *)dictionary {
if (self = [super initWithDictionary:dictionary]) {
for (NSString *key in [self allPropertyNames]) {
[self addObserver:self forKeyPath:key options:NSKeyValueObservingOptionNew context:nil];
}
}
return self;
}
- (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change context:(nullable void *)context {
NSDictionary *dict = [self dictionaryValue];
[[NSUserDefaults standardUserDefaults] setObject:dict forKey:kUserDefaultsUserKey];
[[NSUserDefaults standardUserDefaults] synchronize];
}
- (NSString *)description {
return [NSString stringWithFormat:#"%#; dict:\n%#", [super description], [self dictionaryValue]];
}
#end
Happy coding!
I am trying add a Song* object to a Mutable array and I am stumped as the count of the array is not increasing in spite of adding the object.
Song.h
#import <Foundation/Foundation.h>
#interface Song : NSObject
#property(copy, nonatomic) NSString *title, *album, *artist;
#property(copy, nonatomic) NSNumber *playTime;
#end
Song.m
#import "Song.h"
#implementation Song
#end
Playlist.h
#import <Foundation/Foundation.h>
#class Song;
#interface Playlist : NSObject
#property(copy, nonatomic) NSMutableArray *playListArray;
-(void) addSong: (Song *) tempSongToBeAdded;
-(void) removeSong: (Song *) tempSongToBeremoved;
-(void) listOfSongs;
-(NSUInteger) entries;
#end
Playlist.m
#import "Playlist.h"
#import "Song.h"
#implementation Playlist
-(void) addSong: (Song *) tempSongToBeAdded{
NSLog(#"%s song is being added.", [tempSongToBeAdded.title UTF8String]);
[self.playListArray addObject:tempSongToBeAdded];
}
-(void) removeSong: (Song *) tempSongToBeremoved{
[self.playListArray removeObject:tempSongToBeremoved];
}
-(NSUInteger) entries{
return [self.playListArray count];
}
-(void) listOfSongs{
NSLog(#"Hi");
for (Song *tempSong in self.playListArray) {
NSLog(#"title: %s", [tempSong.title UTF8String]);
}
}
#end
Main.m
#import <Foundation/Foundation.h>
#import "Song.h"
#import "Playlist.h"
int main(int argc, const char * argv[])
{
#autoreleasepool {
Song *song1 = [[Song alloc] init];
song1.title = #"Manasa";
song1.album = #"Ye Maya Chesava";
song1.artist = #"A. R. Rahman";
song1.playTime = [NSNumber numberWithDouble:4.56];
Playlist *playlist1 = [[Playlist alloc] init];
[playlist1 addSong:song1];
NSLog(#"The total number of songs are %lu",[playlist1 entries]);
[playlist1 listOfSongs];
}
return 0;
}
I am getting the entries in the playlist as 0 and and the list of songs as empty. I am not getting any compile errors and I have no idea why the objects are not getting added to the array.
Your variable playListArray is never initialized and is always nil. You need to initialize it using:
playListArray = [[NSMutableArray] alloc] init];
You can add an init method in your Playlist class where you initialize this object.
- (id)init
{
self = [super init];
if (self)
{
playListArray = [[NSMutableArray] alloc] init];
}
return self;
}
EDIT:
The problem seems to be how you declared the property
#property(copy, nonatomic) NSMutableArray *playListArray;
It is declared as copy, that means that even when you do playListArray = [[NSMutableArray] alloc] init], your variable playListArray gets a copy of the initialized array, but the copy protocol is inherited from NSArray, not from NSMutableArray, so you get an immutable array. You can check this in the NSMutableArray documentation. You need to change copy for retain (you're not using ARC, right?).
In fact I see that you're using copy for most of your properties, if there's no particular reason for this, I would change them to retain.
I'm using an ItemController to provide a list of items to use in a tableview. I can't seem to populate the controller though, and I'm not sure why.
Here's the code for the controller class:
.h
#import <Foundation/Foundation.h>
#class Item;
#interface ItemController : NSObject
#property (nonatomic, copy) NSMutableArray *items;
- (NSUInteger)countOfList;
- (Item*)objectInListAtIndex:(NSUInteger)theIndex;
- (void)addItem:(Item *)item;
#end
.m
#import "ItemController.h"
#import "Item.h"
#interface ItemController ()
#end
#implementation ItemController
- (NSUInteger)countOfList {
return [self.items count];
}
- (Item *)objectInListAtIndex:(NSUInteger)theIndex {
return [self.items objectAtIndex:theIndex];
}
- (void)addItem:(Item *)item {
[self.items addObject:item];
}
#end
Item.m
#implementation Item
-(id)initWithName:(NSString *)name{
self = [super init];
if (self) {
_name = name;
return self;
}
return nil;
}
#end
I'm using the following code to populate the list:
ItemController* controller = [[ItemController alloc] init];
for (NSString* key in raw_data) {
NSLog(key); // This outputs the keys fine
[controller addItem:[[Item alloc] initWithName:key]];
}
NSLog([NSString stringWithFormat:#"%d",[controller countOfList]]); // Always 0
You need to initialize the array in the init methond.
- (id)init {
self = [super init];
if (self) {
self.items = [[NSMutableArray alloc] init];
}
return self;
}
You need to initialize your variable items. In your init method, call self.items = [NSMutableArray new]; and also change your array property from copy to retain.
I also believe your class ItemController should be of kind UIViewController and not NSObject.
#interface ItemController : UIViewController
You don't initialise the _items instance variable anywhere, so it's always nil. The result of any integer-returning method called on nil will be 0, so you see that the count is 0.
I am having problem with adding objects to NSMutableArray *array.
// Controller.m
#import "Controller.h"
#implementation Controller
- (void)parser:(NSString *)string{
[array addObject:string];
NSLog(#"answerArray(1): %#",[array objectAtIndex:1]);
[array retain];
}
#end
// Controller.h
#import <Foundation/Foundation.h>
#interface Controller : NSObject {
NSMutableArray *array;
}
- (void)parser:(NSString *)string;
#end
NSLog(#"answerArray(1): %#",[array objectAtIndex:1]);
Results: answerArray(1): (null)
First off, you're over-retaining the array.
Second, you didn't provide the code for initializing the array, so I guess it's not allocated and initialized. This will cause the code to message a nil object and thus return nil.
You should create an init method for the Controller object, and allocate a new NSMutableArray object (and retain it).
Also, a proper dealloc to release the array.
NSMutabaleArray starts at index 0
Here is the method I added to Controller class:
- (id)init {
self = [super init];
if(self){
array = [[NSMutableArray alloc] init];
}
return self;
}
- (void)dealloc {
[array release];
[super dealloc];
}
Put simply, is there a way to receive a general notification when any property in an Objective-C class is changed? I know I can use KVO to monitor particular property changes, but I have the need to call a particular method whenever any setProperty: message is sent to my class. I want to be able to receive a generic notification without any concern about which property in particular was modified.
If it helps to clarify why I want to do this, I am making use of some fast table scrolling code found here: http://blog.atebits.com/2008/12/fast-scrolling-in-tweetie-with-uitableview/
Part of the process of accomplishing this is that whenever a property in a table view cell is modified, [ self setNeedsDisplay ] needs to be called. I'd rather not have to override the setter methods for every property in my class just to make this call.
As Chuck notes, you can create a dependent key, or of course you can directly observe all the properties (which is less work than overloading the setters).
Using the Objective-C runtime, if you exclusively use properties, you can automate this process using class_copyPropertyList(). But I'd probably only do this if this problem comes up a bit for you. If you only have one instance of this problem, it's probably easier and safer and more maintainable just to directly observe the list of properties unless you feel like working in the ObjC runtime.
Here's an example built off of Chuck and Rob's suggestions:
DrakeObject.h
#interface DrakeObject : NSObject
#property (nonatomic, strong) NSNumber *age;
#property (nonatomic, strong) NSNumber *money;
#property (nonatomic, strong) NSString *startPosition;
#property (nonatomic, strong) NSString *currentPosition;
#property (nonatomic, strong, readonly) id propertiesChanged;
#end
DrakeObject.m
#implementation DrakeObject
- (instancetype)init {
self = [super init];
if (self) {
self.age = #25;
self.money = #25000000;
self.startPosition = #"bottom";
self.currentPosition = #"here";
}
return self;
}
- (id)propertiesChanged {
return nil;
}
+(NSSet *)keyPathsForValuesAffectingPropertiesChanged {
return [NSSet setWithObjects:#"age", #"money", #"startPosition", #"currentPosition", nil];
}
observing propertiesChanged will let us know anytime a property has changed.
[self.drakeObject addObserver:self
forKeyPath:#"propertiesChanged"
options:NSKeyValueObservingOptionNew
context:nil];
Not exactly. You can create a dependent key that depends on every property you wish to expose and then observe that. That's about as close as you'll get, I think.
Here an example of code. I have a general object and dother object. Dother object has to save his state on change each property.
#import <Foundation/Foundation.h>
#interface GeneralObject : NSObject
+ (instancetype)instanceWithDictionary:(NSDictionary *)aDictionary;
- (instancetype)initWithDictionary:(NSDictionary *)aDictionary;
- (NSDictionary *)dictionaryValue;
- (NSArray *)allPropertyNames;
#end
implementation
#import "GeneralObject.h"
#import <objc/runtime.h>
#implementation GeneralObject
#pragma mark - Public
+ (instancetype)instanceWithDictionary:(NSDictionary *)aDictionary {
return [[self alloc] initWithDictionary:aDictionary];
}
- (instancetype)initWithDictionary:(NSDictionary *)aDictionary {
aDictionary = [aDictionary clean];
for (NSString* propName in [self allPropertyNames]) {
[self setValue:aDictionary[propName] forKey:propName];
}
return self;
}
- (NSDictionary *)dictionaryValue {
NSMutableDictionary *result = [NSMutableDictionary dictionary];
NSArray *propertyNames = [self allPropertyNames];
id object;
for (NSString *key in propertyNames) {
object = [self valueForKey:key];
if (object) {
[result setObject:object forKey:key];
}
}
return result;
}
- (NSArray *)allPropertyNames {
unsigned count;
objc_property_t *properties = class_copyPropertyList([self class], &count);
NSMutableArray *array = [NSMutableArray array];
unsigned i;
for (i = 0; i < count; i++) {
objc_property_t property = properties[i];
NSString *name = [NSString stringWithUTF8String:property_getName(property)];
[array addObject:name];
}
free(properties);
return array;
}
#end
and after all we have dother class, which should save his state on each change of any property
#import "GeneralObject.h"
extern NSString *const kUserDefaultsUserKey;
#interface DotherObject : GeneralObject
#property (strong, nonatomic) NSString *firstName;
#property (strong, nonatomic) NSString *lastName;
#property (strong, nonatomic) NSString *email;
#end
and implementation
#import "DotherObject.h"
NSString *const kUserDefaultsUserKey = #"CurrentUserKey";
#implementation DotherObject
- (instancetype)initWithDictionary:(NSDictionary *)dictionary {
if (self = [super initWithDictionary:dictionary]) {
for (NSString *key in [self allPropertyNames]) {
[self addObserver:self forKeyPath:key options:NSKeyValueObservingOptionNew context:nil];
}
}
return self;
}
- (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change context:(nullable void *)context {
NSDictionary *dict = [self dictionaryValue];
[[NSUserDefaults standardUserDefaults] setObject:dict forKey:kUserDefaultsUserKey];
[[NSUserDefaults standardUserDefaults] synchronize];
}
- (NSString *)description {
return [NSString stringWithFormat:#"%#; dict:\n%#", [super description], [self dictionaryValue]];
}
#end
Happy coding!