I made a bug when I implement the search function. I opened an asynchronous thread. But when deleting a character (a digit of a phone number), the app would crash.
Error:
*** Terminating app due to uncaught exception 'NSGenericException', reason: '*** Collection <__NSArrayM: 0x117d7320> was mutated while being enumerated.'
Code:
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText{
//
self.isSearch = YES;
//remove the last search all the contacts
[self.resultArr removeAllObjects];
//
[self.rcs_SearchTableView reloadData];
//
dispatch_queue_t uploadQueue = dispatch_get_global_queue(0, 0);
dispatch_queue_t getMainQueue = dispatch_get_main_queue();
dispatch_async(uploadQueue, ^{
NSMutableArray *phoneArr = (NSMutableArray *)[self rcs_GetPhoneNumberFromeDatabaseWithPhone:searchText];
//
//NSLog(#"清空上次搜索的数据:%#", self.resultArr);
//NSLog(#"输入的关键字是---%#---%lu",searchText,(unsigned long)searchText.length);
if (0 == searchText.length || [searchText isEqualToString:#" "]) {
self.isSearch = NO;
//[self.rcs_SearchTableView reloadData];
[self.resultArr removeAllObjects];
}
//[self.rcs_SearchTableView reloadData];
if (0 != phoneArr.count) {
//
for (NSUInteger i = 0; i < phoneArr.count; i ++) {
RCSPhoneModel *flagPhoneModel = phoneArr[i];
for (NSUInteger i = 0; i < self.rcsRecentSearchDataSource.count; i ++) {
RCSContactModel *flagModel = self.rcsRecentSearchDataSource[i];
if ([flagPhoneModel.serverId isEqualToString:flagModel.serverId] || [flagPhoneModel.phone isEqualToString:flagModel.name]) {
//the same contact has multiple Numbers To prevent repeated add the same contacts
if (![self.resultArr containsObject:flagModel]) {
[self.resultArr addObject:flagModel];
continue;
}
}
}
}
}else{
//search contacts by name
for (NSInteger i = 0; i < self.rcsRecentSearchDataSource.count; i ++) {
RCSContactModel *model = self.rcsRecentSearchDataSource[i];
NSString *nameStr = model.name;
if (nameStr.length >= searchText.length) {
//search all the name
if ([nameStr containsString:searchText]) {
[self.resultArr addObject:model];
}
}
}
}
//
if (self.resultArr.count > 0) {
self.isSearch = YES;
//[self.rcs_SearchTableView reloadData];
}
//The phone contacts or local contact synchronized to the server
dispatch_async(getMainQueue, ^{
[self.rcs_SearchTableView reloadData];
});
});
}
A for loop should not enumerate anything that could change on any other thread or that could change within that loop. You should only enumerate an object that you are certain is not going to change while being enumerated (either in another thread, or within the loop itself). One way to do this is to only use a local copy of the array to enumerate over.
I can't see where anything being enumerated in your for loops is changed within the loop, so I would guess that in some other code in some other thread, you are changing either self.rcsRecentSearchDataSource or phoneArr. This crashes the for loop that enumerates self.rcsRecentSearchDataSource or phoneArr because it is required to not change while being enumerated.
Does this really need to be run on a separate thread?
If so, use a thread-local copy of the array to enumerate over, instead of the original array. That way you can be sure that nothing else can modify it, because it does not exist in any other scope.
Eg, there are two places where you could change your code to:
NSArray *localSearchDataSource = [self.rcsRecentSearchDataSource copy];
for (NSUInteger i = 0; i < localSearchDataSource.count; i ++) {
and one place where you could change to:
NSArray *localPhoneArr = [[self rcs_GetPhoneNumberFromeDatabaseWithPhone:searchText] copy];
for (NSUInteger i = 0; i < localPhoneArr .count; i ++) {
I got it answer and like unders codes:
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText{
//
self.isSearch = YES;
//
if (0 == searchText.length || [searchText isEqualToString:#" "]) {
self.isSearch = NO;
//[self.resultArr removeAllObjects];
}
//Remove the last search all the contacts
[self.resultArr removeAllObjects];
//
[self.rcs_SearchTableView reloadData];
//
NSMutableArray *localSearchDataSource = [self.resultArr mutableCopy];
//Create an array of objects as well as the original array
//According to the input access to the phone number of the data
NSArray *localPhoneArr = [[self rcs_GetPhoneNumberFromeDatabaseWithPhone:searchText] copy];
//
//[self.rcs_SearchTableView reloadData];
if (0 != localPhoneArr.count) {
//Because when data matching number takes longer, using asynchronous thread
dispatch_queue_t uploadQueue = dispatch_get_global_queue(0, 0);
dispatch_queue_t getMainQueue = dispatch_get_main_queue();
dispatch_async(uploadQueue, ^{
//
for (NSUInteger i = 0; i < localPhoneArr.count; i ++) {
RCSPhoneModel *flagPhoneModel = localPhoneArr[i];
for (NSUInteger i = 0; i < self.rcsRecentSearchDataSource.count; i ++) {
RCSContactModel *flagModel = self.rcsRecentSearchDataSource[i];
if ([flagPhoneModel.serverId isEqualToString:flagModel.serverId] || [flagPhoneModel.phone isEqualToString:flagModel.name]) {
//The same contact has multiple Numbers To prevent repeated add the same contacts
if (![localSearchDataSource containsObject:flagModel]) {
[localSearchDataSource addObject:flagModel];
}
}
}
}
//Add the search results to the search data source
dispatch_async(getMainQueue, ^{
[self.resultArr addObjectsFromArray:localSearchDataSource];
[self.rcs_SearchTableView reloadData];
});
});
}else{
//Search contacts by name
for (NSInteger i = 0; i < self.rcsRecentSearchDataSource.count; i ++) {
RCSContactModel *model = self.rcsRecentSearchDataSource[i];
NSString *nameStr = model.name;
if (nameStr.length >= searchText.length) {
//Search all name
if ([nameStr containsString:searchText]) {
[self.resultArr addObject:model];
}
}
}
}
//
if (self.resultArr.count > 0) {
self.isSearch = YES;
[self.rcs_SearchTableView reloadData];
}
}
I made two change that 'NSMutableArray *localSearchDataSource = [self.resultArr mutableCopy];' and 'NSArray *localPhoneArr = [[self rcs_GetPhoneNumberFromeDatabaseWithPhone:searchText] copy];'. And finish it . Collection <__NSArrayM: 0x117d7320> was mutated while being enumerated.'
Related
Here's the source code for the method that appears to be causing the leak.
- (void)search:(CDVInvokedUrlCommand*)command
{
NSString* callbackId = command.callbackId;
NSArray* fields = [command argumentAtIndex:0];
NSDictionary* findOptions = [command argumentAtIndex:1 withDefault:[NSNull null]];
[self.commandDelegate runInBackground:^{
// from Apple: Important You must ensure that an instance of ABAddressBookRef is used by only one thread.
// which is why address book is created within the dispatch queue.
// more details here: http: //blog.byadrian.net/2012/05/05/ios-addressbook-framework-and-gcd/
CDVAddressBookHelper* abHelper = [[CDVAddressBookHelper alloc] init];
CDVContacts* __weak weakSelf = self; // play it safe to avoid retain cycles
// it gets uglier, block within block.....
[abHelper createAddressBook: ^(ABAddressBookRef addrBook, CDVAddressBookAccessError* errCode) {
if (addrBook == NULL) {
// permission was denied or other error - return error
CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageToErrorObject:errCode ? (int)errCode.errorCode:UNKNOWN_ERROR];
[weakSelf.commandDelegate sendPluginResult:result callbackId:callbackId];
return;
}
NSArray* foundRecords = nil;
// get the findOptions values
BOOL multiple = NO; // default is false
NSString* filter = nil;
NSArray* desiredFields = nil;
if (![findOptions isKindOfClass:[NSNull class]]) {
id value = nil;
filter = (NSString*)[findOptions objectForKey:#"filter"];
value = [findOptions objectForKey:#"multiple"];
if ([value isKindOfClass:[NSNumber class]]) {
// multiple is a boolean that will come through as an NSNumber
multiple = [(NSNumber*)value boolValue];
// NSLog(#"multiple is: %d", multiple);
}
desiredFields = [findOptions objectForKey:#"desiredFields"];
// return all fields if desired fields are not explicitly defined
if (desiredFields == nil || desiredFields.count == 0) {
desiredFields = [NSArray arrayWithObjects:#"*", nil];
}
}
NSDictionary* searchFields = [[CDVContact class] calcReturnFields:fields];
NSDictionary* returnFields = [[CDVContact class] calcReturnFields:desiredFields];
NSMutableArray* matches = nil;
if (!filter || [filter isEqualToString:#""]) {
// get all records
foundRecords = (__bridge_transfer NSArray*)ABAddressBookCopyArrayOfAllPeople(addrBook);
if (foundRecords && ([foundRecords count] > 0)) {
// create Contacts and put into matches array
// doesn't make sense to ask for all records when multiple == NO but better check
int xferCount = multiple == YES ? (int)[foundRecords count] : 1;
matches = [NSMutableArray arrayWithCapacity:xferCount];
for (int k = 0; k < xferCount; k++) {
CDVContact* xferContact = [[CDVContact alloc] initFromABRecord:(__bridge ABRecordRef)[foundRecords objectAtIndex:k]];
[matches addObject:xferContact];
xferContact = nil;
}
}
} else {
foundRecords = (__bridge_transfer NSArray*)ABAddressBookCopyArrayOfAllPeople(addrBook);
matches = [NSMutableArray arrayWithCapacity:1];
BOOL bFound = NO;
int testCount = (int)[foundRecords count];
for (int j = 0; j < testCount; j++) {
CDVContact* testContact = [[CDVContact alloc] initFromABRecord:(__bridge ABRecordRef)[foundRecords objectAtIndex:j]];
if (testContact) {
bFound = [testContact foundValue:filter inFields:searchFields];
if (bFound) {
[matches addObject:testContact];
}
testContact = nil;
}
}
}
NSMutableArray* returnContacts = [NSMutableArray arrayWithCapacity:1];
if ((matches != nil) && ([matches count] > 0)) {
// convert to JS Contacts format and return in callback
// - returnFields determines what properties to return
#autoreleasepool {
int count = multiple == YES ? (int)[matches count] : 1;
for (int i = 0; i < count; i++) {
CDVContact* newContact = [matches objectAtIndex:i];
NSDictionary* aContact = [newContact toDictionary:returnFields];
[returnContacts addObject:aContact];
}
}
}
// return found contacts (array is empty if no contacts found)
CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsArray:returnContacts];
[weakSelf.commandDelegate sendPluginResult:result callbackId:callbackId];
// NSLog(#"findCallback string: %#", jsString);
if (addrBook) {
CFRelease(addrBook);
}
}];
}]; // end of workQueue block
return;
}
The specific line that is doing most of the leaking is foundRecords = (__bridge_transfer NSArray*)ABAddressBookCopyArrayOfAllPeople(addrBook);, but this is confusing, given that the correct __bridge_transfer call is used. What's going on here?
-(void) objectParsed_ListAllMedia:(NSDictionary *)dictionary
{
#try {
self.viewLoading.hidden=1;
[self.arrGaalleryMediaName removeAllObjects];
[self.arrMediaNames removeAllObjects];
if(self.arrOnlyServerImages == nil){
self.arrOnlyServerImages = [[NSMutableArray alloc] init];
}
if([self.arrOnlyServerImages count] >0){
[self.arrOnlyServerImages removeAllObjects];
}
if (dictionary==nil) {
[self.gridCollectionView reloadData];
return;
}
// Filter Array for Audio file
NSPredicate *predicate = [NSPredicate predicateWithFormat: #"type != 'audio' "];
self.arrOnlyServerImages = [NSMutableArray arrayWithArray:[[dictionary objectForKey:#"objects"] filteredArrayUsingPredicate:predicate]];
// Remove duplicate Start //Read Meta Data and Duplicate from Download, Duplicate from upload START
dispatch_queue_t backgroundQueue = dispatch_queue_create("com.memreas.myqueue", 0);
dispatch_async(backgroundQueue, ^{
NSMutableArray *arr = [NSMutableArray array];
NSMutableIndexSet * indexSet = [NSMutableIndexSet indexSet];
for (int i=0; self.assetAry.count>i; i++) {
ALAsset *result =self.assetAry[i];
ALAssetRepresentation *imageRep = [result defaultRepresentation];
NSDictionary * customMetaDic = [imageRep metadata][(NSString*)kCGImagePropertyIPTCDictionary];
if (customMetaDic) {
[self.arrMediaNames addObject:customMetaDic[(NSString*)kCGImagePropertyIPTCObjectName]?customMetaDic[(NSString*)kCGImagePropertyIPTCObjectName]:#""];
}else{
[self.arrMediaNames addObject:#""];
}
[self.arrGaalleryMediaName addObject:[self getFileNameWithExtensionFromPath:imageRep.url]];
}
for (int i=0; self.arrOnlyServerImages.count>i; i++) {
NSMutableDictionary* obj = self.arrOnlyServerImages[i];
NSMutableDictionary * dic2 = [NSMutableDictionary dictionaryWithDictionary:obj];
BOOL isArrMedia =[self.arrMediaNames containsObject:dic2[#"media_name"]];
BOOL isGallery =[self.arrGaalleryMediaName containsObject:dic2[#"media_name"]];
if (isArrMedia||isGallery) {
dic2[#"isDownloaded"] = [NSNumber numberWithBool:YES];
[indexSet addIndex: isArrMedia?[self.arrMediaNames indexOfObject:dic2[#"media_name"]] :[self.arrGaalleryMediaName indexOfObject:dic2[#"media_name"]]];
}else{
dic2[#"isDownloaded"] = [NSNumber numberWithBool:NO];
}
[arr addObject:dic2];
}
dispatch_async(dispatch_get_main_queue(), ^{
#try {
self.arrOnlyServerImages = arr;
[self.assetAry removeObjectsAtIndexes:indexSet];
[self.gridCollectionView reloadData];
}
#catch (NSException *exception) {
NSLog(#"%#",exception);
[self.gridCollectionView reloadData];
}
});
});
// Remove duplicate END //Read Meta Data and Duplicate from Download, Duplicate from upload END
[self.gridCollectionView reloadData];
[self.gridView.collectionView reloadData];
[self.location performSelector:#selector(stopActivity) withObject:nil afterDelay:2];
}
#catch (NSException *exception) {
NSLog(#"%#",exception);
}
}
I have issue with my code, While I run this code it generates memory pressure issue and crash the app.
Functionality is:
I load all the images from server and local assets and match each other with file name and remove duplicate images from list, So it will visible only once.
Any one have solution so please help.
thanks in advance.
You're accessing memory-intensive things (ALAssetRepresentations) in a tight loop. In these cases a local autoreleasepool can help ARC to keep your memory use down.
Inside the loop where you pass through self.assetAry, wrap everything in an autoreleasepool like so:
#autoreleasepool {
AlAsset *asset = ...
...
// Rest of your code
}
I'm trying to animate my label in my UICollectionViewCell. I want to go randomly through the array and set the label randomly 10 times and then it stops at a random string from my array.
The problem is, it goes through my array (debugged it with a simple NSLog) BUT the label setText isn't used till the last number of my loop and the label is then set.
Here is my code:
-(void)animate{
long index;
for(detail* cells in [[self col] visibleCells]){
NSIndexPath *indexPath = [[self col] indexPathForCell:cells];
if ([array count] >= 3) {
index = 2 * indexPath.section + indexPath.row;
}else{
index = 1 * indexPath.section + indexPath.row;
}
for (int i = 0; i <= 10; i++) {
[[cells detailLabel] setText: [[array objectAtIndex:index] returnRandomOptie]];
[NSThread sleepForTimeInterval:0.10];
NSLog(#"%i", i);
}
}
}
And here is my returnRandomOptie from my custom Cell class:
-(NSString *) returnRandomOptie {
NSUInteger randomIndex;
NSString *string;
if ([opties count] != 0) {
randomIndex = arc4random() % [opties count];
string = [NSString stringWithFormat:#"%#", [opties objectAtIndex:randomIndex]];
}
return string;
}
So basically I want that the for loop in my animate method, always sets the text in my label. And now it doesn't do that. Just at the last loop.
What am I doing wrong?
Kind Regards!
You have to perform the sleep async and update the label in the main thread, try this instead :
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
for (int i = 0; i <= 10; i++) {
dispatch_async(dispatch_get_main_queue(), ^{
[[cells detailLabel] setText: [[array objectAtIndex:index] returnRandomOptie]];
});
[NSThread sleepForTimeInterval:0.10];
NSLog(#"%i", i);
}
});
Try this,
-(void)animate{
....
for (int i = 0; i <= 10; i++) {
dispatch_async(dispatch_get_main_queue(), ^{
[[cells detailLabel] setText: [[array objectAtIndex:index] returnRandomOptie]];
});
[NSThread sleepForTimeInterval:0.10];
NSLog(#"%i", i);
}
...
}
I'm trying to get the metadata from the box api and so I need to traverse the entire directory structure. Here is the code:
- (IBAction)ls {
// INITIALIZE folderID (will be nil only for the root folder)
if (self.folderID == nil)
{
self.folderID = BoxAPIFolderIDRoot;
self.folderName = #"Root";
}
// START TRAVERSING DIRECTORY TREE
[self traverseItemsWithFolderID:self.folderID name:self.folderName];
}
- (void)traverseItemsWithFolderID:(NSString *)folder name:(NSString *)namefolder
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self fetchFolderItemsWithFolderID:folder name:namefolder]; // <- List Files/dirs in directory namefolder
dispatch_async(dispatch_get_main_queue(), ^{
// Create box object "item" to traverse folderItemsArray
BoxItem * item = (BoxItem *)[self.folderItemsArray objectEnumerator];
for (item in self.folderItemsArray)
{
if([item.type isEqualToString:BoxAPIItemTypeFolder])
{
NSLog(#"SubFolder id %#", item.modelID);
NSLog(#"SubFolder name %#", item.name);
[self traverseItemsWithFolderID:item.modelID name:item.name];
}
}
});
});
}
and this last method:
- (void)fetchFolderItemsWithFolderID:(NSString *)innerFolder name:(NSString *)innerFolderName
{
BoxFoldersResourceManager *mgr = [BoxSDK sharedSDK].foldersManager;
BoxFoldersRequestBuilder *bldr = [[BoxFoldersRequestBuilder alloc] initWithQueryStringParameters:#{ #"fields" : #"name,type,id,etag,size,modified_at,hash" }];
[mgr folderItemsWithID:innerFolder requestBuilder:bldr success: ^(BoxCollection *collection)
{
self.folderItemsArray = [NSMutableArray array];
for (NSUInteger i = 0; i < collection.numberOfEntries; i++)
{
[self.folderItemsArray addObject:[collection modelAtIndex:i]];
NSLog(#"FOLDER NAME %#", innerFolderName);
BoxItem *item = (BoxItem *)[self.folderItemsArray objectAtIndex:i];
NSLog(#"Here The list of Files/Directories:");
NSLog(#"TypE %#", item.type); // [file/directory]
NSLog(#"NAME %#", item.name); // filename
NSLog(#"ID %#", item.ETag); // directory level [0 = root]
NSLog(#"ID %#", item.modelID); // metadata unique identifier [FolderID !]
NSLog(#"SIZE %#", item.size); // in bytes
NSLog(#"Modified Time %#", item.modifiedAt); // in UTC format
NSLog(#"HASH %lu", (unsigned long)item.hash); // hash
}
self.totalCount = [collection.totalCount integerValue];
NSLog(#" Number of files/dirs in Level %ld", (long)self.totalCount);
}
The final result is that the code start an infinite loop of requests and don't understand why, as it should stop when there are no more directories. Any help on identifying the issue is much appreciated.
Thanks !
You should avoid using the property self.folderItemsArray since you have a recursive method, try this:
- (NSArray*)fetchFolderItemsWithFolderID:(NSString *)innerFolder name:(NSString *)innerFolderName
{
...
// This is a new line.
NSMutableArray* folderItemsArray = [NSMutableArray array];
[mgr folderItemsWithID:innerFolder requestBuilder:bldr success: ^(BoxCollection *collection)
{
//self.folderItemsArray = [NSMutableArray array]; remove this line.
for (NSUInteger i = 0; i < collection.numberOfEntries; i++)
{
[folderItemsArray addObject:[collection modelAtIndex:i]]; // remove the self here.
...
}
return folderItemsArray;
}
And update this method like this:
- (void)traverseItemsWithFolderID:(NSString *)folder name:(NSString *)namefolder
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSArray* items = [self fetchFolderItemsWithFolderID:folder name:namefolder]; // <- List Files/dirs in directory namefolder
dispatch_async(dispatch_get_main_queue(), ^{
// Create box object "item" to traverse folderItemsArray
//BoxItem * item = (BoxItem *)[self.folderItemsArray objectEnumerator]; why do you need this line.
for (BoxItem *item in items)
{
if([item.type isEqualToString:BoxAPIItemTypeFolder])
{
NSLog(#"SubFolder id %#", item.modelID);
NSLog(#"SubFolder name %#", item.name);
[self traverseItemsWithFolderID:item.modelID name:item.name];
}
}
});
});
}
I'm developing an iOS application with latest SDK.
My app is a port from an Android application and I have these two methods:
- (MyObject*)getMyObject:(MyObjectType)myObjectType
{
#synchronized(self)
{
for (int index = 0; index < [myObjects count]; index++)
{
MyObject* myObject = (MyObject*)[myObjects objectAtIndex:index];
if (myObject.Type == myObjectType)
return myObject;
}
return nil;
}
}
- (BOOL)isMyObjectVisible:(MyObjectType)myObjectType
{
#synchronized(self)
{
return ([self getMyObject:myObjectType] != nil);
}
}
I have isMyObjectVisible:, that is #synchronized, calling another #synchronized method.
Is it necessary that isMyObjectVisible: has to be #synchronized?
To answer your first question, no, the double locking is not needed.
You can keep the lock in getMyObject. That protects it. However, there is nothing in isMyObjectVisible other than a call to getMyObject, so there's nothing else to protect in that method.
However, borrrden's comment is not an issue here. You get a recursive lock when using #synchronized, so you can nest synchronized calls like you're doing without a deadlock. There's just no need to, in your case.
Here is an Example of that you need to use double #synchronized :
NSString * str;
str = [[NSString alloc] initWithFormat:#"str"];
-(void)viewDidLoad{
NSString *foo = #"foo";
NSString *bar = #"bar";
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self firstAction:foo];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self secondAction:bar];
});
}
- (void)firstAction:(NSString *)sender {
NSLog(#"firstAction");
#synchronized (self) {
str = sender;
for (int i=0; i<5; i++) {
NSLog(#"first: %#",str);
}
}
}
- (void)secondAction:(NSString *)sender {
NSLog(#"secondAction");
#synchronized (self) {
str = sender;
for (int i=0; i<5; i++) {
NSLog(#"second: %#",str);
}
}
}
(str is static variable )
-Try to run it without the #synchronized (self) and see what will happen.