본 포스팅은 '스위프트 프로그래밍 (3판)' 도서와 앨런 Swift 문법 마스터스쿨 강의를 참고하여
Swift 프로그래밍에 대해 정리하는 글입니다.
혹시 틀린 부분이 있거나 질문이 있으시다면 언제든지 댓글 달아주시면 정말 감사하겠습니다 :)
프로토콜
프로토콜이란 특정 역할을 하기 위한 메서드, 프로퍼티, 기타 요구사항 등의 청사진(그래서 구현은 없고 형태만 있음)이다.
구조체, 클래스, 열거형은 프로토콜을 채택하여 프로토콜을 구현할 수 있다.
protocol 프로토콜 이름(대문자로 시작){
}
❓ 상속 vs 프로토콜
1) 상속은 클래스만 상속 가능하며, 다중 상속이 불가하다.
프로토콜은 클래스, 구조체, 열거형에서 채택 가능하며 다중 채택이 가능하다.
-> 꼭 클래스가 아니라 구조체에서도 공통된 메서드를 실행하게 하고싶다면?
2) 상속은 상위 클래스의 속성과 메소드를 모두 따라갈 수 밖에 없어서 필요하지 않은 속성/메소드들도 같이 상속되는 불편한 점이 존재하여
프로토콜을 사용하면 해당 인스턴스에 필요한 기능만 구현할 수 있다.
-> 꼭 모든 속성/메서드를 상속을 하지 않고도 일부만 사용하고 싶다면?
✅ 채택, 구현
어떠한 클래스, 구조체, 열거형이 프로토콜을 따르고자 할 때 ": 프로토콜이름" 으로 지정하는 것을 "채택" 이라고 한다.
그리고 프로토콜의 요구사항을 구현하는 것을 "구현" 이라고 한다.
protocol MyProtocol { // 정의
func doSomething() { }
}
class MotherClass {
func doSomething() { }
}
// 채택 (클래스, 구조체, 열거형 다 가능)
class MyClass: MotherClass, MyProtocol { // 상위클래스인 FamilyClass를 먼저 선언
override func doSomething() { // 재정의
}
// MyProtocol 구현
func doSomething() {
print("do")
}
}
프로토콜 요구사항 정의
1) 속성
- var로 선언 (let이 안되는 이유는, 프로토콜은 최소 요구사항을 구현하는 곳이고 채택한 곳에서 let 상수로 구현할 수 있으므로 변할 수 있다는 점에서 var로 선언해야함)
- 읽기 전용 속성 ==> 채택: let/var 저장속성, 읽기 계산 속성, 읽기/쓰기 계산 속성
- 읽기/쓰기 속성 ==> 채택: var 저장 속성, 읽기/쓰기 계산 속성
- 타입 속성 ==> 채택: static(타입 저장/계산)/class (타입 계산) 속성
protocol RemoteControl {
var id: String { get } // ===> let 저장속성 / var 저장속성 / 읽기계산속성 / 읽기,쓰기 계산속성
var name: String { get set } // ===> var 저장속성 / 읽기,쓰기 계산속성
static var type: String { get set } // ===> 타입 저장 속성 (static)
// ===> 타입 계산 속성 (class)
// func getId() -> Int // 리턴타입까지가 헤드부분임
}
struct TV: RemoteControl {
var id: String { // 계산 속성 -> setter가 추가될 수 있는 가능성때문에 var 선언
return "hi"
}
// var id: String = "hi" 가능
// let id: String = "hi" 가능
var name: String = "삼성티비"
static var type: String = "리모콘" // class 불가 -> 상속은 class만 가능하니까
// static var type: String {
// return "리모콘"
// }
}
2) 메소드
- mutating: 구조체도 자기 자신의 속성을 변경하도록 하는 키워드 (클래스에서도 사용 가능함)
- 이외에 메소드 요구사항 정의 제한은 크게?없는듯
protocol Togglable {
mutating func toggle() // 클래스에서는 Mutating 안붙여도 됨
}
enum OnOffSwitch: Togglable {
case on
case off
mutating func toggle() {
switch self { // .on .off
case .off:
self = .on
case .on:
self = .off
}
}
}
protocol DataList {
subscript(idx: Int) -> Int { get } // (서브스크립트 문법에서) get 필수 (set 선택)
}
struct DataStructure: DataList {
subscript(idx: Int) -> Int {
get {
return 0
}
set {
}
}
}
3) 생성자 (프로젝트 할때 흔하지는 않음)
- 클래스에서 생성자 채택시, required를 붙여야함 (왜냐? 하위 클래스가 해당 클래스를 상속할 때 해당 생성자를 반드시 구현해야 함을 명시하는 것임)
- 이 때, 지정생성자를 따로 만들지 않으면 required init은 자동 상속됨
- 만약에 class 앞에 final을 붙이면 required를 붙이지 않아도 무관 (왜냐 final을 붙이면 상속을 못하니까)
타입으로써의 프로토콜 (일급 객체로서의 프로토콜)
프로토콜은 타입이므로 1) 변수에 할당하고, 2) 파라미터로 전달하며 반환할 수 있다.
따라서 타입캐스팅을 통해 구체적인 타입으로 사용하는 것 또한 가능하다.
tv is Remote
sbox1 is Remote
// 업캐스팅(as)
let newBox = sbox1 as Remote
newBox.turnOn()
newBox.turnOff()
// 다운캐스팅(as?/as!)
let sbox2: SetTopBox? = electronic[1] as? SetTopBox
sbox2?.doNetflix()
// 인수로 전달 가능
protocol Drawable {
func draw()
}
class Circle: Drawable {
func draw() {
print("원을 그리다")
}
}
class Rectangle: Drawable {
func draw() {
print("네모를 그리다")
}
}
// 인수로 전달 가능
func drawShapes(_ arr:[Drawable]) {
arr.forEach { $0.draw() }
}
let circle = Circle()
let rec = Rectangle()
drawShapes([circle, rec])
프로토콜의 상속, 클래스 전용 프로토콜
프로토콜은 다중 상속이 가능하며, 다중 상속 시 다른 프로토콜의 모든 요구 사항을 구현해야한다.
AnyObject: 클래스 전용 프로토콜로 만들고자 한다면 AnyObject 프로토콜을 상속하면 된다.
프로토콜 조합(합성)
protocol1 & protocol2 로 프로토콜을 조합하여 한꺼번에 채택할 수 있게 할 수 있으며
타입으로 지정하고자 할 때 해당 프로토콜 2개 모두를 따르는 타입만 저장 가능함
프로토콜의 선택적 요구
@objc 키워드를 붙여서 프로토콜에서 요구사항 구현 시, 선택적인 멤버로 구현 가능하도록 할 수 있음
- objc: Objective-C 코드에서 사용할 수 있도록 하는 키워드
프로토콜 앞에는 @objc 키워드를, 메서드 앞에는 @objc optional 을 붙이면 해당 멤버는 선택적 구현이 가능하다. (구현하지 않아도 무관하도록 바뀜)
단, 유의할 점은 @objc 키워드를 붙이면 해당 프로토콜이 클래스 전용 프로토콜로 바뀌기 때문에 구조체/열거형에서는 사용 불가하다.
@objc protocol Flyable {
@objc optional var wing: Int { get }
@objc optional func fly()
func sayName()
}
class Fly: Flyable {
var wing = 5
func sayName() {
print("name")
}
}
let fly = Fly()
fly.sayName()
프로토콜 지향 프로그래밍
애플은 2015년 6월, WWDC에서 스위프트는 "프로토콜 지향 언어" 라고 선언하였음.
*프로토콜 지향 프로그래밍이란?
https://developer.apple.com/videos/play/wwdc2015/408/
- 프로토콜을 채택하는 타입들이 모두 같은 메소드를 쓴다면 ...? => 많은 코드 중복과 유지보수의 어려움 발생
- 익스텐션과 프로토콜의 결합은 이러한 코드의 중복 구현 불편함을 제거해줌 => 애플은 프로토콜의 채택을 확장에서 구현하는 것을 권장하고 있음.
익스텐션과 프로토콜의 결합
- 프로토콜 초기 구현
protocol Remote {
func turnOn()
func turnOff()
}
class TV1: Remote {
func turnOn() {
// 구현
}
func turnOff() {}
func doAnotherAction(){
// 구현
}
}
// => 만약에 TV가 여러개라면...? 언제다 turnOn/turnOff를 다 구현하냐! 너무 귀찮고 코드 중복이 심하다.
// => 익스텐션과 결합함으로써 해결!
extension Remote {
func turnOn() { print("리모콘 켜기") }
func turnOff() { print("리모콘 끄기") }
func doAnotherAction() {
print("리모콘 또 다른 동작")
}
}
var tv1: Remote = TV1()
tv1.turnOn() // 리모콘 켜기
tv1.turnOff() // 리모콘 끄기
tv1.doAnotherAction()
- 해당 예시와 같이 코드의 중복을 없애줌으로써 유지보수할 수 있는 코드로 만들 수 있다.
이렇게 했을 때 메서드가 실행되는 규칙이 존재함
- 프로토콜 확장의 적용 제한
프로토콜을 확장할 시, 특정 프로토콜을 채택한 프로토콜에만 확장이 적용되도록 제한할 수 있음
protocol Remote {
func turnOn()
func turnOff()
}
protocol Bluetooth {
func blueOn()
func blueOff()
}
extension Bluetooth where Self: Remote {
func blueOn() { print("블루투스 켜기") }
func blueOff() { print("블루투스 끄기") }
}
위임 패턴(델리게이트 패턴)
클래스나 구조체가 자신의 책임이나 임무를 다른 타입의 인스턴스에게 위임하는 것
보통 UITableView나 UITextField 등에서 이들의 기능을 뷰컨트롤러가 대신 수행하고싶을 때 사용
'🐦 Swift' 카테고리의 다른 글
[Swift] 에러 처리, Result Type (0) | 2023.07.20 |
---|---|
[Swift] ARC(Automatic Reference Counting) (0) | 2023.06.29 |
[Swift] 확장 (익스텐션, Extension) (0) | 2023.06.11 |
[Swift] 클래스의 상속, 생성자, 타입캐스팅 (0) | 2023.06.11 |
[Swift] 속성(property)와 메소드(method) (0) | 2023.06.08 |