제공 :
한빛 네트워크
저자 : Andy Oram
역자 : 김동혁
원문 :
New Swift language shows Apple history
새롭지만 여전히 Objective-C가 갖고 있던 의미를 계승하는 Swift
제가 바라보는 애플의 새로운 프로그래밍 언어 Swift는 하나의 신선함 그 자체였습니다. 많은 여타의 애플 개발자들은 물론, Swift가 대체 무엇일까 하는 궁금증과 새로움을 만끽하기 위해 iOS 개발에 뛰어들려는 개발자들도 주변에서 여럿 볼 수 있었습니다. 지속적인 인기를 끌어 오던 O"Reilly의 iOS Programming Cookbook 시리즈 저자인 Vandad Nahavandipoor가 이번에 새로 발간하게 될 책은 그 서명("iOS8 Swift Programming Cookbook")에서 보여주듯이 내용을 모두 Swift 기반으로 쓰인 것에 대해서도 저는 매우 긍정적으로 바라보고 있습니다. 하지만 LLVM 컴파일러와 iOS Runtime은 개발자들이 Swift로 개발을 하더라도 여전히 안고 가야할 많은 특성과 주의점들을 여전히 내포하고 있다는 점을 깨달았습니다. 그래서 저는 이 글을 통해 iOS 개발자들에게 iOS Runtime이 갖고 있는 특이성에 대해 설명하고 개발시 주의해야 할 사항들에 대해 짚어 보고자 합니다.
델리게이트(Delegates)
iOS는 다른 인터랙티브 시스템들과 동일하게 어떤 클래스가 특정 이벤트에 대해 처리하기 위해 대기하고 있을 때(예를 들면 유저가 버튼을 누른다거나) 그것이 발생했을 때 알려주는 역할을 수행합니다. 이렇게 반응적인 동작을 구현하기 위해서, 개발자는 유저가 어떠한 액션을 하는 그 순간을 인지하고 대응할 수 있어야 합니다. 이 점에서 iOS는 다른 시스템들과 달리 이를 전담하는 클래스를 새로 정의해야 하는데, 이것이 바로 델리게이트라 불리는 친구입니다. 델리게이트는 유저가 무언가를 위해 취한 액션에 해당하는 모든 중요한 이벤트들을 받아서 처리하는 역할을 합니다. 간혹 본래 클래스의 델리게이트를 자기 자신으로 정의하는 경우도 있지만, 어느 경우에라도 개발자는 델리게이트의 컨셉을 명확히 숙지하고 있어야 하며 핵심 이벤트에 대응할 수 있도록 정의해야 합니다. 애플 찬양론자들은 이렇게 역할을 명시적으로 분리한 델리게이트의 개념(저는 이를 "디자인 패턴"이라는 거창한 명칭으로 부르고 싶진 않네요)을 예찬하곤 하죠. 아직까지 여타의 시스템에서는 왜 이러한 방식을 채용하지 않고 아무 클래스나 전부 유저 인터페이스로 부터 발생하는 이벤트를 받고 처리할 수 있도록 허용했는지에 대해서는 별다른 해명을 본 적이 없네요. 물론 그들은 왜 굳이 분리해야 하는지를 오히려 묻고 싶었을 지도 모릅니다. 네, 바로 이 애플만의 오묘한 델리게이트는 Objective-C에서부터 Swift까지 오는 과정에서 꿋꿋이 살아남았습니다. 때문에 우린 Swift에서도 여전히 델리게이트와 친해지고 쭉 함께 가야할 운명임을 잊지 말아야 합니다.
메모리 관리(Memory management)
인내심이 없는 분들을 위해 아주 짤막하게만 역사에 대해 간단히 짚고 넘어가고자 합니다. 1980년대에도 Objective-C는 이미 존재했지만 거의 없는 것처럼 인지도가 낮았습니다. C를 사용하던 많은 유저들은 객체 지향 프로그래밍을 위해 단지 C++로 넘어갔을 뿐이었죠. 하지만 당시엔 자유 소프트웨어 재단(Free Software Foundation)의 GNU 컴파일러로 구현이 되었다는 점과 다른 C에 비해서는 개선된 점을 다수 갖고 있었습니다. 스티브 잡스는 GNU 컴파일러에 남다른 애정을 갖고 있었기 때문에 이제는 거의 잊혀진 NeXT사의 컴퓨터도 Objective-C기반으로 구축되어 있었습니다.
이러한 역사적 사실들은 LLVM 컴파일러와 찰떡 궁합 관계인 Objective-C가 아이폰의 공식 언어로 된 이유를 충분히 설명해준다고 생각합니다. 그리고 좀 더 생각해보면 왜 별로 상관도 없는 컴퓨터 언어가 아이폰이 공개된 이후로 이제는 대세를 이루는 언어 중 하나가 되었는지도 알 수 있습니다.
잘 참고 지루한 부분을 읽어주셨군요. 아 그래서 어디까지 이야기 했더라… 아 메모리 관리를 이야기 하고 있었죠. LLVM은 기본적으로 가비지 콜렉션을 제공합니다. 하지만 애플은 이를 iOS에서 사용하길 거부했습니다. 그 이유는 유저에게 중요한 순간에 iOS가 제멋대로 가비지 콜렉터를 동작하여 앱이 느려지는걸 원치 않았으니까요. 여러분이 앱을 하나 만든다고 칩시다. 예를 들어 자바로 만든다고 해볼까요. 여러분은 언제 가비지 콜렉터가 움직일지 알 수 없고, 어느 순간에 CPU를 점유하여 백그라운드에서 돌지도 모르지만 유저는 정작 그런 것에 관심도 없을 뿐더러 뭐가 있는지도 잘 모를 겁니다. 그래서 애플은 LLVM의 가비지 콜렉션을 과감히 저만치 밀쳐버리고 꽤 괜찮은 애플만의 메모리 관리 로직을 새로이 집어넣었습니다.
잠깐만 다시 주제에서 벗어나 할 이야기가 있네요, 왜 메모리 관리가 중요할까요? 음, C/C++은 메모리 관리 작업을 모두 프로그래머에게 일임하였지만 결과적으로 많은 프로그램들이 스스로 메모리를 해제해야 할 순간에 하지 않는 바람에 메모리 누수(memory leak) 현상을 일으키게 되었습니다. 개인적으로도 엉망으로 짜인 프로그램의 메모리 누수 때문에 랩탑이 점점 느려지다가 아예 뻗어버리는 경우를 겪었습니다. 다른 분들도 충분히 이런 경험, 한 번쯤은 있을 거라 믿습니다. 랩탑이 자꾸 느려져서 매주 1번 혹은 그 이상으로 재부팅을 하고 계시다면 현재 여러분 랩탑에 설치된 일부 프로그램들이 메모리 누수를 유발하는 버그를 갖고 있어서 그렇다고 생각하셔도 됩니다.
메모리 관리가 어려운 이유는 단순히 프로그램 입장에서 변수들의 스코프(scope : 한 변수가 접근가능한 프로그램 상의 구역)만을 고려하여 메모리를 해제해서는 안 되기 때문입니다. 스코프는 함수나 루프문내에서 정의된 데이터의 메모리 공간을 해제함으로써 메모리 누수로 부터 프로그램을 보호하는데 쓰일 수도 있습니다. 하지만 많은 함수들이 리턴 값을 가지고 있고, 이는 해당 함수가 종료되어도 함수를 호출 했던 콜러(caller : 현재 실행 함수를 호출한 함수)측에서 사용할 수 있도록 메모리가 유지되어야 합니다. 그럼 언제쯤 해제해야 할까요? 만약 한 변수에 대해서 어떠한 함수도 분명한 소유권을 갖고 있지 않고 아무도 해제하지 않는다면? 이런 현상이 누적된다면 메모리 사용량은 무한대로 치닫고 말겁니다.
자, 그럼 애플은 과연 이렇게 중요한 메모리 관리를 위해 무엇을 했을지 짚어보죠. 우선 일부 구간에서만 사용되는 변수들은 그 구간 밖으로 프로그램의 흐름이 넘어가면 해제되어야 함 정도는 알고 있습니다. 명백하고 확실한 부분이죠, 하지만 만일 기본적으로 그 변수의 메모리 공간이 외부 함수와 공유되고 있고 영원히 할당된다고 칩시다. 여기에 변화를 주기 위해 iOS에서는 weak와 unowned라는 키워드를 사용하여 그 변수가 함수내에서 더 이상 쓰이지 않게 될 때 쥐도 새도 모르게 해제되게 할 수 있습니다. 이 키워드를 적절히 쓴다면 유저들이 자꾸 앱이 다운되거나 느려진다고 불평하는 일도 없겠죠.
이 weak 변수들은 델리게이트를 사용시 메모리 누수없이 깔끔하게 처리되게끔 해주는 일등 공신입니다. 그러니 여러분은 델리게이트를 공부해야 하며, 동시에 iOS의 메모리 관리 시스템과 weak 키워드도 공부하셔야 합니다.
좀 복잡하죠? 하지만 감내하셔야 합니다.
옵셔널 변수(Optional variable)
애플은 iOS 런타임이 갖는 또다른 오묘한 것을 Swift까지 끌고 왔습니다. 바로 옵셔널 변수라는 친구입니다.
일반적인 옵셔널 변수는 단순히 앱 내에서 할당한 메모리 공간의 포인터 역할을 합니다. 예를 들어 여러분이 "있을 수도 있고 없을 수도 있는" 어떤 이미지나 데이터 정보를 불러오려 한다고 칩시다. 불러오는데 성공한다면 그 변수는 해당 이미지나 데이터를 정상적으로 가리키는 포인터가 됩니다. 실패한다면 의미가 정의되지 않은 특수한 것을 가리키는데 iOS에서는 nil이라 불리는 것을 가리키게 됩니다.
nil은 참 설명하기가 힘듭니다. 넓디 넓은 우주 공간에 공허한 빈틈이라고 해야 할까요? 설명을 보다 쉽게 하기 위해 여러분이 무언가를 세는데 쓰일 카운터 변수를 갖고 있다고 가정해보죠. 그리고 여러분은 이 카운터 값이 최종적으로 0에 도달한다고 예상하고 있습니다. nil 값인 카운터 변수는 이야기가 좀 다릅니다. 말하자면 이 카운터는 아무런 의미가 없고 실제로 값을 표현하는 것도 불가능하며 이 값에 접근을 해도 그것은 유효하지 않은 값입니다.
관계형 데이터베이스(Relational Database)도 이와 유사한 NULL이라는 값을 가지고 있습니다. 단순히 0을 의미하는 것이 아니라 "이 데이터는 아무런 의미도 갖고 있지 않습니다"를 의미합니다. 생각보다 아주 강력한 컨셉이지만 간혹 몇몇 프로그래머들을 실수로 이끌기도 합니다. 이를테면 여러분이 Oracle이나 MySQL 같은 관계형 데이터베이스를 사용할 때 어떤 값이 NULL인지 체크하지 않고 코드를 짜게 되면 에러 범벅인 결과를 얻게 될 겁니다.
현대 언어들의 토대가 되고 핵심 인프라스트럭쳐로 사용되어 온 우리의 C언어의 경우를 보아도 바로 이 NULL 값으로 인해 여러 문제점을 겪어 왔습니다. NULL 값이 담긴 포인터 변수로 데이터에 접근하려다 에러를 내는 경우가 여전히 빈번하고, 많은 수의 프로그래머들은 항상 포인터를 사용하기 전에 이 값이 NULL인지 체크하는 습관이 몸에 배어있습니다.
iOS에서는 어떨까요? 이를 위해 애플은 옵셔널 변수라는 것을 새로이 고안해냈습니다. 이 변수는 nil 값을 갖거나 혹은 유효한 어떤 값을 가질 수 있습니다(nil과 NULL에 대한 고상한 토론은 잠시 접어두도록 하죠). 제가 아는 프로그래밍 언어 중에서 "옵셔널" 변수라는 개념을, 그것도 nil 값을 가질 수 있는 형태를 굳이 따로 설계 해 놓은 언어가 이것 말고 딱 한개 더 있습니다. 여러분도 거의 모르실 법한 OCaml라는 언어입니다.
옵셔널 변수가 아주 이상한 개념이라고 매도하려는 것은 절대 아닙니다. 만일 여러분이 작성한 프로그램 내에서 인터넷을 통해 파일이나 리소스를 받으려 하는 상황을 봅시다. 하지만 간혹 인터넷 연결이 제대로 안되었다거나 하는 이유로 실패할 수도 있겠죠, 그럼 nil 값을 얻게 됩니다. 그러니 옵셔널 변수는 항상 이와같이 "나는 nil 값을 가질 수 있다"라는 경고문을 붙이고 다니는 셈입니다. 프로그래머가 인지하기 좀 더 쉽겠죠. Swift에서는 "?" 문자를 변수 뒤에 붙여서 지금 다루려는 변수의 값이 옵셔널임을 명시적으로 선언 할 수 있습니다. 그리고 "!" 를 사용하면 "걱정마! 내가 이 옵셔널 변수를 검사해 봤는데 제대로 값이 들어있더군"이라고 말하는 것과 같은 기능을 하게 해줍니다.
저는 사실 이 옵셔널 변수가 그렇게 대단한 것인지 잘 모르겠습니다. 아래 예제를 한 번 보면 제 말을 이해하실 수 있을 겁니다.
if let theImage = image{
{
/* process the image */
}
else
{
println("No image available")
}
여기서 if문은 image 값이 nil일 때 뿐만이 아니라 0일 때도 false가 되어 else 이하의 구문을 실행하게 됩니다. 숫자 0은 if문에 의해 false로도 해석되고, 불리안(Boolean) 변수의 false 값으로도 해석됩니다. 따라서 이 if문은 숫자 0과 불리안 false 값의 차이를 모르며 어떤 데이터를 가리키는 레퍼런스가 nil인 경우와의 차이도 모르고 있습니다.
이 점이 바로 옵셔널 변수의 이론적인 약점을 여실히 보여주고 있습니다(여러분은 nil과 숫자로서의 0의 값을 어떻게 구분짓고 있나요?)
실질적으로 nil이니 0이니 하는 것들은 의미를 상실하고 맙니다. 우리가 코딩을 할 는 이미 무엇이 카운터 변수이고 무엇이 웹 등 외부 소스로 부터 전달된 데이터를 담는 변수인지 알고 있습니다. 또한 카운터 변수값을 체크할 때도 이 값이 0이 되면 어떻게 써먹어야겠다는 것도 모두 머리속에 있지요. 반대로 어떠한 일련의 네트워크 통신 구문으로 받아온 데이터 값이 nil이었다면 무언가 통신이 제대로 안 되었음을 알 수 있습니다. 그러니 프로그램 입장에선 if문에 의해 카운터 변수 값이 0인 것과 다른 어떤 것이 nil 값이거나 별 문제가 없습니다. 둘 중 어떤 경우지? 하고 혼란스러워할 이유도 없고 그럴 필요도 없습니다.
여러분들은 이 섹션에서 최소한 Swift에서 옵셔널 변수가 무엇인지 이해하고, 어떻게 잘 다뤄야하는지 이해하시면 됩니다. 어찌되었든 nil, NULL 등은 다양한 프로그래밍 언어에서 사용하고 있는 개념이지만 옵셔널 변수를 채용한 언어가 극소수인 것은 사실입니다.
관습적인 언어 특성들(Conventional language features)
저는 이 글을 통해서 iOS의 특이성으로 인해 프로그래머들이 플랫폼 특화 언어를 공부하는데 걸림돌이 될 만한 몇몇 요소를 재조명해보았습니다. Swift가 갖는 대부분의 특성들은 최신 언어를 습득한 프로그래머들에게는 충분히 친숙한 것들입니다. 그러한 특성들의 일부를 들춰보자면
- 블락(Blocks) : Swift에서는 클로져와 콜백 함수를 위해 사용됩니다.
- 매개변수화 인자(Parameterized arguments) : C언어에서도 스트럭쳐를 하나의 인자로 받을 수 있는 동일한 패러다임을 제공합니다. 하지만 대 부분의 라이브러리에서는 이들을 풀어서 하나씩 인자로 취급하는 것을 더 선호합니다.
- 불변(Immutable) 변수 : 함수형 언어에서는 대단히 중요하며, 파이썬 등 다른 언어에서도 폭 넓게 사용되고 있습니다.
- 프로토콜(Protocols) : 자바에서 쓰이는 인터페이스와 동일한 개념이며 애플이 새로이 명명했습니다.
여러분들이 이 글을 처음부터 읽어오면서 애플 특유의 긴 함수명과 인자, 데이터 상수들과 어우러진 오묘함이 친근하게 느껴지셨다면 Swift는 그렇게 높은 벽으로 느껴지지 않을 겁니다.