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

Alaeddine
  • 6,104
  • 3
  • 28
  • 45
  • 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. – dirkgroten Jul 07 '15 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). – Alaeddine Jul 07 '15 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. – dirkgroten Jul 07 '15 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 – Alaeddine Jul 07 '15 at 14:01

2 Answers2

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

Community
  • 1
  • 1
Templar
  • 1,694
  • 1
  • 14
  • 32
  • 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:` ? – Alaeddine Jul 07 '15 at 14:20
  • 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. – Templar Jul 07 '15 at 14:27
  • 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. – Templar Jul 07 '15 at 14:28
  • Sorry I mean `- (id) initWithCityId: (NSString*) cityId` not a NSDictionary – Alaeddine Jul 07 '15 at 14:31
  • 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 ? – Alaeddine Jul 07 '15 at 14:56
  • the edit was rejected _"This edit was intended to address the author of the post and makes no sense as an edit. It should have been written as a comment or an answer"_ So I will add an answer to help others with an example – Alaeddine Jul 07 '15 at 14:59
  • Yeah, something like this. – Templar Jul 07 '15 at 15:23
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

Community
  • 1
  • 1
Alaeddine
  • 6,104
  • 3
  • 28
  • 45
  • 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 Jul 07 '15 at 15:41
  • @dirkgroten you are right, I have changed the code as you suggested. – Alaeddine Jul 07 '15 at 16:56