티스토리 뷰

Intro

오늘은 

하나의 뷰컨트롤러에서 다른 뷰컨트롤러로 데이터를 전달하는 방법을 알아보려고 해요!

 

지금 제가 하는 프로젝트에서, 저는 저 '시간표 알람 추가' 오른쪽에 있는 테이블 뷰에서 과목 이름, 시작 시간, 종료 시간을 입력받는데 이걸 

 

여기 시간표에서 추가해줘야 하거든요!!

 

 

목차

view controller들끼리 데이터를 주고 받는 것은 iOS개발의 매우 중요한 부분이다. 데이터를 주고 받는 것에는 여러 방법이 있고, 각각의 장단점이 있다.

 

이 기사에서는  6가지 방법을 배울것이다 ((-> property, segue, property와 segue, delegation, closure, NotificationCenter을 이용함))

 

제일 쉬운 접근부터 시작해서, 조금조금씩 복잡한 방법까지 써보자 !

1. 프로퍼티를 이용해서 서로 data 주고받기(A->B)

2. 세그를 이용해서 서로 data 주고받기(스토리보드에서)

3. 프로퍼티와 함수를 이용해서 data 받기(A<-B)

4. delegation 이용해서 data 받기

5. closure 이용해서 data 받기

6. NotificationCenter와 Observer pattern을 이용해서 서로 data 주고받기

 

 

 

[1번] 프로퍼티를 이용해서 데이터 주고받기(A->B)

 

 

VC끼리 data를 주고받는 가장 쉬운 방법은 프로퍼티를 사용하는 것이다. 프로퍼티는 클래스에 속해있는 변수이다. 

 

클래스의 모든 인스턴스는 프로퍼티가 있을 것이고, 거기에다 값을 할당할 수 있다. 

 

UIViewController의 인스턴스인 view controller 역시 다른 클래스들 처럼 프로퍼티를 가지고 있을 수 있다.

 

 

(아 맞다...클래스안에 변수가 프로퍼티였지! 맨날 기억하는데 까먹는다 ㅜㅜ 이제는 잘 기억해야지...!)

 

 

 

 

여기에 text라는 프로퍼티를 포함하고 있는 MainViewController가 있다.

 

 

MainViewController의 instance를 만들 때 마다, text property에 값을 넣을 수 있다. 

바로 이렇게 !

 

 

(저 vc 선언 하는 건 예제가 아니에요...따라하기는 조금 이따가 나옵니당)

 

이 MainViewController가 네비게이션 콘트롤러라고 하고, second view controller에 몇개의 데이터를 넘기고 싶다고 해 보자!

 

 

제일 먼저, 만약 스토리보드를 쓰고 있는 상황이라면 이 MainViewController을 navigation controller로 감싸야 한다. (embed in navigation controller 다들 아시죠?)

 

만약 스토리보드를 쓰고 있는 상황이 아니라면, navigation controller를 새로 만들고 MainViewController을 root view controller로 설정해야 한다. 

 

이미 navigation controller을 쓰고 있는 상황이라면... 정말 완벽한 상황이다. 

 

이제, new view controller subclass와 view controller의 xib 파일을 만들자. (File->NewFile->Cocoa Touch Class-> create SecondaryViewController로 하면 됨), also create XIB 체크박스도 체크해 두자! 

 

 

 

 

SecondaryViewController는 이렇게 만든다.

 

그리고 XIB파일 안에는, UILabel을 하나 추가하고 textLabel 아웃렛이랑 연결해준다.

 

 

짠! 이런 상황이다 :)

 

위에 코드에서 볼 수 있듯이, viewDidLoad() 안에 text의 값을 라벨에 넣는다! 라고 되어있다. 

 

이제 이 부분에서 핵심적인 데이터 패싱이 이루어지게 된다. MainViewController에 이 함수를 추가해주자>_< 

 

 

그리고 MainViewController에 버튼을 연결해주고, IBAction에 연결해주면, 버튼을 누를 때마다 코드가 실행되게 된다.

 

 

 

저 코드에서 무슨 일이 일어나냐면....

  • vc라는 상수를 만들어서 SecondaryViewController의 인스턴스로 사용한다. 초기화 할 때 XIB이름을 잘 봐야한다! (여기서 저도 실수해서 시간 한참 날렸어요ㅠㅠㅠㅠㅠㅠㅠㅠ)
  • vc의 text에 "sweetDev"같은 문자열을 할당한다. !!여기가 실제로 데이터를 건네주는 부분이다!!
  • 최종적으로, navigationController의 스택에 이 vc를 push해준다. 이 change는 animated 되니까 slide in하는 것을 볼 수 있을것이다.

이해 하셨나요? 이 쪼만한거 하는데 코드가 엄청 드네요....!

 

나눠서 생각해 봅시다

뷰컨트롤러 A에서 B로 프로퍼티를 이용해서 데이터를 넘겨주는 방법은 다음과 같다.

  1. view controller B(데이터를 받는 VC)에 프로퍼티를 만들어 준다. (위 예제에서는 텍스트)
  2. VC B 에서 무슨일이 일어나는지 결정한다. (예제에서는 label에 text를 할당해준다)
  3. VC A안에서 B를 만들고, 프로퍼티에 값을 할당하고, 네비게이션 스택에 푸시한다.

 

Quick Tip) 만약 (지금 내 상황처럼) 전달하고 싶은 변수들이 여러개라면, 프로퍼티를 여러개 만들지 말고 struct 나 class로 전체 데이터를 감쌀 수 있는 걸 만들고, 그걸 전달하면 된다!

 

=================

🤔흠..근데 이건 내가 원하는 사항이 아닌데.......나는 이미 view controller가 스토리보드에서 만들어져있단 말이지;;;

그리고 네비게이션 컨트롤러도 아니라서...쵸큼;;;;

 

그래서 계속 두번째 예제를 살펴보기로 했습니다

===================

[2번] 세그를 이용해서 데이터 주고받기(A->B)

만약 스토리보드를 사용하고 있다면(딱 내얘기!), 세그의 prepare(for:sender:)함수를 사용해서 뷰컨트롤러들끼리 데이터를 주고받을 수 있다. 뷰컨트롤러들끼리 스토리보드에서 데이터를 주고 받는 것은 XIB를 쓰는것과 크게 다르지 않다.(??XIB는 또 무엇?) 세그를 써서 데이터를 주고받는 방법을 살펴보자 :)

 

시작하기 전에 스토리보드와 세그에 대해서 잠깐 생각해보자. 스토리보드는 유저 인터페이스의 필수적인 요소이다. XCode의 인터페이스 빌더에서 만들 수 있고, VC들 사이에서 코드 없이 transition을 만들 수 있다.

 

Segue는 'Smooth Transition'를 표현하는 고오급진 단어이다. 하나의 VC에서 다른 VC로 이동할 때, (만약 navigation controller라고 가정하면), 내 VC안에서 세그를 만들 수 있다. VC들끼리 데이터를 공유하는 것 또한 세그 안에서 일어날 수 있다. 

 

 

이 예제 프로젝트에서는 MainViewController에서 TetiaryViewController로 넘어가는 세그를 확인할 수 있다.

이것은 다른 버튼을 만들어서 3번째 VC로 이동하게 하고, 거기다가 'Show'같은 액션을 추가하는 것 만큼 간단하다. 이제 MainViewController에서 TetiaryViewController까지의 화살표가 보인다. 이제 VC설정을 custom class로 바꾸고, Identitiy Inspector에서 TertiaryViewController로 바꾸면 VC를 코드로 컨트롤 할 수 있다.

 

 

TertiaryViewController의 코드는 다음과 같다. 

 

 

이 예제도 특별할게 없다 - 지난번 예제처럼, usernameLabel 을 userName이라는 프로퍼티를 넣어서 만드는 것이다.

 

이제, MainViewController에서 TertiaryViewController로 데이터를 건네기 위해, ⭐️prepare(for:sender:)⭐️라는 함수를 사용한다. 이 메서드는 세그 전에 호출 되므로, 커스트마이징 할 수 있다. 코드는 다음과 같다:

 

 

(MainVC안에 넣어야 한다)

 

급 궁금증! 저기서 vc = segue.destination as? TetiaryVC 가 무슨뜻일까??그냥 VC를 하나 만들어주면 되는것같은데.......?

 

 

 

 

 

 

=================

 

 

🤔흠... 이건 세그에 데이터를 넘겨주는 방식이구나... 이건 쓸 수 있겠다!

빈칸을 채워지기 전에 disable해놨다가 enable 하고 세그로 데이터를 넘기면 되겠군!!😂

 

근데 내 프로젝트는 그 전에 빈칸이 다 채워졌나 확인도 해야하고... 왠만하면 코드로 처리하고 싶은데 다른 방법이 없을까?

===================

 

[3번] 프로퍼티와 함수를 이용해서 데이터 받기(B->A)

 

만약 Secondary View Controller에서 Main View Controller로 데이터를 돌려주고 싶다면 어떻게 해야할 까????? 첫번째 VC에서 두번째 VC로 데이터를 전해주는것은 꽤 직관적이다. 하지만 두번째VC에서 첫번째로 주는 건 어떻게 해야할까??? 이것 역시 다양한 방법으로 할 수 있다.

 

 

 만약 이런 상황이라고 해보자:

  • 유저가 첫번째 VC에서 두번째 VC로 넘어갔다
  • 두번째 VC에서는 유저는 data를 쓰고, 그 data를 다시 첫번째 VC로 돌려주고 싶다.

짧게 말하면,A->B로 데이터 건네주는거 말고, B->A로 다시 돌려주고 싶다! 

 

데이터를 돌려주는 가장 쉬운 방법은 VC1에서 VC2로 참조를 만들고, VC2에 속한 함수를 VC1에서 부르는 것이다.  VC2의 코드는 다음과 같다.

  그리고 이 함수를 VC1에 추가해준다.

 

 

 

VC가 네비게이션 스택에 푸시 되었을 때, 마치 방금 전에 본 예제처럼,  VC1과 VC2 사이에 연결이 생기게 된다.

 

(OnButtonTap에 저 코드도 넣으면 된다)

 

위에 예제에서 보듯, mainViewController의 프로퍼티에 self가 할당되었다. Secondary view controller는 이제 main view controller을 "알게"되고, mainVC의 함수들을 콜할 수 있게 된다 - 예제에서 onUserAction을 콜한것처럼! 

예제는 여기까지지만, 이 방법에는 완전 이상적이지는 않다. 이러한 단점들을 갖고 있다.

 

 

  • MainVC와 SecondaryVC는 tightly coupled되어 있다. 소프트웨어 디자인을 할 때는 tight-coupling을 피하는것이 좋은데, 대부분 코드의 모듈성을 해치기 때문이다. 두개의 클래스가 서로 얽히게 되고, 서로의 함수에 기능적으로 의존하게 되고, 결국은 스파게티 코드가 된다.
  • 위의 코드 역시 유지 사이클을 만든다. SecondaryVC는 MainVC가 메모리에서 사라지기 전까지 계속 메모리 위에 있어야 하고 MainVC도 SecondaryVC가 사라지기 전에 계속 메모리 위에 있어야 한다. (이것에 대한 해결책은 weak property 키워드이다?? weak에 대한 정리는 다음에 하는걸루...ㅎㅎ)
  • 또한, 두명의 개발자는 MainVC와 SecondaryVC를 따로따로 작업할 수 없다. 서로 연결되어있기 때문!

 

 

우리는 직접적으로 클래스, 인스턴스, 함수에 reference하지 말아야 한다. 이런 코드는 유지보수하기에 'nightmare'이다. 결국에는 스파게티 코드가 될 뿐.........그럼 어떻게 해야할까???!?!?!

 

=================

 

Q) 궁금한 점 : 그럼 SecondVC에서 만들어진 MainVC는 다른 VC 아닌가?????

 

=================

 

[4번] delegation 이용해서 data 받기

 

Delegation(위임)은 Cocoa Touch SDK의 소프트웨어에서 자주 쓰이는, 중요한 디자인패턴이다. iOS 어플 개발을 할거라면 delegation은 꼭! 알아야한다!! 

Delegation을 쓰면, 첫번째 클래스와 두번째 클래스가 떨어져있을 수 있다. 프로그래머는 두번째 클래스를 구현하고, 첫번째 클래스의 이벤트들에 respond해줄 수 있는 것이다. 즉, 아까와 다르게, 둘은 decoupled 되었다!

 

여기 간단한 예제가 있다:

  • 내가 피자 먹으러 갔다고 해 보자! 그리고 거기에는 피자 요리사가 있다.
  • 손님은 피자를 혼자 먹든, 친구랑 나눠 먹든, 포장해가던 가게는 알 바가 아니다.
  • 피자 요리사는 과거에는 우리가 피자로 뭘 하는지에 관심이 있었다면, 이제 'decoupled'되었으니까, 그냥 피자를 넘겨주고 끝난다. 그는 피자에 관심 갖는 부분을 다른 사람한테 'delegate'해버렸고, 이제 피자 요리에만 신경을 쓴다.


    우리는 먼저 프로토콜을 하나 정의 해줘야 한다.

 

 

(첨알... 함수가 원하는 인자가 있으면 저렇게 쓰는구나...!)

 

프로토콜은 클래스에 어떤 함수가 들어있어야 하는지 약속하는 협약이다. 프로토콜을 채택하고 싶으면 class뒤쪽에다 이케 써주면 된다:

 

ViewController라는 클래스는 이제 이렇게 생긴거다:

  • 클래스 이름은 ViewController
  • UIViewController 클래스의 extend이거나 subclass
  • PizzaDelegate클래스를 채택함 (delegate = self도 당연히 해줬겠지!?)

프로토콜을 채택하고 싶다면, 구현까지 해야하는데, ViewController에 이걸 추가하면 된다: 

 

 

여기서 두번째 뷰 컨트롤러를 만든다면, 여기에도 delegate connection을 추가해줘야하는데, 아까 예제에서 했던 것 처럼:

 

 

 

이제 delegation의 중요한 부분이다!! 클래스에 프로퍼티와 몇가지의 코드를 추가해서 functionality를 delegate 하게 해줘야한다, secondary VC가 그랬던 것 처럼!

프로퍼티는 이케 추가해주면 된다:

 

음...?????

그리고, 코드는 : 

 

onButtonTap()함수가 피자 만드는 사람이 피자 다 만들었을 때 부르는 함수라고 해 보자. 그러면 delegate가 onPizzaReady 함수를 부르게 된다. 

 

delegation의 핵심요소들은 다음과 같다:

  • delegate protocol이 필요하다
  • delegate property가 필요하다
  • data를 받을 클래스는 프로토콜을 채택해야 한다
  • data를 전해줄 클래스는 프로토콜에 있는 함수를 call해줘야 한다

위에서 봤던, 프로퍼티를 이용해서 data를 돌려주는 방법과 어떻게 다를까?

  • 개발자들은 각각 다른 클래스를 잡고 일할 수 있고, 프로토콜과 프로토콜의 함수를 구현하기만 하면 된다. 
  • MainVC와 SecondVC사이에 직접적인 연결이 없어서, 이전 예제보다 loose하게 VC들이 연결되어 있다. 
  • MainVC말고 다른 클래스들 에서도 이 프로토콜을 사용할 수 있다. 

이제 클로져를 쓰는 예제를 살펴보자!

 

[5번] Closure 이용해서 data 받기

클로져를 써서 VC들 사이에서 데이터를 주고 받는 것은 property나 delegation을 사용하는것과 크게 다르지 않다. 하지만 클로저를 써서 얻는 최대 이익은, 1) 쓰기 쉽고, 2) 프로토콜이나 함수의 사용 없이 지역변수 범위 안에서 다룰 수 있다는 것이다.

 

 

먼저 second VC에 프로퍼티를 만들고 시작하게 된다. 이런식으로 만들면 된다: 

 

 

completionHandler는 클로저 타입의 프로퍼티이다. 이 클로저는 optional이고, String을 매개변수로 받아서 Int를 리턴할 타입이다. 또, SecondVC에서, button이 눌렸을 때 클로저를 call할건데: 

 

 

위에 예제에서는 이런 일이 나타난다.

  • completionHandler 클로저가 call 되고, 결과값이 result에 할당 된다.
  • print로 결과값이 보여진다. 

이제 MainVC에서는 클로저를 이렇게 정의하면 되는데:

 

 

이거 자체가 클로저이다. 지역변수로 선언 되어서, 지역 변수, 프로퍼티, 함수 다 쓸 수 있다.

 클로저에서 text 파라미터가 프린트 되었을 때, func의 결과로 string의 길이가 리턴된다. 여기가 흥미로운데, 클로저는 VC들 사이에서 쌍방으로 데이터를 주고 받을 수 있게 해준다. 클로저를 정의하고, 들어오는 데이터를 쓰고, 클로저를 유발한 애한테 다시 data를 돌려줄 수 있다. 

 

여기서 함수도 call이 안되고, delegation, property 하나도 없다. 그런데도 되는게 짱 신기하다 ㅎㅎ

 

클로저는 이런 경우에 편리하게 쓸 수 있다:

  • delegation까지는 필요 없고, 빨리 함수만 하나 만들어서 쓰고 싶을 때.
  • 클로저로 여러개의 클래스 다루고 싶을 때. 클로저를 안쓰면 함수를 꼬리물기해서 call해야 하지만, 클로저를 쓰면 깔-끔 하다. 
  • local에서만 선언하고 싶을 때.


    클로저를 써서 VC들 사이에서 데이터를 주고 받는 것의 위험성 중 하나가, code가 매우 조밀해질 수 있다는 것이다. 클로저가 확실히좋을때만 써야지, 편하다고 그냥 쓰기 시작하면 큰일날 수 있다. 

그럼 VC이 연결되어있지않고, 연결 될수 없는 상태일 때 데이터를 전달하고 싶다면 어떻게 해야할 까?

 

[6번] Notification Center 이용해서 Data 받기

(Notification Center을 어디서 들어본 것 같긴 한데...?)

 

또한 우리는 Notification Center, 즉 NotificationCenterClass를 이용해서 VC들 사이에서 데이터를 주고 받을 수 있다.(첨듣....)

NotificationCenter은 notification을 핸들링하는 클래스다.(음 이건 당연쓰) notification이 들어오면, 듣고 있는 애들한테 notification을 포워딩 해준다. NotificationCenter은 Cocoa Touch SDK의 Observer-Observable 소프트웨어 디자인 패턴이다.(?? 일단은 더 읽어 볼게요..! 이건 나중에 공부하는걸루)

 

Quick Note: Swift 3+에선 NotificationCenter이고, NS 접두사가 안붙는다. 그리고 여기서 말하는 "notification"은 푸시 알람이 아니다!

 

Notification Center은 3개의 키 컴포넌트들이 있다.

  1. Notification Observing
  2. Notication Sending
  3. Respond to Notification

1번 Notification Observing부터 시작하면, 우리는 Notification Center에 우리가 Notification을 전달받고 싶다는 것을 미리 밝혀야 한다!

 

모든 Notification들은 각각을 식별하기 위한 이름이 있다. MainViewController 클래스 제일 위에다가 이 static property를 추가해보자!

 

 

이 static property(class property)는, 코드 어디서든 MainViewController.notificationName 으로 호출하면 바로 부를 수 있다.

 

(** static 키워드를 잘 모른다면 : <여기>로 **)

 

Observe는 이런식으로 하면 된다:

 

이 코드를 주로 viewDidLoad()나 viewWillAppear()에 추가하는데, VC가 화면에 보일 때 observation이 등록되게 하기 위함이다. 위 코드에서 어떤 일이 일어나냐면..

 

  • NotificationCenter.default를 쓰니까, default notification center을 쓰는것이다. (Notification center을 직접 만들 수도 있다고 한다! 예를 들어, 특정한 종류의 notification을 쓸 때나...) 
  • 그리고 Observer을 추가하게 되는데, addObserver(_:selector:name:object:)의 꼴을 따른다. 
    • 첫번째 인자는 observation을 하는 instance를 넣는 자리이고, 주로 self가 들어간다.
    • 두번째 인자는 Notification이 observe 되었을 때, 불리는 selector이고, 거의 대부분 현재 클래스의 함수이다.
    • 세번째 인자는 notification의 이름이고, static constant notificationName을 전달해준다.
    • 네번째 인자는 notification을 전달하는 object이다. 여기에는 주로 nil을 넣지만, 하나의 특정한 오브젝트의 Notification을 받고 싶으면 다른 값을 넣어도 된다.
  • NotificationCenter.default를 쓸거고, 원한다면 자신만의 Notification Center을 만들 수도 있다.(특정한 종류의 notification이라던가...) 하지만 대부분의 경우에 default는 충-분하다. 
  • 이제 addObserver(_:selector:name:object:)함수를 Notification Center에서 콜해준다 
    • 첫번째 인자는 관측을 하는 instance이고, 99프로 self이다. 
    • 두번째 인자는 notification이 관측되었을 때 콜 할 셀렉터이고, 대부분의 경우에 현재 클래스에 있는 함수이다.
    • 세번째 인자는 notification의 이름이고, static constant notificationName을 넣으면 된다.
    • 네번째 인자는 우리가 받고 싶어하는 notifcation의 주인 object이다. 주로 여기에는 nil을 넣지만, 특정한 한 개의 object로 부터 관측하고 싶으면 여기에다가 걔를 넣으면 된다.

notifcation 관측을 멈추려면 얘를 콜하면 된다

 

 

모든 notification을 다 관측 안하려면 얘를 콜해도 된다:

 

항상 notifications들은 명료하고, 그래서 우리는 하나의 종류의 notification을 보게 되는데, 걔는 하나의 오브젝트(주로 self)에 하나의 함수에만 영향을 준다.

notification이 일어났을 때 call되는 함수는 onNotification(notification:)이고, 클래스에 추가해줘 보자!

 

 

앞에 @objc라는 키워드는, Swift 4 이상에서 붙이게 되는데, NSNotificationCenter 프레임워크가 Objective-C로 짜여져 있기 때문이다. 함수 안에서, notification의 내용을 간단하게 프린트만 하면 된다.

 

notification을 posting하는것은 쉽다. 이렇게 하면 되는데:

다시, 몇가지를 확인하면: 

  • post(name:object:userInfo:)함수를 default Notification Center에서 call하는데, 전에 썼던 센터랑 똑같아야 함. 
  • 함수의 첫번째 인자는 notification의 이름이어야 하는데, 전에 정의한 static constant여야 함. 
  • 두번째 인자는 notification을 포스팅 하는 오브젝트이다. 종종 이 값을 nil로 하기도 하는데, 하지만 notification을 observing 하는데 object argument를 이미 썼다면 여기다가 그 same object 값을 넣어서, 그 오브젝트를 독점적으로 관측하고 post 할 수도 있다. 
  • 세번째 인자는 notification payload인데 userInfo라고 불려진다. dicitionary형태의 어떤 data든 여기에 넣을 수 있다. 우리의 예제에서는, data랑 boolean 값을 전달한다. 

이게 끝이다!

여기서 NotificationCenter에 대해서 더 배울 수 있다: How To: Using Notification Center In Swift.

NotificationCenter은 이러한 경우에 편리하게 쓰이는데,,,,....

  • 데이터를 주고 받을 VC들이 closely related 되지 않았을 때. 예를 들어, Table VC에서 RestAPI에서 새로운 데이터를 받았을 때 respond를 해야할 때.
  • VC들이 미리 생길 필요가 없을 때. RestAPI가 테이블 뷰가 화면에 띄워지기 전에, 정보를 받고싶을 수도 있다. notification 관측은 옵셔널이라 개꿀이다.
  • 여러개의 VC들이 한개의 notification에 respond 해야할 때나, 하나의 VC가 여러개의 notification에 답해야 할 때. NotificationCenter은 다대다 구조라서 둘다 커버칠 수 이따.

쉽게 생각하면, NotificationCenter은 정보를 관리하는 고속도로 망? 같은거면, notification은 다양한 차선에서 다양한 방향으로 전해지고 있는 것이다. 

만약 그냥 서울과 부산을 잇는 '길'만 있어도 된다면, 굳이 Notification Center을 쓸 필요가 없다. 그냥 delegate나 프로퍼티나 클로저를 쓰는게 낫다. 하지만, 반복적이고 정규적으로 한쪽에서 다른쪽으로 정보를 전달하고 싶다면 NotificationCenter가 좋은 선택이다.

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/04   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
글 보관함