Establishing a global array of strings [duplicate] - ios

This question already has answers here:
How do I declare an array as a constant in Objective-c?
(6 answers)
Closed 9 years ago.
In my app I refer to a number of the same string variables from lots of different view controllers and I have created a number of global NSStrings using this method:
OpeningController.h (before #interface)
extern NSString *stringName;
OpeningController.m (after the #interface { } #end)
NSString *stringName =#"On";
I can then refer to/alter stringName anywhere in my application.
I want to be able to do the same with an array of strings but when I try the following I get the error Initializer is not a compile-time constant.
How do I achieve what I am trying to achieve?
OpeningController.h
extern NSArray *arrayName;
OpeningController.m (after the #interface { } #end)
NSArray *arrayName = [NSArray arrayWithObjects:
#"String1",
#"String2",
#"String3",
#"String4",
nil];

Assuming openingController is a class name (hint: it should be OpeningController) then you can initialize the array within the class's +initialize method, which will be invoked as soon as the class is referenced at runtime:
OpeningController.m:
#import "OpeningController.h"
NSArray *arrayName = nil;
#implementation OpeningController
+ (void)initialize {
arrayName = [NSArray arrayWithObjects:
#"String1",
#"String2",
#"String3",
#"String4",
nil];
}
....
#end
EDIT: Note that this isn't a particularly good example as referencing the array without referencing OpeningController first will access the un-initialized array. A better approach is a singleton pattern:
OpeningController.h:
#interface OpeningController : UIViewController // Not sure of the subclass
+ (NSArray *)array;
...
#end
OpeningController.m:
#import "OpeningController.h"
#implementation OpeningController
+ (NSArray *)array {
static NSArray *array = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
array = [NSArray arrayWithObjects:
#"String1",
#"String2",
#"String3",
#"String4",
nil];
});
return array;
}
...
#end

Don't use shared global variables. They create very strong coupling between your different classes, and force all sorts of interdependencies. That leads very quickly to spaghetti-code.
Instead, create a data container singleton. That is a singleton object (do a google search on the singleton design pattern in Objective C, or even search here) that contain properties that you want to share.
Then any time you want to read or write a global variable, you use your singleton's accessor method to fetch the singleton and then invoke the desired property.
NSUserDefaults is an example of a singleton in Apple's frameworks. The code
[NSUserDefaults sharedUserDefaults] is a class method call that returns a pointer to the user defaults singleton.
Your code might look something like [MyDataSingleton sharedDataSingleton].

You mus init the array, otherwise it will not work.
You can create a instance of the class and get that instance everytime you want.
If you have questions please tell me :)

Related

Objective C - Static class variable of type NSArray

I'm storing some default initializer values for my class as static class variables. Like this:
// List.h
static NSString *DEFAULT_LIST_NAME = #"Not Set";
static BOOL DEFAULT_RECURSION = NO;
I also need a static variable of type NSArray * set to an empty array. How can this be achieved? Currently I get the error:
Initializer element is not a compile-time constant
You are getting the compile time error "Initializer element is not a compile-time constant" because the static variable's value is actually written into your executable file at compile time. So you can only use the constant values (not alloc/init which are executed at runtime). You can use any of the below option
You can write static NSArray *arr = nil and use +initialize to create your array.
Another options are you can use __attribute__ ((constructor))
Yet another option is to switch the type of your source file from Objective-C to Objective-C++ (or rename it from .m to .mm, which has the same effect). In C++, such initializers don't need to be compile-time constant values, and the original code would work just fine
Also you can use solution given Pat_Morita
define a class method for this:
.m file
#implementation test
static NSArray *array;
+ (NSArray *)array {
if (!array) array = [[NSArray alloc] init];
return array;
}
#end

How add NSDictionary to EKEvent

i would like to add extra data to EKEvent, i tried NSDictionary (there is a lot of data to add) but it doesn't work..
sample code:
NSMutableDictionary *dictionary = [[NSMutableDictionary alloc]init];
[eventStore setValue:dictionary forKey:MAIN_DICTIONARY];
any ideas?
You're using setValue:forKey: in a wrong way. That a look here. There are different options to achieve what you want: category, subclassing or create a class that contains the EKEvent and the NSMutableDictionary. It depends on how you need to use the EKEvent.
You cannot do it this way, because even with key-value coding you can only set (declared or non declared) properties known by the instance. Basically the accessors (setter, getter) are executed. But there is no property MAIN_THREAD,no setter setMAIN_THREAD: in EKEvent.
If you want to extend instances of a foreign class that are created by the system (the instances, not the class), there are to ways to add data:
You create an own class, let's say MyEvent and give them a reference to the system instance (EKEvent) as a property plus the properties you need. When you get an instance of EKEvent you look-up your list of MyEventss using the identifier. With that you have the full access to your data.
You use associated objects. But you have to take care that they are not handled by the instance, i. e. while copying.
The first solution is better by far. Simple sample code:
#interface MyEvent : NSObject
#property (readonly) EKEvent* systemEvent;
#property id customProperty;
- (instancetype)eventForSystemEvent:(EKEvent*)systemEvent;
#end
#implemenation MyEvent
// Look-Up
NSMutableDictionary *eventLookUp;
+ (void)initialize
{
if( self == [MyEvent class])
{
eventLookUp = [NSMutableDictionary new];
}
}
- (instancetype)eventForSystemEvent:(EKEvent*)systemEvent
{
return eventLookUp[systemEvent.calendarItemIdentifier];
}
// Instance creation
- (instancetype)initWithSystemEvent:(EKEvent*)systemEvent
{
// Usual initializer
…
eventLookUp[systemEvent.calendarItemIdentifier] = systemEvent;
return self;
}
+ (instancetype)newEventWithSystemEvent:(EKEvent*)systemEvent
{
return [[self alloc] initWithSystemEvent:systemEvent];
}
#end
Typped in Safari

Where should I initialize my singleton in iOS/OS X app?

I want to initialize my singleton object which stores and manages the application settings over the entire class within my app. Also, the singleton instance should be initialized by loading the data from NSUserDefaults upon launch. However, I'm not fully sure where I should initialize the singleton upon launch.
In Cocoa app, I first wrote the singleton initialization code within applicationWillFinishLaunching:, taking parameters from NSUserDefaults. However, later I found that this doesn't work properly if I also write the singleton initialization code (taking no parameter!) within my initial view controller, set in storyboard, because the viewWillLoad:, viewDidLoad: etc. of the class of the view controller are called before the applicationWillFinishLaunching:.
So now I'm sure I should write the singleton initalization code within viewWillLoad: earlier than applicationWillFinishLaunching, but still not sure whether it is appropriate. Specifically, I know the NSApplicationMain is the first method to be called upon launch, but it seems that the next method is not anything within AppDelegate, at least if you use storyboard.
To summary, what I want to ask are the following:
What method from what class will be called after NSApplicationMain, if you use storyboard.
Where should I write my singleton initialization code within my app? I want to initialize it as soon as possible.
Does it differ between iOS and OS X app?
You should initialize it when it's first accessed. Something like this, maybe:
+ (instancetype)sharedInstance {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instance = [[self alloc] init];
});
return _instance;
}
As a side note, if you're literally only using this class as an accessor to NSUserDefaults, you might want to consider using static methods instead.
+ (id)mySpecificDataPoint {
return [[NSUserDefaults standardUserDefaults] objectForKey:#"whatever"];
}
+ (void)setMySpecificDataPoint:(id)data {
[[NSUserDefaults standardUserDefaults] setObject:data forKey:#"whatever"];
}
Or maybe a more well-designed way might be to add a category to NSUserDefaults for this purpose.
#interface NSUserDefaults (MyData)
#property (nonatomic) NSString *someDataPoint;
#property (nonatomic) NSInteger somePrimitiveDataPoint;
#end
#implementation NSUserDefaults (MyData)
- (NSString *)someDataPoint {
return [self objectForKey:#"someDataPoint"];
}
- (void)setSomeDataPoint:(NSString *)someDataPoint {
[self setObject:someDataPoint forKey:#"someDataPoint"];
}
- (NSInteger)somePrimitiveDataPoint {
return [[self objectForKey:#"somePrimitiveDataPoint"] integerValue];
}
- (void)setSomePrimitiveDataPoint:(NSInteger)somePrimitiveDataPoint {
[self setObject:#(somePrimitiveDataPoint) forKey:#"somePrimitiveDataPoint"];
}
#end
You init the singleton when you have to use it. So as Daji Djan said: lazy wins. Just take attention that, you should not do a long-run process in your applicationWillFinishLaunching, it should return as soon as possible.
If the singleton is not mandatory during applicationWillFinishLaunching, you should call it in viewWillAppear of first view controller if you need to initialize it ASAP.
lazy always wins
if you can get away with it: as late as possible :) AND always do the minimum needed (but do as much as is reasonable to keep your code clean!)

iOS: Singleton Pattern for Data File

Class name MyData has 75+ properties that are needed in throughout 7 Scenes.
Currently, I pass the the instance of MyData file with the code below:
in SceneOne:
MyData *myData = [[MyData alloc]init];
-(void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
UIViewController *destinationViewController = segue.destinationViewController;
SceneTwo *sceneTwo = [destinationViewController isKindOfClass:[SceneTwo class]] ? (SceneTwo*)destinationViewController : nil;
sceneTwo.myData = self.myData;
}
This allows me to access any properties as myData.anyProperty
When the 7th Scene is dismissed, I set myData = NULL, and the app returns to SceneOne and a new instance of MyData is initialized.
I'm trying to accomplish above via sharedInstance.
MyData.h
#import <foundation/Foundation.h>
#interface MyData : NSObject {
#property (nonatomic, retain) NSString *someProperty;
// 74 other properties
+ (id)sharedData;
#end
MyData.m
#import "MyData.h"
#implementation MyData
#synthesize someProperty;
+ (id)sharedData {
static Mydata *sharedData = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedData = [[self alloc] init];
});
return sharedData;
}
#end
Question 1: Will the code above work to access the same instance with the code below in any of the Scenes:
MyData *myData = [MyData sharedData];
Question 2: Can I still access the files as myData.anyProperty ?
Question 3: How do I trigger a new instance of MyData and delete the current instance?
Question 4: I'm downloading a myData file off the web that's a duplicate of MyData class but the properties contain data, and I want Singelton to provide a new instance with the data from the downloaded file, what would be the code for that? i.e myData = [newDownloadedFile copy];
Question 5: Is there an advantage of using Singleton Method Vs. my current method?
Yes
Yes
You don't. Why do you believe this is necessary? Can you instead add a reset method to MyData?
You shouldn't mix the singleton pattern with a multiple-instance usage case. If you truly want a singleton, think about adding an additional layer to your data set. For example, you may have a local data and remote data configuration. If this is what you want, you may have to change the interface (header) of MyData to make this possible.
A singleton is a single instance of a class across a process. When you want to access the same collection of data from multiple locations in your code, a singleton object is one way you can accomplish this. Otherwise you need to instantiate an object and pass its address to all interested classes so they are each accessing the same instance. That's an oversimplification but I believe it addresses your question.
Regarding your comment for number 3, if you have a singleton, you don't want to reset the data for the entire app if you simply don't need the data in one place anymore. So consider the impact that would have. If you no longer need the MyData object, simply don't use it anymore. Singleton objects typically persist for the lifetime of an app, so it is not common to release/dispose of the object.
For number 4, say you currently have a property called player with a method declaration like this:
- (Player *)currentPlayer;
If you have multiple configurations available, you would add a parameter to your method interface and implementation like this:
- (Player *)currentPlayerForConfiguration:(NSInteger)configuration;
You could decide to use a number, string, or something else to differentiate between different configurations of your data. If you use a number, 0 could be local, 1 could be remote, 2 could be either (for example, check local data first, and if not there, then check remote). If you only have two options, you could use a BOOL and define your method like this:
- (Player *)currentPlayerUsingLocalData:(BOOL)useLocalData;

NSDictionary: method only defined for abstract class. My app crashed

My app crashed after I called addImageToQueue. I added initWithObjects: forKeys: count: but it doesn't helped me.
Terminating app due to uncaught exception 'NSInvalidArgumentException',
reason: '*** -[NSDictionary initWithObjects:forKeys:count:]:
method only defined for abstract class.
Define -[DictionaryWithTag initWithObjects:forKeys:count:]!'
my code
- (void)addImageToQueue:(NSDictionary *)dict
{
DictionaryWithTag *dictTag = [DictionaryWithTag dictionaryWithDictionary:dict];
}
#interface DictionaryWithTag : NSDictionary
#property (nonatomic, assign) int tag;
- (id)initWithObjects:(id *)objects forKeys:(id *)keys count:(NSUInteger)count;
#end
#implementation DictionaryWithTag
#synthesize tag;
- (id)initWithObjects:(id *)objects forKeys:(id *)keys count:(NSUInteger)count
{
return [super initWithObjects:objects forKeys:keys count:count];
}
#end
Are you subclassing NSDictionary? That's not a common thing to do in Cocoa-land, which might explain why you're not seeing the results you expect.
NSDictionary is a class cluster. That means that you never actually work with an instance of NSDictionary, but rather with one of its private subclasses. See Apple's description of a class cluster here. From that doc:
You create and interact with instances of the cluster just as you would any other class. Behind the scenes, though, when you create an instance of the public class, the class returns an object of the appropriate subclass based on the creation method that you invoke. (You don’t, and can’t, choose the actual class of the instance.)
What your error message is telling you is that if you want to subclass NSDictionary, you have to implement your own backend storage for it (for example by writing a hash table in C). It's not just asking you to declare that method, it's asking you to write it from scratch, handling the storage yourself. That's because subclassing a class cluster directly like that is the same as saying you want to provide a new implementation for how dictionaries work. As I'm sure you can tell, that's a significant task.
Assuming you definitely want to subclass NSDictionary, your best bet is to write your subclass to contain a normal NSMutableDictionary as a property, and use that to handle your storage. This tutorial shows you one way to do that. That's not actually that hard, you just need to pass the required methods through to your dictionary property.
You could also try using associative references, which "simulate the addition of object instance variables to an existing class". That way you could associate an NSNumber with your existing dictionary to represent the tag, and no subclassing is needed.
Of course, you could also just have tag as a key in the dictionary, and store the value inside it like any other dictionary key.
From https://stackoverflow.com/a/1191351/467588, this is what I did to make a subclass of NSDictionary works. I just declare an NSDictionary as an instance variable of my class and add some more required methods. It's called "Composite Object" - thanks #mahboudz.
#interface MyCustomNSDictionary : NSDictionary {
NSDictionary *_dict;
}
#end
#implementation MyCustomNSDictionary
- (id)initWithObjects:(const id [])objects forKeys:(const id [])keys count:(NSUInteger)cnt {
_dict = [NSDictionary dictionaryWithObjects:objects forKeys:keys count:cnt];
return self;
}
- (NSUInteger)count {
return [_dict count];
}
- (id)objectForKey:(id)aKey {
return [_dict objectForKey:aKey];
}
- (NSEnumerator *)keyEnumerator {
return [_dict keyEnumerator];
}
#end
I just did a little trick.
I'm not sure that its the best solution (or even it is good to do it).
#interface MyDictionary : NSDictionary
#end
#implementation MyDictionary
+ (id) allocMyDictionary
{
return [[self alloc] init];
}
- (id) init
{
self = (MyDictionary *)[[NSDictionary alloc] init];
return self;
}
#end
This worked fine for me.

Resources