Есть ли способ принудительно ввести тип в NSArray, NSMutableArray и т. Д.?


Ответы:


35

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

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


136
Я думаю, что «опытные программисты Cocoa» просто не знают, чего им не хватает - опыт работы с Java показывает, что переменные типа улучшают понимание кода и делают возможным больше рефакторингов.
— tgdavies

11
Ну, поддержка Java Generics сама по себе сильно нарушена, потому что они не
— добавляли

28
Должен согласиться с @tgdavies. Мне не хватает возможностей intellisense и рефакторинга, которые у меня были с C #. Когда мне нужна динамическая типизация, я могу получить ее в C # 4.0. Когда я хочу что-то сильно набирать, я могу и это. Я обнаружил, что для этих двух вещей есть время и место.
— Steve

18
@charkrit Что такого особенного в Objective-C, что делает его «ненужным»? Считали ли вы это необходимым, когда использовали C #? Я слышал, как многие люди говорят, что вам это не нужно в Objective-C, но я думаю, что эти же люди думают, что вам это не нужно ни на каком языке, что делает это вопросом предпочтений / стиля, а не необходимости.
— bacar

17
Разве это не о том, чтобы позволить вашему компилятору действительно помочь вам найти проблемы. Конечно, вы можете сказать: «Если вам нужны только объекты данного класса в массиве, вставляйте туда только объекты этого класса». Но если тесты - единственный способ добиться этого, вы окажетесь в невыгодном положении. Чем дальше от написания кода вы обнаружите проблему, тем дороже она будет стоить.
— GreenKiwi

145

Это еще никто не вывесил, так что я сделаю это!

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

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

Заметка

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


Я здесь ленив, но почему это доступно только в XCode 7? Мы можем использовать их nonnullв XCode 6, и, насколько я помню, они были введены одновременно. Кроме того, зависит ли использование таких концепций от версии XCode или от версии iOS?
— Guven

@Guven - возможность обнуления появилась в 6, вы правы, но дженерики ObjC не были представлены до Xcode 7.
— Логан

Я почти уверен, что это зависит только от версии Xcode. Универсальные шаблоны являются только предупреждениями компилятора и не отображаются во время выполнения. Я почти уверен, что вы сможете компилировать все, что захотите.
— Logan

2
@DeanKelly - Вы могли бы сделать это вот так: @property (nonatomic, strong) NSArray<id<SomeProtocol>>* protocolObjects; Выглядит немного неуклюже, но помогает!
— Logan

1
@Logan, это не только набор скриптов, предотвращающих сборку в случае обнаружения какого-либо предупреждения. Xcode имеет прекрасный механизм под названием «Конфигурация». Проверьте это boredzo.org/blog/archives/2009-11-07/warnings
— adnako

53

Это относительно частый вопрос для людей, переходящих от языков строгого типа (таких как 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класса, который содержит эти методы, и объявление того, что рассматриваемые классы реализуют этот протокол в своем объявлении. В этом случае a @protocolаналогичен интерфейсу Java или абстрактному базовому классу C ++. Затем вы можете проверить соответствие всему протоколу, а не ответ на каждый метод. В предыдущем примере это не имело бы большого значения, но если бы вы вызывали несколько методов, это могло бы упростить ситуацию. Тогда пример будет:

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

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

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


35
Модульное тестирование не заменяет достойную систему типов.
— tba

8
Да, кому нужны инструменты, которые могут себе позволить типизированные массивы. Я уверен, что @BarryWark (и любой другой, кто прикоснулся к любой кодовой базе, которую ему нужно использовать, читать, понимать и поддерживать) имеет 100% покрытие кода. Однако я уверен, что вы не используете raw, idкроме случаев, когда это необходимо, как и кодеры Java не передают Objects. Почему нет? Не нужно, если у вас есть модульные тесты? Потому что он там и делает ваш код более удобным в сопровождении, как и типизированные массивы. Похоже, люди инвестировали в платформу, не желая уступать ни в чем, и поэтому придумывали причины, по которым это упущение на самом деле является преимуществом.
— funkybro 07

"Утка печатает" ?? это весело! никогда не слышал этого раньше.
— Джон Хенкель

11

Вы можете создать подкласс, 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


Приятно, но на данный момент в нем отсутствует строгая типизация за счет переопределения некоторых методов. На данный момент это только слабая типизация.
— Cœur

7

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


1
Я попробовал, очень хорошая идея, но, к сожалению, с ошибками и не проверяет добавленные элементы.
— Binarian

4

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

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

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

NSArray<MyClass>* classArray = [NSArray array];
NSString *name = [classArray lastObject].name; // No cast needed

0

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


1
Проверка статического типа во время компиляции экономит время, редактирование становится еще лучше. Особенно полезно при написании библиотеки для длительного использования.
— pinxue

0

Я создал подкласс 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

Если вы смешиваете C ++ и objective-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

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

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

typedef NSArray<NSString *> StringArray;

в коде мы можем:

StringArray * titles = @[@"ID",@"Name", @"TYPE", @"DATE"];

0

2020, прямой ответ. Так уж вышло, что мне нужен изменяемый массив с типом NSString.

Синтаксис:

Type<ArrayElementType *> *objectName;

Пример:

@property(nonatomic, strong) NSMutableArray<NSString *> *buttonInputCellValues;
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.