Есть ли способ принудительно ввести тип в 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

Bendytree
источник
Приятно, но на данный момент в нем отсутствует строгая типизация за счет переопределения некоторых методов. На данный момент это только слабая типизация.
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.

Mouviciel
источник
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"];
Ingconti
источник
0

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

Синтаксис:

Type<ArrayElementType *> *objectName;

Пример:

@property(nonatomic, strong) NSMutableArray<NSString *> *buttonInputCellValues;
Гленн Посадас
источник