Есть ли способ принудительного набора на NSArray, NSMutableArray и т. Д.? - PullRequest
91 голосов
/ 16 марта 2009

Могу ли я создать экземпляр NSMutableArray, в котором все элементы имеют тип SomeClass?

Ответы [ 10 ]

139 голосов
/ 25 июня 2015

Никто еще не поднял это здесь, так что я сделаю это!

Это официально поддерживается в Objective-C. Начиная с Xcode 7, вы можете использовать следующий синтаксис:

NSArray<MyClass *> *myArray = @[[MyClass new], [MyClass new]];

Примечание

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

52 голосов
/ 16 марта 2009

Это довольно распространенный вопрос для людей, переходящих от языков со строгим типом (таких как C ++ или Java) к языкам со слабой или динамической типизацией, таким как Python, Ruby или Objective-C. В Objective-C большинство объектов наследуют от NSObject (тип id) (остальные наследуют от другого корневого класса, такого как NSProxy и также могут иметь тип id), и любое сообщение может быть отправлено любому объект. Конечно, отправка сообщения экземпляру, который он не распознает, может вызвать ошибку времени выполнения (а также вызвать компилятор предупреждение с соответствующими флагами -W). Пока экземпляр отвечает на отправленное вами сообщение, вам может быть все равно, к какому классу он принадлежит. Это часто называют «типизацией утки», потому что «если она крякает как утка [то есть отвечает на селектор], то это утка [то есть она может обработать сообщение; кому какое дело, какой это класс]».

Вы можете проверить, отвечает ли экземпляр на селектор во время выполнения с помощью метода -(BOOL)respondsToSelector:(SEL)selector. Предполагая, что вы хотите вызывать метод для каждого экземпляра в массиве, но не уверены, что все экземпляры могут обработать сообщение (поэтому вы не можете просто использовать NSArray 's -[NSArray makeObjectsPerformSelector:], что-то вроде это будет работать:

for(id o in myArray) {
  if([o respondsToSelector:@selector(myMethod)]) {
    [o myMethod];
  }
}

Если вы управляете исходным кодом для экземпляров, которые реализуют метод (ы), который вы хотите вызвать, то более распространенным подходом будет определение @protocol, содержащего эти методы, и объявление о том, что рассматриваемые классы реализуют этот протокол в их декларации. В этом случае @protocol аналогичен интерфейсу Java или абстрактному базовому классу C ++. Затем вы можете проверить соответствие всему протоколу, а не ответ на каждый метод. В предыдущем примере это не имело бы большого значения, но если бы вы вызывали несколько методов, это могло бы упростить вещи. Пример будет таким:

for(id o in myArray) {
  if([o conformsToProtocol:@protocol(MyProtocol)]) {
    [o myMethod];
  }
}

при условии MyProtocol объявляет myMethod. Этот второй подход предпочтительнее, потому что он разъясняет цель кода больше, чем первый.

Часто один из этих подходов освобождает вас от заботы о том, принадлежат ли все объекты в массиве заданному типу. Если вам все равно все равно, стандартный динамический языковой подход заключается в модульном тесте, модульном тесте, модульном тесте. Поскольку регрессия в этом требовании приведет к (вероятно, неустранимой) ошибке времени выполнения (не времени компиляции), вам необходимо иметь тестовое покрытие, чтобы проверить поведение, чтобы вы не выпустили аварийный сбой. В этом случае выполните операцию, которая модифицирует массив, а затем убедитесь, что все экземпляры в массиве принадлежат данному классу. При надлежащем тестовом покрытии вам даже не понадобятся дополнительные накладные расходы на проверку подлинности экземпляра. У вас есть хорошее покрытие модульных тестов, не так ли?

35 голосов
/ 16 марта 2009

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

В общем, в Objective-C, похоже, нет необходимости в таком ограничении. Я не думаю, что когда-либо слышал, чтобы опытный программист Какао желал этой функции. Кажется, что единственные люди - это программисты из других языков, которые все еще думают на этих языках. Если вам нужны только объекты данного класса в массиве, вставляйте туда только объекты этого класса. Если вы хотите проверить, правильно ли работает ваш код, протестируйте его.

11 голосов
/ 09 января 2012

Вы можете создать подкласс NSMutableArray для обеспечения безопасности типов.

NSMutableArray - это кластер класса , поэтому создание подклассов не тривиально. В итоге я унаследовал от NSArray и перенаправил вызовы в массив внутри этого класса. Результатом является класс с именем ConcreteMutableArray, который является легко подклассом. Вот что я придумал:

Обновление: Оформить сообщение в блоге Майка Эша о создании подкласса кластера классов.

Включите эти файлы в свой проект, а затем сгенерируйте любые типы, используя макросы:

MyArrayTypes.h

CUSTOM_ARRAY_INTERFACE(NSString)
CUSTOM_ARRAY_INTERFACE(User)

MyArrayTypes.m

CUSTOM_ARRAY_IMPLEMENTATION(NSString)
CUSTOM_ARRAY_IMPLEMENTATION(User)

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

NSStringArray* strings = [NSStringArray array];
[strings add:@"Hello"];
NSString* str = [strings get:0];

[strings add:[User new]];  //compiler error
User* user = [strings get:0];  //compiler error

Другие мысли

  • Он наследуется от NSArray для поддержки сериализации / десериализации
  • В зависимости от вашего вкуса, вы можете переопределить / скрыть общие методы, такие как

    - (void) addObject:(id)anObject

7 голосов
/ 08 августа 2013

Посмотрите на https://github.com/tomersh/Objective-C-Generics, реализацию обобщений во время компиляции (реализованную препроцессором) для Objective-C Этот пост имеет хороший обзор. Как правило, вы получаете проверку во время компиляции (предупреждения или ошибки), но не дается штраф за время выполнения для обобщений.

4 голосов
/ 05 ноября 2013

Этот проект Github реализует именно эту функциональность.

Затем вы можете использовать скобки <>, как в C #.

Из их примеров:

NSArray<MyClass>* classArray = [NSArray array];
NSString *name = [classArray lastObject].name; // No cast needed
0 голосов
/ 23 декабря 2018

мои два цента, чтобы быть немного "чище":

используйте typedefs:

typedef NSArray<NSString *> StringArray;

в коде мы можем сделать:

StringArray * titles = @[@"ID",@"Name", @"TYPE", @"DATE"];
0 голосов
/ 11 декабря 2017

Если вы смешаете c ++ и target-c (то есть, используя тип файла mm), вы можете принудительно вводить текст с помощью пары или кортежа. Например, в следующем методе вы можете создать объект C ++ типа std :: pair, преобразовать его в объект типа оболочки OC (обертка std :: pair, который необходимо определить), а затем передать его некоторым другой метод OC, в котором вам нужно преобразовать объект OC обратно в объект C ++, чтобы использовать его. Метод OC принимает только тип оболочки OC, что обеспечивает безопасность типов. Вы даже можете использовать кортеж, шаблон переменной, список типов, чтобы использовать более продвинутые функции C ++ для обеспечения безопасности типов.

- (void) tableView:(UITableView*) tableView didSelectRowAtIndexPath:(NSIndexPath*) indexPath
{
 std::pair<UITableView*, NSIndexPath*> tableRow(tableView, indexPath);  
 ObjCTableRowWrapper* oCTableRow = [[[ObjCTableRowWrapper alloc] initWithTableRow:tableRow] autorelease];
 [self performSelector:@selector(selectRow:) withObject:oCTableRow];
}
0 голосов
/ 20 марта 2013

Я создал подкласс NSArray, который использует объект NSArray в качестве резервного ivar, чтобы избежать проблем с кластерной природой NSArray. Требуются блоки для принятия или отклонения добавления объекта.

, чтобы разрешить только объекты NSString, вы можете определить AddBlock как

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

Вы можете определить 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);

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

0 голосов
/ 16 марта 2009

Возможным способом может быть создание подкласса NSArray, но Apple рекомендует не делать этого. Проще дважды подумать о фактической необходимости типизированного NSArray.

...