Чи є спосіб примусити набирати текст на NSArray, NSMutableArray тощо?


Відповіді:


35

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

Загалом, здається, немає потреби в такому обмеженні в Objective-C. Не думаю, що я коли-небудь чув, щоб досвідчений програміст какао бажав цієї функції. Єдині люди, які здаються програмістами з інших мов, які все ще думають цими мовами. Якщо вам потрібні лише об’єкти даного класу в масиві, додайте туди лише об’єкти цього класу. Якщо ви хочете перевірити, чи правильно ваш код поводиться, протестуйте його.


136
Я думаю, що "досвідчені програмісти какао" просто не знають, чого їм не вистачає - досвід роботи з Java показує, що змінні типу покращують розуміння коду та роблять можливим більше рефакторингу.
tgdavies

11
Ну, підтримка Java Generics сильно пошкоджена сама по собі, тому що вони не ввели її з самого початку ...
dertoni

28
Потрібно погодитися з @tgdavies. Мені не вистачає можливостей інтелігенції та рефакторингу, які я мав із C #. Коли я хочу динамічного набору тексту, я можу отримати це в C # 4.0. Коли я хочу сильно набирати речі, я їх теж можу мати. Я виявив, що є час і місце для обох цих речей.
Стів

18
@charkrit Що це стосується Objective-C, що робить його "не потрібним"? Чи відчували ви це необхідним, коли користувались C #? Я чую, як багато людей кажуть, що це вам не потрібно в Objective-C, але я думаю, ці самі люди вважають, що вам це не потрібно жодною мовою, що робить це питання переваги / стилю, а не необхідності.
бакар

17
Чи не в тому, щоб дозволити компілятору насправді допомогти вам знайти проблеми. Звичайно, ви можете сказати: "Якщо вам потрібні лише об'єкти даного класу в масиві, додайте туди лише об'єкти цього класу." Але якщо тести - єдиний спосіб цього досягти, ви перебуваєте у невигідному становищі. Чим далі від написання коду ви виявите проблему, тим дорожче ця проблема.
GreenKiwi

145

Це ще ніхто тут не поставив, тож я це зроблю!

Зараз це офіційно підтримується в Objective-C. Станом на Xcode 7 ви можете використовувати наступний синтаксис:

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

Примітка

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


Я ленуюсь тут, але чому це доступно лише в XCode 7? Ми можемо використовувати nonnullXCode 6, і, наскільки я пам’ятаю, вони були введені одночасно. Крім того, чи залежить використання таких концепцій від версії XCode або від версії iOS?
Гувен

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

Я майже впевнений, що це залежить лише від версії Xcode. Загальні препарати є лише попередженнями компілятора і не вказуються під час виконання. Я майже впевнений, що ви могли б скомпілювати на будь-яку ОС, яку хочете.
Логан

2
@DeanKelly - Ви могли б зробити це так: @property (nonatomic, strong) NSArray<id<SomeProtocol>>* protocolObjects; Виглядає трохи незграбно, але робить трюк!
Логан

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

53

Це відносно поширене питання для людей, які переходять від мов сильно типу (наприклад, C ++ або Java) до більш слабко або динамічно набраних мов, таких як Python, Ruby чи Objective-C. У Objective-C більшість об'єктів успадковуються від NSObject(type id) (решта успадковуються від іншого кореневого класу, такого як NSProxyі також може бути type id), і будь-яке повідомлення може бути надіслане будь-якому об'єкту. Звичайно, відправивши повідомлення на екземпляр , що він не визнає , може привести до помилки під час виконання (а також викликати компілятор попередженняз відповідними прапорами -W). Поки екземпляр відповідає на надіслане вами повідомлення, вам може бути все одно, до якого класу він належить. Це часто називають "набранням качки", тому що "якщо воно крякає, як качка [тобто реагує на селектор], це качка [тобто вона може обробляти повідомлення; кому цікаво, який це клас]".

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

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

Якщо ви керуєте вихідним кодом для примірників, які реалізують метод (и), який ви хочете викликати, більш поширеним підходом було б визначити a, @protocolщо містить ці методи, і заявити, що відповідні класи реалізують цей протокол у своїй декларації. У цьому використанні a @protocolє аналогом інтерфейсу Java або абстрактного базового класу C ++. Потім можна перевірити відповідність усьому протоколу, а не відповідати на кожен метод. У попередньому прикладі це не мало б великої різниці, але якщо ви викликали кілька методів, це могло спростити ситуацію. Тоді прикладом може бути:

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

припускаючи MyProtocolзаявляє myMethod. Цей другий підхід сприятливий, оскільки він уточнює наміри коду більше, ніж перший.

Часто один із цих підходів звільняє вас від турботи, чи всі об’єкти в масиві заданого типу. Якщо вам все-таки все одно, стандартний динамічний мовний підхід полягає в одиничному тесті, одиничному тесті, одиничному тесті. Оскільки регресія у цій вимозі призведе до помилки (ймовірно, не підлягає відновленню) часу виконання (не часу компіляції), вам потрібно мати тестове покриття, щоб перевірити поведінку, щоб не випустити збій у природу. У цьому випадку виконайте операцію, яка змінює масив, а потім перевірте, чи всі екземпляри масиву належать до заданого класу. При належному покритті тесту вам навіть не потрібні додаткові накладні витрати для перевірки ідентичності екземпляра. У вас справді гарне охоплення модульних тестів, чи не так?


35
Модульне тестування не може замінити систему гідного типу.
тба

8
Так, кому потрібен інструмент, який дозволить собі набрані масиви. Я впевнений, що @BarryWark (і будь-хто інший, хто торкнувся будь-якої кодової бази, яку він повинен використовувати, читати, розуміти та підтримувати) має 100% покриття коду. Однак я думаю, що ви не використовуєте сировину id, за винятком випадків, коли це необхідно, більше, ніж кодери Java, проходять навколо Objects. Чому ні? Вам це не потрібно, якщо у вас є модульні тести? Тому що він є і робить ваш код більш рентабельним, як і введені масиви. Схоже на те, що люди, які інвестували в платформу, не бажають поступитись балом, а отже, вигадують причини, чому це упущення насправді є перевагою.
funkybro

"Качинка набирає" ?? це весело! ніколи раніше не чув цього.
Джон Хенкель,

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
Я спробував це, дуже гарна ідея, але, на жаль, баггі, і він не перевіряє додані елементи.
Бінаріан

4

Цей проект Github реалізує саме цю функціональність.

Потім ви можете використовувати <>дужки, як у C #.

З їх прикладів:

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

0

Можливим способом може бути підкласифікація NSArray, але Apple рекомендує цього не робити. Простіше подумати двічі про фактичну потребу в набраному NSArray.


1
Це економить час для перевірки статичного типу під час збирання, редагування ще краще. Особливо корисно, коли ви пишете lib для тривалого використання.
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);

Це лише приклад коду, який ніколи не використовувався в реальних програмах. для цього йому, ймовірно, потрібен метод mor NSArray, реалізований.


0

Якщо ви змішали c ++ та object-c (тобто використовуючи тип файлу mm), ви можете примусити набирати текст за допомогою пари або кортежу. Наприклад, у наступному методі ви можете створити об'єкт С ++ типу 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"];
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.