반응형
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가지로 나누어 이해해보는 시간을 가져보고자 한다.
- 클래스, 열거형, 구조체의 선언이 있는 것처럼 프로토콜을 선언하는 방법이 존재한다.
프로토콜은 함수와 변수의 명단이 선언된 것이다. - 클래스, 구조체, 열거형이 프로토콜을 채택해서 구현한다.
프로토콜을 구현해줄 친구가 필요하며 구현하려면 클래스, 구조체, 열거형이 필요하기 때문에 claim 해야한다. - 변수나 메서드를 실제로 구현하는 클래스, 구조체, 열거형의 코드이다.
- 프로토콜에 정의된 변수나 메서드는 반드시 구현되야 하는 것입니다.
- 어떤 타입이 손을 들고 프로토콜을 구현하겠다고 주장하면 해당 프로토콜에 선언된 모든 변수와 함수를 구현해줘야 한다는 의미이다.
- 하지만 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 을 구현해주는 것이다.
- 동작하는 방법은 다음과 같다.
- View 는 delegation 프로토콜을 선언한다 (예를 들어 컨트롤러가 View 를 위해 어떤 일을 수행해줘야 하는지를 생각해볼 수 있다.)
- View 의 API 는 weak (약한 참조) delegate 프로퍼티를 갖고 있으며 delegation 프로토콜을 타입으로 갖고 있다.
- View는 자신이 소유할 수 없는 것 혹은 자신이 소유한 것을 컨트롤 하기위해 delegate 프로퍼티를 사용한다.
- 컨트롤러는 해당 프로토콜을 구현한다.
- 컨트롤러가 자신을 delegate 변수로 선언한다.
- 컨트롤러는 프로토콜의 명세를 모두 구현해야 한다.
이것이 전부이다. 이제 뷰가 컨트롤러에 연결되었으며 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 하도록 설계되었다.
- 따라서 딕셔너리의 선언부를 보면 다음과 같은 제약 사항이 걸려있을 것이다.
반응형
' Apple > Stanford iOS Programming (UIKit)' 카테고리의 다른 글
Lecture 6 : Multitouch (0) | 2021.12.27 |
---|---|
Lecture 13 : TableView & Collection View (0) | 2021.12.03 |
[💻 Xcode] 🍯꿀팁 단축키 (1) | 2021.12.02 |
Lecture 2 Review: MVC (0) | 2021.12.02 |
Lecture 1 Review: Developing iOS 11 Apps with Swift (0) | 2021.12.01 |