Ios 线程安全和;集合在枚举时发生了变异;

Ios 线程安全和;集合在枚举时发生了变异;,ios,objective-c,multithreading,performance,crash,Ios,Objective C,Multithreading,Performance,Crash,我有一个“Ad”类,我在Ad.m中设置了一个静态变量: static NSDictionary *citiesDict = nil; // class variable 在同一个文件中,我实现了一个类方法,基本上加载一个包含城市名称及其索引号的plist文件(如果尚未加载),并最终将作为参数传递的数字值转换为城市名称: +(NSString *) cityFromNumberValue:(NSString *)cityNumberValue { // load the citi

我有一个“Ad”类,我在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方法,将字典转换为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:inside:

+(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
应用程序没有崩溃,但有一个奇怪的“加载plist”两次,因为我已经检查使用if语句!所以我猜有两个线程已经进入:

if (!citiesDict)
同时,他们两个都设置了citiesDict字典,因为这本字典可以在

 [ citiesDict allKeysForObject:cityNumberValue]; 
就在可能导致崩溃的if语句“集合在枚举时发生了变异”之后,这是真正的senario吗

由于我无法再次复制错误,我想知道是否添加:

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

你能解决这个问题吗?您对更好地实现更安全的实现有什么建议吗?当我们必须使用来自不同线程的同一数组时,我们通常如何避免“集合在枚举时发生了变异”错误,仅仅从不同线程读取数组的内容会导致问题吗?或者问题只是在同时写入时发生的?提前感谢您的帮助

您走上了正确的道路。为了从同一方法中实现该数组的线程安全加载,请同步该数组并在其中进行空检查:

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

在读取数组之前,将上述代码放入调用的函数中。基本上,您希望避免在加载数组时读取它。

您的思路是正确的。为了从同一方法中实现该数组的线程安全加载,请同步该数组并在其中进行空检查:

@synchronized(citiesDict)
{
    if (!citiesDict)
    {
        citiesDict = [[NSDictionary alloc] initWithContentsOfFile:path];
        NSLog(@"loading plist");
    }
}
在读取数组之前,将上述代码放入调用的函数中。基本上,您希望避免在加载阵列时读取阵列。

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

static dispatch_once_t once;
dispatch_once(&once, ^{
    citiesDict = [[NSDictionary alloc ]initWithContentsOfFile:path];
    NSLog(@"loading plist");
});
@synchronized(citiesDist)不是最好的,但应该可以解决您的问题。。。无论如何,在我看来,在线程安全环境中初始化变量的最佳方法是以以下方式使用GCD:

static dispatch_once_t once;
dispatch_once(&once, ^{
    citiesDict = [[NSDictionary alloc ]initWithContentsOfFile:path];
    NSLog(@"loading plist");
});

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

对静态对象使用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

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

对静态对象使用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

谢谢您的回答,但是我应该在加载plist之前测试它是否已经设置好,以避免在调用getter时一直加载它吗?或者,我将通过执行
if(!citiesDict)
?无需检查来从调用getter的位置进行检查。dispatch_once块在应用程序的生命周期内只执行一次,这意味着citiesDict将只创建一次,并且将一直处于活动状态并准备就绪。只要打电话给[self getCitiesDict],你就会再次感谢你的重播,只要你不介意最后一个问题,实际上,还有其他处理城市的类方法,它们需要相同的字典,它们测试如果没有加载它,它们从plist加载它,它们有这个代码,可以在同一时间执行:
if(!citiesDict){NSString*path=[[NSBundle mainBundle]pathForResource:@“cities”of type:@“plist”];citiesDict=[[NSDictionary alloc]initWithContentsOfFile:path];}
它安全吗?在使用dispatch_实现getter解决方案一次之后,我可以面对任何问题吗?它是安全的,我们在存储超过3年的应用程序中使用它。如果您需要从外部访问字典,则需要定义
+(NSDictionary*)getCitiesDict;
在你的头文件中。这样,你只会加载一次,即使你的应用程序中有多个地方可以访问它。如果你想了解更多关于obj-c dispatchers的信息,我建议你查阅文档,它们很标准,尽管看起来不是很好看。祝你好运:)它可以正常工作:)但是我不明白的是,我们在方法中声明了变量
static NSDictionary*citiesDict=nil;
,并且在应用程序运行时,它仍然可以从外部和其他对象访问,请您向我解释它是如何工作的,特别是像您的解决方案中声明的变量,谢谢提前感谢您的回答,但是我是否应该在加载plist之前测试它是否已经设置好,以避免在调用getter时一直加载它?或者