SORRY FOR THE LENGTH OF THIS POST; IT IS MEANT TO DOCUMENT MY JOURNEY WITH THIS PROBLEM.
I have a question about a shared object in a Cocoa app that needs to change from time to time and how best to store it so that it's accessible from a few different places. Bear with me.
Class Implementation
The shared object is implemented as a Class Cluster (i.e., https://stackoverflow.com/a/2459385/327179) that looks like the following (note that Document is merely a class name; it is not necessarily indicative of what my actual class does):
In Document.h:
typedef enum {
DocumentTypeA,
DocumentTypeB
} DocumentType;
#interface Document : NSObject {}
- (Document *) initWithDocumentType:(NSUInteger)documentType;
- (void) methodA;
- (void) methodB;
#end
In Document.m:
#interface DocumentA : Document
- (void) methodA;
- (void) methodB;
#end
#interface DocumentB : Document
- (void) methodA;
- (void) methodB;
#end
#implementation Document
- (Document *)initWithDocumentType:(NSUInteger)documentType;
{
id instance = nil;
switch (documentType) {
case DocumentTypeA:
instance = [[DocumentA alloc] init];
break;
case DocumentTypeB:
instance = [[DocumentB alloc] init];
break;
default:
break;
}
return instance;
}
- (void) methodA
{
return nil;
}
- (void) methodB
{
return nil;
}
#end
#implementation DocumentA
- (void) methodA
{
// ...
}
- (void) methodB
{
// ...
}
#end
#implementation DocumentB
- (void) methodA
{
// ...
}
- (void) methodB
{
// ...
}
#end
How The User Interacts with a Document
Via a menu item, the user can switch between DocumentA and DocumentB at will.
What Happens When A "Switch" Occurs
When the user switches from, say, DocumentA to DocumentB, I need two things to happen:
My primary NSViewController (MainViewController) needs to be able to use the new object.
My AppDelegate needs to update an NSTextField that happens to be located in the content border of the main window. (FWIW, I can only seem to assign an outlet for the NSTextField in the AppDelegate)
The Question(s)
I've seen singletons mentioned quite a bit as a way to have a global reference without cluttering up one's AppDelegate (primarily here and here). That said, I've not seen much info on overwriting such a singleton (in our case, when a user switches from DocumentA to DocumentB [or vice versa], this global reference would need to hold the new object). I'm not an expert on design patterns, but I do remember hearing that singletons are not meant to be destroyed and recreated...
So, given all this, here are my questions:
How would you store my Class Cluster (such that MainViewController and AppDelegate can access it appropriately)?
Am I mixing concerns by having both MainViewController (who uses Document heavily) and AppDelegate (who manages the primary window [and thus, my NSTextField]) have knowledge of Document?
Feel free to let me know if I'm thinking about this problem incorrectly; I want this implementation to be as orthogonal and correct as possible.
Thanks!
Status Update #1
Thanks to advice from #JackyBoy, here's the route I've taken:
Document is the one that, upon "switching", "notifies" AppDelegate and MainViewController by passing them the newly created instance.
Both AppDelegate and MainViewController can update the Document object via the Singleton instance as necessary.
Here are my new files (dumbed down so that y'all can see the crux of the matter):
In Document.h:
#import <Foundation/Foundation.h>
#class AppDelegate;
#class MainViewController;
typedef enum {
DocumentTypeA,
DocumentTypeB
} DocumentType;
#interface Document : NSObject
#property (weak, nonatomic) MainViewController *mainViewControllerRef;
#property (weak, nonatomic) AppDelegate *appDelegateRef;
+ (Document *)sharedInstance;
- (id)initWithParser:(NSUInteger)parserType;
#end
In Document.m:
#import "AppDelegate.h"
#import "Document.h"
#import "MainViewController.h"
#interface DocumentA : Document
// ...
#end
#interface DocumentB : Document
// ...
#end
#implementation Document
#synthesize appDelegateRef;
#synthesize mainViewControllerRef;
+ (Document *)sharedInstance
{
static XParser *globalInstance;
static dispatch_once_t predicate;
dispatch_once(&predicate, ^{
// By default, I return a DocumentA object (for no particular reason).
globalInstance = [[self alloc] initWithDocumentType:DocumentA];
});
return globalInstance;
}
- (id)initWithDocumentType:(NSUInteger)documentType
{
Document *instance = nil;
switch (parserType) {
case DocumentTypeA:
instance = [[DocumentA alloc] init];
break;
case DocumentTypeB:
instance = [[DocumentB alloc] init];
break;
default:
break;
}
// QUESTION: Is this right? Do I have to store these references
// every time a new document type is initialized?
self.appDelegateRef = (AppDelegate *)[NSApp delegate];
self.mainViewControllerRef = self.appDelegateRef.mainViewController;
[self.appDelegateRef parserSwitchedWithParser:instance];
[self.mainViewControllerRef parserSwitchedWithParser:instance];
return instance;
}
#end
#implementation Xparser_NSXML
// ...
#end
#implementation DocumentA
// ...
#end
Should I be bothered by the fact that Document has knowledge of the existence of AppDelegate and MainViewController? Additionally, should I be bothered by the fact that when the Document object updates, it re-notifies both AppDelegate and MainViewController (even though one of those initiated the update)?
As always, I appreciate everyone's eyeballs on this as my quest for the ideal implementation continues. :)
Status Update #2
A comment from #Caleb helped me understand that an NSNotification-based setup would be a lot less unwieldy for this particular problem.
Thanks, all!
I don't see he need for a shared object here, much less a singleton. Do you really need to find the current Document at arbitrary times from many different objects? Seems more like you just have two objects (app delegate and view controller) that both need to know about the current Document. Notifications provide an easy way to manage that: whenever a switch happens, you can post a NSNotification that includes the new Document. Any objects that need to know about the current Document will have registered for the "document switch" notification, and when the notification arrives they can stash a pointer to the Document in an instance variable or property.
I do remember hearing that singletons are not meant to be destroyed
and recreated...
Well, you can have references inside of it, so you are not actually "destroying" the singleton, but the objects he points to. I tend to leave the App Delegate without application logic, so I normally put it somewhere else. In your case, since you need to access something from different places, it makes sense to have one. About the cluster, you can still have it, you just ask the singleton to access it and return the appropriate object like so:
Document *myDocument = [[MySingleton defaultManager] createObjectWithType:aType];
You gain some things out of this:
you can access your cluster from any place in your app
you decouple things, only one entity knows about your cluster.
Inside the Singleton you can have a reference to you AppDelegate and interact with it.
Inside the Singleton you can have a reference to the objects that are being used (Document A, Document B)
One more thing, I would advise putting the cluster access method as a class method (instead of an instance one).
Related
I am relatively new to Cocoa programming. Basically, I want to send a message within a method in my Document class to an intense of a class (that inherits from NSView) that I have initialised as a property in the #interface of the Document class.
Here is the simplified version:
///////////////////////////KOZDocument.h///////////////////////////
#import <Cocoa/Cocoa.h>
#import "KOZOtherClass.h"
#interface KOZDocument : NSDocument
#property (assign) IBOutlet KOZOtherClass *otherClassInstance; //this would be connected to the relevant CustomView in the IB
#end
///////////////////////////KOZDocument.m///////////////////////////
#import "KOZDocument.h"
#implementation KOZDocument
- (id)init
{
self = [super init];
if (self) {
// I want to send a message to otherClassInstance from some method e.g. init
NSLog(#"INITIALISING");
[[self otherClassInstance] printMessage];// this is the message I want to work but which doesn't (even though i don't any errors)
//sending the message to a locally initiated instance works but I don't want to use a local instance because i want to connect it to a CustomView in IB
KOZOtherClass *otherClassLocalInstance = [[KOZOtherClass alloc] init];
[otherClassLocalInstance printMessage];
}
return self;
}
//.….
///////////////////////////KOZOtherClass.h///////////////////////////
#import <Foundation/Foundation.h>
#interface KOZOtherClass : NSView
- (void) printMessage;
#end
///////////////////////////KOZOtherClass.m///////////////////////////
#import "KOZOtherClass.h"
#implementation KOZOtherClass
- (void) printMessage{
NSLog(#"This method can be called!!");
}
#end
/////////////////////////////////////////////////////////////////////
The same methodology works for all the native Cocoa objects but not for mine.
Can anyone tell me what I'm doing wrong?
Here is the context of why i want to do this:
I am building an app that plays a video using the AVFoundation. I have an animation I want to trigger in an NSView when the playback reaches a particular part in the video (e.g. after 2 seconds). I am adapting Apple's AVSimplePlayer and using the time observer to get the position of the playhead. The time observer executes the code inside a block for every given time interval. In this block I want to send a message to my animation view to trigger the animation when the time is more that 5 seconds for example.
In -init of your objects the Interface Builder connections are not set yet, the loading mechanism can't set those before your object is initialized.
Instead you want to overwrite the -awakeFromNib method like so:
- (void)awakeFromNib {
[[self otherClassInstance] printMessage];
}
-awakeFromNib is guaranteed to be called after the connections have been made. Depending on the exact implementation you may also need to guard against that code being executed twice, for example by having a boolean instance variable didWake that you check/set in that method.
Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 9 years ago.
Improve this question
I have a problem that I hope you can help me with. I have an app with several storyboard views, each with a separate viewcontroller. I want to be able to use my own class in all views in the storyboard. This will make the code in each viewcontroller much cleaner and the whole app easier to debug etc. The class will contain variables and methods.
The overall aim for me is to collect data from the user via buttons and then store these in a database. It will be possible to view and amend data, as well as generating statistics.
As most variables and methods will be used in different views and at different times, I would like to separate all these in separate files.
I can also tell you that this is not a lazy short-cut attempt from me, I have surfed the internet for many, many hours reading hundreds of posts etc and I am still nowhere nearer a solution.
Any input is very much appreciated!
Thank you for taking your time to read this...
SomeClass.h has the following code:
#import <Foundation/Foundation.h>
#interface SomeClass : NSObject
{
NSString *dataOne;
NSString *dataTwo;
NSString *dataThree;
}
- (void) SetDataOne: (NSString*) dataOneReceived;
- (void) SetDataTwo: (NSString*) dataTwoReceived;
- (void) SetDataThree: (NSString*) dataThreeReceived;
- (void) saveSomeData;
#end
SomeClass.m has the following code:
#import "SomeClass.h"
#implementation SomeClass
- (void) SetDataOne: (NSString*) dataOneReceived {
dataOne = dataOneReceived;
}
- (void) SetDataTwo: (NSString*) dataTwoReceived {
dataTwo = dataTwoReceived;
}
- (void) SetDataThree: (NSString*) dataThreeReceived {
dataThree = dataThreeReceived;
}
- (void) saveSomeData {
// Here I do stuff with dataOne etc…
}
#end
SomeView.h has the following code:
#import <UIKit/UIKit.h>
#import "HeadViewController.h"
#import "SomeClass.h"
#interface SomeView : UIViewController
// contains stuff not needed to show here
- (IBAction)Done:(id)sender;
#end
SomeView.m has the following code:
#import "SomeView.h"
#import "SomeClass.h"
#interface SomeView ()
#end
#implementation SomeView
- (void)viewDidLoad
{
[super viewDidLoad];
SomeClass *someClassObject = [[SomeClass alloc] init];
// Do any additional setup after loading the view.
}
// Other standard methods omitted
- (IBAction)Done:(id)sender {
[someClassObject SetDataOne: #”whatever text”];
[someClassObject SetDataTwo: #”whatever text”];
[someClassObject SetDataThree: #”whatever text”];
[someClassObject SaveSomeData];
Error Msg for all the above: ”Use of Undeclared Identifier ’someClassObject’
}
#end
Comment: You can see the error message I get at the end of the code above. I have no clue what I am doing wrong. I have looked at a lot of examples on how to create and call classes, but cannot seem anything that solves my problem. Also, I see that some of the put the ”SomeClass *someClassObject = [[SomeClass alloc] init];” in the ”main.m file”. If I understand correctly, that file is the first one to load when app starts. If so, then I cannot place it there as I will have to create instances of my class in several different views and other times than when the app starts. That is why I have placed it in the viewDidLoad-method.
A couple of thoughts:
You've made someClassObject a local variable of the viewDidLoad method. Looks like you meant to make it a class instance variable (or, better, a private class property, which will have the instance variable synthesized for you). Thus:
#interface SomeView ()
#property (nonatomic, strong) SomeClass *someClassObject;
#end
#implementation SomeView
- (void)viewDidLoad
{
[super viewDidLoad];
self.someClassObject = [[SomeClass alloc] init];
}
- (IBAction)done:(id)sender {
[self.someClassObject setDataOne: #"whatever text"];
[self.someClassObject setDataTwo: #"whatever text"];
[self.someClassObject setDataThree: #"whatever text"];
[self.someClassObject saveSomeData];
// should resolve the Error Msg for all the above: ”Use of Undeclared Identifier ’someClassObject’
}
BTW, as a matter of convention, your method names should start with a lowercase letter (e.g. setDataOne not SetDataOne, done rather than Done, etc.), as illustrated above.
If you're going to write your own setters, setDataOne, setDataTwo, etc., you might as well remove those three instance variables, remove your three setData___ methods, and replace the three instance variables with class properties (and let the compiler synthesize not only the instance variables, but the setters, too).
someClassObject is set inside viewDidLoad and because it is not stored anywhere inside the view will be deleted at the end of that very same method
You should add your object inside each header file's interface section like this:
SomeView.h
#import <UIKit/UIKit.h>
#import "HeadViewController.h"
#import "SomeClass.h"
#interface SomeView : UIViewController
{
SomeClass *someClassObject;
}
// contains stuff not needed to show here
- (IBAction)Done:(id)sender;
#end
Then when you instantiate someClassObject inside ViewDidLoad it will persist throughout that view.
I have a singleton that I'd like to use to manage the onscreen animation of my views. Here's my.
#import <Foundation/Foundation.h>
#interface OAI_AnimationManager : NSObject {
NSMutableDictionary* sectionData;
}
#property (nonatomic, retain) NSMutableDictionary* sectionData;
+(OAI_AnimationManager* )sharedAnimationManager;
- (void) checkToggleStatus : (UIView* ) thisSection;
#end
.m file
#import "OAI_AnimationManager.h"
#implementation OAI_AnimationManager
#synthesize sectionData;
+(OAI_AnimationManager *)sharedAnimationManager {
static OAI_AnimationManager* sharedAnimationManager;
#synchronized(self) {
if (!sharedAnimationManager)
sharedAnimationManager = [[OAI_AnimationManager alloc] init];
return sharedAnimationManager;
}
}
- (void) checkToggleStatus : (UIView* ) thisSection {
//get the section data dictionary
NSLog(#"%#", sectionData);
}
#end
You'll see in the .h file I added a NSMutableDictionary and am using #property/#synthesize for it's getter and setter.
In my ViewController I instantiate the animation manager as well as a series of subclasses of UIView called Section. With each one I store the data (x/y w/h, title, etc.) in a dictionary and pass that to the dictionary delcared in animation manager. In the Section class I also instantiate animation manager and add a UITapGestureRecognizer which calls a method, which passes along which section was tapped to a method (checkToggleStatus) in animation manager.
As you can I see in the method I am just logging sectionData. Problem is I am getting null for the value.
Maybe my understanding of singletons is wrong. My assumption was the class would only be instantiated once, if it was already instantiated then that existing object would be returned.
I do need all the other Section classes data as if one animates others animate in response and I can get around it by passing the tapped Section to the animation manager and doing [[Section superview] subviews] and then looping and getting the data from each that way but it seems redundant since that data is available in the ViewController when they are created.
Am I doing something wrong in trying to transfer that data? Is there a better solution? I am open to suggestions and criticisms.
Thanks
h file
#interface OAI_AnimationManager : NSObject
#property (nonatomic, retain) NSMutableDictionary* sectionData;
+(OAI_AnimationManager* )sharedAnimationManager;
- (void) checkToggleStatus : (UIView* ) thisSection;
#end
m file
static OAI_AnimationManager* _sharedAnimationManager;
#implementation OAI_AnimationManager
#synthesize sectionData = _sectionData;
+(OAI_AnimationManager *)sharedAnimationManager {
#synchronized(self) {
if (!_sharedAnimationManager) {
_sharedAnimationManager = [[OAI_AnimationManager alloc] init];
}
}
return _sharedAnimationManager;
}
- (void) checkToggleStatus : (UIView* ) thisSection {
//get the section data dictionary
NSLog(#"%#", _sectionData);
}
#end
Notice I moved your sectionData variable from the header and moved it to the implementation file. A while back, they changed it to where you can synthesize properties and specify their instance variable names along side it... hence:
sectionData = _sectionData;
I also added and underscore to the instance variable... this is a universal convention for private variables and it also will throw a compile error now if you try to type just sectionData as you did in the return statement of checkToggleStatus:. Now you either have to type self.sectionData or _sectionData.
You didn't include the code that creates an instance of your dictionary but I bet you didn't set it as self.sectionData = [[NSDictionary alloc] init] which means it would not retain the value and you would get null the next time you called it. Classic memory management mistake... I know it well because I learned the hard way hehehe
I have a Cocoa project with an object that holds information from a SQLite database.
By now the information is stored in memory by this object and is used in the user interface to read and write new information.
But now I came to a small problem... I decided to create a new controller class to handle the actions of an NSTableView and I want to access this same database object that was declared elsewhere.
Which is the best option to access this information? I wish to avoid loading the information more than once in memory and also avoid use pure C/C++ codes with global variables.
It is better to understand my point by looking at the code.
I accept other solutions as well, naturally.
My idea of code is currently like this:
FirstClass.h
#import <Foundation/Foundation.h>
#import "DatabaseModel.h"
#interface FirstClass : NSObject {
IBOutlet NSScrollView *informationListTable;
NSMutableArray *informationList;
}
#end
FirstClass.m
#import "FirstClass.h"
#implementation FirstClass
- (void)awakeFromNib{
DatabaseModel *list = [[DatabaseModel alloc] init];
informationList = [[NSMutableArray alloc] initWithArray:[list loadList]];
[list release];
[machinesListTable reloadData];
}
SecondClass.h
#import <Foundation/Foundation.h>
#interface SecondClass : NSObject {
IBOutlet NSTextField *labelName;
NSString *name;
}
- (IBAction)showName:(id)sender;
#end
SecondClass.m
#import "FirstClass.h"
#import "SecondClass.h"
#implementation SecondClass
- (IBAction)showName:(id)sender{
/*
Here name must get something like:
[[FirstClass.informationList objectAtIndex:3] name]
Here labelName must display name.
*/
}
#end
you can either create the object once then pass the object around, with each controller retaining it as needed. Or you can use a singleton instance. I would say the singleton instance is easier to read, but it depends on the application
One solution would be to make FirstClass a singleton. Then, anywhere else in your code, you could call [FirstClass sharedInstance] (replace sharedInstance with the name you'll give to your class method) and use this object. You'll have to be careful about concurrency issues though.
So, I have a decent idea of what a delegate does, why use it, how to implement it etc. and I'm working on implementing it in one of my projects. The problem I'm trying to solve is to decouple my Controller objects from my Network Access class. In this context, the ideas get a little messy in my head.
I somehow intuitively feel that the NetworkAccessClass should be the delegate for a Controller object, because the NetworkAccessClass is acting as a helper for the Controller object. But it seems to work in a reverse fashion, because the following is apparently the right way to do it:
NetworkaccessClass.h
#protocol NetworkAccessDelegate
-(void) requestSucceded:(NSData *) data
-(void) requestFailed:(int) responseCode;
#end
#interface NetworkAccessClass : NSObject
{
id<NetworkAccessDelegate> networkDelegate;
}
#property(nonatomic, assign) id networkDelegate;
-(void) initWithDelegate:(id) delegate; //
#end
NetworkAccessClass.m
#implementation
#synthesize networkDelegate
-(void) initWithParams:(id) delegate
{
networkDelegate = delegate;
// Assign GET/POST vals, create request etc
[request startAsynchronous];
}
-(void) requestSucceded:(ASIHTTPRequest *) request
{
if([networkDelegate respondsToSelector:#selector(requestSucceded:)]) {
// Send the data to the controller object for it to use
...
}
}
-(void) requestFailed:(ASIHTTPRequest *) request
{
// Same as above. Send to request failed.
}
#end
And finally in my FirstViewController.h
#import "NetworkAccessClass.h"
#interface FirstViewController<NetworkAccessDelegate>
{
}
-(void) requestSucceded:(NSData *) data;
-(void) requestFailed:(int) responseCode;
#end
And the same in SecondViewController.h and so on.
Although this does decouple my Controllers from my Network class, I can't help feel it's wrong because the controllers in this case are acting as delegates or helper methods to the Network Class and not the other way round. Am I missing something basic? Or is this how it is?
Thanks,
Teja.
Delegates aren't "helper methods". Think of them as objects that get notified when something happens. (Although don't confuse them with "Notifications"--that's a different thing entirely.) In this case, your network class does it's stuff and then calls its delegate method on the View Controller that instantiated and fired, it to report the contents of that response to the view controller. The controller will then, presumably, update the view with the data that the network connector got. Classic delegate pattern, right there.