Поддерживает ли Objective-C Generics?

Интересно, поддерживает ли Objective-C поддержку дженериков?

Например, рассмотрим метод:

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

Есть ли способ для меня сделать это только с колодой карт?
Возможно ли такое принудительное исполнение?

-(void) sort: (NSMutableArray <Card *>) deck {
}

Ответ 1

Вы можете использовать инструменты для интроспекции, предлагаемые средой objective-c.

В принципе, это означает, что вы можете проверить, все ли объекты в массиве являются вид класса (класс A или один его подкласс ) или член класса (класс A), или если объекты соответствует протоколу или отвечает на селектор (существует определенный метод).

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

Вы можете написать метод Category на NSArray, который проверяет это на каждом объекте.

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

Обычно вам это не нужно часто, так как вы знаете, что вы вкладываете в коллекцию.

Если вам нужно проверить тип или способность объекта в массиве, это может быть индикатором того, что ваша архитектура сломана


Другой вариант может быть подклассом NSMutableArray, который принимает только определенные классы. Но имейте в виду подклассы для NSMutableArray и NSArray, так как это кластеры классов и поэтому нелегко подклассы.

Примечание: В моем другом ответе я создал подкласс NSMutableArray, который использует блок для проверки, если определенное требование выполнено. Если вы протестуете против членства в классе, это будет делать именно то, что вы хотите. Используйте второй блок для обработки ошибок.

Ответ 2

Objective C теперь поддерживает Generics с XCode 7.

Компилятор XCode 7 предоставит вам предупреждение о компиляторе, если существует несоответствие типа.

Например, следующая строка поднимет предупреждение компилятора, так как второй объект в массиве вызывает несоответствие типов. Массив допускает только объекты NSString.

NSArray <NSString *> *myArray = [@"str2", @1, @"str2"];

Ответ 3

Как и в версии Xcode 7, Apple добавила поддержку Objective-C generics.

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

NSDictionary <NSString *, NSDate *> *dictionaryOfDates = @{ @"a" : @1 };

Ответ 4

Не прямо, нет. Там есть несколько способов имитировать его, но для этого требуется много кода оболочки, кода шаблона и служебных программ. Я просто переключаюсь на Objective-C ++ и использую шаблоны С++, когда мне нужны или нужны правильные дженерики.

Итак, если вы хотите ввести типы/проверки NSArray, вы можете подойти к нему, используя что-то вроде этого:

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;
};

в использовании:

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

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

тогда вы также получаете безопасность и перегрузку типов при передаче коллекции, поэтому вы не можете передать t_typed_NSArray<Card> как t_typed_NSArray<NSURL>.

Ответ 5

Вдохновленный MonomorphicArray Я придумал еще одну идею:

Создайте подкласс в NSMutableArray, который принимает два блока:

  • AddBlock - блок, который тестирует, если одно или несколько требований заполнены полностью и добавляет объект только, если он проходит тест
  • FailBlock - блок, который определяет, что происходит, если тест не был успешным.

AddBlock может тестировать определенное членство класса, например

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

а FailBlock может вызвать исключение, выйти из строя или добавить элемент, который не прошел тест, другому массиву. Если не предоставляется failBlock, блок по умолчанию вызывает ошибку.

Блоки будут определять, если массив действует как общий массив или как фильтр.
Я приведу полный пример для второго случая.

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

Используйте его как:

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);

Примечание: Этот код не полностью протестирован. Вероятно, некоторые из нереализованных методов должны быть реализованы для использования в реальных программах.

Ответ 6

Существует простой и эффективный способ сделать это (я использую его в проектах уже пару лет). К сожалению, кто-то удалил ответ, и мои попытки вернуть его были отвергнуты. Здесь еще раз:

Вы можете повторно реализовать вырезанную версию С++ templating в Obj-C, поскольку Obj-C инкапсулирует все C (и С++-шаблоны - это C-макросы с некоторой улучшенной поддержкой компилятора/отладчика):

Это нужно сделать только один раз, используя один файл заголовка. Кто-то сделал это за вас:

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

В итоге вы получите 100% -ный юридический код Obj-C, который выглядит так:

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

Все это отлично работает в XCode, с автозаполнением и т.д.