I'm trying to do the right thing by not using global variables in my Xcode project. I've successfully created a singleton. I have a simple data store which I named "myContactsStore" that contains an array, with each object in the array having only 3 instance variables (name, phoneNum, and eMail). I have no problem creating, modifying, saving etc. the data in the array when I'm executing the view controller that created the array.
My problem is trying to access the data store from another view controller. I'm halfway there, as proven by my ability to print the contents of the entire test array from another view controller by using the following code in a for loop:
NSLog(#"%#", myContactsStore.description);
Here's the output:
"Mary, 0938420839, PaulDoe#Mac.com",
"John, 9932097372, PaulDoe#Mac.com",
"Mary, 0726756893, RedCat#iwon.com",
"Mary, 8556327199, xxxbct#mac.com",
"John, 0640848317, xxxbct#mac.com"
How do I access just one instance variables? For example, I want to create a read-only array in another view controller that contains just the email addresses of every contact in the "myContactsStore" array. I've tried several things, but I'm new at this and I must be missing something very basic.
Thanks for you help and any code example you might have the time to include.
While Singletons are an easy way to share data across classes they cause cohesion problems in your overall design where your classes start to "import the world". The issue is coming up with exactly the right dependency for a given class and this is very easily missed when you try to design from classes instead of designing from use cases. You want to ask yourself, "what data does this class use?" You then create a protocol (abstraction) that gives the class the view of the data it wants. In your case one class writes to the data store. It doesn't need to create the data store nor does either class need to know that the data is maintained in an array. Follow these steps exactly in order and follow directly or you'll miss my point. Try a protocol in a separate .h file like the following:
#protocol MyDataStore <NSObject> {
}
Import this header in your 1st class that creates the contacts and declare a property of the protocol's type.
#import "MyDataStore.h"
#interface MyContactCreator : UIViewController
#property (nonatomic, retain) id<MyDataStore> dataStore;
-(id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil dataStore:(id<MyDataStore>)aDataStore;
#end
I threw in a custom init method in case you are currently instantiating your view controllers programmatically instead of via InterfaceBuilder. In your implementation you would do something like this:
#implementation MyContactCreator
//other methods...
-(id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil dataStore:(id<MyDataStore>)aDataStore
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
self.dataStore = aDataStore;
}
return self;
}
//other methods...
#end
If you are using Interface Builder to create your view controller you can drag a custom object into play and call it something like "MyDataStoreImpl". The idea here is that you are giving the datastore to the view controller instead of it creating it directly and knowing about it. Also you want to defer worrying about how the data store works until you really need to. Later in your view controller where you create the contacts you would use the data store to create them. Assuming the info comes from standard screen elements you would write code like this:
-(void) addContactTapped:(id)sender
{
[self.datastore createContactWithName:txtNameField.text phoneNumber:txtPhoneField.text email:txtEmailField.text];
}
Your editor would scream at you (with little red marks like what your 2nd grade teacher would use on your spelling homework) because the datastore doesn't respond to the message you are sending. You go back and add that method to the protocol:
#protocol MyDataStore <NSObject> {
-(void) createContactWithName:(NSString*)aName phoneNumber:(NSString*)aPhoneNumber email:(NSString*)anEmail;
}
In your other view controller class that wants the list of email addresses you would import the same data store protocol. You would also declare a datasource property identical as what we did above using a complimentary custom init method or Interface Builder to pass the datasource in. This view controller (assuming it's a table view controller) would probably have some methods like:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [self.datasource numberOfContacts];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *email = [self.datasource emailForContactNumber:indexPath.row];
UITableViewCell *cell = //create tableview cell with the email string
return cell;
}
Your editor will start screaming with the little red lines and all. This is where you go and add more methods to the protocol.
#protocol MyDataStore <NSObject> {
-(void) createContactWithName:(NSString*)aName phoneNumber:(NSString*)aPhoneNumber email:(NSString*)anEmail;
-(NSInteger) numberOfContacts;
-(NSString*) emailForContactNumber:(NSInteger)index;
}
The little red lines go away and finally you can begin thinking about how the contacts are stored and retrieved. Create a separate class called MyDataStoreImpl which extends NSObject and imports and follows the "MyDataStore" protocol. Fill out implementations of all of the methods and you should be up and running. It could be as simple as storing NSDisctionary objects containing the contact info in an internal NSMutableArray property.
- (id)init {
self = [super init];
if (self) {
self.allContacts = [NSMutableArray array];
}
return self;
}
-(void) createContactWithName:(NSString*)aName phoneNumber:(NSString*)aPhoneNumber email:(NSString*)anEmail;
{
NSDictionary *newContact = [NSDictionary dictionaryWithObjectsAndKeys:
aName, #"name", aPhoneNumber, #"phone", anEmail, #"email",
nil];
[self.allConstacts addObject:newContact];
}
-(NSInteger) numberOfContacts;
{
return [self.allContacts count];
}
-(NSString*) emailForContactNumber:(NSInteger)index;
{
[[self.allContacts objectAtIndex:index] valueForKey:#"email"];
}
The advantages here are many. You can later re-implement the datasource to read/write from a plist file, network server, or database without touching any of your controllers. Also, your app will be easier to optimize for performance because you can design the read write methods to pull directly from a source instead of naively copying data from one array to another as you would if you were worrying about how it is managed too early. All of the above thrown together without testing and likely has errors but given to illustrate a point of how to properly share data between controllers without Singletons while maintaining a testable and easily maintainable codebase.
You can stick this to any class to make it a singleton:
+(MySingleton *)singleton {
static dispatch_once_t pred;
static MySingleton *shared = nil;
dispatch_once(&pred, ^{
shared = [[MySingleton alloc] init];
shared.someVar = someValue;
});
return shared;
}
-(void) dealloc {
abort();
[someVar release];
[super dealloc];
}
Better yet, you can add the class as an ivar of the application delegate, which is a singleton you already have. You can get a reference to it like this:
// AppDelegate or whatever name
AppDelegate *delegate = (AppDelegate*)[[UIApplication sharedApplication] delegate];
Then you access the datastore as an ivar on that delegate, and optionally implement one of the several persistence technologies available on iOS, basically plist files through direct file access or NSCoding or even NSUserDefaults (which you shouldn't but it's handy for small tasks), Core Data, or SQLite.
If you are using this data store class only to pass data between controllers, you can do so directly instead. Example:
CitySelectionVC *citySel = [[CitySelectionVC alloc] initWithCities:self.cities];
[self.navigationController pushViewController:citySel animated:TRUE];
[citySel release];
I always use SynthesizeSingleton.h by Mike Gallagher. He has a really informative article relating to this topic here. You should really check it out. It makes the creation of Singleton classes really easy.
Assuming your myContactsStore object has a method contactsArray which returns the internal NSArray object, you can do this:
NSArray *emails = [myContactsStore.contentsArray valueForKey:#"email"];
NSLog(#"output: %#", emails);
Which should output:
output: (
"PaulDoe#Mac.com",
"PaulDoe#Mac.com",
"RedCat#iwon.com",
"xxxbct#mac.com",
"xxxbct#mac.com"
)
You simply have to enumerate through the array and retrieve the data you want. Like so:
NSMutableArray *emails = [[NSMutableArray alloc] initWithCapacity:myContactsStore.count];
for (ContactClassName *contact in myContactsStore) {
NSString *email = contact.eMail; // I'm assuming your ivar eMail is also a property
if (email) [emails addObject:email];
}
You now have an NSMutableArray containing just the list of emails. If you want to make this list immutable simply do:
NSArray *emailList = [emails copy];
Related
Now I am developing an iOS application which works like this:
User scans QR code,
App searches for a specific key - > value,
it gives out a value to the user.
Currently I have two ViewControllers - the main and "value" ViewController, which is inherited from main. The problem is that if I create NSDictionary in main VC it is not visible in "value" VC. Main VC gives only the string (QR code, the key) through the segue. So, the value VC has to search for key and display the value.
What I ask is some kind of global variable or one DataSource visible across the whole app. Of course, I can implement NSDictionary initialisation inside value ViewDidLoad method and it will work, but this is not the point. New modules are to be added there and the variable has to be global. I googled a lot and got the idea that singleton pattern can be helpful here. I tried to implement it, but no idea how to do. Do I need it, or it is too complex for this kind of DataSource?
Thank you!
The basic idea is, you will still need to #include the header file of the place where this dictionary will be. The solution that Naveen proposes means that you will be including the header for the app delegate wherever you want to access it. Whether to use the app delegate for this purpose or not is kinda grayish. Some people often do this, some say its a bad use of it.
The singleton approach means that you will create a class, that will always contain the same information since the init method will return object that was previously created.
For the singleton aproach, imagine I have a database manager class. So in the header of this class (the DatabaseManagerSingleton.h) ill have this:
#interface DatabaseManager : NSObject
+ (DatabaseManager*)sharedInstance;
// Your dictionary
#property (nonatomic,strong) NSMutableDictionary* someDictionary;
The implementation will look like this: (check how "sharedInstance" initializes the object)
#implementation DatabaseManager
#pragma mark - Singleton Methods
+ (DatabaseManager*)sharedInstance {
static DatabaseManager *_sharedInstance;
if(!_sharedInstance) {
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
_sharedInstance = [[super allocWithZone:nil] init];
});
}
return _sharedInstance;
}
+ (id)allocWithZone:(NSZone *)zone {
return [self sharedInstance];
}
- (id)copyWithZone:(NSZone *)zone {
return self;
}
- (id)init
{
self = [super init];
if (self != nil)
{
// Custom initialization
_someDictionary = [[NSMutableDictionary alloc] init];
}
return self;
}
Now, a VERY important thing is that, any place you want to use this object should first include the header:
EDIT: To use it in your code:
1) add the header
#import "DatabaseManager.h"
2) initialize the object
DatabaseManager *databaseManager = [DatabaseManager sharedInstance];
3) do whatever you need
// Initialize the dictionary
databaseManager.someDictionary = [[NSMutableDictionary alloc] initWithObjectsAndKeys:#"OBJECT",#"someKey", nil]; // In this case the object is just a NSString.
// Access
[databaseManager.someDictionary objectForKey:#"someKey"];
Put as a property on Appdelegate
#property (nonatomic,strong) NSDictionary * sharedData;
Access anywhere like
NSDictionary *sharedData= ((APPDelegate *) [UIApplication sharedApplication].delegate).sharedData;
I am trying to update my UIProgressView with some data from a method of my utility class.
Now, just because for updating my UIProgressView, i am holding that method in my view controller class and everything works fine. Because i can reach the loop in that method with a global variable so i can update my progress. But if i want to move this method to my utility class, what am i supposed to do to keep informed my UIProgressView. Thanks.
What I would suggest is to redesign your utility class to be a singleton
Here is an example of code of your utility class:
UtilityClass.h file:
#interface UtilityClass : NSObject
+ (UtilityClass *)sharedInstance;
- (CGFloat)awesomeMehod;
#end
UtilityClass.m
#implementation UtilityClass
+ (id)sharedInstance
{
static UtilityClass *_instance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instance = [[UtilityClass alloc] init];
});
return _instance;
}
- (id)init
{
self = [super init];
if (!self) return nil;
// Regular initialization, however keep in mind that it will be executed just once
return self;
}
- (CGFloat)awesomeMethod
{
return 42.0f
}
#end
Now from your view controller you will call
CGFloat progress = [[UtilityClass sharedInstance] awesomeMethod];
[self.progressView setProgress:progress];
keep in mind several things:
It's one of possible approaches and I would go and read about various
design patterns that might come in handy one day
Probably a good idea to refresh knowledge on view controllers and the way they interact
For class to become a proper singleton, you also should override
methods such as alloc, init, initWithZone, dealloc, release
etc (list of methods to override will vary if you use ARC), here is
an example of doing that, although dispatch_once takes care of
#synchronize() call. For now, as long as you "instantiate" you class only
through calling sharedInstance class method you will be fine.
I want to change the number of tableviews depending on my input, which will change depending on the user and i need to be able to reference these tableviews in my data source, as they should retain different data depending on the tableview. I figured this might be done if I could create a unique variable for each of the created tableviews, but i would need to be able to reference these variables through my whole class?
You can create an NSMutable Array and add UITableView in them directly . You can make create global variable store using static reference and access it globally .
#interface Singleton : NSObject {
NSMutableArray *TableArray;
}
+ (Singleton *)instance;
#end
#implementation Singleton
+ (Singleton *)instance {
static Singleton *instance;
#synchronized(self) {
if(!instance) {
instance = [[Singleton alloc] init];
}
}
return instance;
}
The other alternative would be to include NSMutableArray in you appdelegate and access it throughout your application globally.
Creating objects dynamically is not a problem .
you can create as many UITableview's and add them to an NSMutableArray using.
UITableview *temp = [UITableview alloc] initWithFrame : ... ];
temp.delegate = self ;
tableArray.addObject(temp);
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath(NSIndexPath *)indexPath{
if(tableView = [tableArray objectAtIndex:x]{
// do this
}
else if (select appropriate table view from array ){
}
//do this for the rest
}
What you are facing is an design issue . For other alternatives please refer Apple Guides for Objects Communication
Why not create an NSMutableArray member variable to store them all? When you need to change the data source, set the specific tableview to the data source. I'm not sure of the details of what exactly you're trying to do, but adding them dynamically and changing the data source shouldn't be a problem.
I am currently populating an NSTableView through the controller (MVC design pattern) where I initialise one entry of a NSMutableArray in the controller's init method.
How would I:
Populate my NSMutableArray which is an array of Person objects
Should I populate the NSMutableArray in my mainViewDidLoad method of my base class instead? I have not found any examples or resources for this.
Model (Person.m)
#import "Person.h"
#implementation Person
#synthesize name;
#synthesize gender;
- (id)init
{
self = [super init];
if (self) {
name = #"Bob";
gender = #"Unknown";
}
return self;
}
- (void)dealloc
{
self.name = nil;
self.gender = nil;
[super dealloc];
}
#end
Controller (PersonController.m)
#import "PersonController.h"
#import "Person.h"
#implementation PersonController
- (id)init
{
self = [super init];
if (self) {
PersonList = [[NSMutableArray alloc] init];
// [personList addObject:[[Person alloc] init]];
//
// [personTable reloadData];
}
return self;
}
- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView {
return [personList count];
}
- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row {
Person *person = [personList objectAtIndex:row];
NSString *identifier = [tableColumn identifier];
return [person valueForKey:identifier];
}
- (void)dealloc
{
[super dealloc];
}
#end
Base file (Main.h):
#import "Main.h"
#implementation Main
- (void)mainViewDidLoad
{
}
#end
How would I:
Populate my NSMutableArray which is an array of Person objects
Step 1: Create Person objects.
Step 2: Add them to the array.
Your commented-out code does exactly this, although you should probably create the Person separately in case you want to configure it (e.g., set its name).
Should I populate the NSMutableArray in my mainViewDidLoad method of my base class instead?
It doesn't really matter how far in advance of the user seeing your model you create it, but conceptually, it kind of smells to me. It doesn't have anything to do with the view, so I say it belongs in init.
Of course, if the main view—and every view in it—has already loaded, you'll need to tell the table view to reload your data to get it to show any changes you made to the array. Conversely, if you create the model before loading the view, you don't need to reload initially, because the table view will have already asked you for your model once.
Offhand, your commented out code in PersonController looks right. I am assuming that the NSTableRow has the right identifier. The only issue is that your person object is blank, so there are no strings to display. I bet what was happening is that your row was trying to display nil in the first row, and displayed empty strings instead. Does creating your person object, setting the name and gender fields, and then putting it into the NSMutableArray and calling reloadData work (basically, what your commented code does, except now you are providing some actual data to display)?
Well, I am assuming that your controller has an IBOutlet NSTableView *personTable property, which is bound in the interface builder?
Also, in the controller's interface the protocol should be declared, but again, since the controller's implementation has the appropriate methods, I am assuming you have this set up correctly too.
Another detail, have the table columns' identifiers been set correctly in the interface builder? From this example, it is not clear to me how the column identifiers relate to the Person's properties (name and gender). Shouldn't the array personList hold dictionary objects, where a dictionary's object is the person, and the dictionary's key maps to the column identifier that you set in interface builder?
Yet another technicality, a property's name (PersonList) should not start with a capital letter. Just a typo I think, the compiler should at least protest when you try to get the personList, using a lower-case p.
I can create a stack class quite easily, using push and pop accessor methods to an NSArray, however. I can make this generic to take any NSObject derived class, however, I want to store only a specific class in this stack.
Ideally I want to create something similar to Java's typed lists (List or List) so that I can only store that type in the stack. I can create a different class for each (ProjectStack or ItemStack), but this will lead to a more complicated file structure.
Is there a way to do this to restrict the type of class I can add to a container to a specific, configurable type?
NSArray will take any object that implements the same protocol as NSObject; it doesn't need to be derived from NSObject. Because all dispatch in Objective-C is dynamic, inheritance isn't necessary for polymorphism.
Putting that aside, if you've implemented your own push and pop methods, you can easily test the incoming type using isKindOfClass or isMemberOfClass and reject those of the incorrect type.
To go further down, NSMutableArray documents which methods are conceptually primitive below the heading 'Subclassing Notes' in the Overview section at the top of the page. If you subclass NSMutableArray and alter addObject:, replaceObjectAtIndex:withObject: and insertObject:atIndex: to test the type of the incoming object then you can create a typed array.
You can try something like this, although I do not recommend it:
#interface TypedMutableStack : NSObject {
Class type;
#private
NSMutableArray *internal;
}
- (id) initWithType:(Class) type_;
#end
#define CHECK_TYPE(obj) if(![obj isKindOfClass:type]) {\
[NSException raise:NSInvalidArgumentException format:#"Incorrect type passed to TypedMutableStack"];\
}
#implementation TypedMutableStack
- (void) dealloc {
[internal release];
[super dealloc];
}
- (id) initWithType:(Class) type_ {
self = [super init];
if(self) {
type = type_;
internal = [[NSMutableArray alloc] init];
}
return self;
}
- (void) addObject:(id) obj {
CHECK_TYPE(obj);
[internal addObject:obj];
}
- (void) replaceObjectAtIndex:(NSUInteger) index withObject:(id) obj {
CHECK_TYPE(obj);
[internal replaceObjectAtIndex:index withObject:obj];
}
- (void) insertObject:(id) obj atIndex:(NSUInteger) index {
CHECK_TYPE(obj);
[internal insertObject:obj atIndex:index];
}
//...
#end
Objective-C doesn't have generics or templates. Hence, idiomatic Objective-C code is written using NSObject * and id everywhere, casting as necessary.
Whatever you want to do, you should model it on the existing frameworks. See how NSArray, NSDictionary, NSSet, etc., are implemented, and follow suit.