^(코딩캣)^ = @"코딩"하는 고양이;

[번역글] View Controller 사이에 데이터를 교환하는 방법(5) - closure

Language/Objective-C & Swift
2021. 9. 15. 19:44

본 게시글은 How To: Pass Data Between View Controllers in Swift를 바탕으로 작성하였습니다.

 

여러분의 앱이 여러 개의 사용자 인터페이스(UI)를 가지고 있다면 여러분은 하나의 UI에서 다른 UI로 데이터를 전달해야 하는 경우도 생길 것입니다. Swift에서는 View Controller 사이에 어떤 방법으로 데이터를 전달할 수 있을까요?

뷰 컨트롤러(View Controller) 사이에 데이터를 주고 받는 것은 iOS 개발의 중요한 일부입니다. 여러분은 몇 가지 방법으로 이를 해낼 수 있고 각기 다른 이점과 약점을 가지고 있습니다.

뷰 컨트롤러 사이에 쉽게 데이터를 교환하는 방법을 선택하는 것은 여러분이 앱 구조를 어떻게 할 것인지에 달려 있습니다. 앱의 구조(App architecture)는 여러분이 뷰 컨트롤러 사이에 어떻게 작동이 이루어 질 것인지에 영향을 주고, 반대로 여러분이 뷰 컨트롤러 사이에 데이터 교환을 어떻게 할 것인지에 따라 앱의 구조가 달라집니다.

 

Swift에서 여러분은 뷰 컨트롤러 사이에 다음의 6가지 방법으로써 데이터를 주고 받을 수 있습니다.

  1. 인스턴스 프로퍼티(property)를 사용하는 방법(A → B 방향)
  2. 스토리보드(Storyboard)와 세그웨(segue)를 사용하는 방법
  3. 인스턴스 프로퍼티와 함수를 사용하는 방법(A ← B 방향)
  4. 델리게이션(delegation) 패턴을 사용하는 방법
  5. 클로저(closure) 또는 핸들러(completion handler)를 사용하는 방법
  6. NotificationCenter 또는 Observer 패턴을 사용하는 방법

 

이 게시글에서 여러분은 뷰 컨트롤러 사이에 데이터를 주고 받는 6가지의 각기 다른 방법에 대해 익히게 될 것입니다. 이 방법에는 프로퍼티(property)를 사용하는 방법, 세그웨(segue)를 사용하는 방법 및 NSNotificationCenter를 사용하는 방법도 들어 있습니다. 비교적 간단한 이들 방법을 통해 좀 더 복잡한 응용까지 나아갈 수 있습니다. 준비가 되었다면 이제 시작하겠습니다.

 

클로저(closure) 또는 핸들러(completion handler)를 사용하는 방법

클로저(closure)를 사용하여 뷰 컨트롤러 사이에 데이터를 전달하는 것은 프로퍼티나 델리게이션을 사용하는 것과 크게 다르지 않습니다. 클로저를 사용하면 상대적으로 사용하기가 쉽고 또한 별도의 메소드(함수)나 프로토콜을 선언하지 않아도 지역적으로 데이터 교환 구문을 정의할 수 있다는 이점이 있습니다.

클로저를 사용하여 뷰 컨트롤러 사이에 데이터를 주고받을 수 있다.

나중에 등장하게 될 뷰 컨트롤러에 다음과 같이 프로퍼티를 하나 만드는 것으로 소스 코드를 시작합니다.

// Swift
var completionHandler: ((String) -> Int)?
// Objective-C
@interface ...
@property (atomic, weak) NSInteger (^completionHandler)(NSString *);
@end
@implementation ...
@synthesize completionHandler;
@end

 

여기서 completionHandler는 클로저 타입을 가지고 있습니다. 이 클로저는 옵셔널(optional)이기 때문에 끝에 ?가 붙습니다. 또한 클로저의 시그니처(signature)는 (String) -> Int입니다. 이것은 이 클로저가 String형 매개변수를 하나 받고 Int형 값을 반환하는 메소드(함수 또는 람다식)임을 뜻합니다.

나중에 등장할 뷰 컨트롤러에서 작성할 또 하나의 사항은 버튼이 탭(tap)되었을 때 그 클로저를 호출하는 것입니다.

// Swift
@IBAction func onButtonTap(sender: Any) {
    let result = completionHandler?("FooBar")
    print("completionHandler returns ... \(result)")
}
// Objective-C
-(IBAction) onButtonTapWithSender:(id)sender {
    NSInteger result;
    
    if ([self completionHandler] != nil) {
        result = [self completionHandler](@"FooBar");
        NSLog("completionHandler returns ... %d", result);
    }
}

 

제시된 예제는 다음과 같이 작동합니다.

한 개의 매개변수가 지정되면서 completionHandler 클로저가 호출되면, 그 클로저의 호출 결과가 result 변수에 보관됩니다.

결과는 print()의 호출에 의해 출력됩니다.

그 다음 MainViewController에서 여러분은 다음과 같이 클로저를 정의합니다.

// Swift
secondaryViewController.completionHandler = { text in
    print("text = \"\(text)\"")
    return text.characters.count
}
// Objective-C
[[self secondaryViewController] setCompletionHandler: ^(NSString * text) {
    NSLog("text = \"%@\", text);
    return (NSInteger)[text length];
}

 

이것이 클로저 그 자체입니다. 이것은 지역 범위에서 선언되었기 때문에 클로저가 선언된 스코프 내의 로컬 변수, 프로퍼티 및 메소드(함수)들을 모두 사용할 수 있습니다.

text 매개변수가 출력되어 나오는 클로저에서는 실행의 결과로서 문자열의 길이가 반환됩니다. 여기서 흥미로운 사실이 발견됩니다. 이런 식으로 작성되는 클로저는 뷰 컨트롤러 사이에서 데이터를 양방향으로 전달해준다는 것입니다. 여러분이 클로저를 정의하고 유입될 데이터에 대하여 작업한 다음 이 클로저를 호출한 코드에게 결과 데이터를 반환할 수 있습니다.

물론 여러분은 델리게이션을 사용하거나 프로퍼티 자체를 사용한 메소드(함수) 호출에서도 그 코드 블록을 호출해 준 코드에게 소정의 값을 반환할 수 있다는 것을 알고 있을 지 모릅니다. 전적으로 맞습니다. 다음과 같은 상황에서 클로저는 여러분의 손에 잡히게 될 것입니다.

(1) 여러분은 단지 짧고 간략한 내용의 함수를 작성하고자 굳이 프로토콜을 만들어가며 완전한 델리게이션 방식으로 접근하기를 원하지 않을 수 있습니다.

(2) 여러분은 여러 클래스에 걸쳐 하나의 클로저를 전달하고 싶어질 수도 있습니다. 클로저가 없었다면 여러분은 쏟아지는 함수 호출들을 만들었어야 하겠으나, 클로저와 함께 여러분은 한 블록의 코드만 전달하면 됩니다.

(3) 어떤 데이터가 지역적으로만 존재할 때 여러분은 클로저 안에서 그 데이터에 접근할 수 있는 수준에서 코드 한 블록을 지역적으로(locally) 정의할 필요가 있을 것입니다.

클로저를 사용하여 뷰 컨트롤러 사이에 데이터 전달이 이뤄지게 할 때의 손해는, 여러분의 코드가 다소 복잡해 보일 수 있다는 것입니다. 다른 방법을 사용할 때보다 클로저를 쓰는 것이 더 이치에 맞다고 여겨질 때, 뷰 컨트롤러 사이에 데이터를 전달할 때에 한하여 클로저를 사용하는 것이 현명한 선택입니다.

그렇다면 두 개의 뷰 컨트롤러 사이에 연결고리가 형성되지 않았거나 형성될 수 없음에도 데이터를 주고 받기 위해서는 어떻게 해야 할까요? 다음 장에서 살펴보겠습니다.

카테고리 “Language/Objective-C & Swift”
more...