[iOS]Protocol 사용방법(주의할 점)

신용철·2020년 9월 17일
0

iOS_Swift

목록 보기
4/6

(출처: iOS 13 Programming Fundamentals with Swift, Swift, Xcode, and Cocoa Basics – Matt Neuburg)

1. Protocol을 타입으로 사용하는 경우(Type testing과 casting)

  • protocol을 채택한 객체가 맞는지 Bool을 return하는 방식으로 판별할 수 있습니다.

  • protocol을 채택한 객체를 변수나 함수의 parameter로 받아서 사용하는 경우 type casting을 해주어야 합니다. 그렇지 않으면 객체의 고유한 속성(protocol에 없는)을 사용할 수 없습니다.

    protocol Protocol_A { }
    
    class Class_B: Protocol_A {
       func b() { }
    }
    
    class Class_C: Protocol_A { }
    
    //특정 객체인지 여부를 판별
    func isB(object: Protocol_A) -> Bool{
           return object is Class_B
       }
    
    //객체의 고유 속성을 사용하기 위해 casting 필요
    func funcOfB (object: Protocol_A) {
          object.b() //compile error
          (object as? Class_B)?.b()
    }
    

2. Protocol 선언 방법

Properties의 경우

  • var만 사용할 수 있습니다.(let은 사용 불가)
  • {get} 또는 {get set} 으로 읽기 쓰기 제한을 할 수 있습니다. {get}의 경우 writable로 사용할 수 있지만 {get set}을 read-only로 사용할 수는 없습니다.
  • static을 붙여서 static/class 프로퍼티를 선언할 수 있습니다. (class adopter는 이를 class 프로퍼티로 사용가능합니다.)

Method의 경우

  • 함수의 body부분을 구현하지 않습니다. param과 return 타입은 지정할 수 있습니다. 함수를 구현하고 싶다면 extension protocol을 사용하면 됩니다.
  • init과 subscript 사용할 수 있습니다. 참고로, {get}, {get set}을 사용한다는 점만 제외하면 protocol에서 subscript를 사용하는 것은 object type에서 subscript를 사용하는 것과 같습니다.
  • 앞에 static을 붙여서 class method로 사용할 수 있습니다.
  • struct, enum에서 mutating을 사용할 수 있도록 하기 위해서 mutating을 붙여줘야 합니다. (protocol을 채택한 객체에서 실제로 사용할 때는 mutating을 생략할 수 있습니다.)

3. Protocol에 다른 Protocol 채택(secondary hierarchy of types)

  • Protocol은 다른 Protocol들을 채택할 수 있습니다.
  • 이를 secondary hierarchy of types이라고 합니다. 실제로 Swift의 header파일에는 이러한 protocol이 상당히 많습니다.
  • 다른 protocol을 채택한 protocol을 채택한 객체는 채택한 protocol의 채택한 protocol의 요구조건까지도 모두 충족시켜야 합니다. 말이 상당히 어려운데 밑에 예를 보겠습니다.
protocol A {
  func a()
}
protocol B {
  func b()
}
//프로토콜 C가 다른 프로토콜 A, B를 채택합니다.
protocol C: A, B {
  func c()
}

class SomeClass: C {
  func c()
  //프로토콜 C 뿐만 아니라,
  //C가 채택한 프로토콜 A, B의 요구사항도 충족시켜야합니다.
  func a()
  func b()
}

4. Protocol Composition(프로토콜 결합구성)

  • '&' 기호를 이용하여 한번에 여러개의 protocol을 채택하여 타입구성을 할 수 있습니다.
var a: protocolA & protocolB & protocolC 
  • protocol과 class를 혼합하여 사용하는 것도 가능합니다. 여기서 주의할 점은 아래 예를 볼 때, 변수 b에는 protocolA을 채택하고 classA를 상속받은 subclass의 인스턴스가 할당되어야 한다는 것입니다. 그렇지 않으면 b변수로 classA & protocolA 메세지에 접근이 불가능합니다.
var b: classA & protocolA
  • protocol에 class를 채택하여 이 protocol을 채택할 수 있는 객체의 타입을 제한할 수도 있습니다. 위의 예제와 같은 기능을 하는 다른 표현법입니다.
protocol A : classA { }

class B {
  var b: A //classA를 동시에 포함합니다.
           //즉, class A & protocol A 과 동일합니다.
}
  • protocol에 어떤 class를 제한(혹은 채택)할지 미리 정할 수 없는 경우(struct, enum은 불가)에는 AnyObject를 사용할 수 있습니다. 만약 아래와 같은 형식의 protocol을 본다면 무엇인지 정해지지는 않았지만 나중에 특정 class로 용도가 제한될 것이라 생각하면 됩니다.
protocol A : AnyObject { }
//또 다른 표현 법으로 AnyObject 대신 class를 사용가능
//그러나 swift 5 이전 표기법(나중에deprecated 됩니다)
protocol A : class { }
  • 위의 프로토콜 사용 가능한 객체를 제한 하는 방법으로 where절을 사용할 수도 있습니다.
//Self는 이 프로토콜을 채택하게 될 객체를 의미합니다.
//주의: Self의 S는 대문자입니다!
protocol A Where Self: AnyObject { }

5. class protocol을 이용하여 Delegate 구현

  • 위 4번 항목에서 다룬 class를 채택한 protocol을 class protocol이라 합니다.

  • class protocol을 사용하는 주된 목적은 우리가 흔히 들어 알고 있는 delegate를 사용하기 위해서 입니다.

  • 위의 예에서 class protocol을 type으로하는 property를 선언할 경우 그 property에는 해당 class를 상속받고 동시에 그 protocol을 채택한 인스턴스 객체가 할당되어야 한다고 설명했습니다. 아래의 예를 보겠습니다.

  • protocol DelegateProtocol: AnyOject {
           func updateColor() -> UIColor
    {
    
    class CustomUIView: UIView {
    
         weak var delegate: DelegateProtocol?
         
         var color: UIColor = delegate?.updateColor()?? .white
         
    }
    
    class ViewController: UIViewController, DelegateProtocol {
        let myView: CustomUIView!
       
        func viewDidLoad() {
           myView.delegate = self
        }
        
        func updateColor() -> UIColor {
                
        }
    }
  • 위에서 var delegate의 타입은 class protocol 입니다. 그리고 이 property에는 UIViewController라는 class와 DelegateProtocol을 채택한 ViewController가 self라는 명령어로 할당되었습니다.

  • 여기서 App이 실행되게 되면 var delegate가 호출되는 시점에 ViewController가 추가로 인스턴스화 되면서 참조카운트가 1이 올라가게 됩니다. 결과적으로 ViewController에 대한 참조 카운트가 2가 되는 것입니다.

  • 강한순환참조가 발생되어 메모리의 누수가 발생되지 않도록 하기 위해 weak을 사용하였습니다.

  • delegate가 작동되는 원리는 인스턴스화 된 ViewController가 var delegate에 할당되면서 delgate에 "."입력하면 CustomView class에서 ViewController의 속성들에 접근을 할 수 있게 됩니다. 이로써 두화면의 속성들을 서로 연결하여 기능을 구현할 수 있게 됩니다.

  • 사실 단순히 두 class 인스턴스를 연결하여 사용하고 싶다면 protocol을 사용하지 않아도 무방합니다. 아래 코드를 보겠습니다.

 class CustomUIView: UIView {
 
      weak var delegate: UIViewController?
      
      var color: UIColor = delegate?.updateColor()?? .white
 }
 
 class ViewController: UIViewController {
     let myView: CustomUIView!
     
     func viewDidLoad() {
        myView.delegate = self
     }
     
     func updateColor() -> UIColor {
             
     }
 }
  • 위와 같이 코드를 작성해도 두 class는 연결이 됩니다. 즉, protocol을 사용하지 않아도 동일한 작업을 수행할 수 있습니다. 하지만 protocol을 사용하는 이유는 코드를 해석할 때 두 개의 class가 protocol에서 선언한 속성들을 기준으로 기능이 연결됨을 명시하기 위해서 입니다. 즉, 왜 두 class를 연결했는지 명확하게 보여주기 위해서 입니다.

6. Protocol내부에 initializer를 사용할 경우

  • init()이 구현된 protocol을 채택할 때는 required init() 의 형태로 init을 구현해주어야 합니다. 좀더 자세하게 설명하자면, subclass들은 고유의 지정생성자를 생성하면서 initializer 상속을 차단하기 때문에 반드시 required init을 명시해야합니다.
  • 단, final class의 경우 required를 사용하지 않아도 됩니다. 그 이유는 더이상 subclass가 생성되지 않음으로서 문제가 발생하지 않을 것을 보장하기 때문입니다.
  • 아래의 예에서 UIViewController는 NSCoding 이라는 프로토콜을 채택하고 있고 NSCoding은 init(coder:)생성자를 가지고 있습니다. 따라서 ViewController는 required init(coder:)를 구현해야 합니다.
  • 추가로, init(coder:) 의 조건을 충족시키지 않았음에도 불구하고 에러가 발생하지 않습니다. 그 이유는 flow Control과 관련이있습니다. 만약에 required init에 구현하고 싶은 기능이 있다면 fatalError("init(coder:) has not been implemeted") 부분을 지우고 작성하면 됩니다.
  • 최소한의 구현으로 super.init(coder: coder)를 사용할 수 있지만 현재의 class에서 초기화해야할 속성들이 있다면 먼저 해주어야 합니다.
  • Cocoa의 많은 class들이 NSCoding을 채택하고 있기 때문에 이와 같은 상황을 빈번하게 겪을 것이므로 잘 숙지해두는 것이 좋습니다.
    class viewController: UIViewController {
    	init() {
     	super.init()
         }
         
     required init?(coder: NSCoder) {
     		fatalError("init(coder:) has not been implemeted")
     	}
    }
 
   
profile
iOS developer

0개의 댓글