Array / endIndex

iOS 앱개발 공부

목록 보기
1/30

1) endIndex 작동 문제

럽카이브 프로젝트를 진행하며 어떤 배열의 첫 멤버와 마지막 멤버만 사용해야 하는 로직을 작성해야 했다.
firstlast를 사용해도 되지만, 이 둘은 옵셔널 값이기 때문에 옵셔널 바인딩이 필요하기 때문에 옵셔널 바인딩이 필요 없는 startIndexendIndex를 활용하기로 했다.

// index = 현재 인덱스
// array = 사용하려는 배열

if array.startIndex == index {
	print(array[index])
} else if array.endIndex == index {
	print(array[index])
}

위와 같이 코드를 작성하면 index가 첫 번째 멤버와 마지막 멤버의 index와 같을 경우 각 멤버를 프린트할 것이라고 생각했다.
그러나 실행 결과는 다소 달랐는데...
예를 들어 array라는 배열이 [1, 2, 3, 4, 5]라고 했을 때, 내 예상대로라면 출력은 1, 5가 되어야 했다. 그러나 실제 출력 결과는 아래와 같다.

1

1만 출력이 된 것이다...
무엇인가 실수한 걸까 싶어서 여러번 다시 시도해 보았지만, 결과는 동일했고, 원인을 파악하기 위해 브레이크 포인트를 걸어 확인해 보았다.

먼저 else if문에 브레이크 포인트를 걸고 코드가 실행되는지 확인해 보았는데,
브레이크가 걸리지 않았다. 즉, array.endIndex == index라는 조건이 성립되지 않는다는 뜻이다.

다음으로는 if문이 실행되기 직전에 브레이크를 걸고 원하는 데이터를 출력해 보았다.

po array.count // 출력 5

po index // 출력 0

po array.startIndex // 출력 0

po array.endIndex // 출력 5

위에서 특이한 점을 찾았을까? 바로 endIndex의 값이 count의 값과 같다는 것이다.
내 예상으로는 endIndexstartIndex가 첫 번째 멤버의 인덱스를 반환하는 것처럼, 마지막 멤버의 인덱스를 반환하리라고 생각했는데, 배열의 수와 같은 값을 반환했다.
때문에 index가 4가 되어도 endIndex는 5이기 때문에 조건문이 성립되지 않아 아무것도 출력되지 않은 것이다.

그렇다면 startIndex와 다르게 endIndex는 왜 마지막 멤버의 인덱스가 아닌 배열의 수와 같은 값을 반환하는 것일까?

2) endIndex의 개념

endIndex에 대해 Apple의 정의를 찾아보면 아래와 같다.

배열의 "끝 이후" 위치, 즉 마지막 유효한 아래 첨자 인수보다 하나 더 큰 위치입니다.

즉, endIndex는 배열의 마지막 멤버의 인덱스보다 하나 더 큰 값을 반환하는 코드라는 뜻이다.

그렇다면 Swift에서 endIndex는 어째서 마지막 멤버의 인덱스가 아닌, 그 다음 위치를 반환하도록 만들었을까?
그 이유는 크게 2가지가 있다.

1️⃣ for-in 루프와 일관성 유지

Swift에서는 반개구간(Half-Open Range) [startIndex..<endIndex]의 형식을 많이 이용한다. 여기서 만약 endIndex가 마지막 요소의 인덱스였다면, ..<endIndex를 사용할 때 마지막 요소가 포함되지 않는 문제가 발생하게 된다.
물론 startIndex...endIndex로 사용하게 되면 문제는 사라지지만 말이다...

2️⃣ endIndex는 항상 유효한 위치

endIndex 자체를 직접 사용하지 않는 이상, 배열 범위를 벗어나는 오류를 방지할 수 있다.
예를 들어, C언어 스타일의 배열에서는 for(int i = 0; i <= lastIndex; i++) 같은 코드에서 i == lastIndex일 때 잘못된 접근 오류가 발생할 수 있다.
Swift에서는 ..<endIndex를 사용하면 항상 안전한 범위 내에서 루프가 실행되게 된다.

위 2가지 이유 모두 ...endIndex 형식으로 사용하면 되는게 아닐까? 싶어서 더 찾아보았는데, Swift는 아니지만 C언어에서 배열의 형식을 보고 Swift도 유사한 이유가 있지 않을까 싶었다.

C언어에서는 배열이 [1, 2, 3, 4, 5]가 있다고 할 때, 실제 메모리 상에서는 [1, 2, 3, 4, 5, NULL]의 형태로 저장이 된다고 한다. 이렇게 되면 실제 메모리에서의 인덱스는 0, 1, 2, 3, 4, 5가 되고, 이 경우 endIndex는 5가 되는 것이다.
그러나 5번째 인덱스의 멤버는 NULL이기 때문에(Swift의 nil과 유사한 값) 호출을 하는 순간 에러가 발생할 것이다.

만약 Swift도 C언어의 배열과 유사한 구조라면, endIndex가 왜 마지막 멤버 + 1의 인덱스를 가져오는지 이해가 되고, endIndex를 직접 사용하면 안되는 이유도 이해가 된다.

3) 결론

지금까지 당연히 생각해오던 endIndex의 작동 방식이 예상과 다르다는 것을 알게되어 놀랐다.
배열의 마지막 요소를 사용하고 싶다면 endIndex를 직접 사용하는 것이 아니라 endIndex - 1을 사용한다던가, 다른 형식으로 사용해야 함을 알 수 있는 좋은 기회였다고 생각한다.

profile
이유있는 코드를 쓰자!!

0개의 댓글