티스토리 뷰

반응형

 

 

오늘은 Swift에서 String을 사용할때 주의해야할 점을 알아보겠습니다. 
swift 언어로 set, string, array 등의 컬렉션 타입이 비었을때를 체크할 때, 많은 사람들이 아래와 같이 사용하곤 합니다..


아래의 코드를 보겠습니다.


위의 코드는 string의 count를 통해 문자열의 길이를 체크해서 0일 경우 비어있음으로 판단하는 코드입니다. 
하지만, 위의 코드보다 더 보기좋고, 효울적인 코드가 있습니다. 해당 코드는 아래와 같습니다.

 

 

스위프트의 String 타입에서 count대신 isEmpty를 사용해서 문자열이 비어있는지를 확인하는 것은 보기에도 좋지만 isEmpty는 count == 0에 비해 더 빠른 장점도 갖고 있습니다. 그 이유는 무엇일까요?? 🤔

 

 


How Swift strings are built
Swift의 문자열은 어떻게 만들어질까?

스위프트의 String 문자열이 비어있는지 체크할 때, count == 1보다 isEmpty가 더 좋은 이유에 대해서 확인하기에 앞서, 우리들은 Swift 상에서 String이 어떻게 동작하는지를 이해할 필요가 있습니다.

String 문자열은 Character 문자들로 구성된 복잡한 컬렉션입니다. 또한, 이러한 문자기호들이 다수 모여 하나의 심볼을 만들 수도 있습니다.

예를들면, 지역적 표시자 "G", 지역 표시자 "B" 두 개의 문자(리터럴문자는 아니지만, 유니코드로 구성되어있는 문자)가 합쳐서 영국국기를 상징하는 하나의 이모티콘이 되기도 합니다. 이 문자들은 개별적으로 있을때는 "G", "B"와 유사한 형태로 표시됩니다. 하지만 이들이 나란히 합해지면 하나의 영국국기 이모티콘이 됩니다. 그 예시는 아래와 같습니다. 

 

 

 

위의 count 사용 부분 3줄을 보시면, str1 / str2 / str3은 모두 문자열 길이가 1로 나오고 있습니다. 영국국기 이모티콘을 볼 때, 생 문자열의 관점에서는 2개의 문자를로 이루어져 있으나, 실제 유저의 관점에서는 그저 하나의 문자로 보입니다. 그렇다고 영국국기를 반으로 가르고 싶지는 않을 것입니다.

Swift는 이런 경우의 문자에 대해서 Unicode 문자열을 쪼게는 것을 막도록 설계되어 있습니다. 이러한 이유로, Swift에서는 영국국기 이모티콘 또한 한 자리의 문자로 식별합니다. 

이러한 문자열들을 더 복잡하게 구성하기 위해서는 영국국기 이모티콘을 구성한 유니코드 문자 "G", "B"가 UTF-16이나 UTF-8 등의 값으로 표현될 수 있을 것입니다. 

 

 


Indexing into strings
String 문자열로 인덱싱하기

이렇듯 복잡한 문자들로 구성될 수 있는 컬렉션 형태의 String 문자열이기에, 문자열 내의 문자 하나하나를 정수 값으로서 읽을 수 없습니다. 그러므로 String 문자열에서 아래와 같은 첨자 방식의 접근은 컴파일할 수 없습니다.


위와 같은 코드는 컴파일 에러가 발생합니다. 왜냐하면 String 타입의 컬렉션은 기본적으로 배열과 같은 첨자접근이 불가능하기 때문입니다. 이러한 방법대신 String은 아래와 같은 코드로 인덱싱을 할 수 있습니다. 

 

 

위와 같이 문자열을 첨자접근 가능하도록 할 수는 있습니다만...


하지만 위와 같은 구현은 단순하지 않습니다. 왜냐하면, Swift의 문자열을 구성하는 각각의 문자들은 각각 1, 2, 3, 심지어 12개의 값으로도 저장될 수 있습니다.(그 이유는 해당 링크를 참조하세요.)

이러한 이유로 Swift에서는 5번째에 어떤 문자가 있을지를 한번에 알 수 없습니다. - 위의 extension 코드를 보면, 인덱싱을 할때 startIndex부터 시작하여 원하는 위치에 도달할때까지 한단계씩 카운팅합니다. 원하는 위치에 도달할때까지 말이지요.

즉, 스위프트 문자열의 인덱싱 시간 복잡도(Time Complexity)는 일반적인 컬렉션 첨자접근 시 시간복잡도인 O(1)가 아닌 O(N)가 되는 것입니다.

이러한 특성을 이해하지 못하고 스위프트의 문자열을 사용한다면, 문자열 내 각각의 문자들에 대한 작업을 할 때 많은 문제를 초래할 수 있습니다. 그 예시를 하나 들어보겠습니다.

 

 

위의 코드를 가볍게 생각하면 O(N)의 복잡도로 생각할 수 있습니다. 문자열의 문자를 한번씩 돌아다녔다고 생각할 수 있습니다. 그래서 첫번째 문자에서 1의 시간, 두번째 문자에서 2의 시간, N번째 문자에서 N의 시간을 소요했다고 말이죠. 이는 곧 선형 시간을 갖게 된다고 말이죠.

하지만, print(word[index]) 코드에서 사용된 첨자접근은 그 자체만으로 이미 O(N)의 시간복잡도를 갖습니다. 첨자접근을 할때 해당 위치의 문자열을 얻기 위해 첫 인덱스부터 순차적으로 찾기 때문이지요. 그 결과, for문의 각 연산 당 N의 시간이 소요가 됩니다. 이는 O(N^2)의 시간복잡도를 초래하게 됩니다. - 위의 예시와 같이 사용하게 된다면 10000초의 작업일 경우, 1억초의 시간을 부담해야하는 상황이 되는 것입니다. 

그렇기에 결과적으로 위의 코드는 겉보기에 O(N)의 시간복잡도로 볼 수 있지만 실제로는 훨씬 비효율 적인 O(N^2)의 코드로 동작하는 것이 됩니다. 

 

 


Back to isEmpty
다시 isEmpty로 돌아가서...

이제 isEmpty vs count == 0에 대한 주제로 다시 돌아가 보겠습니다.

앞서 보았던 것 처럼, Swift의 문자열에서 count를 사용할 경우 시간복잡도는 O(N)이 됩니다. 스위프트의 문자열은 문자 하나하나가 복잡한 구조의 값으로 되어있어 길이를 알기 위해 시작 인덱스로부터 한단계 한단계 카운트하면서 계산하기 때문이었습니다. 

이와 비교해볼 때, isEmpty는 단 한번의 간단한 체크로 true나 false를 반환할 수 있습니다. 단지 문자열의 첫 인덱스가 끝 인덱스와 같은지를 비교할 뿐이빈다. 이러한 연산은 단 한번의 연산으로 끝나며 시간복잡도는 O(1)입니다. 이런 점에서 isEmpty 연산은 count 연산과 큰 차이를 보이게 됩니다.

자, 이렇게 지금까지의 내용을 요약정리해보자면 아래와 같겠습니다. 😊

◼︎ isEmpty는 값이 비어있을 때는 체크할때 사용될 수 있는 가장 명확한 연산방법입니다. 특정 값을 하나하나 체크하는 것에 비해서 말이죠.

◼︎ String 문제열의 경우 count == 0 와 같은 연산을 주의해서 사용해야합니다. String의 count 복잡도는 O(N)임을 인지해야 합니다. 

좋은 소식은, SwiftLint, SwiftFormat등의 외부 라이브러리에서는 이러한 String의 사용에 있어서 경고를 주기도 합니다. 이를 통해 실제 개발할때 이에 대한 경각심을 갖고 사용할 수 있겠죠. 물론 이미 이런 부분을 이해하고 코딩한다면, 애초에 경고를 볼 일도 없겠지만요.

 

반응형
댓글
반응형
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/04   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
글 보관함