Существует ли алгоритм Objective-C, такой как `transform` в C ++ STL? - PullRequest
9 голосов
/ 02 января 2011

Моя цель - получить массив, содержащий все имена файлов определенного расширения, но без расширения.

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

Есть лиспособ с Objective-C (может быть похож на механизм предикатов) применить некоторую функцию к каждому элементу массива и поместить результаты во второй массив, как это делает алгоритм transform в C ++ STL?

Что бы я хотел написать:

// let's pretend 'anArray' was filled by querying the filesystem and not hardcoded
NSArray* anArray = [[NSArray alloc] initWithObjects:@"one.ext", @"two.ext", nil];

// that's what I liked to write (pseudo code)
NSArray* transformed = [anArray transform: stringByDeletingPathExtension];

// Yuji's answer below proposes this (which may be as close as you can get
// to my wish with Objective C)
NSArray* transformed = [anArray my_arrayByApplyingBlock:^(id x){
                           return [x stringByDeletingPathExtension];
                       }];

Ответы [ 2 ]

11 голосов
/ 19 сентября 2013

На самом деле, есть очень простой способ. Он существует с 2003 года и плохо назван.

NSArray *array = [NSArray arrayWithObjects:@"one.ext", @"two.ext", nil];

// short solution
NSArray *transformed = [array valueForKey:@"stringByDeletingPathExtension"];

// long solution (more robust against refactoring)
NSString *key = NSStringFromSelector(@selector(stringByDeletingPathExtension));
NSArray *transformed = [array valueForKey:key];

Оба выдают результат:

(
    one,
    two
)
9 голосов
/ 02 января 2011

Эта тема называется «Сообщения высшего порядка в какао» и разработана многими людьми в Интернете.Начните с здесь и попробуйте поискать больше.Они добавляют метод категории к NSArray, так что вы можете сделать

NSArray*transformed=[[anArray map] stringByDeletingPathExtension];

Идея заключается в следующем:

  • [anArray map] создает временный объект (скажем, hom)
  • hom получает сообщение stringByDeletingPathExtension
  • hom повторно отправляет сообщение всем элементам anArray
  • hom собирает результатыи возвращает полученный массив.

Если вы просто хотите быстрое преобразование, я бы определил метод категории:

@interface NSArray (myTransformingAddition)
-(NSArray*)my_arrayByApplyingBlock:(id(^)(id))block;
@end

@implementation NSArray (myTransformingAddition)
-(NSArray*)my_arrayByApplyingBlock:(id(^)(id))block{
     NSMutableArray*result=[NSMutableArray array];
     for(id x in self){
          [result addObject:block(x)];
     }
     return result;
}
@end

Тогда вы можете сделать

NSArray* transformed=[anArray my_arrayByApplyingBlock:^id(id x){return [x stringByDeletingPathExtension];}];

Обратите внимание на конструкцию ^ return-type (arguments) { ...}, которая создает блок.Возвращаемый тип может быть опущен, и clang достаточно умен, чтобы угадать его, но gcc довольно строги в этом и его нужно когда-нибудь указать.(В этом случае это делается из оператора return, который имеет [x stringBy...], который возвращает NSString*. Таким образом, GCC предполагает, что тип возвращаемого блока равен NSString* вместо id, который GCC считает несовместимымТаким образом, появляется ошибка.)

В OS X Leopard или iOS 3 вы можете использовать PLBlocks для поддержки блоков.Мое личное субъективное мнение заключается в том, что люди, которым небезразлично новое программное обеспечение, обычно переходят на новейшую ОС, поэтому поддержка последней ОС должна быть просто отличной;поддержка более старой ОС не увеличит вашего клиента в два раза ...

ЭТО СКАЗАЛ , уже есть хорошая платформа с открытым исходным кодом, которая делает все, что я сказал выше.См. Обсуждение здесь , и особенно связанный с ним FunctionalKit .


Более того: ваш псевдокод на самом деле легко реализовать [array transform:stringByDeletingPathExtension].

@interface NSArray (myTransformingAddition)
-(NSArray*)my_transformUsingSelector:(SEL)sel;
@end

@implementation NSArray (myTransformingAddition)
-(NSArray*)my_transformUsingSelector:(SEL)sel;{
     NSMutableArray*result=[NSMutableArray array];
     for(id x in self){
          [result addObject:[x performSelector:sel withObject:nil]];
     }
     return result;
}
@end

Тогда вы можете использовать его следующим образом:

NSArray*transformed=[array my_transformUsingSelector:@selector(stringByDeletingPathExtension)];

Однако мне это не очень нравится;вам необходимо иметь метод, уже определенный для объекта в массиве, чтобы использовать этот метод.Например, если NSString не имеет операции, которую вы хотите сделать как метод, что бы вы сделали в этом случае?Сначала вам нужно добавить его в NSString через категорию:

@interface NSString (myHack)
-(NSString*)my_NiceTransformation;
@end

@implementation NSString (myHack)
-(NSString*)my_NiceTransformation{
   ... computes the return value from self ...
   return something;
}
@end

Затем вы можете использовать

NSArray*transformed=[array my_transformUsingSelector:@selector(my_NiceTransformation)];

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

NSArray*transformed=[array my_arrayByApplyingBlock:^id(id x){
   ... computes the return value from x ...
   return something;    
}];

Наконец, никогда добавлять методы категории, которые не начинаются с префикса, такого как my_ или чего-либо еще,Например, в будущем Apple может предоставить хороший метод под названием transform, который делает именно то, что вы хотите.Но если у вас уже есть метод с именем transform в категории, это приведет к неопределенному поведению.На самом деле, может случиться так, что в классе Apple уже есть частный метод.

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