I don't know why this is being so difficult but I can't get this to work. Here's my basic flow:
I have a UIViewController with a subview UIView which itself has a subview UIButton. Clicking the button instantiates a new instance of a NSObject called TwitterController, creates a NSURL for the twitter feed and then hands control over to TC to do the URLConnection and serialize the data returned.
Here's the relevant code in ViewController (Pruit_Igoe is me, feel free to follow though I don't post much : D) :
- (void) getTwitter {
//load new manager
twitterManager = [TwitterController new];
[twitterManager showTwitterFeed:vTwitterFeed:self];
NSURL* twitterFeedPath = [NSURL URLWithString: #"http://api.twitter.com/1/statuses/user_timeline.json?screen_name=Pruit_Igoe"];
[twitterManager getTwitterFeed:twitterFeedPath];
//toggle the twitter view
[self toggleView:vTwitterFeed];
[self toggleView:vContactCard];
}
showTwitterFeed dumps the objects in the view vTwitterFeed (button to close the view, images, etc.)
getTwitterFeed begins the NSURLConnection process
in TwitterController, I get the twitter feed and process it here:
- (void)connectionDidFinishLoading:(NSURLConnection *)theConnection {
//do something with the data!
NSError *e = nil;
//parse the json data
NSArray *jsonArray = [NSJSONSerialization JSONObjectWithData: receivedData options: NSJSONReadingMutableContainers error: &e];
//dump it into an array
tweetArray = [[NSMutableArray alloc] init];
for(NSDictionary* thisTweetDict in jsonArray) {
NSString* tweet = [thisTweetDict objectForKey:#"text"];
[tweetArray addObject:tweet];
}
}
this all works fine, log tweetArray and all the text is there, log thisTweetDict and all the ton of data Twitter sends back is there. The problem is I want to pass tweetArray back to ViewController but I can't seem to figure out how to.
I've done the following:
Tried returning TweetArray from getTwitterFeed but it came back as null (my guess is the method returned the array before the connection had finished)
Tried to put it in UserDefaults but I keep getting null (same guess as above, but then I put it in connectionDidFinish and still null)
Tried to pass a reference to ViewController to TwitterController and then call a method in VC to pass the array to but in TwitterController I error out because it says my instance of VC doesn't recognize the selector. (It's there, I've triple checked).
I am sure this is simple and I am just being dense but could someone help me with this?
Edit: Here's how I tried to pass it back to VC:
I would pass VC to TC using this method (this is in VC)
[twitterManager showTwitterFeed:vTwitterFeed:self];
in VC.h I had a UIViewController* thisViewController
in VC.m in the showTwitterFeed:
- (void) showTwitterFeed : (UIView* ) theTwitterView : (UIViewController* ) theViewController {
thisViewController = theViewController;
//...other code to build view objects
then in
- (void)connectionDidFinishLoading:(NSURLConnection *)theConnection {
...
for(NSDictionary* thisTweetDict in jsonArray) {
NSString* tweet = [thisTweetDict objectForKey:#"text"];
[tweetArray addObject:tweet];
}
[thisViewController getTwitterFeed:tweetArray]; //<--this would error out saying selector not available
back in VC.h
- (void) getTwitterFeed : (NSArray* ) theTwitterFeed;
and in VC.m
- (void) getTwitterFeed : (NSArray* ) theTwitterFeed {
NSLog(#"%#", theTwitterFeed);
}
You can't return it from getTwitterFeed because connectionDidFinishLoading has not been called yet.
What you need to do is set up a protocol in your TwitterController and make your ViewController the delegate of the TwitterController.
Then when connectionDidFinishLoading occurs and you save the twitter information you can call the function back to your delegate (the ViewController).
Create a function called something like twitterDataReceived:(NSDictionary *)tweetDict in the TwitterController protocol and call it in the connectionDidFinishLoading function: [self.delegate twitterDataReceived:thisTweetDict]; or if you just want to send the text make it twitterTextReceived:(NSString *)theTweet and call [self.delegate twitterTextReceived:tweet]; or use an array like twitterArrayReceived:(NSArray *)tweetArray and [self.delegate twitterArrayReceived:tweetArray];, or whatever you want to send back.
If you are unfamiliar with setting up a protocol and a delegate there are many questions available which will help you out, like this one:objective-c protocol delegates
You get an unrecognized selector because thisViewController is of type UIViewController which does not have this method defined (Although its not seen from the code you posted I am absolutely sure this is the case as you would get a compilation error when assigning thisViewController = theViewController and theViewController type is UIViewController)
So change thisViewController type to your customized view controller and also change the signature of
(void) showTwitterFeed : (UIView* ) theTwitterView : (UIViewController* ) theViewController
to be :
(void) showTwitterFeed : (UIView* ) theTwitterView : (<Your custom controller type>* ) theViewController
Related
I have a TableViewController which when run, makes an instance of another class and calls json with it eg.
TableViewController;
-(void)viewDidLoad{
JSONClass *jc = [[JSONClass alloc]init];
jc.JSONClassDelegate = (id)self;
[jc view];
}
JSONClass will proceed to retrieve data from the web and once done, will send a delegate method call "JSONClassDataReceived" to tableViewController. Such as,
JSONClass;
-(void)viewDidLoad{
//codes of URL connection goes here...
NSMutableURLRequest *request = [[NSMutableURLRequest new];
NSData *responseData = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:nil];
[self performSelectorOnMainThread:#selector(fetchedData:)
withObject:responseData waitUntilDone:YES];
}
- (void)fetchedData:(NSData *)responseData {
NSMutableDictionary *data = [NSJSONSerialization
JSONObjectWithData:responseData
options:NSJSONReadingMutableContainers
error:&error];
if (JSONPromotionsLocationsDelegate && [JSONPromotionsLocationsDelegate respondsToSelector:#selector(JSONPromotionsLocationsDataReceived)]) {
[JSONPromotionsLocationsDelegate JSONPromotionsLocationsDataReceived];
}
}
TableViewController;
- (void)JSONClassDataReceived{
[tableView reloadTable];
}
After which relevant data is populated.
How do I stop JSONClass when back button is pressed on TableViewController before the delegate method JSONClassDataReceived is called on my tableViewController?
I tried
jc.JSONClassDelegate = nil;
jc = nil;
when back button is pressed, but my app crashes because JSONClass has reached JSONClassDelegate and thus cannot find the method - (void)JSONClassDataReceived due to the fact that tableViewController view no longer exist. I have also tried implement dealloc in JSONClass. None seem to work.
- (void)dealloc{
self.view = nil;
}
I have having the error EXC_BAD_ACCESS on the lines,
if (JSONPromotionsLocationsDelegate && [JSONPromotionsLocationsDelegate respondsToSelector:#selector(JSONPromotionsLocationsDataReceived)]) {
[JSONPromotionsLocationsDelegate JSONPromotionsLocationsDataReceived];
}
I seem to have fixed the crash by simply setting my delegate to weak instead of assign.
crash
#property (nonatomic, assign)id <JSONClassDelegate> JSONClassDelegate;
no crash
#property (nonatomic, weak)id <JSONClassDelegate> JSONClassDelegate;
When posting a question about a crash to Stack Overflow, it's helpful to include the crash log or stack trace to empower your peers to help you solve the problem.
The correct way to troubleshoot an EXC_BAD_ACCESS crash is to use the Zombies instrument of Instruments. This will tell you exactly what is causing this problem.
Without that information, we can only guess.
But in your case, I can take an educated guess.
Your delegate is being deallocated at some point, and after that you are attempting to use the deallocated memory. This is most likely because you have not declared the delegate as a weak reference. This is specifically mentioned at several points in the documentation.
First, declare your delegate as a weak property.
#property (nonatomic, weak) id JSONClassDelegate;
Don't forget to synthesize it. Not synthesizing properties can lead to bad things.
Now in the methods where you call your delegate you should make it a strong reference for the duration of the method. This prevents it from being deallocated while you are using it. For example:
__strong id aDelegate = [self JSONClassDelegate];
if ([JSONClassDelegate respondsToSelector:#selector(JSONPromotionsLocationsDataReceived)]) {
[JSONClassDelegate JSONPromotionsLocationsDataReceived];
}
This creates a strong local stack variable that points to your weak reference. It will be retained until it goes out of scope.
First, make sure that before calling your delegate, you do this:
if (self.delegate && [self.delegate respondsToSelector:#selector(JSONClassDataReceived)]) {
[self.delegate JSONClassDataReceived];
}
Other than that, in your JSONClass' - (void)dealloc method, you can stop the web call. If you're using ARC, make sure you don't call [super dealloc]. For the dealloc method to be called, you'll need to keep nulling out your jc object.
Hope this helps.
No, you can't (usefully) "test if an address contains a valid object". Even if you were able to grub around inside the internals of the memory allocation system and determine that your address points to a valid object, that would not necessarily mean that it was the same object that you were previously referring to: the object could have been deallocated and another object created at the same memory address.
Found from here.
I created a singleton in ios7 like this:
SharedData.h
#interface SharedData : NSObject
{
}
+ (id)sharedInstance;
#property (strong, nonatomic) NSMutableArray *list;
#end
SharedData.m
#import "SharedData.h"
#implementation SharedData
#synthesize list;
// Get the shared instance thread safe
+ (SharedData *)sharedInstance {
static dispatch_once_t once = 0;
static SharedData *sharedInstance = nil;
dispatch_once(&once, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
- (id)init {
self = [super init];
if (self) {
//initialize
list = [[NSMutableArray alloc] init];
}
return self;
}
#end
I always use this code to access this class:
SharedData *sharedData = [SharedData sharedInstance];
The problem is now when I switch the view in my viewDidLoad method the list is empty but in my viewDidAppear method everything is fine. Any ideas?
EDIT:
This is the code how I change the views:
SharedData *sharedData = [SharedData sharedInstance];
//clear feed and add new feed
[sharedData.list removeAllObjects];
[sharedData.list addObjectsFromArray:newList];
//show new gui
[self.navigationController performSegueWithIdentifier:#"goToMain" sender:self];
NOTE: I push from a normal ViewController to a TabBarController -> NavigationController -> TableViewController to display the list.
I guess you have the confusion between these two viewcontroller methods:
-(void)viewDidLoad{
//
}
&
-(void) viewDidAppear{
//
}
viewDidAppear is the method which is called each time your view changes but viewDidLoad is the method which is not necessarily called each time your view changes.
ViewDidLoad method is called when view loads for the first time, after that it doesn't get called until the views are removed/released.
P.S: I suggest you to put the breakpoint in your viewDidLoad and viewDidAppear method and feel it. Your answer lies there.
Hope this helps you alot.
Good Luck.
The problem was i created a segue which went from the button to the next view. Because of this the viewDidLoad gets earlier called than the list assigned. I just changed the segue to go from view to view.
How are you changing from one viewController to the other? Wich classes are the parents of your destination ViewController?,
If you are modifying properties of the view in the prepareForSegue method... you are forcing the view to load.
For example, you are setting the list of your singleton in prepareForSegue, but before setting the list you are modifying a property of your destination viewController. (doing something like destVC.view = XXX or destVC.viewControllers = XX if you are subclassing a UITabBarViewController...) Then you are triggering the viewDidLoad method , and it's executing before you have set the list to the correct value.
Or maybe you are seguing in two different places to the destinationViewController. And when the viewDidLoad happens, you still have not updated the list on the singleton.
Here is the transcription of the chat with the poster of the question: https://chat.stackoverflow.com/transcript/55218
I have an application where A View Controller (A)is called twice in close succession. Now each time it is called, an NSString object is created, and I need this value to be stored in an NSMutableArray that is a public property of ANOTHER View Controller (B).
In A, I create an instance of the second View Controller (B), and using that instance, add the NSString objects into the NSMutableArray which I've created as a public property. Later, when I am inside View Controller B and print the contents of the NSMutableArray property, the array is empty. Why? Here is the code that is inside View Controller A:
-(void)viewDidLoad {
ViewControllerA *aVC = [[ViewControllerA alloc] init];
if (aVC.stringArray == nil) {
aVC.stringArray = [[NSMutableArray alloc] init];
}
[aVC.stringArray addObject:#"hello"];
[aVC.stringArray addObject:#"world"];
for (NSString *wow in aVC.stringArray) {
NSLog(#"The output is: %#", wow);
}
}
Inside my View Controller B class, I have the following code:
- (IBAction)buttonAction:(UIButton *)sender {
NSLog(#"Button selected");
for (NSString *test in self.stringArray) {
NSLog(#"Here are the contents of the array %#", test);
}
}
Now the buttonAction method gets called, as I do see the line Button selected in the system output, but nothing else is printed. Why? One thing I want to ensure is that View Controller A is called twice, which means I would like to see in the output, "Hello World", "Hello World" (i.e. printed twice), and not "Hello World" printed just once.
The other thing I wish to point out is that View Controller B may not be called at all, or it may be called at a later point in time. In any case, whenever View Controller B is called, I would like to have the values inside the array available, and waiting for the user to access. How do I do this?
Your approach is not ideal, potentially leading to a memory cycle, with two objects holding strong pointers to each other.
You can instead achieve your goal in two ways;
Delegate Protocol
This method allows you to set delegates and delegate methods to pass data back and forth between view controllers
in viewControllerA.h
#protocol viewControllerADelegate <NSObject>
- (void)addStringToNSMutableArray:(NSString *)text;
#end
#interface viewControllerA : UIViewController
#property (nonatomic, weak) id <viewControllerADelegate> delegate;
in viewControllerB.m
// create viewControllerA class object
[self.viewControllerA.delegate = self];
- (void)addStringToNSMutableArray:(NSString *)text
{
[self.mutableArray addObject:text];
}
in viewControllerA.m
[self.delegate addStringToNSMutableArray:#"some text"];
Utility Classes
Alternatively you can use a utility class with publicly accessible methods (and temporary data storage). This allows both viewController classes to access a shared data store, also if you use class methods, you don't even need to instantiate the utility class.
in XYZUtilities.h
#import <Foundation/Foundation.h>
#interface XYZUtilities : NSObject
+ (void)addStringToNSMutableArray;
#property (strong, nonatomic) NSMutableArray *array;
#end
in XYZUtilities.m
+ (void)addStringToNSMutableArray
{
NSString *result = #"some text";
[self.array addObject:result];
}
+ (NSArray)getArrayContents
{
return self.array;
}
in viewControllerA.m
NSString *stringFromObject = [XYZUtilities addStringToNSMutableArray];
in viewControllerB.m
self.mutableArray = [[NSMutableArray alloc] initWithArray:[XYZUtilities getArrayContents]];
I'm not sure what kind of a design pattern you are trying to follow but from the looks of it IMHO that's not a very safe one. However, there are many, many ways this could be accomplished.
One thing though, you said that View Controller B may never get allocated and if it is alloc-ed, it will be down the road. So you can't set a value/property on an object that's never been created.
Since you already aren't really following traditional patterns, you could make a static NSMutableArray variable that is declared in the .m of your View Controller B Class and then expose it via class methods.
So it would look like this:
viewControllerB.h
+(void)addStringToPublicArray:(NSString *)string;
viewContrllerB.m
static NSMutableArray *publicStrings = nil;
+(void)addStringToPublicArray:(NSString *)string{
if (publicStrings == nil){
publicStrings = [[NSMutableArray alloc]init];
}
if (string != nil){
[publicStrings addObject:string];
}
}
Then it would be truly public. All instances of view controller B will have access to it. This, of course is not a traditional or recommended way of doing it—I'm sure that you will have many replies pointing that out ;).
Another idea would be to use a singleton class and store the values in there. Then, when or if view controller B is ever created, you can access them from there.
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.
I am wondering what the correct way is to make a copy of an object defined in the app delegate or a singleton object. In short, I am making an app which requires a user to login. This login view is just a modal view controller on top of the 'real' app, which consists of a tabbarcontroller, plus some tableview controllers. After a successful login, there is send a data request to a remote server, and the modal view controller is dismissed, revealing the tabbar controller and table views holding the XML data. To parse the incoming data, I have created a singleton object named DataParser, which has interface
...
#interface DataParser : NSObject {
// Data objects that hold the data obtained from XML files
NSMutableDictionary *personnel;
NSMutableDictionary *schedule;
NSMutableDictionary *today;
}
#property (nonatomic, retain) NSMutableDictionary *personnel;
#property (nonatomic, retain) NSMutableDictionary *schedule;
#property (nonatomic, retain) NSMutableDictionary *today;
...
Now in these dictionaries I store (mutable) dictionaries and arrays holding NSString objects with the parsed XML data. Since I do not want to modify these original objects holding the parsed data (that is to say, I only want to modify them at the login stage, but not in any of the tableview controllers), I am creating a new dictionary object which holds a copy of the content of one of the dictionaries above in each tableview controller. So for instance, in the loadView of a view controller called ScheduleViewController I have
...
#interface ScheduleViewController : UITableViewController {
NSDictionary *copyOfSchedule;
}
#property (nonatomic, retain) NSDictionary *copyOfSchedule;
...
#end
#implementation ScheduleViewController
#synthesize copyOfSchedule;
- (void)loadView {
[super loadView];
DataParser *sharedSingleton = [DataParser sharedInstance];
self.copyOfSchedule = [NSDictionary dictionaryWithDictionary:sharedSingleton.schedule];
}
...
Now this seems to work fine. The only difficulty arises however, when the user 'logs out', which entails popping the login modal view controller back on the stack. When the user presses the login button again, then a new XML data request is send to the server and the dictionaries in the singleton object get refreshed with the (new) data (I check if they contain any data, if so I call removeAllObjects before filling them up again with newly parsed data). At this point the dictionaries in all view controllers should be updated too, however I am not quite sure how to go about this the right way. I have noticed that loadView is not always called again in this case and so to this end I have added the same code as above in loadView to every viewWillAppear method. After navigating back and forth between the different views or navigating back and forth between child views of a tableview a couple of times, I receive an EXC_BAD_ACCESS error however. I suspect this has to do with not properly retaining the copies of the original dictionaries, but I don't seem to be able to find a solution around this. Instead of using dictionaryWithDictionary, which I suspect is not the right way to go anyway, I also tried a different approach, where instead of using objects of type NSDictionary in ScheduleViewController I use NSMutableDictionary. So:
...
#interface ScheduleViewController : UITableViewController {
NSMutableDictionary *copyOfSchedule;
}
#property (nonatomic, retain) NSMutableDictionary *copyOfSchedule;
...
#end
#implementation ScheduleViewController
#synthesize copyOfSchedule;
- (void)loadView {
[super loadView];
DataParser *sharedSingleton = [DataParser sharedInstance];
self.copyOfSchedule = [[NSMutableDictionary alloc] initWithDictionary:sharedSingleton.schedule];
}
- (void)viewWillAppear {
DataParser *sharedSingleton = [DataParser sharedInstance];
[self.copyOfSchedule removeAllObjects];
[self.copyOfSchedule addEntriesFromDictionary:sharedSingleton.schedule];
[self.tableView reloadData];
}
...
But this doesn't get rid of the EXC_BAD_ACCESS errors. To make a very long story short: what would be the best way to go about making independent copies of objects defined in a singleton object or app delegate and which can be dynamically updated at request? Since I am already rather into the project and lots is going on, I realize that my question may be a bit vague. Nonetheless I hope there is somebody who could enlighten me somehow.
Deep copies are often made recursively. One way to do it would be to add -deepCopy methods to NSDictionary and NSArray. The dictionary version might go like this:
- (NSDictionary*)deepCopy
{
NSMutableDictionary *temp = [self mutableCopy];
for (id key in temp) {
id item = [temp objectForKey:key];
if ([item respondsToSelector:#sel(deepCopy)] {
// handle deep-copyable items, i.e. dictionaries and arrays
[temp setObject:[item deepCopy] forKey:key]
}
else if ([item respondsToSelector:#(copy)]) {
// most data objects implement NSCopyable, so will be handled here
[temp setObject:[item copy] forKey:key];
}
else {
// handle un-copyable items here, maybe throw an exception
}
}
NSDictionary *newDict = [[temp copy] autorelease];
[temp release]
return newDict;
}
I haven't tested that, so be a little careful. You'll want to do something similar for NSArray.
Note that views are not copyable.
It is quite a typical pattern that you build an array or dictionary with some code, so clearly it must be mutable while you add bits to it, and when you're done you don't want it ever to change. To do this:
Have a property like
#property (...) NSArray* myArray;
When you calculate the contents of myArray, use a mutable array to build it, like
NSMutableArray* myMutableArray = [NSMutableArray array];
When you're done building the array, just use
self.myArray = [NSArray arrayWithArry:myMutableArray];