Does Objective-C support Generics?

前端 未结 6 599
后悔当初
后悔当初 2020-12-02 00:07

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

For instance, consider a method:

-(void) sort: (NSMutableArray *) deck {
}
相关标签:
6条回答
  • 2020-12-02 00:34

    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"];
    
    0 讨论(0)
  • 2020-12-02 00:35

    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.

    0 讨论(0)
  • 2020-12-02 00:35

    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 };
    
    0 讨论(0)
  • 2020-12-02 00:38

    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.

    0 讨论(0)
  • 2020-12-02 00:41

    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>.

    0 讨论(0)
  • 2020-12-02 00:45

    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.

    0 讨论(0)
提交回复
热议问题