안녕하세요. 개발하는 정주입니다.
오늘은 "nil을 제외하는 방법 비교 (guard, compactMap)"에 대해 알아보겠습니다.
nil이란?
Swift에서 nil이란 변수에 객체가 할당되지 않은 상태입니다. Swift 문서에 따르면 "valueless state by assigning it the special value" 라고 설명하고 있는데요. 즉, 가치가 없는 상태를 nil이라고 합니다.
Objective-C에서는 null과 nil이 따로 존재합니다.
Objective-C의 nil은 포인터지만 Swift의 nil은 포인터가 아니라는 차이점이 있습니다.
nil을 다루는 방법
nil을 다루는 방법에는 여러 종류가 있는데요.
이번 포스팅에서는 guard let과 compactMap만을 다루겠습니다.
왜냐하면... 이 포스팅을 쓴 계기가 guard let과 compactMap의 시간 비교가 궁금했기 때문이죠!
guard 옵셔널 바인딩
guard는 Swift 문서에서 Early Exit으로 따로 다룰 만큼 빠른 종료의 의미가 강한 방법입니다.
즉, guard를 이용한 옵셔널 바인딩은 nil일 경우 빠르게 종료시킨다! 라는 의미입니다.
testArray는 홀수 index에는 nil이, 짝수 index에는 정수가 들어있는 [Int?]형 배열입니다.
for i in testArray {
guard let i = i else { continue }
arr.append(i)
}
if let과 비교하여 guard문을 이용한 옵셔널 바인딩은 코드의 depth가 깊어지지 않는다는 장점이 있습니다.
저는 빠른 종료의 개념이 필요하거나, nil일 때 처리할 코드가 짧다면(최대 3줄) guard문을 사용합니다.
compactMap
compactMap은 non-nil인 원소만 추려서 새로운 배열을 반환해주는 메서드입니다.
Returns an array containing the non-nil results of calling the given transformation with each element of this sequence.
이때 주의할 점은 원본이 nil인 경우가 아닌 compactMap 안에서 처리한 결과가 nil일 때 제외 된다는 점입니다.
간단한 예시를 보겠습니다.
let compactMapped: [Int] = possibleNumbers.compactMap { str in Int(str) }
[String]인 possibleNumbers를 [Int]로 바꾸는 코드입니다.
possibleNumbers에는 정수로 변환할 수 없는 문자열도 존재할 가능성이 있습니다.
만약 예외처리를 해주지 않는다면 에러가 발생할 수 있습니다.
이럴 때 사용하면 좋은 것이 compactMap입니다.
Int(str)의 결과가 non-nil이라면 배열에 포함되고 nil이라면 제외되어 온전한 정수 배열을 생성할 수 있는 것이죠.
guard와 compactMap( ) 속도 비교
guard와 compactMap( ) 중 누가 더 빠르게 nil을 제외시킬 수 있을지 궁금했습니다.
테스트 전 예상으로는 둘 다 비슷할 것이라고 생각했는데요.
과연 그럴지! 한 번 봅시다. (스포: 틀림 ㅎㅎ ㅠ)
guard
guard는 단순 반복문에서 사용할 경우 O(n)입니다.
guard 자체는 메서드가 아니기에 시간복잡도라는 개념이 없기 떄문에 반복문의 시간복잡도에 의존합니다.
따라서 그냥 반복문 그 자체의 시간이 출력됩니다.
compactMap( )
compactMap( )을 이용한 방법입니다.
compactMap( )은 O(n)이 아닌 O(n + m)으로 동작합니다.
n은 원본 배열의 길이이고 m은 결과의 길이로 이 테스트에서는 n은 1000만, m은 500만이 됩니다.
왜 그럴까 생각을 해보았는데요.
원본 배열의 원소를 이용해 연산을 하고(n)
결과를 만들어 새로운 배열로 만들어야 하기 때문에(m) O(n + m)이 되지 않을까 싶습니다.
뭘 써야 하지?
그렇다면 guard와 compactMap( ) 중 어떤 것을 써야할까요?
간단합니다.
반복문이나 함수에서 nil이 아닐 경우 빠른 종료를 하고 싶다면 guard를 사용하면 됩니다.
어떠한 처리한 결과가 nil인 경우를 필터링하고 싶다면 compactMap( )을 사용하면 됩니다.
둘의 목적은 같지만 역할이 전혀 다르기 때문에 각각의 역할에 맞게 사용하면 됩니다.
마무리
compactMap( )에 대해 더 잘 알게 된 시간이었습니다.
O(n + m)이라는 것도 처음 알았고 compactMap( )의 의미에 대해 더 깊게 알게 된 시간이었네요.
감사합니다!
아직은 초보 개발자입니다.
더 효율적인 코드 훈수 환영합니다!
공감과 댓글 부탁드립니다.