Memory problems with [AVAssetWriterInput requestMediaDataWhenReadyOnQueue:usingBlock:] - objective-c

I’m writing a library to export assets to a file using AVFoundation. I create a reader, a writer, connect the inputs and outputs to these and then call the requestMediaDataWhenReadyOnQueue method on the inputs to start pulling the data. The block callback supplied to this method looks a bit like this:
[input requestMediaDataWhenReadyOnQueue:queue usingBlock:^{
while ([input isReadyForMoreMediaData]) {
CMSampleBufferRef buffer;
// The track has some more data for us
if ([reader status] == AVAssetReaderStatusReading
&& (buffer = [output copyNextSampleBuffer])) {
BOOL result = [input appendSampleBuffer:buffer];
CFRelease(buffer);
if (!result) {
// handle error
break;
}
// The track is finished, for whatever reason
} else {
[input markAsFinished]; ⬅
switch ([reader status]) {
// inspect the status and act accordingly
}
}
}
}];
This works perfectly on iOS 5, but on iOS 4 the code dies from EXC_BAD_ACCESS after the line marked with the ⬅ arrow. After some poking around I feel like the block was somehow destroyed immediately after marking the input as finished. The self pointer that’s perfectly valid before executing the bad line somehow turns into 0xfff… or some garbage value as reported by the debugger. But the object pointed to it before is fine, as confirmed by the zombies tool, it does not get deallocated.
What am I missing?

Seeing the same (similar) issue. iOS5 happy, iOS4.3.5, not happy. Interested to learn what you ultimately find.
Got around it by explicitly retaining writer, writer input, reader, reader output before the requestMedatWhenReadyOnQueue block and explicitly releasing all four at the very end of the else clause.
The doc does say that after marking finished, "The block should then exit." Maybe they are not kidding. If you do anything other than exit, it is an error. The above workaround seems to work though.
UPDATE: I still found that it occasionally crashed even after retaining and releasing all of the asset objects. As your question observes, it crashes shortly after you mark the writer input as finished it is as if the block itself is being deallocated. Rather than just pass the block as part of the function. I create a copied block property that is part of a long lived object. I initialize it with Block_copy and only release it in the destructor of the long lived object. This seems to do the trick. I haven't seen any 4.3.5 crashes since.

Try [self retain] as the first line of the block and [self release] as the last line.
Another critical issue is that if the App is suspended (enters background) using requestMediaDataWhenReadyOnQueue you need to explicitly cover all of the [reader status] values as it will fail when the app restarts. In some cases I found the block ran more than once with a fail status flag. In other posts with similar code there's a lot of [retain]ing of the AV variables, which are then released at the end of the block. Because the block can run more than once this approach doesn't work in cases when the app enters the background state.
I found the following to work well in the "switch" (above):
case AVAssetReaderStatusReading:
break;
case AVAssetReaderStatusCompleted:
[videoWriterInput markAsFinished];
//do something else, like add an audio stream
[videoWriter finishWriting];
break;
case AVAssetReaderStatusFailed:
[videoWriterInput markAsFinished];
[videoWriter finishWriting];
break;
case AVAssetReaderStatusCancelled:
case AVAssetReaderStatusUnknown:
[videoWriterInput markAsFinished];
[videoWriter cancelWriting];
break;
}
dispatch_sync(dispatch_get_main_queue(), ^{
//hide any progress indicators
});
break;
other than "self", nothing is retained. The block should retain the variables automatically if they are required.

Related

XCTest Completion Handler Assertion Retain Cycle

I have been writing Xcode tests using the XCTest framework for a while, mostly async tests of the getters of a service with completion handlers of the following format with no issues:
XCTestExpectation *promise = [self expectationWithDescription:#"Get Something should succeed"];
[self.myService getSomethingOnCompletion:^(NSError * _Nullable error) {
XCTAssertNil(error, #"Error should be nil");
[promise fulfill];
}];
[self waitForExpectations:#[promise] timeout:2.0];
Suddenly today I go to write my first async setter test of the below format, but get warnings on the XCTAssert...() statement within the block saying:
Capturing 'self' strongly in this block is likely to lead to a retain cycle
XCTestExpectation *promise = [self expectationWithDescription:#"Set Something should succeed"];
[self.myService setSomething:#"..." onCompletion:^(NSError * _Nullable error) {
XCTAssertNil(error, #"Error should be nil");
[promise fulfill];
}];
[self waitForExpectations:#[promise] timeout:2.0];
I've even gone to the lengths of commenting out the entire contents of setSomething: onCompletion: such that it does not do anything, clean and rebuild, yet the warning still persists.
I do not understand what self it is referring to, as the only thing going on inside the block is an XCTAssert...() and [XCTestExpectation fulfill]. Furthermore, I do not understand why none of the 30+ tests I've written of the first format have no warnings associated with them, but all 5+ I've written of the 2nd format do.
Any explanation on what's going on here and how I can resolve it would be appreciated.
(Using Xcode 10.0)
Edit 1:
The issue seems to be with the method name, setSomething: onCompletion:. Changing it to anything else, such as doSomething: onCompletion: removes the warning. I still don't know how/ why Xcode interprets the set command in such a fashion that it presents the warning, so any information would be appreciated.
Edit 2:
The following are the method signatures of setSomething and doSomething:
- (void)setSomething:(EnumType)type onCompletion:(SetSomethingCompletionHandler)completion;
- (void)doSomething:(EnumType)type onCompletion:(SetSomethingCompletionHandler)completion
Where SetSomethingCompletionHandler is defined as :
typedef void (^SetSomethingCompletionHandler)(NSError * _Nullable error);

Crash when trying to present UIActivityViewController

I see a crash in Crashlytics than happens to my users sometimes. The crash happens when presenting UIActivityViewController in the last line of the following code:
NSData* snapShot = ... ;
UIActivityViewController *activityViewController = [[UIActivityViewController alloc] initWithActivityItems:[NSArray arrayWithObjects:activityTextsProvider, snapShot ,nil] applicationActivities:[NSArray arrayWithObjects:customActivityA, customActivityB, customActivityC, nullptr]];
activityViewController.excludedActivityTypes = [NSArray arrayWithObjects:UIActivityTypePrint, UIActivityTypeAssignToContact, UIActivityTypeMail, UIActivityTypeCopyToPasteboard, nil];
activityViewController.popoverPresentationController.sourceView = self.myButton;
activityViewController.popoverPresentationController.sourceRect = self.myButton.bounds;
activityViewController.completionWithItemsHandler = ^(NSString *activityType, BOOL completed, NSArray *returnedItems, NSError *activityError)
{
...
};
[self presentViewController:activityViewController animated:YES completion:nil];
I perform this in the main thread and unable to reproduce this crash locally. What could be the reason of this crash?
Edit: I changed nullptr to nil and the issue still happened. I managed to reproduce the issue: the crash happens only if before opening the activity controller i showed a UIMenuController. When creating UIActivityViewController it is not nil, but when presenting the controller i see the crash in the presentViewController line and the activity controller there is shown as nil
József addressed the use of nullptr in comments, and Fogh is spot-on that the actual crash log is important (please edit your question and post the full crash log), but I'd like to point out something else.
You're assuming your call to initialize activityViewController is succeeding. You should code defensively (by assuming everything that can fail probably will fail and testing for this at runtime). Wrap the rest of the configuration and presentation inside an if (activityViewController != nil) {} condition (you should probably have an else with proper error handling/reporting too) so you're properly detecting an all-out initialization failure for multiple reasons (like a misplaced nib, missing resource, etc.).
In your case, I think it's likely the initialization is failing because your class is doing something with a faulty array, as József's nullptr catch suggests. Perhaps you're using one or more pre-C++11 c libraries / compiling with a non-C11/gnu11 "C Language Dialect" build setting and nullptr is not equivalent to nil, leading to strange results in a supposed-to-be-nil-terminated array?
Note: If that turns out to be the case, I'll happily take an upvote but would rather József post his comment as an answer so you can give him proper credit. (Feel free to edit this request out of my answer if/when that happens.)

Recursion with blocks in objective-c

I am receiving EXC_BAD_ACCESS signal in my iOS application when doing a recursion that involves objective-c blocks. Here is the simplified code:
- (void)problematicMethod:(FriendInfo*)friendInfo onComplete:(void(^)(NSString*))onComplete1 {
[self doSomethingWithFriend:friendInfo onComplete:^(Response* response) {
switch (response.status) {
case IS_OK:
onComplete1(message);
break;
case ISNT_OK:
// Recursively calls the method until a different response is received
[self problematicMethod:friendInfo onComplete:onComplete1];
break;
default:
break;
}
}];
}
So basically, the problematicMethod, in this simplified version, calls doSomethingWithFriend:onComplete:. When that method finishes (onComplete), and if everything was ok, the original onComplete1 block gets called, and this works fine.
But if something went wrong, problematicMethod needs to be called again (the recursion part), and when this happens for the first time, I immediately get EXC_BAD_ACCESS signal.
Any kind of help would be greatly appreciated.
How are you creating your block? Remember that you have to move it from stack to heap.
Example:
void(^onCompleteBlock)(NSString*) = [[^(NSString* param) {
//...block code
}] copy] autorelease];
[self problematicMethod:friendInfo onCompleteBlock];
If response.status value is ISNT_OK you never finish calling recursively the function.

Cocoa app behaves diffirently with breakpoint on & off

Important update: I found out that most part of my question was based on a false premise (see my answer below). Notifications actually got to the receiver, they just got there too fast. (Although, it still doesn't explain why the behavior with breakpoint and without it was different.)
I'm developing the app that calculates the hashes of files given to it. The calculation takes place in SHHashComputer. It's an abstract class (well, intended to be abstract, as there are no abstract classes in Objective C) that takes the file path and creates an NSInvocationOperation. It, in turn, calls the method (void)computeAndSendHash, which uses the file path saved in the object to compute hash and sends it as notification. The actual computing takes place in (NSString*)computeHash method that child classes need to override.
Here's SHHashComputer.m:
- (NSString*)computeHash {
return [NSString stringWithFormat:#"unimplemented hash for file %#", self.path];
}
- (void)computeAndSendHash {
NSString *result = [self computeHash];
NSString *notificationName = [NSString stringWithFormat:#"%#%#",
gotResultNotification,
self.hashType];
[[NSNotificationCenter defaultCenter] postNotificationName:notificationName
object:result];
self.operation = nil;
}
And here's SHMD5Computer.m (the child class of SHHashComputer):
- (NSString*)computeHash {
return #"MD5 test"; // it actually doesn't matter what it returns
}
I won't bother you with the receivers of notification. Let's just say that as long as I comment out the computeHash method in SHMD5Computer.m everything works just fine: the notification with text "unimplemented ..." is received & displayed in GUI. But if I don't — then it gets really interesting.
If I don't set up any breakpoints, the notification just never comes. However, if I set up a breakpoint at the declaration of computeHash in SHMD5Computer.h and then step over until the line 'self.operation = nil', and continue execution at that point, the notification gets to destination. If I don't stop there, the debugger suddenly switches to the state as if it isn't debugging anything, and the app freezes.
I don't think that 'WTF' is a good form for a question here, so let me put it this way: am I missing something? Are there errors in my code? What can cause this type of behavior in xcode? How can I fix this?
(If you'll want to get all my code to reproduce it, I'll gladly give it to you.)
More experiments:
If I continute execution exactly after stopping at breakpoint, the application encounters EXC_BAD_ACCESS error in the code that receives the notification, at the last line:
id newResult = [newResultNotification object];
if (newResult == nil)
[NSException raise:#"No object"
format:#"Expected object with notification!"];
else if (![newResult isKindOfClass:[NSString class]])
[NSException raise:#"Not NSString"
format:#"Expected NSString object!"];
else
self.result = (NSString*) newResult;
[self.textField setStringValue:self.result];
When I tried to reproduce the previous experiment, something even stranger happenned. In my debug setup, I have two hash computer objects: one SHMD5HashComputer (which we're talking about), and one SHHashComputer (which, of course, produces the "unimpemented" hash). In all previous experiments, as long as app didn't crash, the notification form SHHashComputer always successfully arrived. But in this case, both notifications didn't arrive, and the app didn't crash. (All the steps are exactly the same as in previous one).
As Josh Caswell pointer out in the comments, I wasn't using the notifications correctly. I should've sent the object itself as notification object, as described in documentation. I fixed that, and I'm getting exactly the same results. (Which means that I fixed it correctly, because sometimes the notifications work correctly, and also that it wasn't the problem).
More updates:
The notification that I'm sending should arrive at SHHashResultViewController. That's how I create it and register for notification:
- (id)initWithHashType:(NSString *)hashType {
self = [self initWithNibName:#"SHHashResultView" bundle:[NSBundle mainBundle]];
if (self) {
[self setHashType:hashType];
}
return self;
}
- (void)setHashType:(NSString *)hashType {
[self.label setStringValue:[NSString stringWithFormat:#"%#:", hashType]];
_hashType = hashType;
NSString *notificationName = [NSString stringWithFormat:#"%#%#",
gotResultNotification,
_hashType];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(gotResult:)
name:notificationName
object:nil];
}
Actually, the question was based on a false premise. I thought that notification never came through because I never saw the information displayed in the GUI; however, my error was in the code of controllers (not published there) which made possible the situation in which the GUI first got results of hash calculation and only after that got information about a new input — which resulted in deleting all the text and activating progress animation.

iOS5 crashes during runMode:beforeDate:

I have a problem with compatibility of my application with an iOS5 b7 and GM versions.
The issue occurs in the next lines of code:
do {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
} while (!done);
App crashes with signal EXC_BAD_ACCESS after some iterations.
The number of passed iterations is random (from 2 till 7).
Also everything works quite well on iOS4 and iOS3.
The same issue occurs in Apple's sample: XMLPerformance Sample.
What do you think about this?
October 12th thousands of users of my app will upgrade to iOS5 and I don't want my app to be with such a strange error in the AppStore.
4 hours passed and I've found the problem. I will describe how I've resolved the problem in XMLPerformance sample.
The problem was in NSAutoreleasePool. There is #property (nonatomic, assign) NSAutoreleasePool *downloadAndParsePool;. When the app starts to download Top300 Paid Apps RSS new thread is created using [NSThread detachNewThreadSelector:#selector(downloadAndParse:) toTarget:self withObject:url];. So in that thread we should keep local autorelease pool. It is done in next way:
- (void)downloadAndParse:(NSURL *)url {
self.downloadAndParsePool = [[NSAutoreleasePool alloc] init];
// initializing internet connection and libxml parser.
if (rssConnection != nil) {
do {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
} while (!done);
}
// Release resources used only in this thread.
[downloadAndParsePool release];
self.downloadAndParsePool = nil;
}
So in downloadAndParse: everything looks fine. Now let's look in one method that is called when an item from RSS is parsed:
- (void)finishedCurrentSong {
// sending new item to delegate and other ...
countOfParsedSongs++;
// Periodically purge the autorelease pool. The frequency of this action may need to be tuned according to the
// size of the objects being parsed. The goal is to keep the autorelease pool from growing too large, but
// taking this action too frequently would be wasteful and reduce performance.
if (countOfParsedSongs == kAutoreleasePoolPurgeFrequency) {
[downloadAndParsePool release];
self.downloadAndParsePool = [[NSAutoreleasePool alloc] init];
countOfParsedSongs = 0;
}
}
As you see there lines :
[downloadAndParsePool release];
self.downloadAndParsePool = [[NSAutoreleasePool alloc] init];
So exactly that lines causes the exception. If I comment them everything works great.
But I decided not only to comment that lines but also replace NSAutoreleasePool in - (void)downloadAndParse:(NSURL *)url with #autorelease block as it is said that it is more efficient:
- (void)downloadAndParse:(NSURL *)url {
#autoreleasepool {
// initializing internet connection and libxml parser.
if (rssConnection != nil) {
do {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
} while (!done);
}
// Release resources used only in this thread.
}
}
Now everything works fine. The only problem that I haven't resolved is:
// Periodically purge the autorelease pool. The frequency of this action may need to be tuned according to the
// size of the objects being parsed. The goal is to keep the autorelease pool from growing too large, but
// taking this action too frequently would be wasteful and reduce performance.
So if anybody has any thoughts about this problem can post another one answer and may be try to explain more correctly the bug fix. I will be glad to accept that answer.
Thanks.
This looks like memory problem, please check Apple Technote QA1367 "Finding EXC_BAD_ACCESS bugs in a Cocoa project"
In your code, try this to crash as soon as possible:
[item release], item = nil;
It doesn't solve the problem, just makes the crash happen earlier and hopefully give you a more meaningful callstack to study.
If you're using multi-threading, well... You could try to print "current" thread id into console to verify that everything really is run in thread where you expect them to be running. Especially verify that all UI stuff is in main thread, even when such code is run as side-effect of other code (error popups, maybe).
#include <pthread.h>
- (void)myFunction
{
NSLog(#"Thread (%d)",
pthread_mach_thread_np(pthread_self()));
}
Run your app with Instruments, make sure to change memory verification to happen every 1 or 2 seconds. Slow, but yet again you want to get notified as close to the actual memory problem as possible.
Looking at your code: where did that "done" variable come from and who changes it's value and when? Now it looks pretty magical.
Also you could check the return value of runMode:beforeDate to make sure it was run. If the return value is NO, runLoop was not run at all. Maybe some other part of your code cannot handle such case?
Just my little contribution.
As I've got the same problem, I've discover that in iOS5, you don't need to have your own NSAutoreleasePool in a thread (used by performSelectorOnMainThread).
Then, in your code (a xml parser- same as me), I think you have to separate code from iOS4 and iOS5.
With iOS4, you need NSAutoreleasePool, but not with iOS5.