Using Crittercism with some beta testers I am seeing an error appear several times that I've never experienced myself and I am unable to replicate.
Crittercism tells me:NSInternalInconsistencyException accessing _cachedSystemAnimationFence requires the main thread
And the line it is pointing to is:
[picker dismissViewControllerAnimated:YES completion:^{
Doing some reading on StackOverflow it appears that any UI code should be ran on the main thread. Is the error I am experiencing because the dismissViewControllerAnimated has been ran on a background thread?
Curious why this error is relatively random (i.e. I cannot reproduce it) and also how do I fix it.
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info {
__block PHObjectPlaceholder *assetPlaceholder;
[[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
PHAssetChangeRequest *changeRequest = [PHAssetChangeRequest creationRequestForAssetFromImage:[info objectForKey:#"UIImagePickerControllerOriginalImage"]];
assetPlaceholder = changeRequest.placeholderForCreatedAsset;
} completionHandler:^(BOOL success, NSError *error) {
NSArray *photos = [[NSArray alloc] initWithObjects:assetPlaceholder.localIdentifier, nil];
PHFetchResult *savedPhotos = [PHAsset fetchAssetsWithLocalIdentifiers:photos options:nil];
[savedPhotos enumerateObjectsUsingBlock:^(PHAsset *asset, NSUInteger idx, BOOL *stop) {
NSMutableArray *images = self.event.eventAttachments;
if (images) {
[images addObject:asset];
} else {
images = [[NSMutableArray alloc]init];
[images addObject:asset];
}
self.event.eventAttachments = images;
[picker dismissViewControllerAnimated:YES completion:^{
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:4];
NSArray *indexPaths = [[NSArray alloc] initWithObjects:indexPath, nil];
[self.tblChildrenNotes beginUpdates];
[self.tblChildrenNotes reloadRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationNone];
[self.tblChildrenNotes endUpdates];
}];
}];
}];
}
If we need to call any UI Related operation inside the block use dispatch.
Everything that interacts with the UI must be run on the main thread. You can run other tasks that don't relate to the UI outside the main thread to increase performance.
dispatch_async(dispatch_get_main_queue(), ^{
// Call UI related operations
});
I'm afraid I don't have a solution but I can offer the details of an app that is crashing the same way. We're using the Cordova platform to build our app. This crash only seems to happen in iOS9 (on an iPhone 6 in our case) and only when the user has Swype keyboard installed. We see the crash when opening/closing the keyboard to type in a text field displayed through Cordova's InAppBrowser plugin, and only sometimes. Deleting the Swype app makes the bug go away. Unfortunately, since we didn't write any of the objc involved in the app, we're having a tough time debugging. Good luck!
For Swift 3
DispatchQueue.main.async(execute: {
//UI Related Function
});
For me this solution fixed when trying to open pdfViewer when in landscape mode.
Check to make sure that your completionHandler is actually getting called back on the main thread and not some other thread. That is typically the cause for this particular issue.
Related
I currently have a share extension set up that will upload an image selected from the Photo app to a server. This works fine using the code below.
int fileNum=10;
NSItemProvider *attachment = inputItem.attachments[0];
if ([attachment hasItemConformingToTypeIdentifier:(NSString*)kUTTypeImage])
{
[attachment loadItemForTypeIdentifier:(NSString*)kUTTypeImage options:nil completionHandler:^(id item,NSError *error)
{
if (item)
{
NSLog (#"image %#",item);
//upload image here
NSData *data=[NSData dataWithContentsOfURL:item];
activityRecord.activityType=#"Images";
AppRecord *appRecord=[[AppRecord alloc] init];
appRecord.fileName=[NSString stringWithFormat:#"activity_%#%i(%i).jpg",activityRecord.supplierID,activityRecord.activityID,fileNum];
appRecord.fileBytes=data;
[fileRecords addObject:appRecord];
activityRecord.activityFiles=fileRecords;
[[Settings getInstance] uploadActivityRecord:activityRecord withDelegate:self];
[self.extensionContext completeRequestReturningItems:#[] completionHandler:nil];
}
}];
}
I had a previous problem where the loadItemForTypeIdentifier method wasn't being called, and it was resolved by calling completeRequestReturningItems within the completion block.
The problem I have now is that if I want to upload multiple files then I need to call loadItemForTypeIdentifier within a for loop (for each image) but how can I do that if the completeRequestReturningItems method will be called after the first image/item?
Many Thanks
Paul
I ran into the same problem recently and was able to resolve it by adding a counter and counting down as the images successfully completed their block. Within the loadItemForTypeIdentifier completion block I then check to see if all items have been called before calling the completeRequestReturningItems within a dispatch_once block (just for safety's sake).
__block NSInteger imageCount;
static dispatch_once_t oncePredicate;
NSItemProvider *attachment = inputItem.attachments[0];
if ([attachment hasItemConformingToTypeIdentifier:(NSString*)kUTTypeImage])
{
[attachment loadItemForTypeIdentifier:(NSString*)kUTTypeImage options:nil completionHandler:^(NSData *item ,NSError *error)
{
if (item)
{
// do whatever you need to
imageCount --;
if(imageCount == 0){
dispatch_once(&oncePredicate, ^{
[self.extensionContext completeRequestReturningItems:#[] completionHandler:nil];
});
}
}
}];
}
I can't say I feel like this is an overly elegant solution however, so if someone knows of a more appropriate way of handling this common use case I'd love to hear about it.
So, in my app, I want to share something using the UIActivityViewController.
To make sure that the sharing activity was successful, I have this code:
UIActivityViewController *controller = [[UIActivityViewController alloc]
initWithActivityItems:#[text, shortURL, image]
applicationActivities:nil];
[controller setCompletionWithItemsHandler:^(NSString *activityType, BOOL completed, NSArray *returnedItems, NSError *activityError) {
if (! completed){
// Here I do some stuff irrelevant to the question
}
}];
Since I have copied (and modified) this code, I don't want to claim that I completely understand what is happening here.
What I do know, and this is my question, is that the code above runs fine on iOS 8 but not on iOS 7, hardware or simulator.
I dearly hope that somebody can explain to me what is going on here.
The completionWithItemsHandler property is not available in iOS 7, as it was introduced in iOS 8.
What you're looking for is the now-deprecated completionHandler property; if your deployment target is below iOS 8, you can just use this, but if you want to be future-proof, you can check whether the new handler is supported and, if not, use the old handler:
if([[UIApplication sharedApplication] respondsToSelector:(#selector(setCompletionWithItemsHandler:))]){
[controller setCompletionWithItemsHandler:^(NSString *activityType, BOOL completed, NSArray *returnedItems, NSError *activityError) {
if(!completed){
// Out of scope of question
}
}];
}else{
[controller setCompletionHandler:^(NSString *activityType, BOOL completed) {
if(!completed){
// Out of scope of question
}
}];
}
}
Also, you may have omitted this for brevity, but it's important that you actually present the view controller after initializing it:
[self presentViewController:controller animated:YES completion:nil];
OK, so this is what I did. Most likely, Kremelur answered it in general terms, but I am too much of a novice to understand that. So, I copied and pasted some stuff, after some cross-googling. I hope this is of any use to someone.
[controller setCompletionHandler:^(NSString *activityType, BOOL completed) {
NSLog(#"completed dialog - activity: %# - finished flag: %d", activityType, completed);
if (! completed){
// Out of scope of question
}
}];
This code seems to run fine in both iOS7 and iOS8.
I am having some synchronization issue with loading asset from ALAssetsLibrary.
Actually, what I am trying is to load some pictures from camera roll whose urls are given by some database query. Now after obtaining urls from database I use those urls to load the pictures using assetForURL method of ALAssetsLibrary and after the picture is loaded I display the picture to some view. So I call the method inside a loop that is executed every time the query result-set returns a record. And everything works fine till now. Below is a sample code to demonstrate the process:
ALAssetsLibrary* library = [ALAssetsLibrary new];
//dispatch_group_t queueGroup = dispatch_group_create();
while ([rs next]) {
//some data load up
//load thumbnails of available images
[library assetForURL:url resultBlock:^(ALAsset *asset) {
UIImage* img = [[UIImage imageWithCGImage:asset.aspectRatioThumbnail] retain];
//dispatch_group_async(queueGroup, dispatch_get_main_queue(), ^{
//create views and add to container view
CGFloat left = (8.0f + dimension) * i + 8.0f;
CGRect rect = CGRectMake(left, 8.0f, dimension, dimension);
TileView* tileView = [[NSBundle mainBundle] loadNibNamed:#"TileView" owner:nil options:nil][0];
[tileView setFrame:rect];
tileView.tag = i;
tileView.active = NO;
[self.thumbnailContainer addSubview:tileView];
//display image in tileView, etc.
//.............
if (img) {
[img release];
}
NSLog(#"block %d: %d",i,[self.thumbnailContainer.subviews count]);
//});
} failureBlock:^(NSError *error) {
NSLog(#"failed to load image");
}];
i++;
}
NSLog(#"outside block %d",[self.thumbnailContainer.subviews count]);
[library release];
In my code self.thumbnailContainer is a UIScrollView and inside that I add my custom views to display the thumbnail images and it works as expected.
The real dilemma comes when I try to select the very last view added to self.thumbnailContainer. I cant find any way to determine when all the asynchronous blocks of assetForURL methods completed so that self.thumbnailContainer actually contains some subviews. So if I log count of subviews of self.thumbnailContainer just after the loop completes it shows 0. And after that I find all the block codes get executed increasing count of subviews. It is very expected behavior but contradicts my requirements. I have tried dispatch_group_ and dispatch_wait methods from GCD but without any success.
Can anyone please suggest a workaround or an alternative coding pattern to overcome the situation. Any help would be highly appreciated. Thanks.
You might utilize a dispatch group, as you likely had in mind:
- (void) loadViewsWithCompletion:(completion_t)completionHandler {
ALAssetsLibrary* library = [ALAssetsLibrary new];
dispatch_group_t group = dispatch_group_create();
while ([rs next]) {
dispatch_group_enter(group);
[library assetForURL:url resultBlock:^(ALAsset *asset) {
UIImage* img = [[UIImage imageWithCGImage:asset.aspectRatioThumbnail] retain];
dispatch_async(dispatch_get_main_queue(), ^{
//create views and add to container view
...
dispatch_group_leave(group);
});
} failureBlock:^(NSError *error) {
NSLog(#"failed to load image");
dispatch_group_leave(group);
}];
i++;
}
[library release];
if (completionHandler) {
dispatch_group_notify(group, ^{
completionHandler(someResult);
});
}
... release dispatch group if not ARC
}
The code might have a potential issue though:
Since you asynchronously load images, they may be loaded all in parallel. This might consume a lot of system resources. If this is the case, which depends on the implementation of the asset loader method assetForURL:resultBlock:failureBlock:, you need to serialize your loop.
Note: Method assetForURL:resultBlock:failureBlock: may already ensure that access to the asset library is serialized. If the execution context of the completion block is also a serial queue, [UIImage imageWithCGImage:asset.aspectRatioThumbnail] will be executed in serial. In this case, your loop just enqueues a number of tasks - but processes only one image at a time and you are safe.
Otherwise, if method assetForURL:resultBlock:failureBlock: runs in parallel, and/or the block executes an a concurrent queue - images might be loaded and processed in parallel. This can be a bad thing if those images are large.
Two ways you can fix this issue. They are
1.NSLock. Specifically NSConditionLock. Basically you create a lock with the condition “pending tasks”. Then in the completion and error blocks of assetForURL, you unlock it with the “all complete” condition. With this in place, after you call assetForURL, simply call lockWhenCondition using your “all complete” identifier, and you’re done (it will wait until the blocks set that condition)!
Check this for more detials
2.Manually track the count in resultBlock & failureBlock
Note:
I have mentioned few important things regarding the performance of ALAsset libraries in my answer.
I made my search algorithm for my UISearchBar, and I know that I must search in a background thread. Honestly I'm not familiar with multi-threading, so I'm looking for help. I'm using GCD (Grand Central Dispatch).
Here is my code, I want to know is it correct or not.
-(void)mySearchMethod
{
NSArray *allObjects = self.allMyObjects;
__block NSMutableArray *searchArray = [[NSMutableArray alloc] init];
dispatch_queue_t queue;
queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async( queue, ^{
for (MyObjectClass *myObjectAsDictionary in allObjects) {
for (NSString *titleSubString in [myObjectAsDictionary.title componentsSeparatedByString:#" "]) {
if ([[titleSubString lowercaseString] hasPrefix:[text lowercaseString]]) {
[searchArray addObject: myObjectAsDictionary];
}
}
}
dispatch_async( dispatch_get_main_queue(), ^{
self.tableObjects = searchArray;
[self.myTableView reloadData];
});
});
}
So does this code work in the background, or is it blocking the main thread?
Can the contents of allMyObjects change while the search is running? If so, you have two problems. First problem: you should not accessing an array on one thread while it's being mutated on another. You can fix this simply by copying the array: NSArray *allObjects = [self.allMyObjects copy];. Second problem: your search results might contain objects that have been removed from allMyObjects, or might be missing objects that have been added to allMyObjects. This is a hard problem to solve and how you solve it depends on your app.
What happens if the user cancels the search before your async block finishes running? Will it replace the contents of a table with search results even though the user doesn't want to see the results now?
Hi I have been trying to track down a very elusive memory leak in my application.
In an attempt to isolate the cause I have pared back the leaking code and even moved it to the app delegate for test purposes to try to eliminate as many factors as possible.
The following code is called from did finish launching with options.
- (void)loadDataAsTest {
NSLog(#"%s ", __PRETTY_FUNCTION__);
_library = [[ALAssetsLibrary alloc] init];
[_library enumerateGroupsWithTypes:ALAssetsGroupPhotoStream usingBlock:^(ALAssetsGroup *alAssetsGroup, BOOL *stop) {
#autoreleasepool {
if (alAssetsGroup) {
[alAssetsGroup enumerateAssetsUsingBlock:^(ALAsset *alAsset, NSUInteger index, BOOL *st) {
#autoreleasepool {
if (alAsset) {
NSLog(#"index = %u", index);
UIImage *image = [UIImage imageWithCGImage:[alAsset thumbnail]];
}
else {
}
}
}];
}
}
} failureBlock:^(NSError *error) {
}];
}
In my application this code leaks about 2000 CGImage enumerating over the 1000 photos in the photo stream. All 2000 UIImages that are created are released ok.
If I create a new empty project for testing purposes and put the same code in the app delegate and call it in exactly the same way it does not leak. All CGimage are released and all UIImage are released again about 2000 instances of each.
Both projects are using ARC.
Why would the same code called in exactly the same way behave differently between two projects?