Swift 공부하면서 새롭게 알게된 내용 정리

sorted VS. sort

“자바스크립트 같은 스크립트 언어를 기존에 써왔던 사람이라면 Swift라는 언어는 비교적 쉽게 배울수있는 것 같다.” 라고 생각했다가 뒤통수 맞는 것들이 몇가지 있는데 그중에 하나가 바로 sorted 함수와 sort 함수다. 둘의 차이는 정렬을 해서 반환값이 있냐? 없냐의 차이!

mutating func sort(isOrderedBefore: (T, T) -> Bool)
func sorted(isOrderedBefore: (T, T) -> Bool) -> [T]

함수 시그니처만 봐도 sort는 반환값이 없다. -_-;;

참고, Javascript 에서 sorted 함수는 당연히 없고, sort 함수는 본래 배열을 정렬시키고 정렬된 값을 slice해서 새로운 값으로 반환한다. 즉, Swift의 sort 와 sorted 두가지 역할을 모두 수행한다.

극강의 타입 추론 능력!

Swift 의 또 다른 매력이라고 하면 타입 추론을 빼놓을수없다. 얼마나 똑똑한 언어인지 개발자의 귀차니즘을 많이 줄여준다(?) 하지만 단점은 이게 뭔소린가하는 느낌을 줄정도다. 하지만 타입 추론 방식을 좀 이해하고 나니 지금은 꾀나 익숙해졌다.

var arr = [6,2,1,2,3,3]
arr.sort(<)   <--- 이게 뭐얌?             --- 1번
println(arr)   // "[1, 2, 2, 3, 3, 6]"

arr.sort(<) 이게 도대체 뭔소리야 하는 사람도 있겠지만 결과는 오름차순 정렬이다. sort 함수는 인자로 오퍼레이터를 받는건가? 라고 생각할수도 있겠지만 사실 위 코드는 아래와 같이 풀어 쓸수도 있다.

var arr = [6,2,1,2,3,3]
arr.sort{$0 < $1} <--- 이건 또 뭐얌?      --- 2번
println(arr)  

당연히 2번은 1번과 같은 코드다. 2번에서 $0과 $1이라는 변수는 Swift에서 인자의 타입이 무엇이 정확하게 추론이 가능할때 타입 이름을 특별히 지정하지 않아도 쓸수있는 키워드 정도로 이해하면 된다. 물론 인자가 3개라면 $2도 쓸수있다. 그렇다면 이 함수의 원형은 어떻길래 추론이 가능한걸까? 추론 되기전 상태로 좀 더 거슬러 올라가보자.

var arr = [6,2,1,2,3,3]
arr.sort{                              --- 3번
   (t1, t2) -> Bool in
   t1 < t2  
}
println(arr)  

3번에서 sort 다음에 {} 중괄호는 일단 넘어가자. 괄호안에 in 이라는 키워드는 이 함수가 클로저 함수임을 나타낸다. 즉 in 앞에 내용이 이 함수의 시그니처다. 그러니까 인자 2개를 받아서 Bool형으로 반환한다는 이야기!! 뚜둥~!! 그런데 또 여기에 return 문이 없다. Swift 에서 함수의 마지막 문장은 return 문이 없어도 그 문장의 결과값이 return 문으로 반환된다. 그러니까 4번 처럼 된다는 얘기다.

var arr = [6,2,1,2,3,3]
arr.sort{                              --- 4번
   (t1, t2) -> Bool in

   return t1 < t2  
}
println(arr)  

그런데 여기서 또 의문이 생겼다. 도대체 t1과 t2는 어떤 타입이길래 서로 비교가 가능한걸까? 이걸 추론해 내는 것이 Swift 컴파일러의 능력인것이다!! arr 변수는 숫자형 배열이기때문에 바로 위에 문장을 기반으로 sort 내부에서 수행되는 t1과 t2가 Int형임을 알수있는 것이다. 그러니까 5번처럼 인식한다는 얘기!

var arr = [6,2,1,2,3,3]
arr.sort{                              --- 5번
   (t1:Int, t2:Int) -> Bool in

   return t1 < t2  
}
println(arr)  

맨위에 sort 함수 시그니처를 보면 sort 함수는 같은 제네릭타입 T를 인자로 받는다. 따라서 t1과 t2는 같은 타입이 된다. 만약 t2를 Int가 아닌 다른 타입으로 캐스팅 한다면 어떻게 될까?

arr.sort{                              --- 5-1 번 
   (t1:Int, t2:Float) -> Bool in
   return t1 < t2  
}

당연한 얘기지만 sort 함수의 인자 타입이 달라서 실행할수없다고 에러를 낸다. 역시 똑똑한 녀석!!

“Cannot invoke ‘sort’ with an argument list of type ‘((Int, Float) -> Bool)’

마지막으로 저 뜬금없는 sort 다음에 {} 중괄호!! 너는 도대체 뭐냐? 아래 6번 코드를 보자!

var arr = [6,2,1,2,3,3]
arr.sort() {                              --- 6번
   (t1:Int, t2:Int) -> Bool in
   return t1 < t2  
}
println(arr)  

잉? 이건 또 모야? 앞에 () 괄호를 써도 되는 거네? 맞다! {} 중괄호 앞에 괄호가 와도 된다. 6번에 괄호가 생략된 이유는 괄호 안에 들어가는 마지막 인자가 함수일 경우 Swift는 마지막 함수를 괄호 밖으로 끄집어 내서 쓸수있도록 허용한다. 정리하면 괄호안에 들어가는 인자가 여러개 일때, 중간에 함수가 들어가는 경우는 뺄수없다.

// p는 변수, f는 함수라고 가정한다. 
// 이런 함수는 f1을 끄지어 내지 못한다. 
x.method(p1, f1, p2) ---> X

//  이렇게 맨 마지막 인자가 함수면 끄집어 낼수있다. 
x.method(p1, p2, f1) ---> x.method(p1, p2) {} 

제네릭 타입의 형변환

클로저 함수를 다루다 보면 타입 추론에 의해서 생략되는 녀석들이 많아 가끔씩 헷갈릴때가 있는데 이럴때는 꼭 해당 함수에 마우스를 올리고 커맨드 키와 함께 문서를 열어보는 것이 좋다. 여튼 Swift에서 제공하는 많은 내장 함수들이 Typealias나 제네릭 같은 타입을 많이 쓰고 있어서 클로저 함수에 인자를 받아서 쓰다보면 다음과 같은 에러가 종종 발생한다.

(!) Cannot invoke ‘predicate’ with an argument list of type ‘(T)’

extension Array {
  func myFilter<T>(predicate:(T) -> Bool) -> [T] {
    var result = [T]()

    for i in self {
      if predicate(i) {      // <----- 여기가 문제!!
        result.append(i)
      }
    }
    return result
  }  
}

위에서 myFilter 함수는 predicate 라는 클로저 함수를 인자로 받아서 제네릭 타입인 T를 요소로 갖는 배열을 반환하는 함수다. 내부에서 쓰이는 self는 Array 클래스를 확장했으므로 당연히 내부에서 관리되는 배열일 것이라는 추측을 할수있다. 실제로 출력을 해봐도 배열값은 맞다. 하지만 컴파일러는 self 배열에서 꺼낸 i 타입이 사실 뭔지 모르고, predicate가 T 타입을 받아야한다는 사실만 알고 있기 때문에 “i가 뭔지 모르겠는데 T타입은 아닌거 같아. 제대로 넣어줘~!” 라는 메세지를 준다.

extension Array {
  func myFilter<T>(predicate:(T) -> Bool) -> [T] {
    var result = [T]()

    for i in self {
      if predicate(i as! T) {      // <----- 해결!!
        result.append(i as! T)     // <----- 해결!!
      }
    }
    return result
  }  
}

그래서 이럴때 as! 를 이용해 타입을 확실히 명시해주면 해결된다!

React 사용 1일차 회고

첫만남

F8 컨퍼런스 이후 React Nativce 이야기가 한창 들리더니 이제 다시 잠잠해지는 느낌이다. React를 처음 접한건 지금으로부터 대략 1년전 바르셀로나에서 열렸던 FutureJS에서 였다. 당시 왜들 그렇게 React React 하는지 이해가 안갔다. 그냥 Facebook에서 만들어서 그런건가 싶었다. 그래서 한국에 돌아오고나서 React를 살펴봤다. 역시나 그냥 Facebook이 만들어서 그런건가 싶었다.

첫느낌

Angular에 비해서 개발속도가 엄청 빨라 보이지도 않았을 뿐더라 그때는 한창 Two way binding에 열광하고 있던터라 React의 장점이 크게 다가오지 않았다. 고백하건데 당시 React를 공부해볼까했는데 사실은 연애를 시작하는 바람에 제대로 보지 못했다. ㅎㅎㅎ (핑계는…ㅋㅋㅋ)

첫회고

여하튼 그래서 다시 보기로 맘 먹고 어제 튜토리얼을 한번 쭉 훝어보고 기획해놓은 아이디어에 적용해보고 있다. 써본지 1일차의 느낌은 예전에 네이버에서 근무할때 Jindo Component를 만드는 느낌이다. 돌이켜 생각해보면 Jindo Component에 고민을 좀더 더 했더라면 React 같은 녀석을 훨씬더 일찍 만들어 쓰고 있지 않았을까 싶다. 그나저나 JSX 문법이 처음엔 이상했는데 지금보니 이게 정말 물건이지 싶다. 🙂 역시 사람이나 기술이나 겪어봐야 아는갑다.

오늘은 React에 이어 Flux도 같이 찬찬히 볼 예정이다. 또 적용해보고 생각나는대로 글을 남겨봐야겠다.

내가 몰랐던 재밌는 자바스크립트 이야기

몇달전 자바스크립트 가든이라는 문서의 한글 번역본을 읽다가 포기하고 다시 원문을 보면서 재번역을 했다. 처음엔 그냥 오역이 몇가지 보여서 오역만 바로 잡아볼까하고 원번역자에게 메일을 보냈는데.. 결국엔 전체를 모두 다시 번역했다. 현재는 원저자에게 Pull Request를 보낸상태고 머지는 언제 될찌 모르겠다. 현재 상황에서 기억 남는 몇가지를 남겨보면,

1. 호이스팅(Hoisting)

이게 말이지 참 재밌다.  JSLint에서 이렇게 해라~ 라고 추천했던 가이드들이 몇가지 있었는데.. 예를 들면,

“var문은 맨위로 올려라” 하는 가이드가 있다. 왜 위로 올리라는 거지? 성능 문젠가? 스코핑 때문에 그런가? 그래.. 올리라면 올리지모.. 라고 하면서 var로 선언한 코드를 전부 위로 올렸던 기억이 있는데.. 여기엔 이유가 있었다. 바로 호이스팅!!! 호이스팅은 자바스크립트가 실행되기 전에 선언문을 스코프 위로 올리는 걸 얘기한다. 따라서 다음과 같이 for 문안에 선언한 i 변수는

for(var i=0; i<1000; ++i){
    // 어쩌구 저쩌구
}

  다음과 같은 스코프 상단으로 옮겨진다는 사실!!

var i=0;
for(i=0; i<1000; ++i){
    // 어쩌구 저쩌구
}

이렇게 자동으로 옮겨지는 것을 호이스팅이라고 한다. 이런 특징을 모르면 전역변수를 의도하고 쓰더라도 실제 지역변수에 있는 값을  쓰게 될지도 모른다!! 자세한 내용은 자바스크립트 가든의

함수 스코프를 참고하자!

2. 왜 new Array()는 안되는가?

이것도 역시 JSLint에서 가이드하는 내용중에 하나다. new Array()로 쓰지말고 [] 각괄호를 이용해 배열을 선언하라고 하는데, 이유는 new Array(3) 으로 넣을때 우리가 생각했던대로 동작하지 않기 때문이다. 보통은 배열에 3이라는 값을 하나 추가한 결과를 반환해줄꺼라 기대하지만, 요건 배열의 크기가 3인 빈배열을 반환한다. 심지어 인덱스는 초기화도 안한다는 사실!! 역시 자세한 내용은 자바스크립트 가든의

배열 생성자를 참고하자.

3. 계륵 같은 new 키워드의 비밀

함수를 만들때 new 키워드를 붙여서 생성하지만, new 키워드가 없어도 생성할수있다.

function Foo(){
    this.value = 1;
}
new Foo();
Foo();

위와 같이 호출하면 new 키워드의 유무에 따라 반환 값이 달라진다. new 키워드를 붙이면 Foo 함수가 생성자로써 동작하게 되면서 특별한 반환값이 없으면 this가 가리키는 값을 반환해준다. 즉, Foo 객체의 인스턴스를 반환해주는데,. Foo()는 그냥 함수를 실행한다. 그래서 this 객체는 Foo를 가리키는게 아니라 전역을 가리키게 되고, 반환하는 값이 없으므로 결과값은

undefined 가 된다. 뭐 여기까지는 누구나 아는 사실인데.. Foo 객체가 생성자로 동작할때 즉, new 키워드가 붙을때 , 이 생성자에 명시적인 return 값이 있다면 또 얘기가 달라진다. 명시적인 객체를 리턴하면 그값이 결과값으로 반환되지만, return 값이 없거나 참조타입이 아닌 프리미티브 타입을 반환하면 this가 가리키는 값을 반환한다. 반면에 new 키워드가 없는 경우, Foo 객체는 생성자가 아닌 일반 함수로 동작하면서 return 문의 값을 반환한다. 그래서 결론은 명시적인 반환값을 객체로 주면, new 키워드가 있든 말든 동일하게 동작한다는 사실!! 그래서 팩토리 패턴으로 만들 경우 자연히 new 키워드가 필요없게 된다! 자세한 내용은 역시 자바스크립트 가든의 함수 생성자를 참고하자!

4. 타이머는 스택킹 된다!

자바스크립트에서 타이머는 setTimeout과 setInterval 두가지가 있다. 어찌 생각해보면 setIntveral을 한번만 실행하고 없애면 setTimeout 은 필요없는거 아닌가? 하는 생각을 종종했는데.. 역시나 setTimeout은 ECMA 표준 스펙이 아니었다! 헉! 그랬꾸나.. 그래서 setTimeout의 첫번째 인자에 대한 해석도 엔진마다 다르다는 사실!… 여튼 이건 뭐 그렇게 중요한 얘기는 아니고.. 진짜 재밌는 얘기는 바로 setInterval 함수가 스택킹된다는 사실이다! setInterval 함수는 일정 주기마다 함수가 호출되는데.. 엄밀히 말하면 일정주기마다 함수를 실행하는 것은 아니고 일정 주기마다 실행할 함수를 쌓는다. 그래서 자바스크립트 쓰레드가 그 함수를 실행할때가 되면 실행하게 될테고, 뒤이어 실행되어질 함수는 앞에 함수가 다 끝날때까지 기다리게 된다. 이거다 싱글 쓰레드라서 발생하는 문젠데.. 그럼 한번 생각해보자!. 주기가 엄청 짧은데.. 실행되는 함수는 그 주기보다 길경우 어떻게 될까? 그렇다! 무조건 쌓이게 된다!! 이렇게 쌓인 함수는 시간이란 변수가 더이상 필요없게 된다.  즉, 내가 원하는 주기가 깨질수도 있다는 사실!! 자세한 내용은

타이머를 참고하자!

 5. 삼중 등호의 비밀

이중 등호(==)와 삼중 등호(===) 지금까지는 삼중 등호는 인스턴스를 비교해서 보다 정확하고, 이중 등호는 대충 (?) 비교해서 부정확하다라고 생각하고 있었다.. 하지만 이 둘의 차이는 그런게 아니었다.!!  바로 자동 타입 변환을 하느냐 마느냐의 차이!! 이중 등호는 자동 타입 변환을 해서 끝까지 비교하지만, 삼중 등호는 타입 변환 없이 바로 비교한다. 그래서 훨씬더 빠르다! 자세한 내용은

타입 비교를 봐라! 이 외에도 재밌는 내용이 참 많은데.. 여튼 난 위 5가지가 가장 새로웠다!