Ios 线程安全和;集合在枚举时发生了变异;
我有一个“Ad”类,我在Ad.m中设置了一个静态变量: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
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时一直加载它?或者