Відповіді:
Ви можете створити категорію з -addSomeClass:
методом, який дозволить перевірити статичний тип часу компіляції (щоб компілятор міг повідомити вас, якщо ви спробуєте додати об'єкт, який він знає, інший клас за допомогою цього методу), але немає реального способу примусити це масив містить лише об’єкти даного класу.
Загалом, здається, немає потреби в такому обмеженні в Objective-C. Не думаю, що я коли-небудь чув, щоб досвідчений програміст какао бажав цієї функції. Єдині люди, які здаються програмістами з інших мов, які все ще думають цими мовами. Якщо вам потрібні лише об’єкти даного класу в масиві, додайте туди лише об’єкти цього класу. Якщо ви хочете перевірити, чи правильно ваш код поводиться, протестуйте його.
Це ще ніхто тут не поставив, тож я це зроблю!
Зараз це офіційно підтримується в Objective-C. Станом на Xcode 7 ви можете використовувати наступний синтаксис:
NSArray<MyClass *> *myArray = @[[MyClass new], [MyClass new]];
Примітка
Важливо зазначити, що це лише попередження компілятора, і ви технічно все ще можете вставити будь-який об'єкт у свій масив. Доступні сценарії, які перетворюють усі попередження на помилки, які не дозволять будувати.
nonnull
XCode 6, і, наскільки я пам’ятаю, вони були введені одночасно. Крім того, чи залежить використання таких концепцій від версії XCode або від версії iOS?
@property (nonatomic, strong) NSArray<id<SomeProtocol>>* protocolObjects;
Виглядає трохи незграбно, але робить трюк!
Це відносно поширене питання для людей, які переходять від мов сильно типу (наприклад, 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
. Цей другий підхід сприятливий, оскільки він уточнює наміри коду більше, ніж перший.
Часто один із цих підходів звільняє вас від турботи, чи всі об’єкти в масиві заданого типу. Якщо вам все-таки все одно, стандартний динамічний мовний підхід полягає в одиничному тесті, одиничному тесті, одиничному тесті. Оскільки регресія у цій вимозі призведе до помилки (ймовірно, не підлягає відновленню) часу виконання (не часу компіляції), вам потрібно мати тестове покриття, щоб перевірити поведінку, щоб не випустити збій у природу. У цьому випадку виконайте операцію, яка змінює масив, а потім перевірте, чи всі екземпляри масиву належать до заданого класу. При належному покритті тесту вам навіть не потрібні додаткові накладні витрати для перевірки ідентичності екземпляра. У вас справді гарне охоплення модульних тестів, чи не так?
id
, за винятком випадків, коли це необхідно, більше, ніж кодери Java, проходять навколо Object
s. Чому ні? Вам це не потрібно, якщо у вас є модульні тести? Тому що він є і робить ваш код більш рентабельним, як і введені масиви. Схоже на те, що люди, які інвестували в платформу, не бажають поступитись балом, а отже, вигадують причини, чому це упущення насправді є перевагою.
Ви можете підклас 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
Погляньте на https://github.com/tomersh/Objective-C-Generics , реалізацію загальних засобів компіляції (реалізована препроцесором) для Objective-C. Цей допис у блозі має приємний огляд. В основному ви отримуєте перевірку часу компіляції (попередження або помилки), але не застосовується покарання за дженерики.
Цей проект Github реалізує саме цю функціональність.
Потім ви можете використовувати <>
дужки, як у C #.
З їх прикладів:
NSArray<MyClass>* classArray = [NSArray array];
NSString *name = [classArray lastObject].name; // No cast needed
Можливим способом може бути підкласифікація NSArray, але Apple рекомендує цього не робити. Простіше подумати двічі про фактичну потребу в набраному NSArray.
Я створив підклас 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, реалізований.
Якщо ви змішали 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];
}