How do I extend an NSArray? - objective-c

Here's my try:
H file:
#interface Strings : NSArray
#end
M file:
#implementation Strings
- (id) init
{
[self initWithObjects:
#"One.",
nil];
return self;
}
#end
When I run I get this:
'NSInvalidArgumentException', reason: '* -[NSArray initWithObjects:count:]: method only defined for abstract class. Define -[Strings initWithObjects:count:]!'
This is what I did instead:
H file:
#interface Strings : NSObject
+ (NSArray*) getStrings;
#end
M file:
#implementation Strings
+ (NSArray*) getStrings
{
NSArray* strings = [[NSArray alloc] initWithObjects:
#"One.",
nil];
return strings;
}
#end

NSArray is a class cluster (link to Apple's documentation). This means that when you try to create an NSArray, the system creates some private subclass of NSArray. The NSArray class just defines an interface; subclasses of NSArray provide implementations of the interface.
You can write your own subclass of NSArray, but you have to provide your own storage for the objects in the array. You have to initialize that storage yourself. The error message is telling you this, by saying that you need to override initWithObjects:count: in your subclass. Your override needs to put the objects into whatever storage you allocate as part of your class implementation.
The NSArray implementation of the variadic initWithObjects: method is just a wrapper around initWithObjects:count:, so you don't have to implement initWithObjects:.

Deriving from NSArray is something you should avoid. From the documentation:
Remember that NSArray is the public interface for a class cluster and what this entails for your subclass. The primitive methods of NSArray do not include any designated initializers. This means that you must provide the storage for your subclass and implement the primitive methods that directly act on that storage.
What this means is that when you initialize an array, you don't get an instance of NSArray. You'll get an instance of a totally different class that merely has the same interface. That is why subclassing doesn't work the way you think it works: you'll have to completely implement the storage yourself. This is why the documentation further states:
Any subclass of NSArray must override the primitive instance methods count and objectAtIndex:. These methods must operate on the backing store that you provide for the elements of the collection. For this backing store you can use a static array, a standard NSArray object, or some other data type or mechanism. You may also choose to override, partially or fully, any other NSArray method for which you want to provide an alternative implementation.
Last but not least you would have had the initializing wrong anyway. You would have needed to call super:
- (id)init
{
self = [super initWithObjects:#"One", #"Two", nil];
if (!self) return nil;
return self;
}
But as I just said, it just doesn't work that easily. You'll get the same exception again. So you should simply avoid doing deriving from NSArray.
What you can do is add a category to add methods to all NSArray instances.

NSArray doesn't support being subclassed in this way. You can add a category, though, although that's not universally recommended.
See Objective C - Subclassing NSArray for more thoughts.

perhaps
self = [super initWithObjects:...];

You need to assign self, and call your superclass' init method.
if (self = [super initWithObjects:...]) {
...
}
return self;

Related

Using -mutableCopyWithZone: on custom class makes it immutable

I've created a custom class which conforms to NSCopying and NSMutableCopying.
I've added an implementation for -copyWithZone: and -mutableCopyWithZone:, but whenever I call -mutableCopy on my object and try to call another method, it crashes because some of the ivars have become immutable, even though I call -mutableCopyWithZone: on the ivars.
Here's how I'm copying my class:
MyObject *flipped = [list mutableCopy];
[MyObject flip:flipped];
(the code fails on +flip:, because it tries to use removeObjectAtIndex: and addObject: on a NSMutableArray ivar)
Here's how I'm copying the class:
- (id)mutableCopyWithZone:(NSZone *)zone {
id instance = nil;
if ((instance = [[[self class] alloc] init])) {
[instance setArray:[self.array mutableCopyWithZone:zone]];
[instance setObjects:[self.objects mutableCopyWithZone:zone]];
[instance setItemCount:self.itemCount];
}
return instance;
}
I'm not sure why it's failing, but I really don't understand why it isn't making array and objects mutable.
Any help appreciated.
My last idea: if the setArray: and setObjects: methods are actually setters for properties declared as #property (copy), then they'll copy the arrays passed in - and copy always returns an immutable object. In this case, the easy way to fix this would be declaring them as (retain) instead of (copy).

storing and accessing static NSArray

I have the following:
#implementation DataSource
+ (NSArray *)someData
{
static NSArray *data = nil;
if (!data) {
data = [[NSArray arrayWithObjects:..., nil] retain];
}
return data;
}
#end
Is there a way to access the class method from the class it self?
NSArray *array = [DataSource someData];
Yes. Inside a class method like your someData you can call another class method like
[self anotherClassMethod].
Here self refers to class.
NSArray *accessor = [DataSource someData];
The + refers to class level access.
In other class methods, you can call it as [self someData]. From instances of the class, you can call it as [[self class] someData] (this has the nice property that subclasses can override it and their implementations will automatically be used as appropriate). From outside the class, you can call it as [DataSource someData].
BTW, if this is actually meant to be the data source for some Cocoa or Cocoa Touch class such as NS/UITableView, you should probably implement the class as a singleton instead of making the class itself the data source, because using classes as data sources is not well-tested and the lack of instance variables will probably become a pain as your program grows.
a static method can be called with name of class. Above static method return type NSArray so we can use anywhere
NSArray *Arr=[DataSource someData];

How to create a typed stack using Objective-C

I can create a stack class quite easily, using push and pop accessor methods to an NSArray, however. I can make this generic to take any NSObject derived class, however, I want to store only a specific class in this stack.
Ideally I want to create something similar to Java's typed lists (List or List) so that I can only store that type in the stack. I can create a different class for each (ProjectStack or ItemStack), but this will lead to a more complicated file structure.
Is there a way to do this to restrict the type of class I can add to a container to a specific, configurable type?
NSArray will take any object that implements the same protocol as NSObject; it doesn't need to be derived from NSObject. Because all dispatch in Objective-C is dynamic, inheritance isn't necessary for polymorphism.
Putting that aside, if you've implemented your own push and pop methods, you can easily test the incoming type using isKindOfClass or isMemberOfClass and reject those of the incorrect type.
To go further down, NSMutableArray documents which methods are conceptually primitive below the heading 'Subclassing Notes' in the Overview section at the top of the page. If you subclass NSMutableArray and alter addObject:, replaceObjectAtIndex:withObject: and insertObject:atIndex: to test the type of the incoming object then you can create a typed array.
You can try something like this, although I do not recommend it:
#interface TypedMutableStack : NSObject {
Class type;
#private
NSMutableArray *internal;
}
- (id) initWithType:(Class) type_;
#end
#define CHECK_TYPE(obj) if(![obj isKindOfClass:type]) {\
[NSException raise:NSInvalidArgumentException format:#"Incorrect type passed to TypedMutableStack"];\
}
#implementation TypedMutableStack
- (void) dealloc {
[internal release];
[super dealloc];
}
- (id) initWithType:(Class) type_ {
self = [super init];
if(self) {
type = type_;
internal = [[NSMutableArray alloc] init];
}
return self;
}
- (void) addObject:(id) obj {
CHECK_TYPE(obj);
[internal addObject:obj];
}
- (void) replaceObjectAtIndex:(NSUInteger) index withObject:(id) obj {
CHECK_TYPE(obj);
[internal replaceObjectAtIndex:index withObject:obj];
}
- (void) insertObject:(id) obj atIndex:(NSUInteger) index {
CHECK_TYPE(obj);
[internal insertObject:obj atIndex:index];
}
//...
#end
Objective-C doesn't have generics or templates. Hence, idiomatic Objective-C code is written using NSObject * and id everywhere, casting as necessary.
Whatever you want to do, you should model it on the existing frameworks. See how NSArray, NSDictionary, NSSet, etc., are implemented, and follow suit.

Should I subclass the NSMutableArray class

I have an NSMutableArray object that I want to add custom methods to. I tried subclassing NSMutableArray but then I get an error saying "method only defined for abstract class" when trying to get the number of objects with the count method. Why is the count method not inherited?
I read somewhere else that I will have to import some NSMutableArray methods into my custom class if I want to use them. I just want to add a custom method to the NSMutableArray class. So should I subclass NSMutableArray, or should I do something else?
NSMutableArray is not a concrete class, it is just the abstract superclass of a class cluster. The documentation for NSMutableArray does have information about how to subclass, but also strongly advises you not to! Only subclass if you have a special need for actual storage.
A class cluster means that the actual class will be chosen at run-time. An array created empty, may not use the same class as an array created with 1000 items. The run-time can do smart choices of what implementation to use for you. In practice NSMutableArray will be a bridged CFArray. Nothing you need to worry about, but you might see it if you inspect the type of your arrays in the debugger, you will never see NSArray, but quite often NSCFArray.
As mentioned before, subclassing is not the same as extending a class. Objective-C has the concept of categories. A category is similar to what other programming languages call mix-ins.
If you for example want a convenience method on NSMutableArray to sort all members on a property, then define the category interface in a .h file as such:
#interface NSMutableArray (CWFirstnameSort)
-(void)sortObjectsByProperty:(NSString*)propertyName;
#end
And the implementation would be:
#implementation NSMutableArray (CWFirstnameSort)
-(void)sortObjectsByProperty:(NSString*)propertyName;
{
NSSortDescriptor* sortDesc = [NSSortDescriptor sortDescriptorWithKey:propertName ascending:YES];
[self sortUsingDescriptors:[NSArray arrayWithObject:sortDesc]];
}
#end
Then use it simply as:
[people sortObjectsByProperty:#"firstName"];
If you're just adding a custom method, use a category on NSMutableArray. It's a class cluster, so the implementation is provided by undocumented subclasses. You need to provide a few methods to generate your own subclass. However, if you just add a category then your custom method will work on all NSMutableArrays in your app.
For comparison, here's an example I wrote a while back of implementing a custom NSMutableArray subclass.
Objective-C has a mechanism for adding methods to existing classes called Categories. That way you don't have to create your own subclass.
This is an old post, but thought I'd add my experience. #PayloW's answer is a good answer and I think answers your question perfectly, however, no one really answered your question the other way around, so I'll do that here.
Should you subclass NSMutableArray (or NSArray)? Depends on what you want to achieve. If you only want to add a method to extend an array's BASIC functionality, like sorting, then #PayloW's answer Categories are the way. However, if you want to create a custom class that behaves like an array then yes, subclassing NSMutableArray is quite easy. But because it's a Class Cluster it doesn't exactly subclass as you'd expect. Normally in subclassing the methods available in the Super Class are available to your subclass or you may override them. With Class Clusters you MUST instead include the Super's methods that you're going to use and provide a _backend instance of the super class to wrap those methods around.
Below is an example of how you'd subclass NSMutableArray (or any Class Cluster):
The interface:
#interface MyCustomArrayClass : NSMutableArray {
// Backend instance your class will be using
NSMutableArray *_backendArray;
}
// *** YOUR CUSTOM METHODS HERE (no need to put the Super's methods here) ***
-(bool)isEmpty;
-(id)nameAtIndex:(int)index;
-(int)rowAtIndex:(int)index;
-(int)columnAtIndex:(int)index;
#end
The implementation:
#implementation MyCustomArrayClass
-(instancetype)init {
if (self = [super init]) {
_backendArray = [#[] mutableCopy];
}
return self;
}
// *** Super's Required Methods (because you're going to use them) ***
-(void)addObject:(id)anObject {
[_backendArray addObject:anObject];
}
-(void)insertObject:(id)anObject atIndex:(NSUInteger)index {
[_backendArray insertObject:anObject atIndex:index];
}
-(void)replaceObjectAtIndex:(NSUInteger)index withObject:(id)anObject {
[_backendArray replaceObjectAtIndex:index withObject:anObject];
}
-(id)objectAtIndex:(NSUInteger)index {
return [_backendArray objectAtIndex:index];
}
-(NSUInteger)count {
return _backendArray.count;
}
-(void)removeObject:(id)anObject {
[_backendArray removeObject:anObject];
}
-(void)removeLastObject {
[_backendArray removeLastObject];
}
-(void)removeAllObjects {
[_backendArray removeAllObjects];
}
-(void)removeObjectAtIndex:(NSUInteger)index {
[_backendArray removeObjectAtIndex:index];
}
// *** YOUR CUSTOM METHODS ***
-(bool)isEmpty {
return _backendArray.count == 0;
}
-(id)nameAtIndex:(int)index {
return ((MyObject *)_backendArray[index]).name;
}
-(int)rowAtIndex:(int)index {
return ((MyObject *)_backendArray[index]).row;
}
-(int)columnAtIndex:(int)index {
return ((MyObject *)_backendArray[index]).column;
}
#end
Then to use like so:
MyCustomArrayClass *customArray = [[MyCustomArrayClass alloc] init];
// Your custom method
int row = [customArray rowAtIndex:10];
// NSMutableArray method
[customArray removeLastObject];
// Your custom class used just like an array !!!
index = 20;
MyObject *obj = customArray[index];
It all works very nicely, is clean and actually pretty cool to implement and use.
Hope it helps.
I have to agree with both node ninja and PeyloW because technically they have both right. Actually, that does not help me much.
Preamble:
There are many arrays in code that all to one contain only one but different type of data e.g. classA, classB, classC.
Problem:
I can easily mix arrays by passing wrong one to e.g. some selector because they are all NSMutableArray. There is no static check, only runtime one.
Solution - 1st try:
Make subclass of NSMutableArray so compiler makes static check and warns about wrong data type.
That is good because compiler warns you even when you pass wrong type to -addObject or -objectAtIndex when you overload that ones.
That is bad because you cannot instantiate NSMutableArray superclass this way.
Solution - 2nd try:
Make new (proxy) class of some type e.g. NSObject as for NSMutableArray and add class member of type NSMutableArray.
This is good because you can instantiate NSMutableClass and compiler checks when you pass wrong type to -addObject or -objectAtIndex when you overload that ones.
The bad side of that is that you need to overload every selector of the NSMutableArray that you use, not only that ones that differs in class that array contains.
Conclusion:
When you build some sophisticated code that has many class types in its arrays, believe me it is worth to try. Simply by doing this compiler showed me several errors that I would not recognize until I will face it in runtime. Or even worse, when end user would face it.
From the Apple reference for NSArray, in the Methods to Override section:
Any subclass of NSArray must override the primitive instance methods count and objectAtIndex:. These methods must operate on the backing store that you provide for the elements of the collection. For this backing store you can use a static array, a standard NSArray object, or some other data type or mechanism. You may also choose to override, partially or fully, any other NSArray method for which you want to provide an alternative implementation.
As a side note, in Objective-C, there is no actual feature that allows you to declare a class as an abstract class, per se, as in Java, for instance. So, what they do instead is call something like the code below, from within some method that they want to force to be overridden by a subclass. In effect, they give the class 'abstract class' semantics.
This method definition acts as an abstract method, which raises an Exception if not overridden, with the following output:
-someAbstractFooMethod only defined for abstract class. Define -[YourClassName someAbstractFooMethod]!
- (void) someAbstractFooMethod
{
//Force subclassers to override this method
NSString *methodName = NSStringFromSelector(_cmd);
NSString *className = [self className];
[NSException raise:NSInvalidArgumentException
format:#"-%# only defined for abstract class. Define -[%# %#]!", methodName, className, methodName];
}

Objective-C: Accessing Member Objects that are in an array?!? I think

Totally new to Obj-C, so thanks for patience. :P
Because I'm beginner, I will use the car example. Easier for me to understand.
I have an object, Car. It has two member objects, tire and engine.
Tire and engine have their own member variables too, but they are just int with various names (like pressure, treadDepth).
In all these cases, I have synthesized accessor methods. I'm not sure about accessor methods for objects, so I just did #property id engine / #property id tire. Hope that is right!
Now, I can do dot.notation style to access like: [car.engine cylinders]. Fine! Sending tire and engine messages works fine. I write methods, this notation seems to work.
But when I declare an array of objects like 4 tires for the car:
#interface Car : NSObject {
tire *tires[4];
}
I cannot send it message like this
[car.tire[0] setPressure: int];
It says accessing unknown tires getter method.
Basically I am wondering if someone can help me understand how to correctly access member variables of an object that is in an array.
Thanks!
You are trying to call a getter on car that doesn't exist. You can't return a C-style array by value anyway, so instead of just returning a Tire* pointer i'd rather use a NSArray in this case:
// header:
#interface Car : NSObject {
NSArray *tires;
}
#property (nonatomic, copy) tires;
// ...
// source:
#implementation Car
#synthesize tires;
- (id)init {
if ((self = [super init])) {
tires = [[NSArray alloc] initWithObjects:
[[[Tire alloc] init] autorelease],
// ...
nil];
// ...
}
return self;
}
- (void)dealloc {
[tires release]; // don't forget to clean up
// ...
}
Now you could use the getter:
[[[car.tires] objectAtIndex:0] setPressure:0];
Why not put all of your tire objects into an NSArray or NSSet? Or, since you know there are only four, you could simply define frontLeftTire, frontRightTire, etc. properties.
Well you could use Objective-C style arrays. Then you would have something like:
NSArray *tires = [NSArray arrayWithObjects: tire1, tire2, tire3, tire4];
And then you would access them as:
[tires objectAtIndex:0];
That's assuming you are using the synthesized methods as described. I'm not sure from your question, but it seems like you might want to define a class "tire" for these objects (rather than just a method, which is all I see above) that inherits from NSObject, or maybe from your own class CarPart, etc. Then you allocate 4 tires in a loop and call an init method that sets up some default state (hopefully better than the donut that came as the spare in my car) and then add them to your array in "Car" when you initialize a car.