0

Environment: Mac OS 10.8.5, XCode 5.1.1

Problem: Crash in obj_msgsend on addObject message to a NSMutableArray

Disclaimer: I'm new to Objective-C, so this could an obvious mistake. But it's mysterious.

Details:

I've been able to prune the problem down to a small test case (thankfully), though the exact manifestation of the problem is different from the full application.

Here's the @interface:

@interface ObjCQueue : NSObject
+ (void) push: (NSString *)calEvent;
+ (NSString *) pop;
@end

Here's the Objective-C class implementation.

#import <Foundation/Foundation.h>
#include "ObjcQueue.h"

NSMutableArray *qArray;

@implementation ObjCQueue
{

}

+ (void) init
{
    qArray =  [[NSMutableArray alloc] init]; 
    //    NSLog(@"(init)qArray class is: %@\n", NSStringFromClass([qArray class]));
}

+ (void) push:(NSString *)calEvent
{
    [qArray addObject:calEvent];
} 

+ (NSString *) pop
{
    // This will return nil if there's no first object
    NSString *retEvent = [qArray objectAtIndex:0];
    // Don't delete the front of the queue if nothing is there
    if (retEvent != nil)
        [qArray removeObjectAtIndex:0];

    return retEvent;
}
@end

and main.m does this:

int main(int argc, const char * argv[])
{
    @autoreleasepool {     
        [ObjCQueue init];
        [ObjCQueue push:@"Pushed thing"];
        NSLog(@"Popped: %@\n", [ObjCQueue pop]);
    }
    return 0;
}

For the moment, let's ignore the possibility that how I'm doing this is totally wrong (we'll get back to that).

If I run this code as-is, I get a crash in objc_msgSend called by the addObject message sent from [ObjCQueue push:]

The mystery part is, if I uncomment the NSLog call in [ObjCQueue init] everything runs just fine.

In the larger application, I see a different issue. The failure also occurred in the push method, except the run-time error I got said that addObject was an invalid selector. When I check the type of qArray in that case, it has a type of NSDictionary (that's from memory, it wasn't spelled exactly that way) instead of NSMutableArray. Also, in the larger application, adding the NSLog call in the init method makes everything run smoothly.

In this smaller example, the type of qArray always appears to be NSMutableArray.

In other answers to similar questions, the implication is that the object corresponding to qArray is getting overwritten, and/or released prematurely. I don't see how that could happen here, since it's global, and ObjCQueue only has class methods and no instance of it is created. [ObjCQueue init] is only called once.

One other bit of data: In this smaller case, qArray gets displayed differently depending where (in the debugger) it's displayed.

In init, in the case where it crashes, immediately after qArray gets its value, the debugger shows:

Printing description of qArray:
<__NSArrayM 0x10010a680>(
)

But in push, just before the addObject method is called, the debugger shows:

Printing description of qArray:
(NSMutableArray *) qArray = 0x000000010010a680

The value is the same, but the type is kinda sorta different (maybe). In the case with no crash, the display is identical in both cases (they're both the same as the first display)

This may not be the best way (or it may be a blatantly wrong way) to initialize qArray, and I can accept that. But why would the behavior change with the addition of the NSLog call?

Any help/insights will be appreciated.

-Eric

P.S. Here's the XCode project: Bug Test

Community
  • 1
  • 1
Eric
  • 843
  • 6
  • 15
  • What are the contents of your .h file? – Kyle Emmanuel Aug 16 '14 at 03:23
  • 1
    Any reason all your methods are class methods, and your array is in the global scope? (As opposed to properties on a specific instance?) That likely has a lot to do with the problem. Also, `-[NSArray objectAtIndex:]` doesn't return `nil` if it's empty and you ask for object at index `0`; it throws an exception. You're looking for `-[NSArray firstObject]` to get the correct emptiness handling. – Itai Ferber Aug 16 '14 at 03:34
  • Also, `__NSArrayM` is the concrete class that actually implements `NSMutableArray` methods (see [class clusters](https://developer.apple.com/library/ios/documentation/general/conceptual/CocoaEncyclopedia/ClassClusters/ClassClusters.html)). In any case, I can't reproduce the crash on my system with identical code. Can you attach your project, perhaps? (I'd wager the non-idiomatic usage of Objective-C patterns coupled with a global variable and some other weirdness is causing this crash.) – Itai Ferber Aug 16 '14 at 03:58
  • Are you using ARC? That might be the problem so try disabling it. – TheAmateurProgrammer Aug 16 '14 at 05:42
  • Oh boy, thanks for all the comments/questions... I may need multiple comments to address them all. @ItaiFerber: on your first comment, correct on both counts. I tried using `firstObject` but abandoned it... can't remember why other than I was getting odd compile-time errors. Obj-C seemed to think I was using an NSDictionary. Also, I don't need multiple instances of the class, so I created all class methods. Probably C++ thinking, using static methods. For your second comment, yes, I'm sure it's the odd use of Obj-C (I'm a newbie!) is the core of the problem. [more] – Eric Aug 16 '14 at 22:55
  • Also, can you create a "static" class member in Obj-C? That might just be getting myself deeper. @TheAmateurProgrammer: Yes, I am. I could be the problem, but why? That's actually the biggest part of the question is __why__ is this happening. Bad/anomalous/evil style shouldn't cause a crash if it's legal within the language. The oddest part to me is why the `NSLog` statement makes the problem go away. It goes away even in the `NSLog` statement doesn't reference `qArray`. @Kyle: I'll put it in the initial question (it's short). – Eric Aug 16 '14 at 22:58
  • @ItaiFerber: Almost forgot, I can certain attach the project, I'll do that momentarily – Eric Aug 16 '14 at 23:02
  • The reason non-idiomatic style may be causing strange things to happen is somewhat complicated, but explainable. Objective-C, being a relatively old language, has very strong and widely used design and naming patterns. Although they're informal and not enforced by the compiler (as you can see, your code compiles), they've become the "right" way to do things. Now, because these idiomatic patterns were so widely used, Apple, in implementing ARC, allowed ARC to make assumptions about your code based on those patterns, especially in regards to memory management. – Itai Ferber Aug 16 '14 at 23:24
  • Based on the naming of your methods, ARC makes assumptions about how to handle memory management (instance methods beginning with `-init`, for instance, are assumed to be initializers and do one thing; methods beginning with `+new`, or `-copy` or other prefixes have other assumptions, and do other things). This had the benefit of enabling correct behavior without having developers modify mountains of code. On the other hand, this makes a lot of memory management implicit, and makes code like yours difficult to debug if ARC is making the wrong assumptions. – Itai Ferber Aug 16 '14 at 23:26
  • That's not _necessarily_ what's happening, but it might have something to do with it. Separately, singletons tend to be bad practice (just because you don't need more than one instance of a class doesn't mean you should create a singleton — create a normal class and only instantiate your object once), for many reasons (a quick Google search will bring up reasoning as to why). If you really want to create a singleton, however, there are designated design patterns for that too (http://stackoverflow.com/questions/7568935/how-do-i-implement-an-objective-c-singleton-that-is-compatible-with-arc). – Itai Ferber Aug 16 '14 at 23:29
  • If I had to create a similar singleton queue, it would look a bit more like this: https://gist.github.com/itaiferber/914848b7552a30961357. You still have to account for global access and thread safety (what if you have multiple different threads trying to write to the queue?), but this is a basic template. You access the global queue using `+[IFGlobalQueue sharedQueue]` and then modify the global instance with `-pushObject:` and `-popObject`. – Itai Ferber Aug 16 '14 at 23:38
  • @ItaiFerber Thanks! StackOverflow is now warning me to avoid extended discussions in comments, so I'll continue in some other way... – Eric Aug 16 '14 at 23:47
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/59460/discussion-between-eric-and-itai-ferber). – Eric Aug 16 '14 at 23:49
  • So I just downloaded your code, disabled ARC and it worked perfectly. Because nothing is holding a reference to `qArray` ARC decided that it is unnecessary and released it. Your code is crashing not because it's bad style/what-so-ever but because your code is calling on a variable which has been released already. – TheAmateurProgrammer Aug 17 '14 at 00:24
  • Then... why is it getting released? `qArray` is a global variable. Its value comes from `[[NSMutableArray alloc] init]` Is the generated objected getting released after `[ObjCQueue init]` ends? I would hope assignment to a global variable would increase the use count. – Eric Aug 17 '14 at 03:52
  • Also, when I do `[qArray firstObject]` I get an error `/Volumes/Projects/CalendarThingie/ObjcQueue.m:53:50: No visible @interface for 'NSMutableArray' declares the selector 'firstObject'` But `NSArray` is supposed to have a `firstObject` method, er, message. I preprocessed `ObjCQueue.m` and sure enough, no `firstObject` selector for `NSArray (NSExtendedArray)` or any other variant. I'm compiling with base SDK 10.8, and when I switch to 10.9, it works. But the doc (from within XCode 5.1.1) says it was available since 10.6. The doc must be wrong...? – Eric Aug 17 '14 at 04:00
  • Looks like @TheAmateurProgrammer is right — don't know why I didn't catch that. I guess nothing was actively holding on to the reference, which is why ARC was releasing it. (Though I do disagree, this _is_ about being idiomatic — if you have a proper singleton instance holding on to the queue as an instance variable, this weird implicit release wouldn't happen because ARC would have enough context for knowing when it should be releasing/retaining; it's hard to track those rules for global variables). – Itai Ferber Aug 17 '14 at 07:43
  • Also, you're getting the `-[NSArray firstObject]` error because I guess the method wasn't made public until the 10.9 API; it was around since forever, but privately. Unrelated, @TheAmateurProgrammer, you should post your findings as an answer so it can be marked as correct. – Itai Ferber Aug 17 '14 at 07:44

1 Answers1

1

The problem is because ARC is releasing qArray before you called push so you're calling on an object that is already released. A good solution to this problem would be to either change your class to an actual instance, or create a singleton so that ARC knows to retain the array rather than just releasing it right after you init.

TheAmateurProgrammer
  • 9,252
  • 8
  • 52
  • 71