Optional Unwrapping
2017년 1월 7일

NSMutableArray를 반복하며 객체 제거하기

Objective-C에서 배열을 반복하면서 조건에 따라 일부 객체를 제거해야할 때가 있다. 우선 배열을 NSMutableArray로 만들고, for-in 문을 통해 바로 조건에 맞지 않는 객체를 제거하려고 했다.

NSArray<NSString *> *originalItems = @[@"Sam", @"John", @"Kevin", @"William"];
NSMutableArray<NSString *> *copiedItems = originalItems.mutableCopy;
for (NSString *item in copiedItems) {
    if (item.length > 4) {
        // 문자열의 길이가 4보다 크면 배열에서 제거한다.
        [copiedItems removeObject:item];
    }
}

이 코드를 실행하면 크래시가 발생한다. 컬렉션 타입이 반복중에 변경되었기 때문이다. 그래서 반복중에 변경하지 않는 방법으로 다시 만들었다. 조건에 맞지 않는 객체를 제거하지 않고, 반대로 조건에 맞는 객체만 새 배열에 저장해서 사용했다.

NSArray<NSString *> *originalItems = @[@"Sam", @"John", @"Kevin", @"William"];
NSMutableArray<NSString *> *newItems = [NSMutableArray new];
for (NSString *item in originalItems) {
    if (item.length <= 4) {
        // 문자열의 길이가 4보다 작거나 같으면 새 배열에 추가한다.
        [newItems addObject:item];
    }
}
originalItems = [NSArray arrayWithArray:newItems];

이 방법은 잘 동작하며, 현재 사용중인 방법이다. 하지만 글을 작성하면서 '이 방법이 최선일까?'하는 생각이 들어 다른 방법을 찾아봤다.

NSArray<NSString *> *originalItems = @[@"Sam", @"John", @"Kevin", @"William"];
NSMutableArray<NSString *> *discardedItems = [NSMutableArray new];
for (NSString *item in originalItems) {
    if (item.length > 4) {
        // 문자열의 길이가 4보다 크면 삭제 아이템 배열에 추가한다.
        [discardedItems addObject:item];
    }
}
NSMutableArray<NSString *> *copiedItems = originalItems.mutableCopy;
[copiedItems removeObjectsInArray:discardedItems];
originalItems = [NSArray arrayWithArray:copiedItems];

새 방법은 제거할 아이템 배열을 만들고, removeObjectsInArray: 메서드를 사용해서 한꺼번에 제거한다. 코드는 조금 길어졌지만 의도가 좀 더 잘 반영된 코드다. 원본 배열의 타입이 NSArray가 아니라 NSMutableArray라면 코드도 길어지지 않는다.

Swift

Swift에서는 어떻게 할까?

var originalItems = ["Sam", "John", "Kevin", "William"]
originalItems = originalItems.filter { $0.characters.count <= 4 }

filter 메서드를 통해 함수형 기법을 사용하면 보다 직관적인 코드를 작성할 수 있다.

다시 Objective-C

Swift에서 사용한 filter를 Objective-C에서도 사용할 수 있을까?

NSArray<NSString *> *originalItems = @[@"Sam", @"John", @"Kevin", @"William"];
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF.length <= 4"];
originalItems = [originalItems filteredArrayUsingPredicate:predicate];

가능하다!


참고