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

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

Ответ 1

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

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

Ответ 2

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

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

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

Примечание

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

Ответ 3

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

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

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

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

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

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

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

Ответ 4

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

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

Обновить: проверить это сообщение в блоге от Mike Ash о подклассе кластера классов.

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

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

Ответ 5

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

Ответ 6

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

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

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

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

Ответ 7

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

Ответ 8

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

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

Ответ 9

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

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