0

I have a class City and the list of cities are stored inside a Plist file. I want to load the cities from the file just once, and to have access to the NSArray of cities from a shared instance.

What I want*

I want to have this behavior

/* here if the array cities is nil just load it from plist if not use    the existent array */
City* myCity = [City getCityById:@"1"]; 

Also

/* inside instance method initWithId I should have access to the loaded array of 
 cities or load it one time */

City *aCity = [[City alloc]initWithId:@"4"]

What I have tried:

City.h

@interface City : NSObject

@property                     NSInteger  cityId; // instance variable
@property (strong, nonatomic) NSString   *name; // instance variable
@property (strong, nonatomic) CLLocation *location; // instance variable

@property (strong, nonatomic) NSArray    *cities; // want it to act like a Class variable

-(id) initWithDictionary: (NSDictionary *) dictionay; // instance method
-(id) initWithCityId: (NSDictionary *) cityId; // instance method

+(instancetype)sharedInstance; // Singleton object
+(City*) getCityById:(NSString*) id; // the file need to be loaded! 

@end

City.m

@implementation City

// This method load all cities from a plist file

-(void)loadCitiesFromPlist
{
    NSMutableArray *citiesArray = [NSMutableArray arrayWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"cities" ofType:@"plist"]]; 
    self.cities = citiesArray;
}

+ (instancetype) sharedInstance
{
    // structure used to test whether the block has completed or not
    static dispatch_once_t p = 0;

    // initialize sharedObject as nil (first call only)
    __strong static id _sharedObject = nil;

    // executes a block object once and only once for the lifetime of an application
    dispatch_once(&p, ^{
        _sharedObject = [[self alloc] init];
       [_sharedObject loadCitiesFromPlist];

    });

    // returns the same object each time
    return _sharedObject;
}

+(City*) getCityById:(NSString*)id
{
  // can't access the cities array
}
@end

Problem :

  • Can't access the cities variable from Class methods ( may be store it as static variable ?)
  • If I will use declare getCityById as a instance method, I will load the plist file before search for city because I am not sure that the file has been loaded before.

Note: The instantiation of the object City will happen many times, as I have users and each user belong to a city, but in the same time I want to have a shared instance (Singleton) to manage the load of the file JUST ONE TIME and to handle the class methods that uses the array of cities like getCityById and getIdOfCityByName

4
  • If you're going to use a shared instance, it's an instance, so you don't need class methods but just normal methods. So myCity = [City sharedInstance] will give you the unique shared instance, where you can access the property cities. So why make getCityById a class method? Also, use lazy initialization by creating your own getter for the cities property: that way you fetch from file if nil, otherwise you just return the ivar. Commented Jul 7, 2015 at 13:34
  • because if it's a class method I will be sure that the file cities.plist is already loaded, but with instance methods I will always load the file and then proceed. Like that I will loose the main purpose of using the Singleton. Basically, I want a shared instance to handle the tasks related to the load and the search of the data of the plist file. And in the same time have a behavior of a normal model class (store data). Commented Jul 7, 2015 at 13:38
  • I'm not a big fan of singletons, you probably should use the class initialization method to create a static array once and access it in each city instance. But if you want to use singleton pattern: a singleton is just an instance, so you need an instance method to use it. In your case, you should split in two classes, because your singleton class is a totally different object than your City class. The singleton object would hold all cities, whereas a City object is just what it says, one city. Commented Jul 7, 2015 at 13:49
  • @dirkgroten thank you for the update, actually I have updated my question please reade the section "what I want" in the first paragraph and if your solution can handle what I want it will be great if you share some code about the implementation in an answer and I will accept it Commented Jul 7, 2015 at 14:01

2 Answers 2

1

Check out +(void)initialize of NSObject. Link

It is called for any Class once. Here you could load your list in a static array and use it in each instance.

Or consider an approach like this : SO answer

Sign up to request clarification or add additional context in comments.

7 Comments

I am trying to implement the solution of +(void)initialize. The only issue is that I have a method - (id) initWithCityId: (NSDictionary *) cityId and with this implementation it does not have access to the static variable. should I convert it to class method. Or just use instead the method +(City*)getCityById: ?
The array in which the cities will be stored will have to be a class level variable static NSArray * _cities. I guess in your getCityById you want to iterate over this array and return a city object.
Also, you could modify your existing code and declare citiesArray as static ( class-level ), initialize it once in dispatch_once like you already do, and use it after that.
Sorry I mean - (id) initWithCityId: (NSString*) cityId not a NSDictionary
I ended up using your solution I have edited your answer with a code example, can you review the code and let me know if we can improve it ?
|
0

Solution:

Using the method + (void)initialize : Apple Documentation And we declare the variable as static

City.m

#import "City.h"

static NSArray *cities = nil;

@implementation City

+ (void)initialize
{
    if (self == [City self])
    {
        if (!cities)
        { 
            cities = [self loadCitiesFromPlist];
        }
    }
}

-(id) initWithDictionary: (NSDictionary *) dictionay
{
    self = [super init];
    // convert the NSDictionary to City object
    return self;
}


// This method load all cities from the file

+(NSArray *) loadCitiesFromPlist
{
    NSLog(@"loadCitiesFromPlist");

    NSMutableArray *citiesArray = [NSMutableArray arrayWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"cities" ofType:@"plist"]];

    NSMutableArray *citiesObjectsArray = [[NSMutableArray alloc] init];

    for (NSDictionary *cityDict in citiesArray)
    {
        [ citiesObjectsArray addObject: [[City alloc]initWithDictionary:cityDict]] ;
    }

   return citiesObjectsArray;
}

+(City*) getCityById:(NSString *) cityId
{

    NSUInteger barIndex = [cities indexOfObjectPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) {

        City* city = (City*) obj;

        if(city.cityId == [cityId intValue])
        {
            *stop = YES;
            return YES;
        }
        return NO;
    }];


    if (barIndex != NSNotFound)
    {
       return  [cities objectAtIndex:barIndex];
    }
    else
    {
        return nil;
    }

}

+(NSArray*) getAllCitiesNames
{
    NSMutableArray *citiesNames =[[NSMutableArray alloc] init];

    for(City* city in cities)
    {
        [citiesNames addObject:city.name];
    }

    return citiesNames;
}

@end

More Information in this SO answer

2 Comments

I would make it clearer that you are initialising the cities array in the initialise method: if (!cities)... and cities = [City loadCitiesFromPlist] where you return an NSArray instead of void in loadCitiesFromPlist. Just makes it more readable.
@dirkgroten you are right, I have changed the code as you suggested.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.