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

[번역글] View Controller 사이에 데이터를 교환하는 방법(3) - property와 method

Language/Objective-C & Swift
2021. 9. 14. 21:12

본 게시글은 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를 사용하는 방법도 들어 있습니다. 비교적 간단한 이들 방법을 통해 좀 더 복잡한 응용까지 나아갈 수 있습니다. 준비가 되었다면 이제 시작하겠습니다.

 

프로퍼티(property)와 메소드(method, function)를 사용하는 방법(A ← B 방향)

나중에 등장한 뷰 컨트롤러가 자신을 호출해 준 뷰 컨트롤러에게 어떤 값을 돌려 보내려면 어떻게 해야 할까요?

프로퍼티를 사용하여 나중에 등장할 뷰 컨트롤러로 데이터를 전달하는 방법은 이미 살펴보았지만 확실히 순방향(straightforward)으로의 전달입니다. 그렇다면 거꾸로 자신을 호출해 준 뷰 컨트롤러로 어떤 값을 전달해 주려면 어떻게 해야 할까요?

이러한 상황이 필요한 시나리오입니다.

1) 사용자가 뷰 컨트롤러 A를 떠나서 뷰 컨트롤러 B로 향합니다.

2) 나중에 나타난 뷰 컨트롤러에서 사용자는 데이터를 가지고 상호작용하고 있고 여러분은 그 데이터를 뷰 컨트롤러 A로 되돌려 보내고 싶습니다.

다시 말하자면 A에서 B로 데이터를 전달하는 것이 아니라 B에서 A로 데이터를 전달하는 것입니다.

...

데이터를 돌려보내는 가장 쉬운 방법은 뷰 컨트롤러 B에다가 뷰 컨트롤러 A에 대한 참조를 생성하는 것이고, 뷰 컨트롤러 B에서 그 A의 함수(메소드)를 호출하는 것입니다. 그렇다면 나중에 나타나는 뷰 컨트롤러 B의 소스 코드는 다음과 같을 것입니다.

// Swift
class SecondaryViewController: UIViewController {
    var mainViewController: MainViewController!
    
    @IBAction func onButtonTap() {
        mainViewController.onUserAction(data: "Hello, World!")
    }
}
// Objective-C
@interface SecondaryViewController: UIViewController {
    @property (atomic, retain) MainViewController * mainViewController;
}
@implement SecondaryViewController {
    @synthesize mainViewController;
    
    -(IBAction) onButtonTap {
        [[self mainViewController] onUserActionWithData: @"Hello, World!"];
    }
}

 

그리고 MainViewController는 다음과 같은 메소드(함수)가 추가될 것입니다.

// Swift
func onUserAction(data: String) {
    print("Data received: \"\(data)\"")
    // ...
}
// Objective-C
-(void) onUserActionWithData:(NSString *)data {
    NSLog(@"Data received: \"%@\"", data);
    // ...
}

 

그리고 이전 장에서 앞서 보였던 예제와 같이 나중에 등장할 뷰 컨트롤러가 생성된 후 내비게이션 스택에 푸쉬(push)될 것입니다. 이 때 MainViewControllerSecondaryViewController 사이의 연결은 다음과 같이 만들어집니다.

// Swift
class MainViewController: UIViewController {
    // ...
    let secondaryViewController = SecondaryViewController(nibName: "SecondaryViewController", bundle: nil)
    secondaryViewController.mainViewController = self
// Objective-C
@implement MainViewController {
    // ...
    SecondaryViewController * secondaryViewController
        = [[SecondaryViewController alloc] init];
    [secondaryViewController setMainViewController: self];
}

 

위의 예제에서 보듯 mainViewController라는 프로퍼티에 self가 대입됩니다. 이로써 나중에 등장할 뷰 컨트롤러는 자신을 호출해 준 뷰 컨트롤러의 존재를 알고 있습니다. 때문에 나중에 등장할 뷰 컨트롤러는 onUserAction(data:)와 같이 해당 뷰 컨트롤러의 메소드(함수)들을 호출할 수 있습니다.

이것은 전부입니다. 하지만... 데이터를 전달하기 위한 이와 같은 접근법은 그리 이상적인 방안이 아닙니다. 몇 가지 문제점을 꼽자면 다음과 같습니다.

(1) MainViewControllerSecondaryViewController는 너무 강하게 결합되어 있습니다. 여러분은 소프트웨어를 설계할 때 강한 결합성을 피하라는 말을 들어 보았을 것이고 그렇게 하기를 원할 것입니다. 왜냐하면 강한 결합성은 여러분의 코드에 대한 모듈화 정도를 떨어뜨리기 때문입니다. 이와 같이 작성된 두 개의 클래스는 너무 과도하게 얽혀있으면서 서로의 메소드(함수)에 의존하고 있는데 이는 종종 “스파게티 코드”로 이어지게 됩니다.

(2) 위의 예제 코드에서는 객체에 대한 retain이 순환합니다. 나중에 등장한 뷰 컨트롤러 객체는 먼저 등장했던 뷰 컨트롤러가 제거될 때까지는 메모리에서 해제되지 않습니다. 하지만 먼저 등장했던 뷰 컨트롤러도 나중에 등장한 뷰 컨트롤러가 제거되기 전까지는 메모리에서 해제될 수 없습니다. 이에 대한 해법은 weak 프로퍼티 키워드를 사용하는 것입니다.

(3) 만일 MainViewControllerSecondaryViewController의 개발자가 서로 다르다면 두 클래스에 대한 작업이 쉽게 수행될 수 없습니다. 왜냐하면 두 개의 뷰 컨트롤러는 서로의 뷰 컨트롤러가 어떻게 작동되는지를 이해해야 할 필요가 있기 때문입니다. 이 경우 두 개발자 사이에는 관심사의 분리(separation of concerns)가 이뤄질 수 없습니다.

이제 여러분은 클래스, 인스턴스 및 메소드(함수)들을 위와 같이 직접적으로 참조하는 것을 피하고 싶어질 것입니다. 이와 같이 코딩해 둔다면 나중에 유지관리하는 데 악몽이 될 수도 있습니다. 이러한 방식은 종종 스파게티 코드로 이어지면서 여러분이 코드의 어느 일부분을 수정할 경우 이와 관련된 다른 부분들도 연쇄적으로 충돌이 발생하게 됩니다. 그렇다면 어떻게 하는 것이 좋을까요? 그건 델리게이션(delegation)을 사용하는 것입니다.

간단한 요령 뷰 컨트롤러 사이에 소속될 변수가 여러 개 있다면, 프로퍼티를 이에 맞춰 여러 개 만들려 하지 마세요. 그 대신 필요한 모든 데이터들을 담는 구조체(struct) 또는 클래스(class)를 선언 및 정의하고 이 형식으로 표현되는 하나의 프로퍼티를 만들어서 뷰 컨트롤러 사이에 전달을 수행하면 됩니다.

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