NSMutableArray-принудительный массив для хранения только определенного типа объекта


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

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

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


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

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

12 64

12 ответов:

обновление в 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"];

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

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

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

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

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

NSArray<Protocol Name>

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

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

пример:

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

что-то вроде этого. я не помню точный синтаксис.

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

колеса.h file:

@protocol Wheel
@end

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

автомобиля.h file:

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

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

автомобиля.м file:

#import "Car.h"
@implementation Car

-(id)init{
   if (self=[super init]){
   self.wheels = (NSMutableArray<Wheel,Optional>*)[NSMutableArray alloc]init];
   }
return self;
}
@end

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

@protocol Person <NSObject>
@end

@interface Person : NSObject <Person>
@end

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

NSArray<Person>*  personArray;

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

NSArray<NSString*>* array = @[@"hello world"];

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

Это невозможно; NSArray (изменяемый или нет) будет содержать любой тип объекта. Что вы можете сделать, так это создать свои собственные подклассы, как уже предлагал Джим. Кроме того, если вы хотите отфильтровать массив для удаления объектов, которые не имеют нужного типа, то вы можете сделать:

- (void)removeObjectsFromArray:(NSMutableArray *)array otherThanOfType:(Class)type
{
    int c = 0;
    while(c < [array length])
    {
        NSObject *object = [array objectAtIndex:c];
        if([object isKindOfClass:type])
          c++;
        else
          [array removeObjectAtIndex:c];
    }
}

...
[self removeObjectsFromArray:array otherThanOfType:[Car class]];

или сделать другие суждения, основанные на результате isKindOfClass:, например, разделить массив, содержащий смесь автомобилей и колес на два массива, каждый из которых содержит только один типа объектов.

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

for (int i = 0; i<items.count;i++) {
 if([[items objectAtIndex:i] isKindOfClass:[Wheel class]])
 {
  // do something..!
 }else{
  [NSException raise:@"Invalid value" format:@"Format of %@ is invalid", items];
  // do whatever to handle or raise your exception.
 }
}

вот что я сделал, чтобы избежать подклассов 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

есть один заголовок файла проекта, который позволяет это: Objective-C-Generics

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

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

#import "ObjectiveCGenerics.h"

GENERICSABLE(MyClass)

@interface MyClass : NSObject<MyClass>

@property (nonatomic, strong) NSString* name;

@end

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

код: enter image description here