Jonathan Brown
Jonathan Brown

Reputation: 3822

NSMutableArrays in Singleton not holding values

Any help figuring this out would be appreciated. I have 3 NSMutableArrays in a singleton (Game Manager) and only one of them will work when I go to access the data I try to store, even though I use them all the same.

Here is where I initialize them in my GameManager.h

- (GameManager *)init
{
    if ((self = [super init]))
    {
        gameTexts = [[NSMutableArray alloc] init];
        gameDrawings = [[NSMutableArray alloc] init];
        playerNames = [[NSMutableArray alloc] init];
    }
    return self;
}

Here is where I go to store values in them. This seems to be where the problem is from NSLogs I put in.

[[GameManager sharedManager].playerNames addObject:[NSString stringWithFormat:@"%@",nextPlayerName.text]];
[[GameManager sharedManager].gameTexts addObject:[NSString stringWithFormat:@"%@",inputText.text]];
[[GameManager sharedManager].gameDrawings addObject:([[GameManager sharedManager] previousDrawing])];

Here is where I try to retrieve the data.

names.text = [[GameManager sharedManager].playerNames objectAtIndex:(nameIndex)];
texts.text = [[GameManager sharedManager].gameTexts objectAtIndex:(textIndex)];
drawings.image = [[GameManager sharedManager].gameDrawings objectAtIndex:(drawingIndex)];

Only the playerNames array will work correctly. The other two give me bad access errors when I try to use the NSLog to see what is in them. Any ideas are welcome, as I have been stuck on this a while. Thanks

Upvotes: 0

Views: 903

Answers (3)

unsynchronized
unsynchronized

Reputation: 4938

I have just been working on a set of macros for handling NSMutableArrays from multiple threads, and decided to do a few searches on stack overflow to see if the issues that led me to write this code were commonplace, and as a result your question came up.

This may not be "the droid you are looking for", so feel free to "move along", if it does not answer the question you asked :-)

here are 2 header files (with comments on how to use them) for several versions of the same concept - an easy one-liner to auto generate some class/instance methods that do the somewhat tricky aspects of thread-safe array manipulation.

StaticMutableArrays.h is a global class-wide array concept where each instance of the class shares a common array - where multithreaded operation is most likely to be an issue.

There is another variant (ThreadsafeMutableArrays.h) which is for creating arrays that each instance of the class has it's own array, that's distinct from any other instances (the way it would normally be if you just created an array in your code. Since your question was regarding issues accessing arrays within singletons, I'll just post StaticMutableArrays.h for now. They work the same way, but wrap around a standard ivar array.

I am releasing this code as public domain, and would welcome any comments on how it could be improved, or if there are any glaring issues anyone sees.

StaticMutableArrays.h

//
//  StaticMutableArrays.h
//
//  Created by Jonathan M Annett on 10/10/11.
//   The author has placed this work in the Public Domain, thereby relinquishing all copyrights. Everyone is free to use, modify, republish, sell or give away this work without prior consent from anybody.

//   This documentation is provided on an “as is” basis, without warranty of any kind. Use at your own risk! Under no circumstances shall the author(s) or contributor(s) be liable for damages resulting directly or indirectly from the use or non-use of this documentation.
//



/*

 StaticMutableArrays are thread safe arrays that are associated with a particular class of object 

 they are accessed via class methods in the class in which they are defined.
 each instance of the class sees the same physical array. 

 they are generated by insert a macro in the interface and implementation seconds of the class:

@interface myObject : NSObject {

 }

    staticMutableArray_interface(arrayName);


 @end


 @implementation myObject

    staticMutableArray_implementation(arrayName);

@end


 these arrays are syntactially identical to those generated by  ThreadsafeMutableArrays.h

 you can then access the array in your code as follows (using "arrayName" as the example)


 NSMutableArray *tempArray = [self.class arrayName_];

 note the trailing underscore - this indicates it's an autoreleased threadsafe mutable copy of the underlying array, that was "correct" at the time you called the method. you can safely iterate the contents, knowing the array you are holding won't be mutated by another thread whilst you have it - the underlying array may have been mutated, so if you need exclusive access to the array knowing it can't be changed use [self.class arrayName] instead. note that even if the underlying array is mutated, and some elements are removed, and as long as this object is around (ie the pool is not drained) they will be quite safe as this (copied) array has them retained. this array is an NSMutableArray, so you can chop and change it as you want (eg whilst processing it), and it won't affect the underlying array at all. be sure to call [array removeAllObjects] when you are done to explicitly release the retains it has for each member. you don't need to release the array itself as it is already autoreleased.  


 NSArray *tempArray = [self.class copyOf_arrayName]

 This is the same as [self.class arrayName_], however the returned copy is immutable, and a retained copy.
 you can (for example) iterate the contents, and then release it, knowing that no matter what another thread does to the array while you are iterating it, they won't mess with your copy of the array. again - if you need exclusive access to the array knowing it can't be changed use [self.class arrayName] instead. 



 [self.class arrayName]; 

 returns the raw underlying array - warning: if you call this directly only, you can only USE the returned value safely inside an @synchronized() block for the array itself. the easiest way to do this is something like:

 NSMutableArray *array;
 @synchronized(array = [self.class arrayName]){ 

 // do something with "array"

 }

 if you study the macros you will see a variant of this throughout. (the method wrapper arrayName omitted to save an extra message being sent, but for readability of your code it's suggested you use the above construct.


 [self.class addObjectTo_arrayName:object];

 this is a thread safe way of adding to the array, that you can use from anywhere (EXCEPT from inside and @synchronized block for the array! in that case just add the object as you would normally in a single threaded environment) 

[self.class removeObjectFrom_arrayName:object];

 this is a thread safe way of removing an object from an array, that you can use from anywhere (EXCEPT from inside and @synchronized block for the array! in that case just add the object as you would normally in a single threaded environment) 

if ([self.class objectExistsIn_arrayName:object]) {...}

 this is a thread safe way of testing if an object is in an array, that you can use from anywhere (EXCEPT from inside and @synchronized block for the array! in that case just add the object as you would normally in a single threaded environment) 


 now the clever stuff:

 @synchronzed exclusive unconditional iteration of each element, blocking all other threads.

 [self.class iterate_arrayName_withBlock:NSArrayIterateBlock {

    // do something with: element 

 }];

 in this code construct you get 3 variables defined. for convenience a macro - NSArrayIterateBlock is defined to save you having to type long hand the block header:

    ^void(id element, NSInteger index,NSInteger ofCount)

    element  - this is the item you are iterating. you just typecast it to whatever you expect it to be (or test it to see what it is, the choice is yours)
    index & count - for simple accounting purposes you can tell at any stage in the loop how many you have processed (index == 0 means this is first), or how many you have to go (index==count-1) means this is the  last. you get the idea.




 @synchronzed exclusive conditional iteration of each element, blocking all other threads.

 [self.class conditionallyIterate_arrayName_withBlock:NSArrayConditionallyIterateBlock {

 // do something with: element and..

    return NO;  // we have done looping.

 // or 

    return YES;  // we want to keep looping.

 }];

 in this code construct you get 3 variables defined. for convenience a macro - NSArrayConditionallyIterateBlock is defined to save you having to type long hand the block header:

 ^BOOL(id element, NSInteger index,NSInteger ofCount)

 element  - this is the item you are iterating. you just typecast it to whatever you expect it to be (or test it to see what it is, the choice is yours)
 index & count - for simple accounting purposes you can tell at any stage in the loop how many you have processed (index == 0 means this is first), or how many you have to go (index==count-1) means this is the  last. you get the idea.

 the BOOL return value from the block indicates if you want to keep iterating the array (YES) or not (NO)



 and the final Pièce de résistance - conditionally delete elements from an array, whilst blocking access to the array by other threads.


 [self.class conditionallyDeleteFrom_arrayName_withBlock:
         NSMutableConditionallyDeleteArrayBlock {


        if (<element is something i want to delete>) {
            return YES;
        }

    return NO;
 }]



internal method that holds the static variable for the singleton array:

 [self.class arrayNameAlloc:YES];   // creates/returns the array - you can call this in your +(void) load {} for the class to ensure it is created. if you don't do this however, it will automatically be called the first time you do it. 
 [self.class arrayNameAlloc:NO];    // dumps the array - once you do this, you can't re alloc, so only do it once!



*/


// custom block header macros where you can specify a name and class type for the array element
// for example NSArrayIterateBlock_(personsName,NSString *)
// for example NSArrayIterateBlock_(itemInfo,NSDictionary *)
# define NSArrayIterateBlock_(element,id) ^void(id element, NSInteger index,NSInteger ofCount)
# define NSArrayConditionallyIterateBlock_(element,id) ^BOOL(id element, NSInteger index,NSInteger ofCount)
# define NSMutableConditionallyDeleteArrayBlock_(element,id) ^BOOL(id element, NSInteger originalIndex)


// generic version that just defines each element as "id element"
# define NSArrayIterateBlock NSArrayIterateBlock_(element,id)
# define NSArrayConditionallyIterateBlock NSArrayConditionallyIterateBlock_(element,id)
# define NSMutableConditionallyDeleteArrayBlock NSMutableConditionallyDeleteArrayBlock_(element,id)



#define staticMutableArray_interface(arrayName)\
+(NSMutableArray *) arrayName;\
/*+(NSMutableArray *) arrayName##Alloc:(BOOL)alloc;*/\
+(void) addObjectTo_##arrayName:(id) object;\
+(void) removeObjectFrom_##arrayName:(id) object;\
+(BOOL) objectExistsIn_##arrayName:(id) object;\
+(NSMutableArray *) arrayName##_;\
+(NSArray *) copyOf_##arrayName;\
+(void) iterate_##arrayName##_withBlock:\
(void (^)(id element,NSInteger index,NSInteger ofCount))codeBlock;\
+(void) iterateCopyOf_##arrayName##_withBlock:\
(void (^)(id element,NSInteger index,NSInteger ofCount))codeBlock ;\
+(void) conditionallyIterate_##arrayName##_withBlock:\
(BOOL (^)(id element,NSInteger index,NSInteger ofCount))codeBlock;\
+(void) conditionallyIterateCopyOf_##arrayName##_withBlock:\
(BOOL (^)(id element,NSInteger index,NSInteger ofCount))codeBlock;\
+(void) conditionallyDeleteFrom_##arrayName##_withBlock:\
    (BOOL (^)(id element,NSInteger originalIndex))codeBlock;


#define staticMutableArray_implementation(arrayName)\
/*quasi singleton factory method*/ \
+(NSMutableArray *) arrayName##Alloc:(BOOL)alloc {\
    static NSMutableArray *result = nil;\
    static BOOL dealloced = NO;\
    if (alloc) {\
            if (!result) {\
               if (!dealloced) {\
                  result = [[NSMutableArray alloc] init ];\
                }\
           }\
    } else {\
      if (!dealloced) {\
          if(result) {\
              @synchronized(result){ \
                 [result removeAllObjects];\
                 [result release];\
              }\
              result = nil;\
           }\
           dealloced = YES;\
       }\
    }\
    return result;\
}\
/*  add an object the arrray */ \
+(void) addObjectTo_##arrayName:(id) object   {\
     NSMutableArray *array;\
       @synchronized(array= [self.class arrayName##Alloc:YES]){ \
       [array addObject:object];\
     }\
}\
/*  add an object, if it is not already in the array */ \
+(void) includeObjectIn_##arrayName:(id) object   {\
    NSMutableArray *array;\
    @synchronized(array= [self.class arrayName##Alloc:YES]){ \
      if ([array indexOfObject:object]==NSNotFound){\
         [array addObject:object];\
     }\
   }\
}\
/*  remove an object from the array */ \
+(void) removeObjectFrom_##arrayName:(id) object   {\
   NSMutableArray *array;\
   @synchronized(array= [self.class arrayName##Alloc:YES]){ \
      [array removeObject:object];\
  }\
}\
/*  test object existance*/ \
+(BOOL) objectExistsIn_##arrayName:(id) object   {\
  NSInteger result = NSNotFound; \
  NSMutableArray *array;\
  @synchronized(array= [self.class arrayName##Alloc:YES]){ \
    result = [array indexOfObject:object];\
  }\
  return result!=NSNotFound;\
}\
/*  raw underlying access - use inside @synchronized(array= [self.class arrayName##Alloc:YES]) only*/ \
+(NSMutableArray *) arrayName { \
    return [self.class arrayName##Alloc:YES];\
}\
/*  mutable  autoreleased copy of underlying array - ie snapshot which may contain objects that have been removed since snapshot was taken - you need to call removeAllObjects when done, to expedite adjusting affected retainCounts that the arrayWithArray process implies */ \
+(NSMutableArray *) arrayName##_ { \
    NSMutableArray *result = nil;\
    NSMutableArray *array;\
    @synchronized(array= [self.class arrayName##Alloc:YES]){ \
        result = [NSMutableArray arrayWithArray:array];\
    }\
    return result ;\
}\
/*  immutable retained copy of underlying array - ie snapshot which may contain objects that have been removed since snapshot was taken - you need to call release when done, to expedite adjusting affected retainCounts that the initWithArray process implies */ \
+(NSArray *) copyOf_##arrayName { \
    NSArray *result = nil;\
    NSMutableArray *array;\
    @synchronized(array = [self.class arrayName##Alloc:YES]){ \
        result = [[NSArray alloc] initWithArray:array];\
    }\
    return result ;\
}\
/*  iteration of the array for each element, using a thread safe snapshot copy*/\
+(void) iterateCopyOf_##arrayName##_withBlock:\
(void (^)(id element,NSInteger index,NSInteger ofCount))codeBlock  {\
    NSArray *array = [self.class copyOf_##arrayName]; \
    NSInteger index = 0;\
    NSInteger count = array.count;\
        for (id element in array) {\
            codeBlock (element,index,count);\
            index++;\
        }\
   [array release];\
}\
/*  @synchronized iteration the array for each element */\
+(void) iterate_##arrayName##_withBlock:\
(void (^)(id element,NSInteger index,NSInteger ofCount))codeBlock  {\
   NSMutableArray *array;\
   @synchronized(array = [self.class arrayName##Alloc:YES]){ \
        NSInteger index = 0;\
        NSInteger count = array.count;\
        for (id element in array) {\
            codeBlock (element,index,count);\
            index++;\
        }\
    }\
}\
/* iteration of the array for each element, using a thread safe snapshot copy, with option to exit loop */ \
+(void) conditionallyIterateCopyOf_##arrayName##_withBlock:\
(BOOL (^)(id element,NSInteger index,NSInteger ofCount))codeBlock  {\
    NSArray *array = [self.class copyOf_##arrayName];\
    NSInteger index = 0;\
    NSInteger count = array.count;\
    for (id element in array) {\
        if (!codeBlock (element,index,count)) break;\
        index++;\
    }\
    [array release];\
}\
/*  @synchronized iteration the array for each element, with option to exit loop */ \
+(void) conditionallyIterate_##arrayName##_withBlock:\
(BOOL (^)(id element,NSInteger index,NSInteger ofCount))codeBlock  {\
    NSMutableArray *array;\
    @synchronized(array = [self.class arrayName##Alloc:YES]){ \
        NSInteger index = 0;\
        NSInteger count = array.count;\
        for (id element in array) {\
            if (!codeBlock (element,index,count)) break;\
            index++;\
        }\
    }\
}\
/* conditionally delete each element */ \
+(void) conditionallyDeleteFrom_##arrayName##_withBlock:\
(BOOL (^)(id element,NSInteger originalIndex))codeBlock  {\
NSArray *array = [self.class copyOf_##arrayName]; \
NSInteger originalIndex = 0;\
for (id element in array) {\
    \
    if (codeBlock (element,originalIndex)) [self.class removeObjectFrom_##arrayName:element];\
    originalIndex++;\
}\
[array release];\
}

Upvotes: 0

Wilbur Vandrsmith
Wilbur Vandrsmith

Reputation: 5050

It sounds like gameTexts and gameDrawings are getting over-released somewhere. That is almost always the cause of EXC_BAD_ACCESS.

Upvotes: 1

fzwo
fzwo

Reputation: 9902

You should encapsulate your arrays and create accessors for the functions you want to provide, i. e. have an - (void)addPlayerName:(NSString *)name method, etc.

This will make your code more readable and also allows your manager to do stuff with the values, like rejecting them, checking for duplicates, etc.

Upvotes: 1

Related Questions