I'm using a singleton called CSAppData to store data for my iPhone app. I'm storing an object called CSInbox in the singleton. When I logout of my app, I want to clear the data for that object.
Here is my singleton code, including the method for clearing the data:
- (id)init {
self = [super init];
if (self)
{
self.inbox = [[CSInbox alloc] init];
}
return self;
}
+ (CSAppData *)appData {
static CSAppData * appDataInstance;
#synchronized(self) {
if(!appDataInstance) {
appDataInstance = [[CSAppData alloc] init];
}
}
return appDataInstance;
}
+(void) clearData {
CSAppData *appData = [CSAppData appData];
appData.inbox = [[CSInbox alloc] init];
}
However, in one of my view controllers, in the initWithCoder method, I'm storing the inbox variable:
-(id) initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if(self) {
self.inbox = [[CSAppData appData] inbox];
}
return self;
}
So, when the app logs out and the clearData method is called, the view controller is still pointing to the old CSInbox object. And even though I am initializing a new view controller and setting it to the root view controller (in the AppDelegate), like this:
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"MainStoryboard" bundle:nil];
MainTabControllerViewController *viewController = (MainTabControllerViewController *)[storyboard instantiateViewControllerWithIdentifier:#"mainView"];
[self.window setRootViewController:viewController];
The one child view controller that has the CSInbox is never reinitialized, and is still pointing to that old CSInbox object. (I'm not sure why this is happening.)
So, what is the best way to solve this?
Change the clearData method in the singleton to just reset the properties of the CSInbox object, rather than alloc and init and new one?
Move the self.inbox = [[CSAppData alloc] init]; to the viewDidLoad in the view controller class so it gets set properly upon the second login?
Change the logout function in the AppDelegate so that the root view controller and all other view controllers are released, so they will reinitialize upon the second login?
I'm leaning toward #1 or #3...
As requested, here is CSInbox.h:
#interface CSInbox : NSObject
#property (nonatomic,strong) NSMutableArray *threads;
#property (nonatomic, assign) NSInteger newCount;
#property (nonatomic,strong) NSDate *lastUpdate;
-(void) setThreadsFromJSON:(NSDictionary *)json;
#end
And here is CSInboxViewController.h:
#interface CSInboxViewController : UITableViewController <UITableViewDataSource, UITableViewDelegate, CSThreadViewControllerDelegate>
#property (strong, nonatomic) IBOutlet UITableView *inboxTableView;
#property (strong,nonatomic) CSInbox *inbox;
#end
And CSAppData.h:
#interface CSAppData : NSObject {
CSInbox *inbox;
}
#property(nonatomic,strong) CSInbox *inbox;
+ (CSAppData *)appData;
+ (void)clearData;
#end
I think the answer lies not in destroying the singleton object and recreating it, but to actually clear the instance variables within that singleton object.
You don't show the declaration of [CSAppData inbox], but if it's an NSMutableArray, for example, then you can clear that, and any existing references to the singleton object can remain:
+(void) clearData {
CSAppData *appData = [CSAppData appData];
[appData.inbox removeAllObjects];
}
One way to handle this, complying with the spirit of using a singleton, is having your view controllers access directly your singleton inbox, i.e.: [CSAppData appData].inbox instead of self.inbox. This is a bit wordier, but it would "magically" fix your issue.
If that is not acceptable to you, I would go with option #1 of those you list. Even better, I would make the inbox in the singleton a singleton itself, or make sure it is never replaced by another instance.
EDIT:
Another approach you have, is using KVO in your controller so that it gets notified when the inbox object has changed. Don't know if it is quite worth it, but could be used.
Related
I have an NSMutableArray declared as property in .h and initialized in viewDidLoad in my SPOCVC .m (UIViewController)...
#property (strong, nonatomic) NSMutableArray* SPOCTrackList;
in viewDidLoad
if ([self SPOCTrackList] == nil) {
self.SPOCTrackList = [[NSMutableArray alloc]init];
NSLog(#"SPOTTrackList INITIALIZED");
}
In a separate VC, I'm trying to pass/addObject to SPOCTracklist...
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
SCTrack* selectedTrack = self.trackList[indexPath.row];
[[[SPOCVC sharedInstance]SPOCTrackList]addObject:selectedTrack];
NSLog(#"%lu", (unsigned long)[[[SPOCVC sharedInstance]SPOCTrackList]count]);
So my NSMutableArray is initialized and I can add dummy objects, but why can't I pass it from another VC using singleton or anything, such as...
SPOCVC* spocVC = self.tabBarController.viewControllers[2];
[spocVC.SPOCTrackList addObject:selectedTrack];
Thanks for pointing me in the right direction.
View controllers are only intended to be around while they are on screen. They are not a place to store data. Generally when one view controller talks directly to another view controller that it didn't create, you're doing something wrong.
Move SPOCTrackList to your model and have both view controllers talk to it rather than to each other.
There should never be a "sharedInstance" on a view controller. That's a sure sign that you're abusing the view controller as the model.
What's probably happening in your particular case is that viewDidLoad is running on a completely different SPOCVC than your sharedInstance.
why not use appdelegate to handle this
appdelegate.h
//add property to hold the reference
#property (nonatomic, copy) NSMutableArray *referenceArray;
//share the app delegate
+(AppDelegate *)sharedAppDelegate;
#end
in appdelegate.m
//synthesize the property
#synthesize referenceArray;
//return the actual delegate
+(AppDelegate *)sharedAppDelegate {return (AppDelegate *)[UIApplication sharedApplication].delegate;}
in viewdidload method
//add the delegate
import "appdelegate.h"
//init the array
self.SPOCTrackList = [[NSMutableArray alloc]init];
//Add reference
[AppDelegate sharedAppDelegate].referenceArray = self.SPOCTrackList;
and add anywhere like this
import "appdelegate.h"
[[AppDelegate sharedAppDelegate].referenceArray addobject:object];
I execute this code in my applicationDidBecomeActive callback.
- (void)applicationDidBecomeActive:(UIApplication *)application{
[self showMainWindow];
}
- (void) showMainWindow{
PushNotificationTabBarController *pushNotificationTabBarController = [[PushNotificationTabBarController alloc] initWithNibName:#"PushNotificationTabBarController" bundle:nil];
self.viewDeckController.centerController =
pushNotificationTabBarController; // registered for Notification via addObserver...
}
The setter is defined like this:
#property (nonatomic, strong) IBOutlet UIViewController* centerController;
IIViewDeckController property in my AppDelegate:
#property (strong, nonatomic) IIViewDeckController *viewDeckController;
I found out that I have 2 PushNotificationTabBarController objects when I close and re-enter the app, with no reference to one of them, like a memory leak. However I dont understand why this code is causing it because to me it seems ok. I'm using ARC.
Before setting pushNotificationTabBarController check for self.viewDeckController.centerController object. If it exists, release and assigned to nil and assign new object of 'pushNotificationTabBarController'
id controller = self.viewDeckController.centerController;
if (controller) {
[controller release];
controller = nil;
}
self.viewDeckController.centerController = pushNotificationTabBarController;
Just started xcode 5 and xctest. How do I test that a view loads on button press. I have programatically added method that gets called when the rightBarButtonItem is clicked
action:#selector(onSettingsButton)
and in onSettingsButton
-(void) onSettingsButton{
SettingsViewController *svc = [[SettingsViewController alloc] init];
[self.navigationController pushViewController:svc animated:YES];
}
How to write xctest to ensure SettingsViewController brings up the Settings view? Thank you.
You need an interaction test — that is, a test that checks interactions between objects. In this case, you want to test that -pushViewController:animated: is called on the navigation controller with a SettingsViewController. So we want to put a mock object into self.navigationController which we can ask, "Were you called as expected?"
I'll assume a simple name for the class: MyView.
The way I'd do this by hand is to Subclass and Override navigationController. So in my test code, I'd do something like this:
#interface TestableMyView : MyView
#property (nonatomic, strong) id mockNavigationController;
#end
#implementation TestableMyView
- (UINavigationController *)navigationController
{
return mockNavigationController;
}
#end
Now instead of creating a MyView, the test will create a TestableMyView and set its mockNavigationController property.
This mock can be anything, as long as it responds to -pushViewController:animated: and records the arguments. Here's a simple example, by hand:
#interface MockNavigationController : NSObject
#property (nonatomic) int pushViewControllerCount;
#property (nonatomic, strong) UIViewController *pushedViewController;
#property (nonatomic) BOOL wasPushViewControllerAnimated;
#end
#implementation MockNavigationController
- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated
{
self.pushViewControllerCount += 1;
self.pushedViewController = viewController;
self.wasPushViewControllerAnimated = animated;
}
#end
Finally, here's a test:
- (void)testOnSettingsButton_ShouldPushSettingsViewController
{
// given
MockNavigationController *mockNav = [[MockNavigationController alloc] init];
TestableMyView *sut = [[TestableMyView alloc] init];
sut.mockNavigationController = mockNav;
// when
[sut onSettingsButton];
// then
XCTAssertEquals(1, mockNav.pushViewControllerCount);
XCTAssertTrue([mockNav.pushedViewController isKindOfClass:[SettingsViewController class]]);
}
These things can be simplified by using mock object frameworks such as OCMock, OCMockito, or Kiwi's mocking. But I think it helps to start by hand first, so that you understand the concepts. Then choose the tools that help. And if you know how to do it by hand, you'll never say, "Mocking framework X doesn't do what I need! I'm stuck!"
Found one way. Maybe there are others ..
- (void)testSettingsViewShowsWhenSettingsButtonIsClicked{
[self.tipViewController onSettingsButton];
id temp = self.tipViewController.navigationController.visibleViewController;
XCTAssertEqual([temp class], [SettingsViewController class], #"Current controller should be Settings view controller");
}
First call the onSettingsButton, which is the same as clicking the button, but not really. Maybe it's okay for this simple test case? How to simulate the actual press?
Then get the current view from the tipviewcontoller which is the rootview of the app and check that it is a SettingsViewController.
Let's say I have a tabbed application in which each tab displays different categories of similar data (eg. Tab1: "Cat Pictures", Tab2: "Dog Pictures", Tab3: "Horse Pictures", etc...).
The view controllers for each tab present data identically. The only differences are the title property of the vc and the parameters of the method that fetches the data. So what are the pros and cons of the following options:
Create one ViewController master with the (limited) branching
logic for each category.
Create a ViewController
superclass and several subclasses for the different category types.
EDIT
So to clarify "the parameters of the method that fetches the data": the fetch method could look like:
-(void)fetchDataForType:(NSString*)type {
if ([type isEqual:#"cat"])
// fetch cat pics
if ([type isEqual:#"dog"])
// fetch dog pics
...
}
In your case I wouldn't even subclass. Create one class with properties you would change and instantiate with different properties values.
Rough example:
#interface ViewController : UIViewController
#property (retain, nonatomic) NSString *title;
- (void)getJSONWithParams:(NSDictionary *)params;
#end
Instances:
ViewController *controller1 = [[ViewController alloc] init];
controller1.title = #"First";
NSDictionary *params1 = #{#"url":#"http://graph.facebook.com", #"query":#"q=somethingInFacebook"};
[self.navigationController pushViewController:controller1];
[controller1 getJSONWithParams:params1];
ViewController *controller2 = [[ViewController alloc] init];
controller2.title = #"Second";
NSDictionary *params2 = #{#"url":#"http://api.twitter,com", #"query":#"q=somethingInTwitter"};
[self.navigationController pushViewController:controller2];
[controller2 getJSONWithParams:params2];
Short term project:
I would keep one UIViewController where the data source would change (in this case the kind of pictures)
Long term project:
I would probably sub-class, because you might need to add smaller differences after and creating the sub-classes now will save you some time later. Even in a short term project, you can use this approach. Since the data source is the only that changes, the sub-classes would be mostly empty.
Create AbstractViewController holds all common Information and has all methods. Create several ViewControllers as you wish and they inherit from the super and override functions with their behavior.
I believe a single UIViewController subclass can provide everything you require. You just need to provide the differences required to support each different type of picture. Let's assume those differences are just the description and type of picture it shows:
MyViewController.h:
#MyViewController : UIViewController
#property (strong, nonatomic) NSString *description;
#property (strong, nomatomic) NSString *type;
// Designated initializer
- (id)initWithNibName:(NSString *)nibName
bundle:(NSBundle *)nibBundle
description:(NSString *)description
type:(NSString *)type;
#end
MyViewController.m:
#implementation MyViewController
- (id)initWithNibName:(NSString *)nibName
bundle:(NSBundle *)nibBundle
description:(NSString *)description
type:(NSString *)type
{
self = [super initWithNibName:nibName bundle:nibBundle];
if (self != nil)
{
self.description = description;
self.type = type;
}
return self;
}
- (NSArray *)fetchPics
{
NSMutableArray *pics = [[NSMutableArray alloc] init];
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:#"http://www.somewhere.com/fetchPics?type=%#", self.type]];
...
return pics;
}
#end
And it should then just be a case of creating the view controllers whereever necessary:
MyViewController *catVC = [[MyViewController alloc] initWithNibNamed:#"Something" bundle:nil description:#"Cats" type:#"cat"];
...
I wouldn't subclass at all. You create one view controller with properties that you can change at set up, and create an instance per tab. There is nothing stopping you create multiple instances of a view controller. You don't need one view controller for one view hierarchy.
I´m having problems declarating my own delegate. Well...thats not exactly true: i have it declarated and, when i build the project, the compiler reports no issues. I declarated it in this way:
I made a file (enviarDatos.h) for declare the protocol:
#protocol enviarDatos <NSObject>
- (void)addItemViewController:(NSMutableArray *)item;
#end
In the Vista2.h (ViewController) file I imported the file enviarDatos.h and declared a property:
#property (nonatomic, weak) id <enviarDatos> delegare;
In the Vista2.m (ViewController) file I use the protocol method:
#interface ViewController : UIViewController <enviarDatos> {
And, finally, in the ViewController.m file I implement the delegates method:
- (void)addItemViewController:(NSMutableArray *)ar {
origen = ar;
}
Does anyone see something wrong? the code of the last function its never executing.
Thanks for your help.
EDIT:
What i need is to change an array in ViewController from Vista2 (another viewcontroller)
Then create delegate property in next view(child view) & set it to self in parent view while pushing or showing child view.
ParentView.m
1.Implement protocol methods
- (void)addItemViewController:(NSMutableArray *)ar
{
origen = ar;
}
2.While showing child view
ChildViewController *child = [[ChildViewController alloc] init];
child.delegate = self;
//present child view
ChildView.h
#property (nonatomic, weak) id <enviarDatos> delegare;
ChildView.m
-(void) anyMethod
{
if([self.delegate respondsToSelector:#selector(addItemViewController:)])
{
[self.delegate addItemViewController:mutableArray];
}
}
Ah, it looks like you are declaring the delegate property in the wrong place.
You should declare the property delegate in enviarDatos.h.
#property (nonatomic, weak) id <enviarDatos> delegate;
Then in Vista2.m you will do something like this...
EnviarDatos *myObject = [[EnviarDatos alloc] init];
myObject.delegate = self;
This then sets up the EnviarDatos object and assigns the Vista2 object as the delegate.
Now, in EnviarDatos.m you can run...
[self.delegate addItemViewController:someObjectArray];
And this will then run that code in the Vista2 object.
Delegates are used for calling back to objects that create them (or some other objects). If you create an object and then want to run a method in it then you won't need a delegate.
Can you say at what condition addItemViewController is invoked?
You seem to be on the right track, but are you sure you are setting the delegate as
[yourObject setDelegate: self];
Have you tried debugging it? Does the debugger pause at addItemViewController if you set a breakpoint there? Can you confirm the delegate is not null inside the method? I may post some code but your seems to be right except for the assigning of delegate, I think you should check it.