본문 바로가기

iOS

프로토콜 - Basic To Advanced

728x90

프로토콜을 잘 사용하면 보다 효율적인 코드를 작성할 수 있다.

 

먼저 프로토콜의 기본적인 내용은 아래 [더보기]를 통해서 확인할 수 있다.

더보기

프로토콜이란?

 

"A protocol defines a blueprint of methods, properties, and other requirements that suit a particular task or piece of functionality"

(= protocol은 특정 작업 혹은 기능들을 구현하기 위한 메소드, 프로퍼티, 그리고 기타 다른 요구사항들의 청사진이다.)

 

.. 보다 이해하기 쉽게 설명하자면,

프로토콜은 구현해야 하는 것들의 리스트이다.

그리고 프로토콜은 이름을 통해서 보다 직관적으로 명시할 수 있어야 한다. 이름만 봐도 그 역할을 짐작할 수 있어야 한다는 것이다.

 

예를 들어서 "디지털미디어학과 학부생" 프로토콜을 설계해보자.

디미라면 노트북을 보유해야 한다. 하지만 노트북이 아닌 데스크 탑을 보유하고 있는 경우도 있을 것이다.

그리고 디미를 졸업하기 위해 필수로 이수해야 하는 과목들이 존재한다. 또한 필수는 아니더라도 개인의 진로와 방향에 따라서 부수적으로 수강해야 하는 과목들도 존재한다.

 

위의 상황에 맞게 프로토콜을 작성하면 아래와 같다.

protocol DigitalMediaStudent {
    var name: String { get }
    var laptop: String? { get set }
    
    func doDataStuctrue()
    func doOperationSystem()
    
    func do3DModeling() 
}

기본적인 프로퍼티로 학부생의 이름을 나타내는 name 프로퍼티가 존재한다. 그리고 노트북을 보유할 수도 있고 아닐 수도 있기 때문에 laptop 프로퍼티에 관해서는 옵셔널 스트링으로 지정했다.

여기서 봐야할 것은 프로퍼티의 { get } 과 { get set } 이다.

우선 { get } 의 경우, 해당 변수는 { get } 과 { get set } 둘 중 하나만 구현해도 된다. 주의할 점은 { set } 만 구현할 수는 없다. 

그러나, { get set } 의 경우 해당 변수는 반드시 { get  set } 을 모두 구현해야 한다. 

 

class Student: DigitalMediaStudent {
    var myLaptop:String?
    
    var laptop:String?{
        get{
            return self.myLaptop
        }
        
        set{
            self.myLaptop = newValue
        }
    }
    
    /*
    // Error : get과 set모두 구현해야 한다.
    var laptop:String?{
        get {
            return self.myLaptop
        }
    }
    */
    
    /*
        method code
    */
}

 

메소드의 경우도, 필수로 구현해야 하는 메소드들은 반드시 구현을 해야 한다.

일반적으로 나열된 메소드 중에서 하나라도 구현하지 않으면 오류 메시지가 나타난다.

 

그러나, 메소드 중에도 노트북의 유무처럼 옵셔널로 지정하는 것들이 있을 수 있다. 3D Modeling 수업의 경우 들을 수도 있지만 듣지 않을 수도 있다. 이러한 선택적인 메소드 구현을 위해서는 @objc 키워드와 optional 를 같이 작성하면 된다.

@objc protocol DigitalMediaStudent {
    var name:String { get }
    var laptop:String? { get set }
    
    func doDataStructure()
    func doOperatingSystem()
    
    @objc optional func do3DModeling()
}

위와 같이 해당 protocol 키워드 앞과 함수 앞에 @objc를 붙이고 함수의 경우 optional 도 함께 작성하면 된다.

 

만약, 개발자가 레이싱 게임을 개발한다고 가정해보자.

레이싱의 종류는 자동차가 될 수도 있고 오토바이가 될 수도 있고 비행기가 될 수도 있다.

 

이 때 공통적인 기능을 빼서 부모 클래스로 만들고 이를 상속하는 클래스들을 작성하여 코드를 설계할 수 있다.

(만약, 앱의 배경화면이 모두 systemPink 색인 경우, BaseViewController를 만들어서 배경색을 지정한 다음, InfoViewController: BaseViewController .. 처럼 상속해서 사용할 수 있다.)

 

이러한 설계는 납득은 가능하지만 부작용이 있다.

만약 공통적인 기능을 묶어서 만든 Base 클래스에 대해서 이 기능을 필요로 하지만 전혀 다른 요소를 추가하게 될 경우는 어떻게 해야 할까?

-> 공통점들도 있겠지만 불필요한 것들도 상속될 수도 있고 메소드로 처리하자니 중복이 되는 경우도 있다.

 

이러한 상황에서 프로토콜의 장점이 보인다.

프로토콜은 보다 좋은 확장성과 재사용성을 갖고 있다.

 

Protocol As Type

기본적으로 프로토콜은 일종의 타입이다.

그렇기 때문에 프로토콜 간의 채택도 가능하다.

protocol Readable{
    func read()
}

protocol Writeable {
    func write()
}

protocol ReadWriteable: Readable, Writeable{ }

protocol ReadWriteTalkable: ReadWriteable {
    func talk()
}

class CanRead: Readable {
    func read(){
        print("I can read!")
    }
}

class CanWrite: Writeable {
    func write() {
        print("I can write!")
    }
}

struct CanReadWrite: ReadWriteable {
    func read() {
        print("I can raad!")
    }
    func write() {
        print("I can write!")
    }
}

struct CanReadWriteTalk: ReadWriteTalkable {
    func read() {
        print("I can raad!")
    }
    func write() {
        print("I can write!")
    }
    func talk() {
        print("I can talk!")
    }
}

print("string" is String) // true
print(CanRead() is Readable) // true
print(CanReadWrite() is Writeable) // true
print(CanReadWriteTalk() is ReadWriteable) // true

클래스 상속의 경우는 클래스끼리만 가능했지만, 프로토콜의 경우, 값 타입인 구조체와 열거형에도 적용할 수 있다.

(= 이런 점에서 넓은 확장성과 재사용성을 보장한다고 할 수 있다.)

 

프로토콜을 활용한 예시

앱을 만드는 프로젝트에서 프로토콜을 어떻게 활용할 수 있을까?

(규칙처럼 사용되는 부분들이 있을 때 중복되는 코드를 줄이거나 오류를 막기 위해서 사용할 수 있다.)

 

뷰 컨트롤러 식별자 또는 셀의 identifier 에 적용할 수 있다.

import UIKit

protocol ReusableViewProtocol {
    static var reuseIdentifier: String { get }
}

extension UIViewController: ReusableViewProtocol {
    static var reuseIdentifier: String {
        return String(describing: self)
    }
}

extension UITableViewCell: ReusableViewProtocol {
    static var reuseIdentifier: String {
        return String(describing: self)
    }
}

extension UICollectionViewCell: ReusableViewProtocol {
    static var reuseIdentifier: String {
        return String(describing: self)
    }
}

이렇게 작성을 한 뒤에 실제 사용하는 곳에서, 따로 코드를 중복해서 작성하지 않아도 바로 reuseIdentifier를 사용할 수 있다.

 

또는 화면 전환에도 사용할 수 있다.

import UIKit

protocol Presentable {
  func toPresent() -> UIViewController?
}

extension UIViewController: Presentable {
  func toPresent() -> UIViewController? {
    return self
  }
}

이런 식으로 작성하여, coordinator 패턴에 적용할 수 있다.

'iOS' 카테고리의 다른 글

Pagenation  (0) 2022.08.05
.gitignore  (0) 2022.08.05
Network - 가볍게  (0) 2022.08.02
SSAC - 다마고치 프로젝트 피드백 정리  (0) 2022.08.02
0729 Q&A 정리  (0) 2022.08.01