2011-02-27 41 views
5

我有一個帶有UISearchBar的書籍應用程序,用戶輸入任何書名並在鍵入時獲取搜索結果(來自外部API調用)。使用帶有NSThread的單例同步數組

我在我的應用程序中使用了一個名爲retrieveArray的singleton變量,它存儲所有書籍。

@interface Shared : NSObject { 
    NSMutableArray *books; 
} 

@property (nonatomic, retain) NSMutableArray *books; 

+ (id)sharedManager; 

@end 

這是訪問多個.m文件使用NSMutableArray * retrieveArray; ...在頭文件中

retrievedArray = [[Shared sharedManager] books]; 

我的問題是如何確保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]; 
} 

似乎有一些同步問題如果用戶鍵入速度非常快(這似乎是工作的罰款,如果慢的用戶類型)......因此,有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; 
} 

這裏是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; 
} 

請建議一些適合的修復。

回答

5

從你發佈的內容來看,每個retrieveArray都指向同一個NSMutableArray對象。所以沒有任何單獨的數組保持同步,它們都是相同的數組。

但是,NSMutableArray不是線程安全的;如果一個線程在改變它而另一個線程正在讀取它,事情可能會炸燬。簡單地將屬性從非原子更改爲原子是不夠的,因爲它只涉及獲取數組對象本身,而不涉及訪問數組內部元素的後續方法調用。不過,我認爲這不會導致您的主要問題,並且解決方案應該可以避免線程安全問題。

我想事件的順序是這樣的:

  1. 列表視圖顯示的是一組結果,其包括在索引N.
  2. 用戶類型的東西。 XML解析器開始逐步更新共享數組。列表視圖尚未更新。
  3. 用戶觸摸列表視圖中位於索引N處的項目。列表視圖指示「詳細信息」視圖在索引N處顯示項目。
  4. 「細節」視圖從共享數組中提取索引N處的項目,但由於在步驟2中啓動的更新,索引N現在包含B.其中,查看顯示。
  5. 在某些時候,XML解析完成,現在更新列表。

如果來自Web服務的加載和解析速度足夠慢,則步驟4也可能會因NSRangeException而導致崩潰。

一個解決方案將是對List中的每個項目保存實際的結果對象,並將其傳遞給Detail視圖而不僅僅是索引。在這種情況下,如果List和Detail是唯一的消費者,或者可以更改任何其他消費者以相同的方式獲取對象而不是索引,那麼您可能會完全擺脫共享數組。另一種方法是解析器將結果累加到專用數組中,並在發送List視圖以更新自身之前立即更新共享數組;在後臺線程的更新和主線程的方法調用之間的時間間隔內,競爭仍然存在一點小小的可能性,但是該窗口可能比較小。

或者我可能完全錯誤的猜測更新是如何工作的,在這種情況下,您應該提供更多的細節。

+0

嘿,失戀......我很難在這裏解釋這個問題。但你似乎很瞭解這個問題非常準確...... 現在來解決問題,我想要走向你提出的第二種方法。 「 」另一種方法是解析器將結果累積到私有數組中,並且在發送List視圖以更新自身之前一次更新共享數組「 」您是否可以提供僞代碼你試圖說。我可以在我的應用程序中實現相同的功能,並查看它是否有效。 – testndtv 2011-02-28 18:53:01

+0

但是,正如我所說的,因爲只有當用戶鍵入的速度非常快時纔會出現問題,這似乎與更新2個地方的陣列所花費的時間有關。再次感謝您對此的所有幫助。我已盡最大努力解決這個問題,沒有任何運氣,現在我真的很想解決這個問題。 – testndtv 2011-02-28 18:53:22

+0

在您的NSXMLParserDelegate方法中,您必須將對象添加到retrieveArray中。 相反,在retrieveArray字段旁邊添加'temporaryArray'字段,在調用'[parser parse]之前將其設置爲新的NSMutableArray,將結果添加到delegate方法中的temporaryArray中,然後在[[parser parse]後面]返回調用'[retrieveArray replaceObjectsInRange:NSMakeRange(0,retrieveArray.count)withObjectsFromArray:temporaryArray]'。 – Anomie 2011-02-28 19:05:35

2

我最初建議您從屬性聲明中刪除nonatomic關鍵字。原子是默認設置(沒有atomic設置,省略nonatomic就足夠了) - 這將通過將合成的設置器包裝在@synchronize塊中來處理線程安全。

不幸的是,很多人已經學會了將nonatomic放在他們的代碼中,但沒有真正理解它。我一直認爲這是通過複製/粘貼Apple示例代碼 - 它們經常用於UI相關的東西 - 請記住UIKit不是線程安全的。

Anomie在他/她的回答中指出,這不是 - 很可能 - 因爲你正在從不同的線程變異一個可變數組。 對我來說聽起來像是正確的答案 - 我會刪除我的答案,但我會把它留在這裏,因爲我認爲我的評論是有價值的(但與您的問題沒有100%相關)。

+0

感謝您的回覆...我從Shared.h文件中刪除了nonatomic @property(retain)NSMutableArray * books; 但仍然存在問題... – testndtv 2011-02-27 13:33:27

+1

擁有一個原子getter和setter意味着獲取和設置數組將是原子的,但訪問數組的內容仍然不會。你說得對,它應該是原子的,但他也需要圍繞修改/讀取數組的代碼使用@synchronize。使用@ Anomie在更新期間使用單獨數組的解決方案會更簡單和更高效,在這種情況下,原子設置器就足夠了。 – ughoavgfhw 2011-03-08 05:31:13

+0

如果Shared對象具有@synchronized方法來操作books數組,則可以解決對數組內容的併發訪問。 – 2011-03-11 01:40:29

0

嘗試在數組的訪問器中使用NSRecursiveLock。

查看NSRecursiveLock文檔。從概覽:

NSRecursiveLock定義了可以由同一個線程,而不會引起死鎖,其中一個線程被永久阻塞,等待本身放棄一個鎖的情況下獲取多次的鎖。雖然鎖定線程有一個或多個鎖,但是所有其他線程都無法訪問由鎖保護的代碼。

CoreVideo示例代碼具有正確使用的示例。

+0

我從來沒有聽說過或使用過NSRecursiveLock。您能否詳細說明如何在我的應用程序中實現相同的功能。 – testndtv 2011-03-04 05:11:16

+0

爲什麼遞歸鎖定?不應該有正常的鎖? – ughoavgfhw 2011-03-08 05:28:21

0

問題是retrievedArray被兩個線程引用。從XML解析代碼中刪除對retrievedArray的所有引用,並僅在主線程上更改它。

具體的過程:

  1. 變化parseXMLFile:創建一個新的數組:parsedArray = [NSMutableArray array]
  2. 變化parser:didEndElement:要追加到此新數組:[parsedArray addObject:aBook]
  3. parser:didEndDocument:通過新的陣列關閉的主線程:

    [delegate performSelectorOnMainThread: @selector(updateRetrievedArray:) 
              withObject: parsedArray 
             waitUntilDone: NO]; 
    
  4. updateRetrievedArray:主線程上運行將負責更新retrievedArray代碼 - 這種方式只有一個線程改變這個對象:

    - (void) updateRetrievedArray: (NSArray *)parsedArray { 
        [retrievedArray setArray:parsedArray]; 
        [self parseDidComplete_srch_book]; // Be sure to call [tableView reloadData] 
    } 
    
+0

retrieveArray是一個共享數組,在XMLParser中進行如下更新。首先,每次解析完成後都會刪除所有對象; (void)parseXMLFile:(NSURL *)url {\t \t [retrieveArray removeAllObjects]; .... } 然後在 解析器:didEndElement( F([的ElementName isEqualToString:@ 「BookDetails」]) \t \t [retrievedArray ADDOBJECT:ABOOK]; } 請讓我知道如果您需要任何額外的詳細信息, – testndtv 2011-03-05 13:19:37

+0

我根據您的評論更新了我的答案。試試看,如果它不能解決您的問題,請告訴我。 – skue 2011-03-06 22:26:16

+0

是的確定..我會嘗試在我的應用中執行相同的操作。我會回來的調查結果。 – testndtv 2011-03-07 05:12:20

1

我明白,你已經投入了大量的時間和精力解決這個問題和Anomie的解決方案是最佳的。但也許一種不同的方法可能更容易實施。

例如,您可以讓解析器處理數據並將其提供給Core Data存儲。該列表將依次由NSFetchedResultsController提供。控制器會自動處理表格內容以及需要完成的任何同步。

這是值得一試,我希望它有幫助。