Object released too soon using ARC on iOS 5? - iphone

I created a class AppSettings containing booleans:
#import <Foundation/Foundation.h>
#interface AppSettings : NSObject{
bool bip10secCountdown;
bool jv10secCountdown;
bool jv30secAlert;
bool jv1minAlert;
bool jvp5minAlert;
}
#property bool bip10secCountdown;
#property bool jv10secCountdown;
#property bool jv30secAlert;
#property bool jv1minAlert;
#property bool jv5minAlert;
#end
And the implementation:
#import "AppSettings.h"
#implementation AppSettings
#synthesize bip10secCountdown, jv10secCountdown, jv30secAlert, jv1minAlert, jv5minAlert;
- (id)init
{
self = [super init];
if (self){
}
return self;
}
#end
Then I'm trying to use this class in my main class but after initializing the object in the viewDidLoad, when I want to use it again it appears as null.. So I guess it's released too early. I'm using ARC so I don't manually manage the memory. Am I doing something wrong ?
The declaration in the main class:
AppSettings *appSettings;
}
#property(nonatomic)bool activated;
#property (nonatomic, strong) AppSettings *appSettings;
And the implementation:
#synthetize appSettings
...
- (void)viewDidLoad
{
// Initialize the model
self.appSettings = [[AppSettings alloc]init];
NSLog(#"appSettings = %#",self.appSettings);
The first output is OK.
But then when I try to access appSettings from another method in the Main class, the appSettings is (null)
Thank you for your help.

The view has most likely unloaded, or your view controller has been released. Try moving the initialization code to the method where your view controller is initiated(- (id)initWithNibName: bundle:)

Instead of:
#property (nonatomic, strong) AppSettings *appSettings;
Try:
#property (nonatomic, retain) AppSettings *appSettings;
And I'd recommend to make your AppSettings as a Singleton-class.

When you say you initialise the view contoller in AppDelegate I presume you mean MyViewController *theView = [[MyViewController alloc] init]; or something like that. That will be your first view and you should initialise AppSettings in viewDidLoad of MyViewController, not the AppDelegate.
demon9733 suggestion regarding making AppSettings a singleton is a good one as you will be able to easily access your settings from anywhere in the app.
Also verify whether ARC is being used for MyViewController (or whatever it might be)

Related

Overridden properties from superclass

I have a superclass of UIViewController - MasterViewController which declares a property called itemsViewController. This declares a method called from the MasterViewController, and is wired up via a storyboard in IB.
I have a subclass of MasterViewController which redeclares this property as a specific iPad version, but I can't access the redeclared property from the parent class.
MasterViewController
#interface MasterViewController : UIViewController {
}
#property (nonatomic, strong) IBOutlet ItemsViewController *itemsViewController;
#end
#implementation MasterViewController
#synthesize itemsViewController;
-(void)viewDidLoad {
// I can access itemsViewController in viewDidLoad.
}
#end
MasterViewController_iPad
#interface MasterViewController_iPad : MasterViewController {
IBOutlet ItemsViewController_iPad *_itemsViewController;
}
#property (nonatomic, strong) IBOutlet ItemsViewController_iPad *itemsViewController;
#end
#implementation MasterViewController_iPad
#synthesize itemsViewController = _itemsViewController;
-(void)viewDidLoad {
[super viewDidLoad];
// when I call super viewDidLoad, itemsViewController is nil, as though the property hasn't been overriden
// _itemsViewController is not nil in viewDidLoad.
}
#end
Am I misunderstanding the way property inheritance works in Objective-C?
You can't change the type signature of a method when you override a superclass method.
MasterViewController has these methods:
(void)setItemsViewController:(ItemsViewController *)foo
(ItemsViewController *)itemsViewController
But you're trying to give MasterViewController_iPad these methods:
(void)setItemsViewController:(ItemsViewController_iPad *)foo
(ItemsViewController_iPad *)itemsViewController
Which you can't do: you can't overload the same method name but have different types for the arguments.
If ItemsViewController_iPad is a subclass of ItemsViewController, a quick solution would be to keep the same signature as in MasterViewController but simply use an ItemsViewController_iPad when you set the property.
You can use category if you'd like to override property. Here is example:
I have PDFFileChooserViewController with PDFFileModel and PDFFilesDataSource and some logic related to this properties.
#class PDFFileModel, PDFFilesDataSource;
#interface PDFFileChooserViewController : UIViewController
#property (nonatomic, strong) PDFFileModel* selectedModel;
#property (nonatomic, strong) PDFFilesDataSource*dataSource;
#end
Then I'd like to add specific ViewController for choosing files from Dropbox but my model have some additional fields for example dropboxPath and my DropboxDataSource gets files using another way. So I decided to create category and override this properties:
#import "PDFFileChooserViewController.h"
#class DropboxFileModel,DropboxDataSource;
#interface DropboxViewController : PDFFileChooserViewController
#end
#interface DropboxViewController (ModelCategory)
#property(nonatomic, strong) DropboxFileModel* selectedModel;
#property(nonatomic, strong) DropboxDataSource* dataSource;
#end
Notice that this category will be visible inside DropboxViewController only where I can manipulate with that properties but another classes see only super class interface

Difficulty accessing objects in another view controller

I'm setting a string in a view controller called ViewController and trying to access it somewhere else. This is the code:
ViewController.h
NSString *string;
...
#property (retain) NSString *string;
ViewController.m
#synthesize string;
...
-(void)viewDidLoad {
...
string = #"Test";
}
OtherViewController.m
#import "ViewController.h"
...
-(void)viewDidLoad {
ViewController *vc;
vc = [[ViewController alloc] init];
NSLog(#"String: %#", vc.string);
}
However, the log is showing: String: (null). What am I doing incorrectly? Thanks.
The viewDidLoad of ViewController is only called when the view is loaded. The view is lazily loaded when required e.g. when a call to vc.view is made.
I'm not sure what you are trying to achieve but this certainly seems like a code smell to me.
As #Fscheidl points out you are creating a new instance and not accessing an existing instance so this may add to your problem. I still believe your main issue is that you assume viewDidLoad is being called just by creating the viewController, which is not the case
edit : it doesn't necessarily need to be an NSObject class, if you want to, you could also do this on your viewController class, just be sure to also include
-(id)init
on your header
---- end of edit
if you're trying to make a class that's accessible to another view controller, why not try NSObject instead of view controller (considering you only need to take that string value)
for instance, lets call that viewController class "global" class
so at global.h, you put up
#import <Foundation/Foundation.h>
#interface GlobalVar : NSObject
#property (nonatomic, strong) NSString *myString;
-(id)init;
#end
and then, at global.m you put up
#import "GlobalVar.h"
#implementation GlobalVar
#synthesize myString;
-(id)init
{
self = [super init];
if(self)
{
myString = [[NSString alloc]initWithFormat:#"the String"];
}
return self;
}
#end
after this, everytime you need to access the "myString" object that contained in global class, you could put up
at header :
#import "GlobalVar.h"
...
...
#property (nonatomic, strong) GlobalVar *globalVar;
at implementation file :
#synthesize globalVar;
...
...
self.globalVar = [[GlobalVar alloc]init];
NSString *theString = globalVar.myString;
NSLog(#"content of my string is : %#",theString);
there you go ;)
You do create a new instance of ViewController by calling [[ViewController alloc] init]; This instance hasn't had string even set. You have to access that exact instance of ViewController.
If you create the instance of OtherViewController directly from ViewController, you can add the following to OtherViewController.h:
#import "ViewController.h"
#property (nonatomic, retain) ViewController *previousViewController
When creating the OtherViewController, you can then set:
//alloc and init instance of OtherViewController
myOtherViewController.previousViewController = self;
In your viewDidLoad: method, you can then access your string as follows:
NSLog(#"String: %#", previousViewController.string);

How to obtain UITextFields from a singleton

I need just four parameters to drive the calculations in three view controllers. The parameters come from four UITextFields in the first view. I'm not using global variables but have developed a singleton class to enable the parameters to be available to each controller. I can reference the variables OK in the controllers but I don't know how to initialize them. I've obtained the variables from the first viewcontroller through a windows I developed with IB but can't seem to find a way to get these initialized in the singleton.
Help and guidance gratefully appreciated.
//
// GlobalParameters.h
// ProjectEstimator
//
// This is a SINGLETON class used to handle global parameters for use in the various view controllers.
//
//
//
// Created by Frank Williamson on 10/06/2010.
//
#import <Foundation/Foundation.h>
#interface GlobalParameters : NSObject {
// Place any "global" variables here
// float *processes;
// float *entities;
// float *transactions;
// float *users;
IBOutlet UITextField *noOfProcesses;
IBOutlet UITextField *noOfEntityClusters;
IBOutlet UITextField *noOfTransactions;
IBOutlet UITextField *noOfUserArea;
}
#property (retain, nonatomic) UITextField *noOfProcesses;
#property (retain, nonatomic) UITextField *noOfEntityClusters;
#property (retain, nonatomic) UITextField *noOfTransactions;
#property (retain, nonatomic) UITextField *noOfUserArea;
// message from which our instance is obtained
+ (GlobalParameters *)sharedInstance;
#end
//
// GlobalParameters.m
// ProjectEstimator Singleton for handling (global) parameters.
//
// Created by Frank Williamson on 10/06/2010.
//
#import "GlobalParameters.h"
#implementation GlobalParameters;
#synthesize noOfProcesses;
#synthesize noOfEntityClusters;
#synthesize noOfTransactions;
#synthesize noOfUserArea;
+ (GlobalParameters *)sharedInstance{
// the instance of this class is stored here
static GlobalParameters *noOfProcesses = nil;
static GlobalParameters *noOfEntityClusters = nil;
static GlobalParameters *noOfTransactions = nil;
static GlobalParameters *noOfUserArea = nil;
// check to see if an instance already exists
if (nil == noOfProcesses) {
noOfProcesses = [[[self class] alloc] init];
// **How to I initialize UITextFields from a ViewController in here??**
}
// return the instance of this class
return noOfProcesses;
if (nil == noOfEntityClusters) {
noOfEntityClusters = [[[self class] alloc] init];
// **How to I initialize UITextFields from a ViewController in here??**
}
// return the instance of this class
return noOfEntityClusters;
if (nil == noOfTransactions) {
noOfTransactions = [[[self class] alloc] init];
// **How to I initialize UITextFields from a ViewController in here??**
}
// return the instance of this class
return noOfTransactions;
if (nil == noOfUserArea) {
noOfUserArea = [[[self class] alloc] init];
// **How to I initialize UITextFields from a ViewController in here??**
}
// return the instance of this class
return noOfUserArea;
}
#end
There's a number of things here I would change. First, I wouldn't create a "Globals" class. Second, if I did, would never store UIView objects in class designed to be a "model". Third, if I absolutely needed to implement a singleton, I would follow the design pattern for Cocoa.
I need just four parameters to drive the calculations in three view controllers.
And at that point I recommend you throw out this class entirely and re-think your design problem. Is it simply that you need to store four NSString parameters (or NSNumbers or NSIntegers or floats or...)? If so, it's complete overkill to store the parameters inside a view class. Just create a simple class to store the parameters, whose header would look something like:
#interface MyParameters : NSObject
{
NSString* someString;
NSNumber* someNumber;
NSInteger someInteger;
float someFloat;
}
#property (nonatomic, retain) NSString* someString;
#property (nonatomic, retain) NSNumber* someNumber;
#property (nonatomic, assign) NSInteger someInteger;
#property (nonatomic, assign) float someFloat;
#end
I would instantiate an object of this class either in my application delegate or in my root view controller, and then I would pass it along to any other view controllers that needed access to it. Finally, I would use Key-Value Observing so every view controller that needs it will get automatic updates to any changes to the parameters.

How can I simply change a class variable from another class in ObjectiveC?

I simply want to change a variable of an object from another class. I can compile without a problem, but my variable always is set to 'null'.
I used the following code:
Object.h:
#interface Object : NSObject {
//...
NSString *color;
//...
}
#property(nonatomic, retain) NSString* color;
+ (id)Object;
- (void)setColor:(NSString*)col;
- (NSString*)getColor;
#end
Object.m:
+(id)Object{
return [[[Object alloc] init] autorelease];
}
- (void)setColor:(NSString*)col {
self.color = col;
}
- (NSString*)getColor {
return self.color;
}
MyViewController.h
#import "Object.h"
#interface ClassesTestViewController : UIViewController {
Object *myObject;
UILabel *label1;
}
#property UILabel *label1;
#property (assign) Object *myObject;
#end
MyViewController.m:
#import "Object.h"
#implementation MyViewController
#synthesize myObject;
- (void)viewDidLoad {
[myObject setColor:#"red"];
NSLog(#"Color = %#", [myObject getColor]);
[super viewDidLoad];
}
The NSLog message is always Color = (null)
I tried many different ways to solve this problem, but no success.
Any help would be appreciated.
Thanks for the help so far.
I modified the code as follow, but it still doesn't work as it should.
MyViewController.h:
#import <UIKit/UIKit.h>
#import "Object.h"
#interface MyViewController : UIViewController {
Object *myObject;
}
#property (nonatomic, retain) Object *myObject;
#end
MyViewController.m:
#import "MyViewController.h"
#import "Object.h"
#implementation MyViewController
#synthesize myObject;
- (void)viewDidLoad {
Object *myObject = [Object new];
myObject = 0;
[myObject setColor:#"red"];
NSLog(#"color = %#", myObject.color);
[super viewDidLoad];
}
If I do it like this, NSLog returns color = null (and I think myObject is only visible in viewDidLoad). How can declare myObject and make it visible in MyViewController?
I stripped down my Object class to
Object.h:
#interface Object : NSObject {
NSString *color;
}
#property(nonatomic, retain) NSString *color;
#end
Object.m:
#import "Object.h"
#implementation Object
#synthesize color;
#end
I wasn't able to define an object myObject in ViewDidLoad so that I can access its properties from the whole ViewController class? What did I miss?
Side question: Why do I have to set myObject to 0?
You're declaring a property, then explicitly declaring the accessors in Object.h. You only need to do one or the other - they mean the same thing (well, almost - you'll have color instead of getColor)
To implement the property in Object.m you should use #synthesize color. The explicit implementations, again, are then redundant (unless they do anything extra).
The explicit setColor implementation in Object.m is calling the property - which you are implementing explicitly, so I would have expected you to get an infinite recursion here.
MyViewController.m should probably synthesize label1, since you declare the property in the header (although it's not being used in your snippet).
[myObject getColor] is calling the color property, which you declared but did not synthesize. If you had explicitly implemented it as color it would have picked that up - but it won't match getColor (which is fortunately as that would have led to an infinite recursion again.
I don't see anywhere where you create your myObject instance. If you don't it will be nil and methods called on it (including property accesses) will return 0 or nil.
I suspect (6) is the cause of your issue, but the others need to be addressed too. Make sure you read up on property syntax.

How do I share an object between UIViewControllers on iPhone?

My application is a tab bar application, with a separate view controller for each tab.
I have an object in my first view controller (A) which contains all my stored application data (Please ignore NSUserDefaults for this) which needs to be accessed by the second view controller (B) when I press a button on it. How can I achieve this in the best way?
One option you have is to declare your date model as instance variables of your app delegate (as mentioned by other commenters).
Instead of referencing the app delegate as suggested by nevan an alternative is to add a property to your view controller classes (A and B) for your data model.
Say you wanted to share a data model object between your view controllers you can add a property to each:
#interface AViewController : UIViewController {
MyDataModel *model;
}
#property (nonatomic, retain) MyDataModel *model;
#end
#interface BViewController : UIViewController {
MyDataModel *model;
}
#property (nonatomic, retain) MyDataModel *model;
#end
When you initialise your view controller you can then set this property to the object context initialised previously.
You have mentioned a tab bar controller. If your view controllers are wired through IB all you have to do is to set these parameters in your application delegate applicationDidFinishLaunching: method, before the tab bar controller is displayed:
#interface MyAppDelegate : NSObject <UIApplicationDelegate, UITabBarControllerDelegate>
{
MyDataModel *model;
AViewController *aViewController;
BViewController *bViewController;
...
}
#property (retain) IBOutlet AViewController *aViewController;
#property (retain) IBOutlet BViewController *aViewController;
#end
#implementation MyAppDelegate
...
- (void)applicationDidFinishLaunching:(UIApplication *)application
{
...
aViewController.model = model;
bViewController.model = model;
[window addSubview:tabBarController.view];
[window makeKeyAndVisible];
}
Don't forget to release the model in your view controller's dealloc method.
The alternative is to use a singleton object. An simple singleton example:
#interface MyDataModel : NSObject
{
}
+ (MyDataModel *) sharedDataModel;
#end
#implementation MyDataModel
static MyDataModel *sharedDataModel = nil;
+ (MyDataModel *) sharedDataModel
{
#synchronized(self)
{
if (sharedDataModel == nil)
{
sharedDataModel = [[MyDataModel alloc] init];
}
}
return sharedDataModel;
}
#end
You can access this data model from all your view controllers with something similar to the following:
MyDataModel *model = [MyDataModel sharedDataModel];
See also this stack overflow discussion about singletons.
The most common way I've seen this is to set up the thing you want to access in the app delegate and reference it in other places like this:
MyAppDelegate *appDelegate = (MyAppDelegate *)[[UIApplication sharedApplication] delegate];
myStuff = appDelegate.stuff;
In the app delegate, set up a stuff variable and use #property and #synthesize as usual.
Some people say that it's not a good approach, since it's the same as using global variables, but it's very common.
I like to create a top level Model class that is a singleton and contains all the elements I might need.
It's helpful to also give it a top level load method that populates objects with just the db keys, using the hydrate/dehydrate pattern common in the Apple examples.
Typical usage in the app delegate would be simply,
[[MyModel sharedModel] load];
And then in a view controller:
NSArray *myThing1s = [[MyModel sharedModel] thing1s];
NSArray *myThing2s = [[MyModel sharedModel] thing2s];
You can then iterate over your thing1s and thing2s and when you need details, you can just call
[myThing1 hydrate];
which will populate the object.
Of course, you probably want to use CoreData to manage the persistence from 3.0 onwards.
I always create a special object called DataModel and use it's singleton sharedInstance.
And this object then holds all the app-related-data. No need for accessing the dreaded appDelegate.
DataModel.h
#import <Foundation/Foundation.h>
#class MyClass1, MyClass2;
#interface DataModel : NSObject
#property (copy, nonatomic) NSString *aString;
#property (assign) BOOL aBool;
#property (strong) MyClass1 *myObject1;
#property (strong) MyClass2 *myObject2;
+ (DataModel *)sharedModel;
#end
DataModel.m
#import "DataModel.h"
#import "Class1.h"
#import "Class2.h"
#implementation DataModel
- (id) init
{
self = [super init];
if (self)
{
_myObject1 = [[MyClass1 alloc] init];
_myObject2 = [[MyClass2 alloc] init];
aBool = NO;
aString = nil;
}
return self;
}
+ (DataModel *)sharedModel
{
static DataModel *_sharedModel = nil;
static dispatch_once_t onceSecurePredicate;
dispatch_once(&onceSecurePredicate,^
{
_sharedModel = [[self alloc] init];
});
return _sharedModel;
}
#end
And (bacause I'm lazy) i put DataModel.h in application-prefix.pch.
That way i can access my data from anywhere in the application simply by calling
[DataModel sharedModel]
Both view controllers should reference a third object (C) as their dataSource; this object (C) containing all the stored application data.
C would be, in this case, the M in the MVC.
Add to each of your ViewControllers the following declarations:
// SomeViewController.h
// Before #interface
#class MyDataSource;
// In the interface
IBOutlet MyDataSource *datasource;
#property(retain) IBOutlet MyDataSource *datasource;