I just noticed that calling addObject: on an NSMutableArray doesn't access that array's setter.
E.g., for NSMutableArray self.myArray, [self.myArray addObject:object] does not use [self setMyArray:array] to add the object.
Previously I have been using custom setters and getter to check assignment before assigning; e.g., if I wanted an array that only accepted objects of class MyClass, I would do the following:
- (void)setMyArray:(NSMutableArray *)myArray
{
for (id object in myArray)
{
if (![object isKindOfClass:[MyClass class]]) return;
}
_myArray = myArray;
}
- (NSMutableArray *)myArray
{
if (!_myArray) _myArray = [[NSMutableArray alloc] init];
_myArray = myArray;
}
How do I go about achieving this same functionality when changing the array via addObject:, removeObject:, and other similar functions that may circumvent the setter?
Generally this kind of problem is the reason why NSMutableArray is usually avoided in preference of NSArray.
This is the simple solution, use NSArray instead of NSMutableArray:
self.myArray = [self.myArray arrayByAddingObject:foo];
However, if the array is really big that will cause performance issues. Then you've got two options:
you can have your own addObjectToMyArray: method in your class and always use that
you can create an NSArrayController and use that to access your array. It will implement key value observing and bindings and all of that stuff.
NSMutableArray is designed to perform addObject: with as few CPU instructions as possible and therefore does not proved any way for external code to be notified that the object was added. You have to have some other class wrapped around it.
Do not try to subclass NSMutableArray, because it is a "class cluster" making subclasses extremely complicated.
If what you wish to do is ensure objects in the array are of a particular class then this answer to the question "NSMutableArray - force the array to hold specific object type only" provides code to do exactly that.
If you wish to do other checks on assignment then you can use the code in that answer as a starting point.
Related
I want to create a subclass of NSMutableArray and need to override the -initWithObjects: method.
But How to call [super xxx];?
- (id) initWithObjects:(id)firstObj, ... {
[super initWithObjects:firstObj]; // Error: Missing sentinel in method dispatch
// Error: The result of a delegate init call must be immediately returned or assigned to "self"
}
Thanks.
Then "missing sentinel" message refers to the missing nil termination. In fact, according to font-of-all-knowledge-Wikipedia:
The name of the nil that terminates a variable length list of parameters in Objective-C
also: Sentinel node, an object to represent the end of a data structure
also: Sentinel value, a value used to terminate a loop
also: In network protocols such as Bisync, sentinel values indicate where frames start and end
You can't. As discussed in the documentation for NSArray:
You might want to implement an initializer for your subclass that is
suited to the backing store that the subclass is managing. The NSArray
class does not have a designated initializer, so your initializer need
only invoke the init method of super. The NSArray class adopts the
NSCopying, NSMutableCopying, and NSCoding protocols; if you want
instances of your own custom subclass created from copying or coding,
override the methods in these protocols.
So you can assign self = [super init]; and add the objects from your initialiser to the resulting object. Indeed, because of the way that NSArray is implemented, calling any -initWith… method is likely to return an instance of a different NSArray subclass.
Notice that the documentation also discusses alternatives to subclassing NSArray that may be easier, more reliable or better in some other way.
Subclassing NSArray/NSMutableArray doesn't work like subclassing most classes. NSArray is a class cluster, please see subclassing notes from the NSArray documentation.
Now, for your specific question, subclassing va_list methods is a bit tricky, there are a number of ways to handle this. The 'safest' would be to process your va_list into an NSArray and pass that into another method that dealt with whatever you wanted. The other, slightly less portable, slightly hackier way is to create a new va_list list on the stack to pass through.
id __unsafe_unretained * stack = (typeof(stack))calloc(numOfObjects, sizeof(id));
//filloutStack
[super initWithObjects:*stack, nil];
free(stack);
Subclassing Apples Collection classes isn't that difficult — if you use a tiny trick (see also: cocoawithlove).
A subclass is a "is-a" relationship in object-orientated Design. But there are also "has-a" relationships, i.e. wrappers.
If you would try to create a subclass of NSArray by using a pure is-a relationship, I guess, it would be kind of hard, as you would have to do C-level memory management.
But if you add a has-a relationship — or: create a wrapper — at the same time, you can the subcalssing quite easily: Just make your custom array class have a member of a regular NSArray. Now override its method by forwarding the calls to the member object. I showed this in this post, where I just add objects, that pass a certain test.
But you will see, that I didn't implement the method you talked about correctly, but I raise a error. The reason is: that method is a variadic methods, that has a variable number of objects you can pass in — and to handle this, you have to to a bit of work. cocoawithlove has an great article about it.
For you — if using that has-a trick — it could look like
- (id) initWithObjects:(id)firstObj, ... {
if (self = [super init]) {
_realArray = [[NSMutableArray alloc] initWithCapacity:1];
}
va_list args;
va_start(args, firstObj);
for (id obj = firstObj; obj != nil; obj = va_arg(args, id))
{
[self.realArray addObject:obj];
}
va_end(args);
return self;
}
Try
self = [super initWithObjects:firstObj,nil];
The NSMutableArray can store every NSObject, but can I mention the NSMutableArray can get store my item only, for example, a NSMutableArray that store NSString only?
I remember that the java array can do that, can the objective C array do the similar things? Thanks.
Objective-C does not have this kind of generic constraint on NSArray/NSMutableArray. You have therefore two solutions:
Subclass NSArray/NSMutableArray and check for element type. It is strongly discouraged as NSArray/NSMutableArray is a class "cluster" and not obvious to subclass.
Create a category with specific methods that check the right type. You will have a compile-time enforcement of the type.
You can try it like this -
NSMutableArray *arr = [[[NSMutableArray alloc] init] autorelease];
if([obj isKindOfClass:[NSString class]])
[arr addObject:obj];
This way you end up adding only NSString to your arr.
Not by default, no. NSArray and its mutable counterpart just store pointers which happen to point obj-c objects. These objects can of any type. It would be up to you to make sure that only NSString's get in your array.
You could potentially subclass NSArray and override the addObject: methods such that they throw an exception if you try to add a non-NSString object.
I have a cocoa 'category' for adding inflections (pluralize, singularize, etc.) to NSString. The code requires loading a set of regular expression rules and exceptions from a PLIST into dictionaries and arrays, as well as adding manual exceptions from code. I need a way to persist these data structures (as class members) between multiple calls to the inflection code (all instance methods). I attempted:
+ (NSMutableArray *)uncountables
{
static NSMutableArray *uncountables = nil;
if (uncountables == nil) uncountables = [NSMutableArray array];
return uncountables;
}
However, it appears to fail occasionally. Does a good way of doing this exist? I don't want to subclass NSString if possible. Thanks.
[NSMutableArray array];
returns an autoreleased array. Use this instead:
[[NSMutableArray alloc] init];
I think this code is OK. I use the same thing a lot for singletons. But be aware that it is not thread safe this way. Maybe you calling it from different threads?
As drawnonward already mentioned, [NSMutableArray array]; returns an autoreleased array. But I don't think, it's a good idea to return non-autoreleased array, because it contradicts with Cocoa memory management conceptions - only alloc, copy and new should be released manually. All other initializations are autoreleased.
So, you should just use
interface:
NSArray *a;
...somewhere in a code...
a = [[NSString uncountables] retain];
...
- (void)dealloc {
[a release];
}
to get properly retained/released objects.
Say you have a method that returns a newly generated NSArray instance that is built internally with an NSMutableArray. Do you always do something like this:
- (NSArray *)someArray {
NSMutableArray *mutableArray = [[NSMutableArray new] autorelease];
// do stuff...
return [NSArray arrayWithArray:mutableArray]; // .. or [[mutableArray copy] autorelease]
}
Or do you just leave the mutable array object as-is and return it directly because NSMutableArray is a subclass of NSArray:
- (NSArray *)someArray {
NSMutableArray *mutableArray = [[NSMutableArray new] autorelease];
// do stuff...
return mutableArray;
}
Personally, I often turn a mutable array into an NSArray when I return from methods like this just because I feel like it's "safer" or more "correct" somehow. Although to be honest, I've never had a problem returning a mutable array that was cast to an NSArray, so it's probably a non-issue in reality - but is there a best practice for situations like this?
I used to do the return [NSArray arrayWithArray:someMutableArray], but I was slowly convinced that it doesn't offer any real benefit. If a caller of your API is treating a returned object as a subclass of the declared class, they're doing it wrong.
[NB: See bbum's caveat below.]
It's very common to return an NSMutableArray cast as an NSArray. I think most programmers would realize that if they downcast an immutable object and mutate it, then they're going to introduce nasty bugs.
Also, if you have an NSMutableArray ivar someMutableArray, and you return [NSArray arrayWithArray:someMutableArray] in a KVC accessor method, it can mess up KVO. You'll start getting "object was deallocated with observers still attached" errors.
NSArray is in fact a class cluster, not a type, anyway. So anywhere you see an NSArray, chances are it's already one of several different types anyway. Therefore the 'convert to NSArray' is somewhat misleading; an NSMutableArray already conforms to the NSArray interface and that's what most will deal with.
CocoaObjects fundamentals
In any case, given that you're returning an array (and not keeping it afterwards, thanks to the autorelease) you probably don't need to worry whether the array is mutable or not.
However, if you were keeping the array, then you might want to do this, to prevent the clients from changing the contents.
I'm creating a KVC/KVO-compliant mutable array on one of my objects the recommended way:
#interface Factory {
NSMutableArray *widgets;
}
- (NSArray *)widgets;
- (void)insertObject:(id)obj inWidgetsAtIndex:(NSUInteger)idx;
- (void)removeObjectFromWidgetsAtIndex:(NSUInteger)idx;
#end
Clearly this is a tricky thread-safety issue. In the insert and remove methods I'm locking around array access to prevent concurrent modification, as recommended.
My question is, what is the proper way to implement the widgets accessor? Here's my implementation:
- (NSArray *)widgets {
[widgetLock lock];
NSArray *a = [[widgets copy] autorelease];
[widgetLock unlock];
return a;
}
Is it threadsafe?
Your widgets accessor should be fine, although you should be aware that none of the objects in that array are locked. So, you could run into problems trying to concurrently run code like
[[[myFactory widgets] objectAtIndex:7] setName:#"mildred"];
and
[myTextField setStringValue:[[[myFactory widgets] objectAtIndex:7] name]]; // mildred? or something else?
Since the objects in your array are not locked, you could run into race conditions or readers/writers-type problems. Isn't multithreading a joy?
On a different note, for KVC-compliance, I'd advise implementing objectInWidgetsAtIndex: and countOfWidgets instead of a widgets accessor. Remember, KVC models relationships, not array properties. So you would call something like [myFactory mutableArrayValueForKey:#"widgets"] to get an array representing the widgets property.
Rather than creating your own lock, you could also use the locking built into the language:
i.e
- (NSArray *)widgets {
#synchronized(widgets)
{
NSArray *a = [[widgets copy] autorelease];
return a;
}
}
and use similar locking in all other methods that access widgets. (The parameter widgets passed into #synchronized refers to the instance variable, not the method.)
alex's comment about access to contained objects still apply.
You will need locking on all reading and writing methods. If your insert and remove are also locking (like you said) then the accessor method should be fine like that.