12

I wonder whether Objective-C offers any support for generics?

For instance, consider a method:

-(void) sort: (NSMutableArray *) deck {
}

Is there any way for me to make it only deal with Deck of Cards?
Is something like this possible to enforce?

-(void) sort: (NSMutableArray <Card *>) deck {
}
vikingosegundo
  • 51,126
  • 14
  • 131
  • 172
James Raitsev
  • 82,013
  • 132
  • 311
  • 454

6 Answers6

11

Objective C supports Generics now, with the Xcode 7.

The Xcode 7 compiler will give you the compiler warning if there is a type mismatch.

For example, the following line will raise a compiler warning as the second object in the array causes type mismatch. The array allows only NSString objects.

NSArray <NSString *> *myArray = [@"str2", @1, @"str2"];
Iulian Onofrei
  • 7,489
  • 8
  • 59
  • 96
Ramaraj T
  • 5,096
  • 4
  • 32
  • 65
6

You can use the introspection tools offered by the objective-c runtime.

Basically, it means you can check if all objects in an array either are a kind of class (Class A or one subclass of it) or a member of class (class A), or if a objects conforms to a protocol or responds to a selector (a certain method is present).

-(void) sort: (NSMutableArray *) deck {
    for(id obj in deck){
        if(obj isKindOfClass:[A class]]){
            //this is of right class
        }
    }
}

You could write a Category method on NSArray that checkouts this on every object.

BOOL allAreKindOfA = [array allObjectsAreKindOfClass:[A class]];

Normally you actually don't need this very often, as you know what you put inside a collection.

If you need to check the type or ability of an object in a Array, this might be an indicator, that your Architecture is broken


Another option could be a subclass of NSMutableArray that only accepts certain classes. But be aware of the subclassing notes for NSMutableArray and NSArray, as these are Class-Clusters and therefore not easy to subclass.

Note: In my other answer I created a NSMutableArray subclass, that uses a block to test, if a certain requirement is fulfilled. If you test against class-membership, this will do exactly what you want. Use the second block for error handling.

Community
  • 1
  • 1
vikingosegundo
  • 51,126
  • 14
  • 131
  • 172
  • someone did the last approach http://stackoverflow.com/questions/5197446/nsmutablearray-force-the-array-to-hold-specific-object-type-only — impressive – vikingosegundo Nov 19 '11 at 00:16
  • a blog post about subclassing class cluster http://cocoawithlove.com/2008/12/ordereddictionary-subclassing-cocoa.html – vikingosegundo Nov 19 '11 at 01:10
  • Generally, use of `isKindOfClass:` is an indication that the architecture is broken... – bbum Nov 19 '11 at 04:34
  • @bbum true, that is what I meant with "…don't need this very often, as you know, what you put inside a collection." I will emphasize it more. – vikingosegundo Nov 19 '11 at 04:44
  • Coming from a strongly typed language such as C# its quite hard to understand the thinking behind not having a strongly typed collection available? I'm still reading and learning and I've got a long way to go, but surely a collection that can be locked to a protocol or a specific implementation would provide huge type safety to an application? What am I missing here? – The Senator May 29 '13 at 18:02
  • @TheSenator: you usually know, what you put into an array. Isnt that safe enough? – vikingosegundo May 29 '13 at 19:18
  • @TheSenator: did u see my other answer, an array, that can perform a tests prior to addition? one nice thing about weakly typed languages/collections is, that with a very thin layer you can assure things like type safety — or any assurance you need. – vikingosegundo May 30 '13 at 14:33
  • I don't see why there must be argument about type safe in Objective C. Just embrace the fact that Objective C is weak typing language, and we should assume that caller will always send correct object type and, in case of bad caller, die with `unrecognized selector sent to instance` rather than ensuring compatible class type. – tia Jul 19 '13 at 18:42
  • The way for Objective-C to support Generics-like type-checking is NOT to literally support generics as in Java or C# where a generic-collection would allow only objects derived from a single base-class. -- The better way would be to support parametric protocol checking, so we could instantiate a collection type such that everything going into or out of it support protocol-X. This maintains Objective-C's dynamism, while also allowing us to catch errors at compile-time rather than wait for them to fail at runtime. -- Google Go does this auto-magically with inferred structural typing. – David Jeske Aug 01 '13 at 16:54
  • Xcode 7 has added support for Generics. See: http://stackoverflow.com/a/30736487/817598 – Pedro Mancheno Jun 09 '15 at 15:31
5

As of Xcode 7's release, Apple has added support for Objective-C generics.

NSArray <NSString *> *arrayOfStrings = @[@"a", @"b"];

NSDictionary <NSString *, NSDate *> *dictionaryOfDates = @{ @"a" : @1 };
Pedro Mancheno
  • 5,177
  • 4
  • 21
  • 30
4

There is an easy, effective way of doing this (I've been using it on projects for a couple of years now). Sadly, someone deleted the answer, and my attempts to get it re-instated were rejected. Here goes again:

You can re-implement a cut-down version of C++ templating within Obj-C because Obj-C encapsulates all of C (and C++ templates are C-macros with some improved compiler/debugger support):

This only needs to be done once, using a single header file. Someone has done it for you:

https://github.com/tomersh/Objective-C-Generics

You end up with 100% legal Obj-C code that looks like this:

NSArray<CustomClass> anArray= ...
CustomClass a = anArray[0]; // works perfectly, and Xcode autocomplete works too!

This all works fine in XCode, with autocomplete, etc.

Infinite Recursion
  • 6,315
  • 28
  • 36
  • 51
Adam
  • 32,197
  • 16
  • 119
  • 146
4

Not directly, no. There a few ways to simulate it, but it requires a lot of wrapper code, boilerplate code, and runtime overhead. I just switch to Objective-C++ and use C++ templates when I want or need proper generics.

So if you wanted to introduce typesafety/checks to an NSArray, you could approach it using something like this:

template <typename T>
class t_typed_NSMutableArray {
public:
    t_typed_NSMutableArray() : d_array([NSMutableArray new]) {}
    ~t_typed_NSMutableArray() { [d_array release]; }

    /* ... */

    T* operator[](const size_t& idx) {
        T* const obj([this->d_array objectAtIndex:idx]);
        assert([obj isKindOfClass:[T class]]);
        return obj;
    }

    void addObject(T* const obj) {
        assert([obj isKindOfClass:[T class]]);
        [this->d_array addObject:obj];
    }

private:
    NSMutableArray * const d_array;
};

in use:

 t_typed_NSMutableArray<Card> array([self cards]); // < note this exact constructor is not defined

 Card * firstCard = array[0]; // << ok
 NSString * string = array[0]; // << warning

then you also get type safety and overloading when passing the collection, so you could not pass t_typed_NSArray<Card> as an t_typed_NSArray<NSURL>.

justin
  • 101,751
  • 13
  • 172
  • 222
4

Inspired by MonomorphicArray I came up with another idea:

Create a subclass on NSMutableArray, that takes two blocks:

  • AddBlock — a block that test, if one or more requirements are full filed and adds the object only, if its passes the test
  • FailBlock — a block, that defines what happens, if the test was not successful.

The AddBlock could test for a certain class membership like

^BOOL(id element) {
    return [element isKindOfClass:[NSString class]];
}

and the FailBlock can raise an exception, fail silently or add the element, that failed the test, to another Array. If no failBlock is provided, a default block will raise an error.

The blocks will define, if an array acts like an generic array, or as a filter.
I will give an complete example for the second case.

VSBlockTestedObjectArray.h

#import <Foundation/Foundation.h>
typedef BOOL(^AddBlock)(id element); 
typedef void(^FailBlock)(id element); 

@interface VSBlockTestedObjectArray : NSMutableArray

@property (nonatomic, copy, readonly) AddBlock testBlock;
@property (nonatomic, copy, readonly) FailBlock failBlock;

-(id)initWithTestBlock:(AddBlock)testBlock FailBlock:(FailBlock)failBlock Capacity:(NSUInteger)capacity;
-(id)initWithTestBlock:(AddBlock)testBlock FailBlock:(FailBlock)failBlock;
-(id)initWithTestBlock:(AddBlock)testBlock;    
@end

VSBlockTestedObjectArray.m

#import "VSBlockTestedObjectArray.h"

@interface VSBlockTestedObjectArray ()
@property (nonatomic, retain) NSMutableArray *realArray;
-(void)errorWhileInitializing:(SEL)selector;
@end

@implementation VSBlockTestedObjectArray
@synthesize testBlock = _testBlock;
@synthesize failBlock = _failBlock;
@synthesize realArray = _realArray;


-(id)initWithCapacity:(NSUInteger)capacity
{
    if (self = [super init]) {
        _realArray = [[NSMutableArray alloc] initWithCapacity:capacity];
    }

    return self;
}

-(id)initWithTestBlock:(AddBlock)testBlock 
             FailBlock:(FailBlock)failBlock 
              Capacity:(NSUInteger)capacity
{
    self = [self initWithCapacity:capacity];
    if (self) {
        _testBlock = [testBlock copy];
        _failBlock = [failBlock copy];
    }

    return self;
}

-(id)initWithTestBlock:(AddBlock)testBlock FailBlock:(FailBlock)failBlock
{
    return [self initWithTestBlock:testBlock FailBlock:failBlock Capacity:0];
}

-(id)initWithTestBlock:(AddBlock)testBlock
{
    return [self initWithTestBlock:testBlock FailBlock:^(id element) {
        [NSException raise:@"NotSupportedElement" format:@"%@ faild the test and can't be add to this VSBlockTestedObjectArray", element];
    } Capacity:0];
}


- (void)dealloc {
    [_failBlock release];
    [_testBlock release];
    self.realArray = nil;
    [super dealloc];
}


- (void) insertObject:(id)anObject atIndex:(NSUInteger)index
{
    if(self.testBlock(anObject))
        [self.realArray insertObject:anObject atIndex:index];
    else
        self.failBlock(anObject);
}

- (void) removeObjectAtIndex:(NSUInteger)index
{
    [self.realArray removeObjectAtIndex:index];
}

-(NSUInteger)count
{
    return [self.realArray count];
}

- (id) objectAtIndex:(NSUInteger)index
{
    return [self.realArray objectAtIndex:index];
}



-(void)errorWhileInitializing:(SEL)selector
{
    [NSException raise:@"NotSupportedInstantiation" format:@"not supported %@", NSStringFromSelector(selector)];
}
- (id)initWithArray:(NSArray *)anArray { [self errorWhileInitializing:_cmd]; return nil;}
- (id)initWithArray:(NSArray *)array copyItems:(BOOL)flag { [self errorWhileInitializing:_cmd]; return nil;}
- (id)initWithContentsOfFile:(NSString *)aPath{ [self errorWhileInitializing:_cmd]; return nil;}
- (id)initWithContentsOfURL:(NSURL *)aURL{ [self errorWhileInitializing:_cmd]; return nil;}
- (id)initWithObjects:(id)firstObj, ... { [self errorWhileInitializing:_cmd]; return nil;}
- (id)initWithObjects:(const id *)objects count:(NSUInteger)count { [self errorWhileInitializing:_cmd]; return nil;}

@end

Use it like:

VSBlockTestedObjectArray *stringArray = [[VSBlockTestedObjectArray alloc] initWithTestBlock:^BOOL(id element) {
    return [element isKindOfClass:[NSString class]];
} FailBlock:^(id element) {
    NSLog(@"%@ can't be added, didn't pass the test. It is not an object of class NSString", element);
}];


VSBlockTestedObjectArray *numberArray = [[VSBlockTestedObjectArray alloc] initWithTestBlock:^BOOL(id element) {
    return [element isKindOfClass:[NSNumber class]];
} FailBlock:^(id element) {
    NSLog(@"%@ can't be added, didn't pass the test. It is not an object of class NSNumber", element);
}];


[stringArray addObject:@"test"];
[stringArray addObject:@"test1"];
[stringArray addObject:[NSNumber numberWithInt:9]];
[stringArray addObject:@"test2"];
[stringArray addObject:@"test3"];


[numberArray addObject:@"test"];
[numberArray addObject:@"test1"];
[numberArray addObject:[NSNumber numberWithInt:9]];
[numberArray addObject:@"test2"];
[numberArray addObject:@"test3"];


NSLog(@"%@", stringArray);
NSLog(@"%@", numberArray);

Note: This code is not fully tested. Probably some of the unimplemented method should be implemented for usage in real world programs.

Community
  • 1
  • 1
vikingosegundo
  • 51,126
  • 14
  • 131
  • 172