使用带有NSThread的Singleton同步数组

tes*_*dtv 5 iphone cocoa-touch objective-c nsxmlparser uisearchbar

我有一个带有UISearchBar的书籍应用程序,用户可以在其中键入任何书名并在下面输入搜索结果(来自ext API调用).

我在我的应用程序中使用了一个名为retrieveArray的单例变量,它存储了所有书籍.

@interface Shared : NSObject {
    NSMutableArray *books;
}

@property (nonatomic, retain) NSMutableArray *books;

+ (id)sharedManager;

@end
Run Code Online (Sandbox Code Playgroud)

使用NSMutableArray*retrieveArray在多个.m文件中访问它; ...在头文件中

retrievedArray = [[Shared sharedManager] books];
Run Code Online (Sandbox Code Playgroud)

我的问题是如何确保ret​​rieveArray中的值在所有类中保持同步.

实际上,retrieveArray中的值通过NSXMLParser(即通过外部Web服务API)添加.有一个单独的XMLParser.m文件,我在那里进行所有解析并填充数组.解析在单独的线程上完成.

    - (void) run: (id) param  {
        NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

        NSXMLParser *parser = [[NSXMLParser alloc] initWithContentsOfURL: [self URL]];
        [parser setDelegate: self];
    [parser parse];
        [parser release];

        NSString *tmpURLStr = [[self URL]absoluteString];

        NSRange range_srch_book = [tmpURLStr rangeOfString:@"v1/books"];

        if (range_srch_book.location != NSNotFound) 
            [delegate performSelectorOnMainThread:@selector(parseDidComplete_srch_book) withObject:nil waitUntilDone:YES];

        [pool release];
    } 


    - (void) parseXMLFile: (NSURL *) url
    {   
        NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
        [self setURL: url];
        NSThread* myThread = [[NSThread alloc] initWithTarget:self
                                                     selector:@selector(run:)


object: nil];
    [retrievedArray removeAllObjects];
    [myThread start];
    [pool release];
}
Run Code Online (Sandbox Code Playgroud)

如果用户输入很快就会出现一些同步问题(如果用户输入的速度很慢,它似乎工作正常)....所以有2个视图,其中显示了该共享数组项中对象的内容; 清单和细节.如果用户快速键入并在列表视图中单击A,则在详细视图中显示B ...这是主要问题.

我已经尝试过所有我能想到的解决方案,但仍然无法解决问题.

编辑同步问题示例:在列表视图中,如果显示3个项目,比如说Item1,Item2和Item3,如果用户点击Item2,他会在详细视图中显示Item3(即说不是正确的细节)

下面是单击列表视图中的项目时执行的代码;

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    // Navigation logic -- create and push a new view controller

    if(bookdetailCustom == nil)
        bookdetailCustom = [[BookDetailCustom alloc] initWithNibName:@"BookDetailCustom" bundle:[NSBundle mainBundle]];

    //aBook = [retrievedArray objectAtIndex:indexPath.row];

    bookdetailCustom.selectedIndex = indexPath.row;

    [self.navigationController pushViewController:bookdetailCustom animated:YES];
    [bookdetailCustom release];
    bookdetailCustom = nil;
}
Run Code Online (Sandbox Code Playgroud)

以下是searchTabkleView的外观

- (void) searchTableView {
    NSString *searchText = searchBar.text;
    NSMutableArray *searchArray = [[NSMutableArray alloc] init];

    for (int i=0;i<[retrievedArray count];i++)
    {
        Stock *aBookTemp = [retrievedArray objectAtIndex:i];
        NSString *temp = [aBookTemp valueForKey:@"BookName"];
        [searchArray addObject:temp];
    }

    for (NSString *sTemp in searchArray)
    {
        NSRange titleResultsRange = [sTemp rangeOfString:searchText options:NSCaseInsensitiveSearch];

        if (titleResultsRange.length > 0)
            [copyListOfItems addObject:sTemp];
    }

    [searchArray release];
    searchArray = nil;
}
Run Code Online (Sandbox Code Playgroud)

请提供一些合适的修复方法.

Ano*_*mie 5

根据您发布的内容,每个retrieveArray都指向相同的NSMutableArray对象.因此,没有任何单独的数组可以保持同步,它们都是相同的数组.

但是,NSMutableArray不是线程安全的; 如果一个线程正在改变它而另一个线程正在读取它,事情可能会爆炸.简单地将属性从非原子更改为原子是不够的,因为它只包括获取数组对象本身而不是后续方法调用来访问数组内的元素.但是,我不认为这会导致您的主要问题,并且修复此问题应该可以避免线程安全问题.

我想事件的顺序是这样的:

  1. 列表视图显示一组结果,其中包括索引为N的A.
  2. 用户输入内容.XML解析器开始逐步更新共享数组.列表视图尚未更新.
  3. 用户在列表视图中触摸索引N处的项目.列表视图指示"详细信息"视图显示索引N处的项目.
  4. 详细信息视图从共享阵列中提取索引N处的项目,但由于在步骤2中开始更新,索引N现在包含B.其中显示详细信息视图.
  5. 在某些时候,XML解析完成,现在List已更新.

如果来自Web服务的加载和解析足够慢,那么第4步也应该可以简单地使用NSRangeException崩溃.

一种解决方案是List中的每个项目都可以保存实际的结果对象并将其传递给Detail视图而不仅仅是索引.在这种情况下,如果List和Detail是唯一的使用者,或者可以更改任何其他使用者以相同的方式获取对象而不是索引,则可能可以完全摆脱共享数组.另一个是解析器将结果累积到私有数组中,并在发出List视图信号以更新自身之前立即更新共享数组; 在后台线程的更新和主线程上的方法调用之间的时间间隔内仍有可能发生竞争,但窗口可能相当小.

或者我对更新工作原理的猜测完全错误,在这种情况下你应该提供更多细节.