I have an helper class which has a function that makes an api call and get some json data and formats and returns an array. My TableViewController is trying to access that returned array. Yes, as you expected my tableviewcontroller viewDidLoad method is not able to access the array object returned by my helper class.
#interface MyHelperClass : NSObject
#property (nonatomic,retain)NSArray *myArray;
#end
#implementation MyHelperClass
#synthesize myArray;
- (NSArray *) returnArray{
// make api calls and return array
return myArray;
}
#end
#implementation MyTableViewController
{
- (void)viewDidLoad
{
[super viewDidLoad];
MyHelperClass *myhelper = [[MyHelperClass alloc]initWithPath:getSpacePath];
allTopics = (NSArray *)[myhelper returnArray];
NSLog(#"Load my Array%#",allTopics);
}
}
My question is, do I need to implement a delegate to pass the data around or is there any other way to pass around the data to my view controller?
P.S : I do not want to use global variable
Did this code give you any warning ?
You are trying to return an NSArray * from void returning method.
Modify it to
- (NSArray *) returnArray{ // YOU CAN RETURN id AS WELL, AS YOU ARE TYPE CASTING THE RESULT AT CALLING TIME
// make api calls and return array
NSLog (#"myArray :: %#", [myArray description]); // Post the output back here
return myArray;
}
Let me know if the problem persists.
EDIT
Set breakpoints at
allTopics = (NSArray *)[myhelper returnArray]; // AT - (void)viewDidLoad
and
return myArray; // AT HelperClass method
If first one it getting fired first, then You have to implement as #A-Live suggested in the comment.
Sorry for posting the answer so late. I figured out what the problem is. As #A-Live mentioned, the Rest API calls using AFNetworking is using async calls and hence it's not returning the array to the main thread within it's execution time. In my case,
-(void)viewDidLoad {
NSLog(#"I get called first");
MyHelper *helper = [[MyHelper alloc]init];
// returns array. However, [helper getData] is an async call under the hood. Hence myArray is nil
myArray = [helper getData];
}
To solve this problem, I took advantage of NSNotification.
#implementation MyHelper{
-(NSArray *)getData(){
[[NSNotificationCenter defaultCenter] postNotificationName:#"some.name.notification" object:JSON];
}
}
-(void)viewDidLoad(){
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(loadData:) name:#"some.name.notification" object:nil];
}
-(void)loadData:(NSNotification *)notif {
// You can access the JSON object passed by the helper in here
NSArray *myArray = [notif object];
// do whatever you want with the array.
}
I hope I am detailed enough. I hope this helps someone and saves a lot of headache.
Related
I have trouble implementing a Key-Value Observer at my attempt to follow the MVC pattern. I have a controller class, a model class and a view class. I update my model from the controller class and I want to put a key value observer in my view class to monitor when a NSMutableArray changes in model (like through addObject) and then redraw itself automatically. I used answer in this thread to guide me: How to add observer on NSMutableArray?
Code so far:
From my Scene (using sprite kit if it matters). Setting of letters will be done from Ctrl class, this is just to test.
BarCtrl *barCtrl = [[BarCtrl alloc] init];
BarModel *barModel = [[BarModel alloc] init];
BarView *barView = [[BarView alloc] init];
barCtrl.barModel = barModel;
barCtrl.barView = barView;
barView.barModel = barModel;
ScrabbleDeck *sd = [[ScrabbleDeck alloc] init];
if([barModel addLetter:[sd getLetter] onSide:BarModelSideRight])
NSLog(#"Added letter");
BarModel.h
#import <Foundation/Foundation.h>
#import "Letter.h"
typedef NS_ENUM(int, BarModelSide) {
BarModelSideLeft,
BarModelSideRight
};
#interface BarModel : NSObject
#property (nonatomic, strong) NSMutableArray *addedLetters;
- (instancetype)init;
- (BOOL) addLetter: (Letter*) letter onSide: (BarModelSide) side;
#end
BarModel.m
#import "BarModel.h"
#interface BarModel ()
#property (nonatomic) int capacity;
#end
#implementation BarModel
- (instancetype)init
{
self = [super init];
if (self) {
self.capacity = letterCapacity;
_addedLetters = [[NSMutableArray alloc] init];
}
return self;
}
// We'll use automatic notifications for this example
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key
{
if ([key isEqualToString:#"arrayLetter"]) {
return YES;
}
return [super automaticallyNotifiesObserversForKey:key];
}
- (BOOL) addLetter: (Letter*) letter onSide: (BarModelSide) side{
if([_addedLetters count] > _capacity){
return FALSE;
}
switch (side) {
case BarModelSideLeft:
[_addedLetters insertObject:letter atIndex:0];
return TRUE;
break;
case BarModelSideRight:
[_addedLetters addObject:letter];
return TRUE;
break;
default:
return FALSE;
break;
}
}
// These methods enable KVC compliance
- (void)insertObject:(id)object inDataAtIndex:(NSUInteger)index
{
self.addedLetters[index] = object;
}
- (void)removeObjectFromDataAtIndex:(NSUInteger)index
{
[self.addedLetters removeObjectAtIndex:index];
}
- (id)objectInDataAtIndex:(NSUInteger)index
{
return self.addedLetters[index];
}
- (NSArray *)dataAtIndexes:(NSIndexSet *)indexes
{
return [self.addedLetters objectsAtIndexes:indexes];
}
- (NSUInteger)countOfData
{
return [self.addedLetters count];
}
#end
BarView.h
#import <SpriteKit/SpriteKit.h>
#import "BarModel.h"
#interface BarView : SKSpriteNode
#property (nonatomic, strong) BarModel *barModel;
#end
BarView.m
#import "BarView.h"
#implementation BarView
static char MyObservationContext;
- (instancetype)init
{
self = [super init];
if (self) {
//_barModel = [[BarModel alloc] init];
}
return self;
}
-(void)setBarModel:(BarModel *)barModel{
if(_barModel != barModel)
_barModel = barModel;
[_barModel addObserver:self
forKeyPath:#"arrayLetter"
options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew)
context:&MyObservationContext];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
// Check if our class, rather than superclass or someone else, added as observer
if (context == &MyObservationContext) {
// Check that the key path is what we want
if ([keyPath isEqualToString:#"arrayLetter"]) {
// Verify we're observing the correct object
if (object == self.barModel) {
[self draw:change];
}
}
}
else {
// Otherwise, call up to superclass implementation
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
- (void) draw: (NSDictionary*) change{
NSLog(#"KVO for our container property, change dictionary is %#", change);
}
#end
When I ru this I get this "error":
2014-08-31 00:23:02.828 Testing[329:60b] Added letter
2014-08-31 00:23:02.830 Testing[329:60b] An instance 0x17803d340 of class BarModel was deallocated while key value observers were still registered with it. Observation info was leaked, and may even become mistakenly attached to some other object. Set a breakpoint on NSKVODeallocateBreak to stop here in the debugger. Here's the current observation info:
<NSKeyValueObservationInfo 0x17804eb50> (
<NSKeyValueObservance 0x1780cf180: Observer: 0x178111670, Key path: arrayLetter, Options: <New: YES, Old: YES, Prior: NO> Context: 0x100101428, Property: 0x17804eb80>
I tried to follow the instructions in error but can not find where to set break point. Please help me figure this out!
The error is pretty descriptive. You add self as an observer of a BarModel object. At some point that object gets deallocated. But you never remove self as an observer by calling removeObserver:forKeyPath:context:. You need to do that.
First, in setBarModel, make sure to remove self as an observer of the previous value of _barModel.
Next, you probably need to add a dealloc method that does the same thing.
There are multiple problems with the code. In addition to what Tom Harrington said with respect to the specific error that was logged about failing to remove the observation:
You implemented the indexed collection accessors for a (non-existent) property named "data". That is, they have "Data" in their name where the property name should be.
The indexed collection property is addedLetters. So, the indexed collection mutating accessors should be:
- (void)insertObject:(id)object inAddedLettersAtIndex:(NSUInteger)index;
- (void)removeObjectFromAddedLettersAtIndex:(NSUInteger)index;
You don't really need the non-mutating accessors, since you have an array-type public property with a normal getter (i.e. -addedLetters).
By the way, that property is of type NSMutableArray which it should not be. The property should be of type NSArray, backed by an instance variable of type NSMutableArray. That is, the mutability of the type (as opposed to the property) should not be exposed through the public interface. When you do this, you have to manually declare the instance variable (since it should differ from the type of the property and auto-synthesis will get it wrong), make the property copy instead of strong, and implement the setter yourself to do a mutable copy of the passed-in immutable array:
- (void) setAddedLetters:(NSArray*)addedLetters
{
if (addedLetters != _addedLetters)
_addedLetters = [addedLetters mutableCopy];
}
Once you have implemented the indexed collection mutating accessors with the correct names, you must use only those methods to mutate the collection (after initialization). In particular, your -addLetter:onSide: method must not directly operate on the _addedLetters instance variable. This is the part that makes the class KVO-compliant for that property. The mere presence of the indexed collection mutating accessors does not help. They must be used for all actual mutations.
Your implementation of +automaticallyNotifiesObserversForKey: is redundant. Automatic notification is the default.
The BarView class is key-value observing a key path "arrayLetter" on its _barModel object, but that's not the name of the property on BarModel. I suppose you meant to use the key path "addedLetters".
Finally, for proper adherence to MVC design, your view should not have a reference to your model. It should have a reference to the controller. The controller can reflect the model to the view (or, in theory, adapt a model of a different internal design to what the view expects). Or, in a more traditional non-KVO approach, the controller would actually tell the view when something has changed and give it the updated data it should show.
So, your controller could expose its own addedLetters property:
#property (readonly, copy, nonatomic) NSArray* addedLetters;
It could be implemented as a derived property, forwarded through to the _barModel object:
+ (NSSet*)keyPathsForValuesAffectingAddedLetters
{
return [NSSet setWithObject:#"barModel.addedLetters"];
}
- (NSArray*)addedLetters
{
return self.barModel.addedLetters;
}
Then, the view would have a reference to the controller and not the model, and it would key-value observe the "addedLetters" key path on the controller.
I have one NSMutableArray in FirstViewController declared as firstArray.
I want to copy the secondArray into firstArray.
In the SecondViewController,
Self.FirstViewController.firstArray = self.secondArray;
When I attempt to NSLog the firstArray.count from the FirstViewController, it display 0. It should have two objects in the array
Anyone can advise on this?
You can choose one of this solutions:
Singleton
Passing Data between ViewControllers
Delegation
You can find all the info you need right here: https://stackoverflow.com/a/9736559/1578927
Singleton example:
static MySingleton *sharedSingleton;
+ (void)initialize
{
static BOOL initialized = NO;
if(!initialized)
{
initialized = YES;
sharedSingleton = [[MySingleton alloc] init];
}
}
It looks like either the second array has already been deallocated when passing the reference to the first view controller, or the first view controller itself has already been nilled out. If the first is true, then you may need a different model object to hold your data rather than persisting it in the controller layer of your app. If that is not the case, then you may want to consider a direct copy. The easiest way of doing this is to declare the firstArray property as the keyword copy rather than strong in your interface file.
If you do need to persist the data in the model layer of your app, a singleton pattern object would indeed be one way of achieving this as EXEC_BAD_ACCESS (nice name!) points out. A slightly more modern (though functionally equivalent) way of writing a singleton is as follows.
#interface MySingleton : NSObject
#property (strong, readwrite) id myData;
+ (id)sharedSingleton
#end
#implementation MySingleton
+ (id)sharedSingleton
{
static MySingleton *singleton = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
singleton = [[MySingleton alloc] init];
// Do other setup code here.
});
return singleton;
}
#end
Note the use of dispatch_once - this makes certain that the static singleton can only be created once (whereas technically, you can invoke +[NSObject initialize] as many times as you feel like manually, though I'd never advise doing so).
You may also take advantage of NSNotificationCenter
SecondViewController.m
[[NSNotificationCenter defaultCenter] postNotificationName:#"arrayFromSecondVC" object:secondArray];
FirstViewController.m
- (void)viewDidLoad
{
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(populateArray:) name:#"arrayFromSecondVC" object:nil];
}
-(void)populateArray:(NSNotification *)notif
{
self.firstArray = [notif object];
}
And remove the notification when the viewUnload or didRecieveMemoryWarning method.
Hope it helps.
I have done a delegate , so in the class delegated, which is a UIPopover, there is a method that received an object of my PreguntasViewController. If i print the variables , there is not anything. Why could it be? Thanks.
// -- PreguntasViewController
- (void)getRespuestasDelegate {
[delegate getRespuestas:self];
}
//Delegated class
- (void)viewDidLoad
{
[super viewDidLoad];
PreguntasViewController *preguntasViewCotroller = [[PreguntasViewController alloc] init];
preguntasViewCotroller.delegate = self;
[preguntasViewCotroller getRespuestasDelegate];
}
- (void)getRespuestas:(PreguntasViewController *)preguntasViewController{
for(NSString *respuesta in preguntasViewController.preguntasArray) {
NSLog(#"%#", respuesta);
}
NSLog(#"%d", [preguntasViewController.preguntasArray count]);
NSLog(#"%#", preguntasViewController.ayudapreguntas);
}
It looks to me from your example like you are allocating an instance of PreguntasViewController and then immediately calling its getRespuestasDelegate method. I don't see anywhere that preguntasArray gets populated. Is it populated as part of the PreguntasViewController's init method? If not, I wouldn't expect there to be anything in the array.
I know this question is asked once every two days. I can not see what I am doing wrong though.
I have a storyboard navigation controller based app.
My notification and pop / push segues works well, only thing is I can not add string to parents view NSmutablearray.
I want to add a string object to parent view's nsmutablearray. My decent code does not pass any data.
parent.h
#interface CreaatePlistTableViewController : UITableViewController<UITableViewDelegate, UITableViewDataSource>{
NSMutableArray *presenterList;
}
#property (nonatomic, strong) NSMutableArray *presenterList;
parent.m
NSString * const NOTIF_CreatePlist_UpdateTableview= #"CreatePlist/UpdateTableview";
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* Private interface definitions for update tableview
*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
#interface CreaatePlistTableViewController (private)
- (void)CreatePlistUpdateTableview:(NSNotification *)notif;
#end
#implementation CreaatePlistTableViewController
#synthesize presenterList=_presenterList;
- (void)viewDidLoad
{
[super viewDidLoad];
_presenterList=[[NSMutableArray alloc] init];
// Register observer to be called when logging out
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(CreatePlistUpdateTableview:)
name:NOTIF_CreatePlist_UpdateTableview object:nil];
NSLog(#"Presenter List: %#", _presenterList);
}
- (void)CreatePlistUpdateTableview:(NSNotification *)notif{
NSLog(#"Notification recieved");
NSLog(#"Presenter List: %#", _presenterList);
[_createPlistTableview reloadData];
}
child.h
#interface AddPresenterViewController : UITableViewController<UITextFieldDelegate,UIAlertViewDelegate>{
CreaatePlistTableViewController *crereaatePlistTableViewController;
}
#property(nonatomic,strong) CreaatePlistTableViewController *crereaatePlistTableViewController;
child.m
#synthesize crereaatePlistTableViewController=_crereaatePlistTableViewController;
//finished adding presenter
-(IBAction)finishedAddingPresenter:(id)sender{
//some xml string here
NSLog(#"final result XML:\n%#", writer.XMLString);
_crereaatePlistTableViewController=[[CreaatePlistTableViewController alloc]init];
//add object to parents view data source
[_crereaatePlistTableViewController.presenterList addObject:writer.XMLString];
//dismiss the view
[self.navigationController popViewControllerAnimated:YES];
//notify the parent view to update its tableview
[[NSNotificationCenter defaultCenter] postNotificationName:#"CreatePlist/UpdateTableview" object:nil];
}
Output
Notification recieved
Presenter List: (
)
So notification works when I click the button. But it does not pass object to nsmutablearray.
What I am doing wrong here ? How can I add an object to parent view's nsmutablearray?
It seems everything is good except your alloc of parent view object I am not that familiar with storyboard but You said you are using navigation navigation controller
so change this
_crereaatePlistTableViewController=[[CreaatePlistTableViewController alloc]init];
to
_crereaatePlistTableViewController= [self.navigationController.viewControllers objectAtIndex:0];
It may work I am not sure
You wrote this.
[_crereaatePlistTableViewController.presenterList addObject:writer.XMLString];
Do you ever initialize the array? No. Use the debugger and you will see that at this line the presenterList is nil.
Now as a point of style. Avoid using NSNotificationCenter to pass data or signaling other objects. #TheRonin gave a handy link. You should also look into some tutorials on Segues, because these are solved problems.
This is another related post that you might find interesting.
There's a little bit uncommon situation in my app, that is,
I have to reload some retain properties everytime when the view is going to appear,
the code looks like this:
// .h
#property (nonatomic, retain) NSArray *myData;
// .m
#synthesize myData;
- (void)viewWillAppear:(BOOL)animated {
... // get FetchRequest and so on
self.myData = [self.context executeFetchRequest:request error:&error]; // Line 1
[super viewWillAppear:animated];
}
- (void)viewDidUnload {
self.myData = nil;
[super viewDidUnload];
}
- (void)dealloc {
[myData release]; // Line 2
[super dealloc];
}
there are several points:
1st. as you see, the property "myData" is retain, so I think every I set some object for it, it would automatically retain that object?
2nd. I have to reload "myData" everytime the view will appear, just like the code of Line 1 above.
3rd. Since it is a retain property, I have to release it myself correctly.
Now, question is, do I correctly managed the memory without any leaking of "myData" using the codes above?
If the view would appear many times before it is dealloc, (like push in a further view in a UINavigationController and pop out for several times),
then myData would retain some object more than once, but I only release it in the dealloc for 1 once in Line 2, so is that ok?
But if I add this method the to viewController,which I think is more safe for avoiding memory leaks:
- (void)viewWillDisappear:(BOOL)animated {
self.myData = nil;
[myData release];
[super viewWillDisappear:animated];
}
- (void)dealloc {
// [myData release]; // don't release it here.
[super dealloc];
}
my app would crash after one or two times I push in and pop out the view,
So which one is really wrong?
Thanks a lot!
You are not only releasing it in Line 2, it will be also released in Line 1 when replaced as well as in viewDidUnload, so your code on top is just fine. The key is that
self.myData = anything;
is expanded to
[self->myData release];
self->myData = [anything retain];
so by assigning anything (including nil) you are already calling release implicitly. You could in fact replace Line 2 with self.myData = nil; to have never to call release since you don't have any explicit retain.
.h
#property (nonatomic, retain) NSArray *myData;
.m
#synthesize myData;
By including these lines in your code a setter and getter is created for your property myData. The setter generated at run time for objects looks something like this,
- (void)setMyData: (id)newValue
{
if (myData != newValue)
{
[myData release];
myData = newValue;
[myData retain];
}
}
The total effect is that whenever you access the property by appending self in front you are actually calling the setters and getters. So the following two lines are the exact same.
self.myData = nil;
[self setMyData:nil];
So your original code was already correct.