 Apple Lover Developer & Artist

영속적인 디자인에 현대의 공감을 채워넣는 공방입니다

 Apple/Stanford iOS Programming (UIKit)

Lecture 4 Review - More Swift

singularis7 2021. 12. 3. 00:09
반응형

Overview

  • Swift 에 관한 세부 개념을 다루며 가장 중요한 주제인 프로토콜과 Closure 에 관하여 다룬다.
  • 일반적으로 타입으로서의 함수도 다루게 된다.
  • String 도 다루기는 하는데 주요한 주제는 아니다.

  • 기존에 작성된  Concentration 게임 모델은 클래스로 작성되어있었다. struct 가 적합할 것 같아서 타입을 선언해주는 방법을 바꾸게 되었다. 왜냐하면 어딘가로 넘겨주지 않고 컨트롤러 내부에서만 위치하기 때문이다.
  • struct 로 키워드를 바꾸니까 오류가 발생한다. self 가 immutable 하기 때문이다. 
  • 일단 함수에 mutating 키워드가 사용되지 않았기 때문에 타입의 상태를 바꾼다고 생각하지 않는다. 
  • 하지만 카드를 선택하는 과정은 faceup 상태를 바꾸기 때문에 mutating 키워드를 붙여줘야 한다.

  • 그렇다면 의문점이 들 수 있다. 왜 연산 프로퍼티에서는 mutating 키워드를 붙여주지 않아도 동작하는가?
  • 이미 getter 와 setter 를 통해 mutating 을 구분해줄 수 있기때문이다. 
  • 그렇다면 private(set) 으로 명시된 변수는 왜 mutating 을 명시해주지 않아도 동작하는가?
  • 외적으로는 read-only 지만 내부에서는 writeable 하는 것이 보장되기 때문이다.
  • 만약 let 이었다면 mutable 하지 않는다.

 Struct가 필요한 상황은 무엇이고 Class 가 필요한 상황은 무엇인가?

  • 중요한 질문이다. 이해하는 것이 중요한 내용이다
  • struct는 값타입이며 heap 에서 살지 않는다. 함수나 변수로 넘겨질 때 매번 복사가 발생한다.
  • 생각해보면 비효율적일 수 있는데 실제로 Swift 는 똑똑하기 때문에 mutating 이 발생할 때 복사 작업을 수행하게 된다.
  • 이러한 동작 방식을 copy-on-write 시멘틱이라고 부른다. -> 이와 같은 사유로 mutating 하는 것을 신경써줘야 한다.
  • 클래스는 힙에서 유일하게 존재하며 다른데로 넘겨도 포인터만 넘겨준다. copy on write 개념이 존재하지 않는다.

Protocol

  • Swift 에서 4가지 필수적인 자로구조 컨셉 중 하나이다. (class, struct, enum, protocol)
  • 프로토콜은 기본적으로 구현되지 않은 메서드나 변수의 목록이다.
  • 하지만 스위프트에서 프로토콜의 쓰임세는 매우 강력하다.
  • 프로토콜 문법과 함께 어디에 사용할 수 있을지, 왜 프로토콜이 가치있는지 살펴보게 될 것이다.
  • 프로토콜은 본질적으로 호출자가 원하는 모든 API를 호출하는 방법이다.
  • 모든 구조체, 열거형 클래는 모든 것을 전달할 수 있다.
  • 동시에 수신하는 입장에서는 자신이 원하는 행위를 지정해줄 수 있다.
  • 수신자와 발신자 모두 자신이 원하는 것을 할 수 있게된다.
  • 이 모든 작업을 수행하기 위해 필요한 것은 구현되지 않은 변수와 함수의 명단이다.
  • API 로써 어떻게 활용하는가는 호출자와 수신자가 행위를 갖고 그들이 원하는 방식으로 표현하는 것이다.

 

  • 프로토콜의 장점은 API 를 유연하고 표현적인 방식으로  만들어준다는 것이다.
  • 특히 MVC 구조에서 본 blind structured communication 에서 강력한 힘을 발휘한다.
  • 뷰와 컨트롤러가 서로 소통할 때 will, did, should 를 활용하며 data at count 와 같은 소통을 할 때를 생각해보자
  • blind 된 방식으로 소통이 이뤄지는 이유는 view 가 제네릭 타입이기 때문이다. 반면 컨트롤러는 구체적이다.
  • 위 상황에서 프로토콜이 커뮤니케이션을 동작시키는 방법이다.

 

  • mandating(명령) 에도 적합한 방식이다.
  • 예를 들어 사전식 자료구조는 해시 테이블인데 딕셔너리의 키값은 반드시 hashable 해야 한다.
  • 해시값을 가져올 수 있어야하며 아니라면 해싱해서 해시 테이블에 담아줄 수 없다.
  • 프로토콜은 dictionary 를 선언할 때 hashable 하지 않은 키를 사용하지 못하도록 만들어줄 때 활용된다.

 

  • 프로토콜은 서로 다른 타입간에 함수(기능)를 공유하는 용도로 사용된다.
  • 객체지향에서의 상속을 사용하여 base 클래스를 공유하지 않은 이질적인 타입에서 함수를 공유하는데도 유용하다.
  • 더 나아가, 문자열, 배열, countable range 를 생각해보면 모두 서로 유사한 Collection 으로 생각할 수 있다.
  • 따라서 서로 다른 3개의 개념은 Collection 이라는 일부 개념을 공유하고 있다.
  • 프로토콜은 사물의 컬렉션에 대해 알고 있는 일종의 공통의 base 클래스에서 모든 것을 상속받지 않아도 타입간에 유사성을 공유할 수 있는 메커니즘을 제공합니다.
  • 어떤 측면에서 프로토콜은 다중 상속을 지원한다. 
  • 프로토콜은 변수나 함수만 선언하기 때문에 기능을 상속하는 것이다.

  • 여튼 프로토콜도 타입이며 일급 시민이다.
  • 이제부터 프로토콜의 자세한 사항을 3가지로 나누어 이해해보는 시간을 가져보고자 한다.
  1. 클래스, 열거형, 구조체의 선언이 있는 것처럼 프로토콜을 선언하는 방법이 존재한다.
    프로토콜은 함수와 변수의 명단이 선언된 것이다.
  2. 클래스, 구조체, 열거형이 프로토콜을 채택해서 구현한다. 
    프로토콜을 구현해줄 친구가 필요하며 구현하려면 클래스, 구조체, 열거형이 필요하기 때문에 claim 해야한다.
  3. 변수나 메서드를 실제로 구현하는 클래스, 구조체, 열거형의 코드이다.

  • 프로토콜에 정의된 변수나 메서드는 반드시 구현되야 하는 것입니다.
  • 어떤 타입이 손을 들고 프로토콜을 구현하겠다고 주장하면 해당 프로토콜에 선언된 모든 변수와 함수를 구현해줘야 한다는 의미이다.
  • 하지만 objective-c 에서는 프로토콜이 옵셔널 함수를 가질 수 있었으며 구현 여부를 선택할 수 있는 경우가 있었다.
  • Swift 에서는  @objc 키워드를 붙여서 Objective-C 에서의 프로토콜로 정의해줄 수 있다.
  • 이때 발생하는 차이점은 프로토콜에 선언된 메서드의 구현여부를 선택 사항으로 표시할 수 있다는 점이다.
  • 옵셔널 한 메서드를 구현할 수 있지만 원하지 않는 경우라면 반드시 구현할 필요는 없다는 의미이다.
  • 위 작업이 필요해지는 상황은 objective-c 세계에서 사용되던 iOS API 를 사용하는 경우이다.
  • 대표적으로 delegation이 있을 수 있으며 이는 View 와 Controller 사이에 blind 방식으로 통신하도록 도움을 줬다.
  • @objc 와 함께 옵셔널하게 구현 가능한 메서드를 볼 수 있는데 objective-c 상호 호환을 위한 코드이니 놀라지 말자!

Protocol 을  선언하는 방법

protocol SomeProtocol : InheritedProtocol1, InheritedProtocol2 {
    var someProperty: Int { get set }
    func aMethod(arg1: Double, anotherArgument: String) -> SomeType
    mutating func changeIt()
    init(arg: Type)
}
  • 클래스를 선언하는 방법과 유사하다.
  • someprotocol 은 프로토콜의 이름이며 콜론 옆에 붙은 친구들은 기능을 상속하는 프로토콜이다.
  • 즉, some protocol 을 채택한 타입은 InheritedProtocol1 과 InheritedProtocol2 에 정의된 기능들도 함께 구현해줘야 한다.
  • 이 과정을 프로토콜 상속이라고 부른다.
  • 프로토콜의 내부에는 선언만 있으며 구현은 존재하지 않는다.
  • 변수 옆에 get, set 은 읽기 전용인지 읽고 쓸 수 있는지를 명시해줘야한다.
  • 구조체에서 프로토콜을 채택했는데 해당 메서드가 자신의 값을 수정할 가능성이 있는 경우에 mutating 으로 표시되어야한다.
  • 구조체에서 구현될 수 없고 오직 클래스에서만 구현될 수 있는 경우에 과거에는 class 키워드를 상속부에 적어줬지만 지금은 deprecate 되었다. -> 다시 알아보니 AnyObject 가 사용된다 클래스로 제약을 걸어도 iOS 의 99% 는 해당 기능을 구현할 수 있다.
  • 프로토콜에 init 을 선언할 수 있으며 해당 프로토콜을 채택하는 타입은 init 을 구현해줘야 한다는 의미이다.
  • 다음은 클래스가 프로토콜을 채택하는 방법이다.
class SomeClass : SuperclassOfSomeClass, SomeProtocol, AnotherProtocol {
    // implementation of SomeClass here
    // which must include all the properties and method in SomeProtocol & AnotherProtocol
}
  • 콜론 이후에 super class, 프로토콜, 순으로 적어주면 된다.
  • 소스코드 컴파일러는 요구사항이 충족되었는지 확인해보고 아닐 경우 컴파일 에러를 발생시킨다.
  • 프로토콜은 다중 상속을 지원하기 때문에 class 선언부 이후에 추가적으로 필요한 프로토콜을 추가적으로 상속받을 수 있다.
  • 프로토콜에 init이 선언되어있는데 class 가 이 프로토콜을 채택할 경우 앞에 required 키워드를 붙여줘야 한다.
  • 프로토콜을 충족시키는 방법으로 extension 을 활용할 수 있다.

  • 타입으로써 프로토콜의 활용 예시에 관해 살펴본다.
  • Car, Shape 라는 타입을 선언하였다.
  • 이 때, 두개의 타입이 Moveable 이라는 동일한 프로토콜을 채택하고 있음을 볼 수 있다.
  • 아래에 Car, Shape에 대한 인스턴스를 생성한 것을 확인할 수 있다.

  • 위 상황에서 Moveable 타입의 변수에 prius 혹은 square 인스턴스를 담아줄 수 있을까?
  • 정답은 Yes 이다! -> 다만 프로토콜에 선언된 행위만 할 수 있다. Car 혹은 Shape 만이 할 수 있으면서 프로토콜에 명시되지 않은 행위는 호출할 수 없다. (아주 중요한 부분이다!)

  • Moveable 을 타입으로 채택할 경우 Car 과 Square 모두를 담아줄 수 있으며 이 프로토콜 타입의 컬렉션에는 Car 과 Shape 모두를 담아줄 수 있다.
  • 함수의 인자 타입이 Moveable  인 경우에도 마찬가지로 Car 과 Shape 모두를 넘겨줄 수 있으며 사유는 둘다 Moveable 행위를 할 수 있기 때문이다.
  • 다만 두가지 이상의 프로토콜을 충족할 것을 요구하는 경우 Moveable 만 채택한다고 함수의 인자로 넘겨줄 수 없다.

  • iOS UIKit 에서 프로토콜이 사용되는 예시를 생각해보자!
  • MVC Delegation 패턴을 구현하는데 활용됩니다.
  • 대표적으로 ScrollView, TableView 를 예시로 들어볼 수 있다.
  • View 는 Generic 한 친구인데 구체적인 Controller 와 어떤 방식으로 소통하는가?
  • Apple 에서는 우리가 구현한 내용에 대해 알지 못하는데 말이다.. -> 해결책: 프로토콜을 사용한다
  • 프로토콜의 가장 중요한 활용 예시로 View 와 Controller 사이에 blind Communication 을 구현해주는 것이다.
  • 동작하는 방법은 다음과 같다.
  1. View 는 delegation 프로토콜을 선언한다 (예를 들어 컨트롤러가 View 를 위해 어떤 일을 수행해줘야 하는지를 생각해볼 수 있다.)
  2. View 의 API 는 weak (약한 참조) delegate 프로퍼티를 갖고 있으며 delegation 프로토콜을 타입으로 갖고 있다.
  3. View는 자신이 소유할 수 없는 것 혹은 자신이 소유한 것을 컨트롤 하기위해 delegate 프로퍼티를 사용한다.
  4. 컨트롤러는 해당 프로토콜을 구현한다.
  5. 컨트롤러가 자신을 delegate 변수로 선언한다.
  6. 컨트롤러는 프로토콜의 명세를 모두 구현해야 한다.

이것이 전부이다. 이제 뷰가 컨트롤러에 연결되었으며 View는 컨트롤러가 누구인지 모르는 상태이기 때문에 여전히  generic 하며 재사용가능한 상태이다. 이런 메커니즘은 iOS 내부에서 흔하게 발견할 수 있다.


예시 UIScrollView

UIScrollView 에는 delegate 변수가 존재한다. 이 때 delegate 변수는 약한 참조로 이뤄지는데 사유는 뷰는 컨트롤러에 대한 포인터가 있으며 컨트롤러는 뷰에 대한 많은 포인터가 있다는 점을 알고 있기 때문이다. 

누가 내 will did 와 같은 것을 행동해야 하는지 pointing 하고 싶습니다. 근데 그 작업이 내가 포인팅하는 친구가 heap 에 남아 있는 동안만 이뤄졌으면 좋겠습니다. 만약 내가 가리키는 친구가 heap 에서 떠나면 나도 nil 로 설정해줘도 좋습니다. 라는 의도가 담겨있다.

nil로 설정되면 메시지를 보내는 것도 중단하게된다!


프로토콜의 다른 예시이다!

  • 앞서 Dictionary 의 키값이 Hashable 해야 한다는 점을 언급한 기억이 있을 것이다.
  • API 에서 Hashable 하도록 강제하는 방법은 무엇이 있을까? -> Hashable 프로토콜을 활용하면 된다.
  • Hashable 프로토콜은 Equatable 프로토콜을 상속받아서 선언되었다.
  • 무언가를 해시할 때 고유한 값을 가질 가능성이 높지만 언제나 그런 것은 아니다.
  • 따라서 후작업으로 equals 를 통해 비교해주는 작업이 필요하다.
  • Hashable 은 해시 가능한 정수값 변수를 구현해줄 것을 요구한다!

  • Equatable 은 static 메서드를 정의해줄 것을 요구하는데  == 연산자 메서드를 구현해줄 것을 요구한다.
  • type function 이기 때문에 Int의 경우 Int에 대하여 정의하는 == 메서드가 될 것이다. 
  • Hashable 은 Equatable 을 상속하며 Dictionary 의 키는 Hashable 하도록 설계되었다.
  • 따라서 딕셔너리의 선언부를 보면 다음과 같은 제약 사항이 걸려있을 것이다.

 

반응형