클로져 사용방법(Swift)

클로저 사용 시 인자 전달방법

함수의 인자로 클로저를 전달하는 경우를 생각해보자. 배열의 정렬된 사본을 만드는 sorted(_:,_:)의 경우 대략 다음과 같은 시그니처를 가지고 있다.

func sorted<C: SequenceType>(source: C, isOrderedBefore: (C.Geneartor.Element, C.Generator.Element) -> Bool) -> [C.Generator.Element]

만약 정수 배열을 정렬한다고하면 다음과 같이 사용한다.

let arr = [5, 8, 2, 4, 2, 1, 7]
let sortedArr = sorted(arr, { (x:Int, y:Int) -> Bool  in
    return x < y
})

이 때 함수의 마지막 인자는 trailing closure라고 해서 괄호 밖으로 빼낼 수 있다.

let sortedArr = sorted(arr){ (x:Int, y:Int) -> Bool in return x < y }

이때 클로저가 받는 각 인자는 순서대로 $0, $1, … 로 명명하는 것이 가능하다. 이 표현방식도 많이 알려져있는 형태이다.

let sortedArr = sorted(arr){ return $0 < $1 }

물론 단순히 표현식의 결과를 리턴하는 것이라면, 클로져 내부는 단순한 표현식이어도 된다.

let sortedArr = sorted(arr){ $0 < $1 }

비교연산자 <도 역시 함수로 func <<T:Comparable>(lhs:T, rhs:T) -> Boolsorted함수가 요구하는 클로저의 시그니쳐와 동일한 시그니처를 가지고 있어서

let sortedArr = sorted(arr, <)

이렇게만 쓸 수도 있다.

다시 위의 예제 중에 sorted(arr){ $0 < $1 }을 보자. 클로져로 전달되는 각각의 인자는 달러사인을 붙인 번호로 표시가 가능하다고 했는데, 경우에 따라서는 튜플로 전달된다고 볼 수도 있다. 그래서,

let sortedArr = sorted(arr){
    let (x, y) = $0
    return x < y
}

이렇게도 쓸 수 있다. 클로져내부에서 달러사인이 붙은 숫자를 하나만 쓴 경우, 복수의 인자는 튜플로 인식된다. 왜 이런 표현을 쓰냐면… 정규식을 사용하여 매치에 대해 각각 블럭호출을 하는 emuerateMatchesInsString(_:options:range:usingBlock) 함수의 경우에 클로져의 타입 시그니쳐가 (NSTextCheckingResult!, NSMatchingFlags, UnsafePointer<ObjCBool>) -> Void인데, 이를 포멀한 형식으로 쓰려면 타이핑해야하는 양이 매우 많아진다. 그런데 특이하게 $0, $1, $2로 받아오는 동작이 되질 않는다. 버그인지 모르겠는데, 해당 블럭에 튜플로 파라미터들을 묶어서 던지는 것 같다. 따라서

var count = 0
regex.enumerateMatchesInString(str, 
    options:NSMatchingOptions(0), 
    range:NSMakeRange(0, (str as NSString).length)){
        let (match, _, stop) = $0
        println(match.range)
        count++
        if count >= 100 {
            stop = true
        }
    }

추가: 다시 해보니 지금은 또 된다. 인자의 마지막 인덱스의 변수가 언급되면 언팩한 인자로 받는 듯 하다.

var error:NSError? = nil
var count = 0
let reg = NSRegularExpression(pattern:"d", options:.CaseInsensitive, error:&error)
let str = NSString(string:"gq2hga321gasdg4353y")
reg!.enumerateMatchesInString(str,
    options:NSMatchingOptions(0),
    range:NSMakeRange(0, str.length)){

        println($0.range.location)
        count++
        if count > 3 {
            $2.memory = false
        }
    }

참고로 여기서 stopUnsafeMutablePointer<ObjCBool> 타입이다. 만약 Swift에서 C 포인터를 변경하고자 할 때는 직접 대입은 불가능하고 .memory 프로퍼티를 사용해야 한다.