Jun 12, 2022, TIL (Navigation 데이터 전달방법 : Static & Protocol 비교)

Woong·2022년 6월 20일
0
post-thumbnail

이 내용은 4월24일(입문과정)에 만들었던 코드를 6월12일(실무과정)에서 Navigation의 데이터 전달에 대해서 내용을 발전시킨과정이다.
당시에는 새로운 swift파일을 만들고 클래스를 생성하고, static을 활용하여 다음 뷰컨트롤러 간 데이터를 전달해줬다.
하지만, 이번엔 방식을 바꿔 Protocol을 활용하여 뷰컨 간의 데이터를 전달받았다.

1. 기존방식

1-0. Message Class

struct Message{
    static var message = ""
    static var isOn = true
}

두개의 ViewController 사이에서
static var message를 활용하여, textField의 텍스트를 주고 받을 것이다.
static var isOn 을 활용하여, 전구의 꺼지고 켜진 상태값을 주고 받을 것이다.

1-1. Viewcontroller

class ViewController: UIViewController {

    @IBOutlet weak var tfMessage: UITextField! 
    @IBOutlet weak var imgView: UIImageView!
    
    let imgOn = UIImage(named: "lamp_on.png")
    let imgOff = UIImage(named: "lamp_off.png")
    var isOn = true
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        imgView.image = imgOn
    }

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        let editViewController = segue.destination as! EditViewController
        
        if segue.identifier == "editButton"{
            editViewController.textWayValue = "Segue: Use Button!"
        }else{
            editViewController.textWayValue = "Segue: Use BarButton!"
        }
        
        Message.message = tfMessage.text!
    }

    override func viewWillAppear(_ animated: Bool) {
        tfMessage.text = Message.message
        if Message.isOn{
            imgView.image = imgOn
        }else{
            imgView.image = imgOff
        }
    }
}// ViewController
  1. prepare 메소드를 통해 다른 EditViewController로 전환하기 전에 EditViewController로 데이터들을 보낸다.
  2. 만약, 지정한 segue의 identifier가 "editButton" 이라면, EditViewcontroller의 textWayValue에 "Segue : Use Button" 이라는 값을 저장시켜주고,
    그게 아니라면 Bar Button Item을 통해 EditViewController에 접근 한 것이므로 EditViewController의 textWayValue "Segue : Use BarButton" 이라는 값을 저장시켜준다.
  3. 그리고 Message 클래스의 message 타입프로퍼티(static)에 값을 저장시켜준다.
EditViewCon에서 데이터 전달받은 후..
  1. ViewController의 View가 호출 되기 전에 호출되는 viewWillAppear 를 통해 텍스트필드에 Message.message에 저장된 값을 불러온다.
  2. Message.isOn의 true / false 여부에 따라 전구에 불이 들어오게하거나, 들어오지않도록 만들어준다.

1-2. EditViewController

import UIKit

class EditViewController: UIViewController {

    @IBOutlet weak var lblWay: UILabel!
    @IBOutlet weak var tfMessage: UITextField!
    @IBOutlet weak var lblOnOff: UILabel!
    @IBOutlet weak var swIsOn: UISwitch!
    
    var textWayValue: String = ""
    
    
    override func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup after loading the view.
        lblWay.text = textWayValue
        tfMessage.text = Message.message
        
        swIsOn.isOn = Message.isOn
        if Message.isOn{
            lblOnOff.text = "On"
        }else{
            lblOnOff.text = "Off"
        }
    }
    

    @IBAction func btnDone(_ sender: UIButton) {
        Message.message = tfMessage.text!
        navigationController?.popViewController(animated: true)
        
    }
    
    @IBAction func swImageOff(_ sender: UISwitch) {
        if sender.isOn{
            Message.isOn = true
            lblOnOff.text = "On"
        }else{
            Message.isOn = false
            lblOnOff.text = "Off"
        }
    }
    
    /*
    // MARK: - Navigation

    // In a storyboard-based application, you will often want to do a little preparation before navigation
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        // Get the new view controller using segue.destination.
        // Pass the selected object to the new view controller.
    }
    */

} // EditViewController
ViewDidLoad 내부에..(한번실행)
  1. prepare를 통해 전달받은 데이터 textWayValue 값을 라벨에 정의해준다. ("Segue : Use Button" or Segue : Use Bar Button")
  2. Viewcontroller에서 Message 클래스의 static에 저장해줬던 값을 EditViewController 텍스트필드에 값이 표기되도록 한다.
  3. Message.isOn 값이 true 일때, 라벨에 On / false 일때, 라벨에 Off표기
  4. Switch의 On/Off 상태는 Message 클래스의 isOn 프로퍼티의 true or false값에 따라 그 값이 실행되도록 만들어둔다.
버튼액션함수부
  1. 완료버튼 : Message.message에 EditViewController의 텍스트필드에 입력한 값을 저장시켜준다. (이 후, PopViewController로 앞으로 Pop!)
  2. 스위치액션 : 센더가 On일 때, Message.isOn에 true값을 저장하고, 라벨을 On으로 변환시킨다. 센더가 Off일 때, Message.isOn에 false값을 저장시키고 라벨을 Off로 변환시켜준다.


2. Protocol 활용한 방식

2-0. Message Protocol

didMessageEditDone 메서드 : EditViewController에서 함수를 호출하며, message를 전달해줌.
didImageOnOffDone 메서드 : EditViewController에서 함수를 호출하며, isOn 상태를 전달해줌.

위 두가지 약속을 준수하는 Message Protocol을 선언해준다.

protocol MessageProtocol {
    func didMessageEditDone(_ controller : EditViewController, message :String)
    func didImageOnOffDone(_ controller : EditViewController , isOn : Bool)
}

2-1. ViewController

import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var tfMessage: UITextField!
    @IBOutlet weak var imgView: UIImageView!
    
    let imgOn = UIImage(named: "lamp_on.png")
    let imgOff = UIImage(named: "lamp_off.png")
    var VisOn = true
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        imgView.image = imgOn
    }
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        let editViewController = segue.destination as! EditViewController
        
        if segue.identifier == "barButtonItem" {
            editViewController.textWayValue = "Seuge : Use BarbuttonItem"
        }else{
            editViewController.textWayValue = "Segue : Use EditButton"
        }      
        editViewController.textMessage = tfMessage.text!
        editViewController.delegate = self
        editViewController.EisOn = VisOn        
    }
}

extension ViewController : MessageProtocol {
    
    func didImageOnOffDone(_ controller: EditViewController, isOn: Bool) {
        if isOn == true{
            imgView.image = imgOn
            self.VisOn = true
        }else{
            imgView.image = imgOff
            self.VisOn = false
        }
    }  
    func didMessageEditDone(_ controller: EditViewController, message: String) {
        
        tfMessage.text = message
    }
}
  1. MessageProtocol을 확장시켜 로직을 추가해줬다.
    didImageOnOffDone 메서드에서 받아온 isOn(EditViewcon에서 받아온 EisOn값)이 true값을 가진다면 이미지뷰에 On상태의 전구이미지가 표기되도록 만들었다. false값일 때는 Off상태의 전구이미지 표기.
  2. 또한, true조건일 때 : self.VisOn = true 을 만들어줘서 EditViewcontroller의 EisOn 값이 온전히 VisOn 으로 전달되도록 해준다.
    (이 정보는 prepare메서드에서 전달해주고 있다.)

2-2. EditViewController

import UIKit

class EditViewController: UIViewController {

    @IBOutlet weak var lblWay: UILabel!
    @IBOutlet weak var tfMessage: UITextField!
    @IBOutlet weak var lblOnOff: UILabel!
    @IBOutlet weak var swIsOn: UISwitch!
    
    var textWayValue = ""
    var textMessage = ""
    var EisOn = true
    
    var delegate : MessageProtocol?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        lblWay.text = textWayValue
        tfMessage.text = textMessage
        
        swIsOn.isOn = EisOn
        if EisOn == true {
            lblOnOff.text = "On"
        }else{
            lblOnOff.text = "Off"
        }
        
        // Do any additional setup after loading the view.
    }
    @IBAction func btnDone(_ sender: UIButton) {
        delegate?.didImageOnOffDone(self, isOn: EisOn)
        delegate?.didMessageEditDone(self, message: tfMessage.text!)
        
        navigationController?.popViewController(animated: true)
    }
    
    @IBAction func swImageOnOff(_ sender: UISwitch) {
        
        if sender.isOn {
            EisOn = true
            lblOnOff.text = "On"
        }else{
            EisOn = false
            lblOnOff.text = "Off"
        }
    }
    
    /*
    // MARK: - Navigation

    // In a storyboard-based application, you will often want to do a little preparation before navigation
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        // Get the new view controller using segue.destination.
        // Pass the selected object to the new view controller.
    }
    */
}
  1. btnDone을 눌렀을 때, MessageProtocol의 didImageOnOffMessage의 isOn 값에 EditViewController 의 EisOn 값이 저장되도록 한다.

  2. btnDone을 눌렀을 때, MessageProtocol의 didMessageEditDone의 message에 EditViewController 의 tfMessage 값이 저장되도록 한다.

  3. 스위치 sender 값에 isOn을 줬을 때, EisOn = true 으로 주고, 라벨이 On이 될 수 있도록 한다. false 조건일 때는 EisOn = false으로 주고, 라벨이 Off되도록 한다.

3. Protocol이란?

3-1. Porotocol

프로토콜은 특정 역할을 하기 위한 method, property 기타 요구사항 등의 청사진을 정의함. (?)
구조체, 클래스, 열거형은 프로토콜을 채택해서 특정 기능을 실행하기 위한 프로토콜의 요구사항을 구현 할 수 있음.
어떤 프로토콜의 요구사항을 모두 따르는 타입은 '해당 프로토콜을 준수한다'라고 표현함. 하지만,
프로토콜은 정의를 하고 제시를 할 뿐이지 스스로 기능을 구현하진 않음.

struct SomeStruct : AProtocol, AnotherProtocol {
	// 구조체 정의
}

class SomeClass : AProtocol, AnotherProtocol {
	// 클래스 정의
}

enum SomeEnum : Aprotocol, AnotherProtocol {
	// 열거형 정의
}

각 타입은 Aprotocol과 AnotherProtocol을 채택 한 것임. 만약, 클래스가 다른 클래스를 상속받는다면 상속 받을 클래스 이름 다음에 채택할 프로토콜을 나열해준다.

class SomeClass : SuperClass, AProtocol, AnotehrProtocol

SomeClass는 SuperClass를 상속받기와 동시에 AProtocol과 AnotherProtocol 프로토콜을 채택한 클래스가 되는것임.

즉, 한마디로 통신규약 약속 (우리 이렇게 하자!)(보통, -able, -ing를 붙여서 많이 표현함.)

protocol Naming {
// 우리는 이런 변수(property)를 가지고 있을겁니다 라고 약속!
	var name : String { get set }  	// get set <- 값을 가져올 수 도 있고 값을 넣을수도 있다.
// 우리는 이런 메소드(method)를 가지고 있을겁니다 라고 약속!
    func getName() -> String
}

이것을 사용하려면 아래와 같다. (*Friend 는 Naming Protocol을 준수한다.)

struct Friend : Naming {
	var name : String
    
    func getName() -> String {
    	return "내친구" + self.name
    }
}

var myFriend = Freind(name : "정대리")

print(myFreind.getName())

//출력 "내친구 정대리" 

3-2. Protocol 상속

Naming이라는 프로토콜과 Aging이라는 프로토콜을 만들어보자. 이는 각각의 프로퍼티와 메서드를 갖는다. 이 두가지 프로토콜을 상속하여 준수하는 UserNotifiable이라는 프로토콜을 만들어보자.

protocol Naming {
	var name : String { get set }
    func getName() -> String
}

protocol Aging {
	var age : Int { get set }
}

protocol UserNotifiable : Naming, Aging {
	// 이 프로토콜은 Naming과 Aging protocol 을 준수하고 있다.
}

만약, 여기서 UserNotifiable 을 준수하는 struct 혹은 class를 만들어준다면?

struct MyFriend : UserNotifiable {
	var name : String
    func getName() -> String {
    }
    var age : Int
}

위와같이 Naming과 Aging Protocol이 갖고있는 프로퍼티와 메서드를 표기하지 않는다면 struct내 오류가 발생하게된다. 즉, 약속(프로토콜)을 준수하지 않으면 실행이 불가하다.

3-3. Protocol 확장

protocol Naming {
	var lastname : String { get set }
    var firstname : String { get set }
    func getName() -> String

}

extension Naming {
	func getFullname() -> String{
    	return self.lastname + "," + self.firstname
    }
}

struct Friend : Naming {
	var lastname : String
    var firstname : String
    func getName() -> String {
    	return self.lastname
    }
}
let myFriend = Friend(lastname : "정", firstname : "대리")

myFriend.getName()			// 출력 "정"
myFriend.getFullName		// 출력 "정,대리"

즉, 프로토콜만을 사용하여 정의했을 때는 lastname, firstname 프로퍼티 + getName 메서드를 사용할 것이다라고 선언만 할 수 있을 뿐이지 로직을 집어넣거나 할 순 없다.
하지만, extension 으로 프로토콜을 확장 시켜주었을 때에는 로직을 넣을 수 있다.
여기선 getFullname() 메서드를 활용했을 때, lastname + "," + firstname 이 되도록 로직을 만들어 줬다.

4. 느낀점

Protocol을 활용하여 수업에서 처음보고 화면 간 데이터를 주고받았을 때 굉장히 당황스러웠다.
왜냐하면, 문법에 대한 개념강의를 하고 수업을 한게 아니라 사용부터 했기에..

이러한 이유로 인해 SeSac iOS 면접 당시 Protocol에 대해 언급했다가 피를 봤다.. 모르는 개념이였지만, 배우고 있는 것을 말해주다가 면접관님이 내게 'Protocol이 뭔지 설명해주실 수 있을까요?'라는 질문에 나는 무너졌다. 하지만 이젠 설명해줄 수 있을 것 같다.

내가 Protocol에 대해서 완벽하게 개념을 이해한 것은 아닌 것 같다. { get set } 을 프로토콜이 갖고있는 property에 표기해주는 이유?
그리고, 유튭 정대리 문법의 Protocol의 Associated Type에 대해선 내가 개념에 대해서 전혀 모르고있어서 정리를 못했다.
이는 공부하면서 Velog에 더 자세하게 정리해보도록 하자.

profile
https://github.com/iOS-Woong

0개의 댓글