NSMutableArray - заставляет массив содержать только определенный тип объекта - PullRequest
66 голосов
/ 04 марта 2011

Есть ли способ заставить NSMutableArray содержать только один конкретный тип объекта?

У меня есть следующие определения классов:

@interface Wheel:NSObject  
{    
  int size;  
  float diameter;  
}  
@end  


@interface Car:NSObject  
{  
   NSString *model;  
   NSString *make;  
   NSMutableArray *wheels;  
}  
@end

Как заставить массив колеса удерживать колесо объекты только с кодом? (и абсолютно не другие объекты)

Ответы [ 12 ]

92 голосов
/ 04 марта 2011

Обновление в 2015 году

Этот ответ был впервые написан в начале 2011 года и начал:

Что нам действительно нужно, так это параметрический полиморфизм, чтобы вы могли объявить, скажем, NSMutableArray<NSString>; но увы такого нет в наличии.

В 2015 году Apple, очевидно, изменила это с введением «облегченных обобщений» в Objective-C, и теперь вы можете объявить:

NSMutableArray<NSString *> *onlyStrings = [NSMutableArray new];

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

[onlyStrings addObject:@666]; // <- Warning: Incompatible pointer types...

выдаст предупреждение, как указано, безопасность типа едва зашита. Рассмотрим метод:

- (void) push:(id)obj onto:(NSMutableArray *)array
{
   [array addObject:obj];
}

и фрагмент кода в другом методе того же класса:

NSMutableArray<NSString *> *oops = [NSMutableArray new];
[self push:@"asda" onto:oops]; // add a string, fine
[self push:@42 onto:oops];     // add a number, no warnings...

То, что Apple реализовала, - это, по сути, система подсказок, помогающая в автоматическом взаимодействии со Swift, которая имеет вид безопасных типов. Однако на стороне Objective-C, в то время как компилятор предоставляет некоторые дополнительные подсказки, система «легковесна», а целостность типов все еще в конечном счете зависит от программиста - как и способ Objective-C.

Так что вы должны использовать? Новые легковесные / псевдо-дженерики или придумать свои собственные шаблоны для своего кода? Там действительно нет правильного ответа, выяснить, что имеет смысл в вашем сценарии и использовать его.

Например: если вы нацелены на взаимодействие со Swift, вы должны использовать облегченные генерики! Однако, если целостность типов коллекции важна в вашем сценарии, вы могли бы объединить облегченные шаблоны с вашим собственным кодом на стороне Objective-C, который обеспечивает целостность типов, что Swift будет на его стороне.

Остаток ответа 2011 года

В качестве другого варианта здесь приведен быстрый общий подкласс NSMutableArray, который вы инициируете с типом объекта, который вы хотите в своем мономорфном массиве. Эта опция не дает вам статической проверки типов (в той степени, в которой вы когда-либо ее получали в Obj-C), вы получаете исключения времени выполнения при вставке неправильного типа, так же, как вы получаете исключения времени выполнения для индекса вне границ и т. Д.

Это , а не , тщательно протестировано и предполагает, что документация по переопределению NSMutableArray верна ...

@interface MonomorphicArray : NSMutableArray
{
    Class elementClass;
    NSMutableArray *realArray;
}

- (id) initWithClass:(Class)element andCapacity:(NSUInteger)numItems;
- (id) initWithClass:(Class)element;

@end

И реализация:

@implementation MonomorphicArray

- (id) initWithClass:(Class)element andCapacity:(NSUInteger)numItems
{
    elementClass = element;
    realArray = [NSMutableArray arrayWithCapacity:numItems];
    return self;
}

- (id) initWithClass:(Class)element
{
    elementClass = element;
    realArray = [NSMutableArray new];
    return self;
}

// override primitive NSMutableArray methods and enforce monomorphism

- (void) insertObject:(id)anObject atIndex:(NSUInteger)index
{
    if ([anObject isKindOfClass:elementClass]) // allows subclasses, use isMemeberOfClass for exact match
    {
        [realArray insertObject:anObject atIndex:index];
    }
    else
    {
        NSException* myException = [NSException
            exceptionWithName:@"InvalidAddObject"
            reason:@"Added object has wrong type"
            userInfo:nil];
        @throw myException;
    }
}

- (void) removeObjectAtIndex:(NSUInteger)index
{
    [realArray removeObjectAtIndex:index];
}

// override primitive NSArray methods

- (NSUInteger) count
{
    return [realArray count];
}

- (id) objectAtIndex:(NSUInteger)index
{
    return [realArray objectAtIndex:index];
}


// block all the other init's (some could be supported)

static id NotSupported()
{
    NSException* myException = [NSException
        exceptionWithName:@"InvalidInitializer"
        reason:@"Only initWithClass: and initWithClass:andCapacity: supported"
        userInfo:nil];
    @throw myException;
}

- (id)initWithArray:(NSArray *)anArray { return NotSupported(); }
- (id)initWithArray:(NSArray *)array copyItems:(BOOL)flag { return NotSupported(); }
- (id)initWithContentsOfFile:(NSString *)aPath { return NotSupported(); }
- (id)initWithContentsOfURL:(NSURL *)aURL { return NotSupported(); }
- (id)initWithObjects:(id)firstObj, ... { return NotSupported(); }
- (id)initWithObjects:(const id *)objects count:(NSUInteger)count { return NotSupported(); }

@end

Использовать как:

MonomorphicArray *monoString = [[MonomorphicArray alloc] initWithClass:[NSString class] andCapacity:3];

[monoString addObject:@"A string"];
[monoString addObject:[NSNumber numberWithInt:42]]; // will throw
[monoString addObject:@"Another string"];
28 голосов
/ 21 сентября 2015

С XCode 7 дженерики теперь доступны в Objective-C!

Таким образом, вы можете объявить свой NSMutableArray как:

NSMutableArray <Wheel*> *wheels = [[NSMutableArray alloc] initWithArray:@[[Wheel new],[Wheel new]];

Компилятор выдаст вам предупреждение, если вы попытаетесь поместить объект не-Wheel в ваш массив.

9 голосов
/ 17 августа 2011

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

NSArray<Protocol Name>

Это должно предотвратить добавление объектов, которые не следуют указанному протоколу.

5 голосов
/ 04 марта 2011

Насколько я знаю ... прежде чем вы добавили какой-либо объект в колеса mutableArray, вы должны добавить галочку.Является ли объект, который я добавляю, класса "колесо".если это так, добавьте, в противном случае нет.

Пример:

if([id isClassOf:"Wheel"] == YES)
{
[array addObject:id) 
}

Как-то так.я не помню точный синтаксис.

3 голосов
/ 29 сентября 2014

Надеюсь, это поможет (и сработает ...: P)

Wheel.h file:

@protocol Wheel
@end

@interface Wheel : NSObject
@property ...
@end

Car.h файл:

#import "Wheel.h"
@interface Car:NSObject  

{  
   NSString *model;  
   NSString *make;  
   NSMutableArray<Wheel, Optional> *wheels;  
}  
@end

Car.m файл:

#import "Car.h"
@implementation Car

-(id)init{
   if (self=[super init]){
   self.wheels = (NSMutableArray<Wheel,Optional>*)[NSMutableArray alloc]init];
   }
return self;
}
@end
2 голосов
/ 02 октября 2015

Xcode 7 позволяет вам определять массивы, словари и даже ваши собственные классы как имеющие обобщения. Синтаксис массива следующий:

NSArray<NSString*>* array = @[@"hello world"];
1 голос
/ 04 марта 2011

Я не верю, что есть способ сделать это с NSMutableArray из коробки.Возможно, вы могли бы реализовать это, создав подкласс и переопределив все конструкторы и методы вставки, но, вероятно, это того не стоит.Чего ты надеешься достичь с помощью этого?

0 голосов
/ 12 декабря 2014

Существует проект файла с одним заголовком, который позволяет это: Objective-C-Дженерики

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

Скопируйте ObjectiveCGenerics.h в ваш проект. При определении нового класса используйте макрос GENERICSABLE.

#import "ObjectiveCGenerics.h"

GENERICSABLE(MyClass)

@interface MyClass : NSObject<MyClass>

@property (nonatomic, strong) NSString* name;

@end

Теперь вы можете использовать дженерики с массивами и наборами, как это обычно делается в Java, C # и т. Д.

Код: enter image description here

0 голосов
/ 05 января 2014
Протокол

может быть хорошей идеей:

@protocol Person <NSObject>
@end

@interface Person : NSObject <Person>
@end

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

NSArray<Person>*  personArray;
0 голосов
/ 24 января 2013

Вот что я сделал, чтобы не создавать подклассы NSMutableArray: использовать категорию. Таким образом, вы можете иметь аргумент и возвращать типы, которые вы хотите. Обратите внимание на соглашение об именах: замените слово «объект» в каждом из методов, которые вы будете использовать, на имя класса элемента. «objectAtIndex» становится «wheelAtIndex» и так далее. Таким образом, нет конфликта имен. Очень аккуратно.

typedef NSMutableArray WheelList;
@interface NSMutableArray (WheelList) 
- (wheel *) wheelAtIndex: (NSUInteger) index;
- (void) addWheel: (wheel *) w;
@end

@implementation NSMutableArray (WheelList)

- (wheel *) wheelAtIndex: (NSUInteger) index 
{  
    return (wheel *) [self objectAtIndex: index];  
}

- (void) addWheel: (wheel *) w 
{  
    [self addObject: w];  
} 
@end


@interface Car : NSObject
@property WheelList *wheels;
@end;


@implementation Car
@synthesize wheels;

- (id) init 
{
    if (self = [super init]) {
        wheels = [[WheelList alloc] initWithCapacity: 4];
    }
    return self;
}

@end
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...