0

我有一个“广告”类,在其中我在 Ad.m 中设置了一个静态变量:

static NSDictionary *citiesDict = nil; // class variable 

在同一个文件中,我实现了一个类方法,如果尚未加载,则基本上加载城市名称及其索引号的 plist 文件,最后将作为参数传递的数字值转换为城市名称:

+(NSString *) cityFromNumberValue:(NSString *)cityNumberValue
{   
    // load the cities from the plist file named "cities" if it's not already loaded
    if (!citiesDict)
    {  
        NSString *path = [[NSBundle mainBundle] pathForResource:@"cities" ofType:@"plist"];

       citiesDict = [[NSDictionary alloc ]initWithContentsOfFile:path];
       NSLog(@"loading plist");

    }

NSLog(@"will return value");
NSArray *temp = [ citiesDict allKeysForObject:cityNumberValue];  
NSString *key = [temp lastObject]; 
return key ;
}

并且总是在同一个文件中,我实现了一个 init 方法来将 Dictionary 转换为 Ad 对象,其中它使用 Class 方法 +cityFromNumberValue:cityNumberValue :

-(id) initWithDictionary: (NSDictionary *) dictionay
{
    self = [super init];
    if (self)
    {
    // convert the number to name of city
    self.departureCity= [Ad cityFromNumberValue:[dictionay objectForKey:@"ad_villedepart"]]; 
    self.arrivalCity=   [Ad cityFromNumberValue:[dictionay objectForKey:@"ad_villearrivee"]]; 
    }
return self;
}

而且我在同一个文件中还有一个从 Web 服务获取广告的方法,它在 for 循环中调用方法 +cityFromNumberValue:cityNumberValue: :

+(NSDictionary *) fetchAdOfPage : (NSInteger) page PerPage : (NSInteger) perPage
{
    NSMutableArray *  adsArray = [[NSMutableArray alloc] init];
   ...... 
    NSMutableArray *array = [NSArray arrayWithContentsOfURL:url];

// convert the new fetchted dictionnaries of Ads to an Ad objects

for (NSMutableDictionary *dictAd in array) 
{ 
  // convertion using the designated initializer

    Ad *ad = [[Ad alloc]initWithDictionary:dictAd]; 
    [adsArray addObject:ad]; 

    }
      ....
    return dictFinal ;
}

在 mu 控制器的其他地方,我将这个 fetch 方法称为:

 // do request on async thread
        dispatch_queue_t fetchQ = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        dispatch_async(fetchQ, ^{

            NSDictionary * dict = [Ad fetchAdOfPage:currentPage PerPage:kAdsPerPage];

            dispatch_sync(dispatch_get_main_queue(), ^{
                .....
            });
        });

        dispatch_release(fetchQ);

现在我的问题是,上次我在模拟器中运行应用程序时出现错误“集合在被枚举时发生了变异”,它指向以下行:

 NSArray *temp = [ citiesDict allKeysForObject:cityNumberValue];

但是我第一次遇到这个错误,我之前没有得到它,我使用相同的代码三个多月,一切正常,我什至无法再次重现相同的错误!在放置 NSLog 以查看发生了什么后,我总是得到:

2013-05-21 19:39:20.141 myApp[744:3b03] loading plist
2013-05-21 19:39:20.151 myApp[744:6303] will return value
2013-05-21 19:39:20.151 myApp[744:3b03] will return value
2013-05-21 19:39:20.152 myApp[744:6303] will return value
2013-05-21 19:39:20.153 myApp[744:6303] will return value
2013-05-21 19:39:20.154 myApp[744:6303] will return value
2013-05-21 19:39:20.153 myApp[744:3b03] will return value
2013-05-21 19:39:20.155 myApp[744:6303] will return value

但有一次我得到:

2013-05-21 19:39:20.141 myApp[744:3b03] loading plist
2013-05-21 19:39:20.142 myApp[744:6303] loading plist
2013-05-21 19:39:20.151 myApp[744:6303] will return value
2013-05-21 19:39:20.151 myApp[744:3b03] will return value
2013-05-21 19:39:20.152 myApp[744:6303] will return value
2013-05-21 19:39:20.153 myApp[744:6303] will return value
2013-05-21 19:39:20.154 myApp[744:6303] will return value
2013-05-21 19:39:20.153 myApp[744:3b03] will return value
2013-05-21 19:39:20.155 myApp[744:6303] will return value

该应用程序没有崩溃,但是自从我使用 if 语句检查后,出现了两次奇怪的“加载 plist”!所以我猜有两个线程已经进入:

if (!citiesDict)

同时,然后他们俩都设置了citiesDict字典,因为该字典可以用于

 [ citiesDict allKeysForObject:cityNumberValue]; 

就在可能导致崩溃的 if 语句“枚举时集合发生突变”之后,这可能是真正的情景吗?

因为我无法再次重现错误我想知道是否添加:

   @synchronized(citiesDict)
    {
        citiesDict = [[NSDictionary alloc ]initWithContentsOfFile:path];
        NSLog(@"loading plist");
    }

可以解决这个问题吗?您有什么建议可以更好地实现更安全的实现,以及当我们不得不使用来自不同线程的相同数组并且只能从不同的线程会导致问题或问题只是在同时写入时?预先感谢您的帮助

4

3 回答 3

2

@synchronized(citiesDist) 不是最好的,但应该可以解决您的问题...无论如何,在我看来,一旦线程安全环境中的变量以这种方式使用 GCD,初始化的最佳方法是:

static dispatch_once_t once;
dispatch_once(&once, ^{
    citiesDict = [[NSDictionary alloc ]initWithContentsOfFile:path];
    NSLog(@"loading plist");
});
于 2013-05-21T19:11:49.530 回答
2

使用静态对象时,最佳实践是使用 dispatch_once,这将确保您的代码只执行一次,从而为您提供完整的线程安全

为静态对象使用 getter 方法也是一个好习惯

+ (NSDictionary *)getCitiesDict {
    static dispatch_once_t pred;
    static NSDictionary *citiesDict = nil;

    dispatch_once(&pred, ^{
        NSString *path = [[NSBundle mainBundle] pathForResource:@"cities" ofType:@"plist"];

        citiesDict = [[NSDictionary alloc ]initWithContentsOfFile:path];
    });

    return citiesDict;
}

要访问它,就像这样

[[OwnerClass getCitiesDict] valueForKey:@"key"]; // OwnerClass is the name of the class where this is defined
于 2013-05-21T19:13:43.273 回答
1

You're on the right track. In order to implement thread-safe loading of that array from within the same method, synchronize it and put a null check in it:

@synchronized(citiesDict)
{
    if (!citiesDict)
    {
        citiesDict = [[NSDictionary alloc] initWithContentsOfFile:path];
        NSLog(@"loading plist");
    }
}

Put that above code into a function that you call before you read the array. Essentially you want to avoid reading the array while it is loading.

于 2013-05-21T19:10:35.547 回答