Simple NSOperationQueue ends in EXC_BAD_ACCESS - objective-c

NSBlockOperation *blockOperation = ^{NSLog(#"This is an NSBlockOperation");};
NSOperationQueue *ownQueue = [[NSOperationQueue alloc] init];
[ownQueue setMaxConcurrentOperationCount:2];
[ownQueue addOperation:blockOperation];
I am just trying out NSBlockOperation, however this simple code ends with a EXC_BAD_ACCESS.
The code is in main and surrounded by #autorelease.
libsystem_c.dylib`OSAtomicCompareAndSwapIntBarrier$VARIANT$mp:
0x7fff8b8dc524: movl %edi, %eax
libsystem_c.dylib`OSAtomicCompareAndSwap32$VARIANT$mp + 2:
0x7fff8b8dc526: lock
0x7fff8b8dc527: cmpxchgl%esi, (%rdx)
0x7fff8b8dc52a: sete %al
0x7fff8b8dc52d: movzbl %al, %eax
0x7fff8b8dc530: ret
0x7fff8b8dc531: nopl (%rax)
The Program stops and points at 0x7fff8b8dc526: lock

You try to assign a block to a NSBlockOperation, but that are different types. Correct is
NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(#"This is an NSBlockOperation");
}];

Related

Memory increase when scrolling NSCollectionView images

I'm trying to display pdf pages in a collection view, but accumulate memory in the magnitude of several gigabytes when I scroll the view. The application itself is pretty basic, it's using PDFKit to preload every page of a pdf file as NSImage objects and stores them in an array indexed by an NSCollectionView. The view controller implements the NSCollectionViewDataSource protocol, and set the rendered pdf image from the array as the NSCollectionViewItem image whenever collectionView:itemForRepresentedObjectAtIndexPath: for that index is called. The problem seems to be rooted in the way the pages are rendered to images. There are no memory issues when I use thumbnailOfSize:forBox: to generate NSImages. The application does this concurrently in the background, but the same result can be achieved (for the sake of readability) when it loops on the main thread.
// _width, _height are determined by the pdf
CGSize pdfSize = CGSizeMake(_width, _height);
for (int i = 0; i < _pageCount; i++)
{
PDFPage *page = [_document pageAtIndex:i];
NSImage *pdfImage = [page thumbnailOfSize:pdfSize forBox:kPDFDisplayBoxArtBox];
[_pages replaceObjectAtIndex:i withObject:pdfImage];
}
With this method the application tops around 150 MB in memory usage, also when scrolling. This effectively solves the problem, but my issue with this method is that the the weight of the rendered typeface is a bit too light for the post-processing I intend to do, so I would like to use drawWithBox:toContext: instead. Sadly, that method triggers the memory issue.
// _width, _height are determined by the pdf
CGSize pdfSize = CGSizeMake(_width, _height);
CGRect pdfRect = CGRectMake(0.0f, 0.0f, _width, _height);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef gfxcontext = CGBitmapContextCreate(nil, (size_t)_width, (size_t)_height, 8, 0, colorSpace, kCGImageAlphaPremultipliedLast);
CGContextSetRGBFillColor(gfxcontext, 1.0f, 1.0f, 1.0f, 1.0f);
CGContextSetInterpolationQuality(gfxcontext, kCGInterpolationHigh);
for (int i = 0; i < _pageCount; i++)
{
PDFPage *page = [_document pageAtIndex:i];
CGContextFillRect(gfxcontext, pdfRect);
[page drawWithBox:kPDFDisplayBoxBleedBox toContext:gfxcontext];
CGImageRef cgPdfImage = CGBitmapContextCreateImage(gfxcontext);
NSImage *nsPdfImage = [[NSImage alloc] initWithCGImage:newImage size:pdfSize];
[_pages replaceObjectAtIndex:i withObject:nsimage];
CGImageRelease(newImage);
}
CGColorSpaceRelease(colorSpace);
CGContextRelease(gfxcontext);
Now I can watch the application memory increase from 150 MB to several hundred MB in a few scrolls, and easily well over a GB if I continue scrolling. Profiling allocations in Instruments reveals the issue when comparing the two methods, and the major difference is that QuartzCore allocates substantial chunks of memory (assuming image data) when I scroll the page.
It seems that mmap is what actually causes the memory increase. From what I understand, mmap is a Unix system call that implements "demand paging" which is obviously what I want, and likely works as intended in the first method. The image data seems to be retained in memory in the second method instead of being paged. The associated assembly may or may not be relevant.
+0x00 pushq %rbp
+0x01 movq %rsp, %rbp
+0x04 pushq %r15
+0x06 pushq %r14
+0x08 pushq %r12
+0x0a pushq %rbx
+0x0b testq %rsi, %rsi
+0x0e je "mmap+0x6e"
+0x10 movl %ecx, %r12d
+0x13 movl %ecx, %eax
+0x15 andl $3, %eax
+0x18 je "mmap+0x6e"
+0x1a movl %r8d, %ebx
+0x1d movq %rsi, %r14
+0x20 movl %r12d, %ecx
+0x23 orl $262144, %ecx
+0x29 callq "0x7ff81b738594"
+0x2e movq %rax, %r15
+0x31 leaq 240919(%rip), %rax
+0x38 movq (%rax), %rax
+0x3b testq %rax, %rax
+0x3e je "mmap+0x7f"
+0x40 andl $4278190080, %ebx
+0x46 orl $16, %ebx
+0x49 btl $12, %r12d
+0x4e movl $144, %edi
+0x53 cmovbl %ebx, %edi
+0x56 leaq 256718(%rip), %rcx
+0x5d movl (%rcx), %esi
+0x5f movq %r14, %rdx
+0x62 xorl %ecx, %ecx
+0x64 movq %r15, %r8
+0x67 xorl %r9d, %r9d
+0x6a callq *%rax
+0x6c jmp "mmap+0x7f"
+0x6e movl $22, %edi
+0x73 callq "0x7ff81b7382f1"
+0x78 movq $-1, %r15
+0x7f movq %r15, %rax
+0x82 popq %rbx
+0x83 popq %r12
+0x85 popq %r14
+0x87 popq %r15
+0x89 popq %rbp
+0x8a retq
The Annotations view in Instruments simply says 100.00% +0x6c jmp "mmap+0x7f. My confusion is that the memory increase is caused by mmap during scrolling, but the problem seems to be rooted in the way NSImage is generated. I assume thumbnailOfSize:forBox: does a lot of optimization behind the curtains, while it is quite straightforward in the second method. What can I do to make the second method work?
Update
This looks like a bug. The memory issue is triggered by the colorspace used to generate image data. Setting the colorspace to NSScreen.mainScreen.colorSpace.CGColorSpace solves the memory issue in the second method, but can be reintroduced by connecting an external monitor while the application is running for both methods. Unlikely a feature.

NSOperation dependency and completionBlock

We're having a simple problem regarding NSOperationQueue, here's a simple operation logic:
self.queue = [[NSOperationQueue alloc] init];
NSOperation *operationA = [NSBlockOperation blockOperationWithBlock:^{
NSLog(#"- Running operation A");
[NSThread sleepForTimeInterval:1.2];
NSLog(#"- Done operation A");
}];
NSOperation *operationB = [NSBlockOperation blockOperationWithBlock:^{
NSLog(#"- Running operation B");
[NSThread sleepForTimeInterval:2];
NSLog(#"- Done operation B");
}];
[operationA setCompletionBlock:^{
NSLog(#"-- Completion Block A");
}];
[operationB setCompletionBlock:^{
NSLog(#"-- Completion Block B");
}];
[operationB addDependency:operationA];
[self.queue addOperations:#[operationA, operationB] waitUntilFinished:NO];
Here is the final output
2015-12-21 14:59:57.463 SampleProject[18046:310901] - Running operation A
2015-12-21 14:59:58.664 SampleProject[18046:310901] - Done operation A
2015-12-21 14:59:58.664 SampleProject[18046:310900] - Running operation B
2015-12-21 14:59:58.664 SampleProject[18046:310904] -- Completion Block A
2015-12-21 15:00:00.736 SampleProject[18046:310900] - Done operation B
2015-12-21 15:00:00.736 SampleProject[18046:310904] -- Completion Block B
As we can see, the operation B is executed before the operation A's completionBlock. In our real application, we have many operation A and only one operation B that is dependant on all operation A. But then the problem we have is that operationB is launched before the last operation A's completion block has been called, which would normally give information to the operation B.
How would I make operation B to execute after all the operation A's completion blocks?
As you have found in your testing completion blocks are not 'part of the queue' instead they run outside of the Operation Queue (and on another thread). Thus Operation A's completionBlock will run at the same time (ish) as Operation B.
I suggest you refactor your code to remove all the completion blocks.
You say you are using the completionBlocks to pass information from operation A's to B, there are two options for this: Give B references to all the A's (not weak) so when B runs it can pick the results from all the A's. Or if keeping all the A's around until B runs is infeasible for some reason, then recreate your completionBlock's as another NSOperation:
NSOperation *operationA = [NSBlockOperation blockOperationWithBlock:^{
// do stuff
}];
NSOperation *operationATail = [NSBlockOperation blockOperationWithBlock:^{
// do completionBlock stuff
}];
[operationATail addDependency:operationA];
[operationB addDependency:operationATail];
[self.queue addOperations:#[operationA, operationATail, operationB] waitUntilFinished:NO];
why can you not invoke the operation inside of completion block a, thats what a completion block is there for.
[operationA setCompletionBlock:^{
NSLog(#"-- Completion Block A");
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSOperation *operationB = [NSBlockOperation blockOperationWithBlock:^{
NSLog(#"- Running operation B");
[NSThread sleepForTimeInterval:2];
NSLog(#"- Done operation B");
}];
[queue addOperations:#[operationB] waitUntilFinished:NO];
}];
[operationA setCompletionBlock:^{
NSLog(#"-- Completion Block A when we dont need B");
}];
There is some nicer ways to perform this instead by using
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self operationB];
}
Avoid completion blocks - they're out-of-queue mechanism unsuitable
for synchronising any operations or their communications.
Introducing dependency (B depends on A) means B will only run AFTER A has completed successfully.
For this reason - any simple data object can be used to "pass the information" safely between these two operations - that could simply share it (it suffices that you create it outside the blocks defining both operations. When "B" runs, it can ASSUME that "A" has already put needed info in the data object, and simply access it.
self.queue = [[NSOperationQueue alloc] init];
NSMutableDictionary *info = [NSMutableDictionary new]; // data object for communication
NSOperation *operationA = [NSBlockOperation blockOperationWithBlock:^{
NSLog(#"- Running operation A");
[NSThread sleepForTimeInterval:1.2];
info[#"DoThis"] = #YES;
info[#"DoThat"] = #NO;
NSLog(#"- Done operation A");
}];
NSOperation *operationB = [NSBlockOperation blockOperationWithBlock:^{
NSLog(#"- Running operation B");
if ([info[#"DoThis"] boolValue] NSLog(#"Op A said to do this.");
if ([info[#"DoThat"] boolValue] NSLog(#"Op A said to do that.");
[NSThread sleepForTimeInterval:2];
NSLog(#"- Done operation B");
}];
[operationB addDependency:operationA];
[self.queue addOperations:#[operationA, operationB] waitUntilFinished:NO];

Subclass as Delegate

I have spent quite a lot of time searching for this answer, but I'm estimating that it is my newbie status that is preventing me from seeing the light.
I am subclassing UIScrollView to make it into an infinite pager. As part of that, I need to see when a new page comes up, as I intend to only have three "pages" in use at a time for memory conservation. That means I need to have my subclass also act as a delegate.
I have followed the directions of: https://stackoverflow.com/a/9986842/773329. But I am running into some strange (to me) problems.
The primary one here is that when I override setDelegate:(id<UIScrollViewDelegate>), to insert the user's delegate, I get a loop in assembly that does not exit:
-(void)setDelegate:(id<UIScrollViewDelegate>)delegate {
_selfDelegate->_userDelegate = delegate;
super.delegate = nil;
super.delegate = (id)_selfDelegate;
}
The assembly:
libobjc.A.dylib`objc_release:
0x12be090: pushl %ebp
0x12be091: movl %esp, %ebp
0x12be093: subl $8, %esp
0x12be096: calll 0x12be09b ; objc_release + 11
0x12be09b: popl %ecx
0x12be09c: movl 8(%ebp), %eax
0x12be09f: testl %eax, %eax
0x12be0a1: je 0x12be0d5 ; objc_release + 69
0x12be0a3: movl (%eax), %edx ; <<< This is where the loop is
0x12be0a5: movl 16(%edx), %edx
0x12be0a8: andl $-4, %edx
0x12be0ab: testb $2, 2(%edx)
0x12be0af: je 0x12be0c5 ; objc_release + 53
0x12be0b1: movl 1002149(%ecx), %ecx
0x12be0b7: movl %ecx, 4(%esp)
0x12be0bb: movl %eax, (%esp)
0x12be0be: calll 0x12bd08c ; objc_msgSend
0x12be0c3: jmp 0x12be0d5 ; objc_release + 69
0x12be0c5: movl %eax, (%esp)
0x12be0c8: movl $0, 4(%esp)
0x12be0d0: calll 0x12bf9d0 ; -[NSObject release]
0x12be0d5: addl $8, %esp
0x12be0d8: popl %ebp
0x12be0d9: ret
Is there something that might be causing the problem?
Based upon what you are doing, I think a UICollectionView would fit perfectly with what you want to accomplish. This removes you from having to write your own code for reusing views and other things you would have to do in order to have performant code.
UICollectionView Reference
You absolutely can and should implement infinite scrolling by using a regular delegate. Which particular functionality are you looking to override inside your subclass?

Unexplained bad access error

I have some code where I iterate through an array. I am adding a second mutable array so that I can add objects to it, which I wish to remove after the iteration is complete. This however is causing bad access errors when I attempt to add the object to the second array I created. This is confusing me, because the object I am trying to add can be used for all types of things, just not adding to this array. This lead me to believe that maybe the array was getting released, so I retained it, but that had no effect. Here is the code:
CCTMXObjectGroup *objectGroup = [ self objectGroupNamed: #"object" ];
NSMutableArray *objectsToRemove = [ NSMutableArray array ];
for ( NSDictionary *object in [ objectGroup objects ] ) {
[ objectsToRemove addObject: object ]; // crash occurs here
// name, type, x, y, width, height
NSString *name = [ object valueForKey: #"name" ];
if ( [ name isEqualToString: #"sprite" ] ) {
[ self createSpriteFromObject: object ];
} else if ( [ name isEqualToString: #"spriteController" ] ) {
[ self createSpriteControllerFromObject: object ];
}
}
If I remove the addObject line, the crash will no longer occur. Here is the kicker, the crash only seems to occur on the 6th iteration. Stepping through the code, the mutable array I am adding to, and the object that I am adding to it both seem to be fine (not released). Why the bad access error then?
Edit
CoreFoundation`-[__NSArrayM addObject:]:
0x23c39e0: pushl %ebp
0x23c39e1: movl %esp, %ebp
0x23c39e3: pushl %edi
0x23c39e4: pushl %esi
0x23c39e5: subl $16, %esp
0x23c39e8: calll 0x23c39ed ; -[__NSArrayM addObject:] + 13
0x23c39ed: popl %edi
0x23c39ee: movl 1512267(%edi), %eax
0x23c39f4: movl %eax, 4(%esp)
0x23c39f8: movl 8(%ebp), %esi
0x23c39fb: movl %esi, (%esp)
0x23c39fe: calll 0x24e35c8 ; symbol stub for: objc_msgSend
0x23c3a03: movl 1512287(%edi), %ecx
0x23c3a09: movl %eax, 12(%esp)
0x23c3a0d: movl 16(%ebp), %eax
0x23c3a10: movl %eax, 8(%esp)
0x23c3a14: movl %ecx, 4(%esp)
0x23c3a18: movl %esi, (%esp)
0x23c3a1b: calll 0x24e35c8 ; symbol stub for: objc_msgSend
0x23c3a20: addl $16, %esp ; bad access code 2
0x23c3a23: popl %esi
0x23c3a24: popl %edi
0x23c3a25: popl %ebp
0x23c3a26: ret
0x23c3a27: nopw (%eax,%eax)
and
libsystem_sim_c.dylib`bzero$VARIANT$sse42:
0x1f9c200: pushl %ebp
0x1f9c201: movl %esp, %ebp
0x1f9c203: pushl %edi
0x1f9c204: movl 8(%ebp), %edi
0x1f9c207: movl 12(%ebp), %edx
0x1f9c20a: xorl %eax, %eax
0x1f9c20c: cmpl $80, %edx
0x1f9c20f: jg 0x1f9c24c ; bzero$VARIANT$sse42 + 76
0x1f9c211: cmpl $12, %edx
0x1f9c214: jge 0x1f9c226 ; bzero$VARIANT$sse42 + 38
0x1f9c216: testl %edx, %edx
0x1f9c218: je 0x1f9c246 ; bzero$VARIANT$sse42 + 70
0x1f9c21a: movb %al, (%edi) ; bad access code 2
0x1f9c21c: incl %edi
NSMutableArray *objectsToRemove = [ NSMutableArray array ];
for ( NSDictionary *object in [ objectGroup objects ] ) {
[ objectsToRemove addObject: object ]; // crash occurs here
If the crash is occurring there, then it can't have anything to do with objectsToRemove (unless there is code you haven't shown).
It means that the object is bunko. Likely, it has been released out from under objectGroup. Turn on Zombie detection and re-run your app.
Note: It can be daunting to debug this stuff when you are new. Some tips:
• If you are staring at assembly code, you are almost assuredly off in the weeds.
• If you are crashing in a Foundation framework method, it is pretty much guaranteed the bug is elsewhere because those methods get executed hundreds of millions of time to boot your Mac or your iOS device. Assuming they work is a safe bet.
It turns out the issue lay with the NSMutableArray initialization. I was using:
NSMutableArray *objectsToRemove = [ NSMutableArray array ];
When it should have been:
NSMutableArray *objectsToRemove = [ NSMutableArray arrayWithCapacity: 10 ];

(NSError *__strong *)magic

Without any preamble I want to show you problem I have in my program, I commented out steps and my thoughts for that steps. (I didn't include #interface part for shortness, it has same method with the same signature as in #implementation)
#implementation Dummy
- (int)testing:(NSError *__strong *)error
{
*error = [[NSError alloc] initWithDomain:#"hello" code:42 userInfo:nil];
// 3. retain count = 1
// 4. because of ARC 'error' object was released for this time
// (assembly output is my proof) object is deallocated
// retain count = 0
return 0;
}
#end
int main()
{
NSError *e = nil; // 1. retain count = 0 (obviously)
Dummy *dummy = [[Dummy alloc] init];
[dummy testing:&e]; // 2. passing reference to an error object
// 'e' for this time has to be just a trash, or nil maybe,
// but next log gives me correct output:
NSLog(#"%# %li", [e domain], [e code]); // 'hello 42'
return 0;
}
How does an error object exist after it death? I understand that using NSError *__autoreleasing * will be right way to go, and situation will be trivial in that case, but how compiler reasoning for this code, where is my mistake in judgements?
It's a bit an artificial question, but I can't throw out this situation from my head, I think I'm loosing something.
Here is part of disassembly for -[Dummy testing:]
callq 0x100000e8c <dyld_stub_objc_msgSend>
mov -0x18(%rbp),%rcx
mov (%rcx),%rdx
mov %rax,(%rcx)
mov %rdx,%rdi
callq 0x100000e92 <dyld_stub_objc_release>
mov -0x24(%rbp),%eax
add $0x40,%rsp
pop %rbp
retq
If I understood correctly, there is only one object in this method, and it's clearly released, not autoreleased or something else.
I suspect your confused about what's getting released. I just checked the assembly output, and there is a call to objc_release(), though I'm not familiar enough with x86 assembly to actually trace precisely what's going on. However, I do know that the code here is expected to emit something of the equivalent of:
NSError *temp = [[NSError alloc] initWithDomain:#"hello" code:42 userInfo:nil];
[*error release];
*error = [temp retain];
[temp release];
and of course the optimizer will shrink that to
NSError *temp = ...
[*error release];
*error = temp;
So I think you're seeing the call to objc_release() and thinking that your newly-allocated error is being released. It's not. The previous value of *error is being released before the newly-allocated error is placed in that location.