17. 서브스크립트
클래스, 구조체, 열거형에는 컬렉션, 리스트, 시퀀스 등 타입의 요소에 접근하는 단축 문법인 서브스크립트(subscript)를 정의할 수 있습니다.
서브스크립트는 별도의 설정자(Setter) 또는 접근자(Getter)등의 메서드를 구현하지 않아도 인덱스를 통해 값을 가져올 수 있습니다.
struct Student {
var name: String
var number: Int
}
class School {
var number: Int = 0
var students: [Student] = [Student]()
func addStudent(name: String) {
let student: Student = Student(name: name, number: self.number)
self.students.append(student)
self.number += 1
}
func addStudents(names: String...) {
for name in names {
self.addStudent(name: name)
}
}
// set,get을 지정하지않으면 get만 선언됨
subscript(index: Int) -> Student? {
if index < self.number {
return self.students[index]
}
return nil
}
}
let highSchool: School = School()
highSchool.addStudents(names: "MiJeong", "JuHyun", "JiYoung", "SeongUk", "Moonduk")
let aStudent: Student? = highSchool[1]
print("\(aStudent?.number) \(aStudent?.name)") // Optional(1) Optional("JuHyun")
하나의 타입이 여러개의 서브스크립트를 가질 수도 있습니다.
매개변수 타입이 다르면 여러용도로 서브스크립트를 사용할 수 있습니다.
struct Student {
var name: String
var number: Int
}
class School {
var number: Int = 0
var students: [Student] = [Student]()
func addStudent(name: String) {
let student: Student = Student(name: name, number: self.number)
self.students.append(student)
self.number += 1
}
func addStudents(names: String...) {
for name in names {
self.addStudent(name: name)
}
}
subscript(index: Int) -> Student? { //첫 번째 서브스크립트
get {
if index < self.number {
return self.students[index]
}
return nil
}
set {
guard var newStudent: Student = newValue else {
return
}
var number: Int = index
if index > self.number {
number = self.number
self.number += 1
}
newStudent.number = number
self.students[number] = newStudent
}
}
subscript(name: String) -> Int? {
get {
return self.students.filter{ $0.name == name }.first?.number
}
set {
guard var number: Int = newValue else {
return
}
if number > self.number {
number = self.number
self.number += 1
}
let newStudent: Student = Student(name: name, number: number)
self.students[number] = newStudent
}
}
// 세번째 서브스크립트
subscript(name: String, number: Int) -> Student? {
return self.students.filter{ $0.name == name && $0.number == number}.first
}
}
let highSchool: School = School()
highSchool.addStudents(names: "MiJeong", "JuHyun", "JiYoung", "SeongUk", "Moonduk")
let aStudent: Student? = highSchool[1]
print("\(aStudent?.number) \(aStudent?.name)") // Optional(1) Optional("JuHyun")
print(highSchool["MiJeong"]) // Optional(0)
print(highSchool["DongJin"]) // nil
highSchool[0] = Student(name: "HongEui", number: 0)
highSchool["MangGu"] = 1
print(highSchool["JuHyun"]) // nil
print(highSchool["ManGu"]) // Optional(1)
print(highSchool["SeongUk", 3]) // Optional(__lldb_expr_314.Student(name: "SeongUk", number: 3))
print(highSchool["HeeJin", 3]) // nil
복수의 서브스크립트을 통해서 highSchool에 들어가는 값이 다를때마다 다르게 set하고 다르게 값을 얻어오는 예제였습니다.
이름이나 숫자만 전달하면 해당하는 인덱스를 반환하고, 이름과 숫자모두 전달하면 Student라는 이름의 struct을 반환해줬습니다.
상속
클래스는 메서드나 프로퍼티 등을 다른 클래스로부터 상속받을 수 있습니다.
어떤 클래스로 부터 상속을 받으면 그 클래스는 자식클래스(subClass/Child-class)라고 표현합니다.
자식클래스에게 자신의 특성을 물려준 클래스를 부모클래스(superClass/Parents-class)라고 표현합니다.
다른 클래스로부터 상속을 받지 않은 클래스를 기반클래스(Basic class)라고 부릅니다.
// 기반 클래스
class Person {
var name: String = ""
var age: Int = 0
var introduction: String {
return "이름 : \(name), 나이: \(age)"
}
func speak() {
print("가나다라마바사")
}
}
let kemi: Person = Person()
kemi.name = "kemi"
kemi.age = 99
print(kemi.introduction)
kemi.speak()
위의 기반클래스를 상속받아서 내 클래스에 없는 메서드나 프로퍼티들을 사용 할 수 있습니다.
// 기반 클래스
class Person {
var name: String = ""
var age: Int = 0
var introduction: String {
return "이름 : \(name), 나이: \(age)"
}
func speak() {
print("가나다라마바사")
}
}
let kemi: Person = Person()
kemi.name = "kemi"
kemi.age = 99
print(kemi.introduction)
kemi.speak()
// 클래스 상속
class Student: Person {
var grade: String = "F"
func study() {
print("Study hard...")
}
}
let jay: Student = Student()
jay.name = "jay"
jay.age = 10
jay.grade = "A"
print(jay.introduction) // 이름 : jay, 나이: 10
jay.speak() // 가나다라마바사
jay.study() // Study hard...
class UniversityStudent: Student {
var major: String = ""
}
let jenny: UniversityStudent = UniversityStudent()
jenny.major = "Art"
jenny.speak() // 가나다라마바사
jenny.study() // Study hard...
메서드, 프로퍼티, 프로퍼티 감시자, 서브스크립트 재정의
자식클래스는 부모클래스에게 받은 메서드, 프로퍼티, 프로퍼티 감시자, 서브스크립트를 재정의 할 수 있다.
부모클래스의 특성을 활용하고 싶을 때 super 키워드를 사용하여 부모클래스에 접근 할 수 있게되는데 이를 이용해서 값 재정의에 사용 할 수도 있다.
재정의를 하기 위해선 앞에 override 키워드를 붙여주면된다.
1. 메서드 재정의
// 기반 클래스
class Person {
var name: String = ""
var age: Int = 0
var introduction: String {
return "이름 : \(name), 나이: \(age)"
}
func speak() {
print("저는 인간입니다.")
}
}
// 클래스 상속
class Student: Person {
var grade: String = "F"
func study() {
print("Study hard...")
}
override func speak() {
print("저는 학생입니다.")
}
}
let personA: Person = Person();
personA.speak()
let studentA: Student = Student();
studentA.speak()
2. 프로퍼티 재정의
프로퍼티를 재정의 한다는 것은 프로퍼티 자체가 아니라 프로퍼티의 접근자, 설정자, 프로퍼티 감시자 등을 재정의 하는 것입니다.
// 기반 클래스
class Person {
var name: String = ""
var age: Int = 0
var koreanAge: Int {
return self.age - 1
}
var introduction: String {
return "이름 : \(name), 나이: \(age)"
}
func speak() {
print("저는 인간입니다.")
}
}
// 클래스 상속
class Student: Person {
var grade: String = "F"
func study() {
print("Study hard...")
}
override func speak() {
print("저는 학생입니다.")
}
override var koreanAge: Int {
get {
return super.koreanAge
}
set {
self.age = newValue - 1
}
}
}
let personA: Person = Person();
personA.name = "kemi"
personA.age = 28
// personA.koreanAge = 12 // 에러.. get Only
let studentA: Student = Student();
studentA.name = "jay"
studentA.koreanAge = 20 // koreanAge값 set가능
print(studentA.age) // koreanAge만 세팅해도 값이 들어감
3. 서브스크립트 재정의
class Student {
var grade: String = "F"
func study() {
print("Study hard...")
}
}
class School {
var students: [Student] = [Student]()
subscript(number: Int) -> Student {
print("School subscript")
return students[number]
}
}
class MiddleSchool: School {
var middleStudents: [Student] = [Student]()
// 부모클래스(School)에게 상속받은 서브스크립트 정의
override subscript(index: Int) -> Student {
print("MiddleSchool subscript")
return middleStudents[index]
}
}
let university: School = School()
university.students.append(Student())
university[0]
let middle: MiddleSchool = MiddleSchool()
middle.middleStudents.append(Student())
middle[0]
3. 클래스의 이니셜라이저
이니셜라이저는 클래스가 생성될 때 프로퍼티값을 설정을 해주는것을 말합니다.
상속을 받았을 때 클래스는 어떻게 이니셜라이저를 할 수 있는지 살펴봅니다.
두가지 이니셜라이저가 존재하는데 지정 이니셜라이저와 편의 이니셜라이저가 존재합니다.
1) 지정 이니셜라이저(Designate Initialzer)는 클래스의 주요 이니셜라이저 입니다.
모든 프로퍼티를 초기화해야 하는 임무를 갖고 있습니다.
2) 편의 이니셜라이저(Convenience Initializer)는 초기화를 좀 더 쉽게 도와주는 역할을 합니다.
지정이니셜을 자신 내부에서 호출하여 매개변수를 외부에서 일일이 전달인자로 받지 않아도 됩니다.
매개변수가 많을 때 사용하면 좋을 것 같습니다.
두가지의 이니셜방법으로 클래스 초기화를 위임 할 수 있습니다.
그래서 자식 클래스에서 부모클래스가 이용한 지정 이니셜라이저를 사용 할 수도 있습니다.
class Person {
var name: String
var age: Int
init(name: String, age: Int) {
self.name = name
self.age = age
}
}
class Student: Person {
var major: String
init(name: String, age: Int, major: String) {
self.major = "Swift"
super.init(name: name, age: age)
}
convenience init(name: String) {
self.init(name: name, age: 7, major: "")
}
}
let studentA = Student(name:"hoon", age: 23, major: "Computer")
print(studentA.age)
let studentB = Student(name:"kemi")
print(studentB.age)
conveience init은 init의 오버로드 개념으로 이해하면 편할 것같다. 매개변수를 적게받고, 받지않은 매개변수는 기본값으로 세팅하는 오버로드다. init을 무조건 호출하는 놈이라고 생각하겠다.
4. 이니셜라이저 자동상속
상속을 받아서 재정의를 할 수 있지만 대부분의 경우 자식클래스에서 이니셜라이저를 재정의 해줄 필요가 없다고 한다.
자동상속을 받는 경우
1. 자식클래스에서 별도의 지정 이니셜라이저를 구현하지 않는다면, 부모클래스의 지정이니셜라이저가 자동으로 상속된다.
2. 규칙1에 따라 지정 이니셜라이저를 자동으로 상속받은 경우 또는 부모클래스의 지정이니셜라이저를 모두 재정의하여 부모클래스와 동일한 지정 이니셜라이저를 모두 사용 할 수 있는 상황이라면 부모클래스의 편의 이니셜라이저가 모두 자동으로 상속된다.
부모 단에서 require로 선언된 init은 상속을 받은 자식에서 override가 아닌 require를 붙여 꼭 오버라이드를 해줘야한다.
다만 자동상속을 받았으면 안해도 되긴하다.
클래스의 자동상속내용이 필요하면 다시와서 책을 보도록하자.
class Person {
var name: String
init(name: String) {
self.name = name
}
convenience init() {
self.init(name: "Unknown")
}
}
class Student: Person {
var major: String
//
override init(name:String) {
self.major = "Swift"
super.init(name: name)
}
init(name: String, major: String) {
self.major = major
super.init(name: name)
}
}
let wizplan: Person = Person()
let jinSung: Student = Student()
print(wizplan.name) // Unknown
print(jinSung.name) // Unknown
19.2 스위프트 타입캐스팅
스위프트 타입캐스팅은 인스턴스의 타입을 확인하거나 자신을 다른 타입의 인스턴스인양 행세할 수 있는 방법으로 사용 할 수 있습니다.
스위프트는 is 와 as 연산자로 구현했습니다.
is연산자를 통해 타입을 구분 할 수 있습니다.
class Coffee {
let name: String
let shot: Int
var description: String {
return "\(shot) shot(s) \(name)"
}
init(shot: Int) {
self.shot = shot
self.name = "coffee"
}
}
class Latte: Coffee {
var flavor: String
override var description: String {
return "\(shot) shot(s) \(flavor) latte"
}
init(flavor: String, shot: Int) {
self.flavor = flavor
super.init(shot: shot)
}
}
class Americano: Coffee {
let iced: Bool
override var description: String {
return "\(shot) shot(s) \(iced ? "iced" : "hot")"
}
init(shot: Int, iced: Bool) {
self.iced = iced
super.init(shot: shot)
}
}
// Latte와 Americano는 Coffe인척 할 수 있다.
let coffee: Coffee = Coffee(shot: 1)
print(coffee.description)
let myCoffee: Americano = Americano(shot: 2, iced: false)
print(myCoffee.description)
let yourCoffee: Latte = Latte(flavor: "green tea", shot: 3)
print(yourCoffee.description) // 3 shot(s) green tea latte
print(coffee is Coffee) // true
print(coffee is Americano) // false
print(coffee is Latte) // false
print(myCoffee is Coffee) // true
print(yourCoffee is Coffee) // true
// is 외에도 메타 타입 타입을 이용하여 타입을 확인 할 수 있습니다.
// 메타 타입은 클래스,구조체,열거형,프토토콜 타입등의 타입의 타입입니다.
// 타입 자체가 하나의 타입으로 또 표현할 수 있다는 것입니다.
protocol SomeProtocol { }
class SomeClass: SomeProtocol { }
let intType: Int.Type = Int.self
let stringType: String.Type = String.self
let classType: SomeClass.Type = SomeClass.self
let protocolProtocl: SomeProtocol.Protocol = SomeProtocol.self
var someType: Any.Type
someType = intType
print(someType) //Int
someType = stringType
print(someType) // String
someType = classType
print(someType) // SomeClass
someType = protocolProtocl
print(someType) // SomeProtocol
다운 캐스팅
부모타입으로 선언해도 자식 인스턴스로 값을 넣을 수 있습니다.
부모 인스턴스인척을 할 수가 있어요.
let actingConstant: Coffee = Latte(flavor: "vanilla", shot: 2)
print(actingConstant.description) // 2 shot(s) vanilla latte
그런데 선언은 부모타입으로 되어있어요. 그래서 자식 인스턴스에 있는 메서드나 프로퍼티를 참조하려고 할 때면 실제로 자식 인스턴스에 접근을 해야하고 타입을 변환해야하는데, 이를 다운캐스팅이라고 합니다.
타입이 부모 타입 -> 자식 타입으로 변경되는것이죠
타입캐스트 연산자(Type Cast Operator)에는 as?와 as! 두가지가 있습니다. 타입캐스트 연산자를 ㅏㅅ용하여 자식클래스 타입으로 다운캐스팅 할 수 있습니다.
class Coffee {
let name: String
let shot: Int
var description: String {
return "\(shot) shot(s) \(name)"
}
init(shot: Int) {
self.shot = shot
self.name = "coffee"
}
}
class Latte: Coffee {
var flavor: String
override var description: String {
return "\(shot) shot(s) \(flavor) latte"
}
init(flavor: String, shot: Int) {
self.flavor = flavor
super.init(shot: shot)
}
}
class Americano: Coffee {
let iced: Bool
override var description: String {
return "\(shot) shot(s) \(iced ? "iced" : "hot")"
}
init(shot: Int, iced: Bool) {
self.iced = iced
super.init(shot: shot)
}
}
// Latte와 Americano는 Coffe인척 할 수 있다.
let coffee: Coffee = Coffee(shot: 1)
let myCoffee: Americano = Americano(shot: 2, iced: false)
let yourCoffee: Latte = Latte(flavor: "green tea", shot: 3)
// 다운캐스팅
if let actingOne: Americano = coffee as? Americano {
print("This is Americano")
} else {
print(coffee.description)
}
// 1 shot(s) coffee
if let actingOne: Latte = coffee as? Latte {
print("This is Latte")
} else {
print(coffee.description)
}
// 1 shot(s) coffee
if let actingOne: Coffee = coffee as? Coffee {
print("This is just Coffee")
} else {
print(coffee.description)
}
// This is just Coffee
if let actingOne: Americano = myCoffee as? Americano {
print("This is Americano")
} else {
print(coffee.description)
}
// This is Americano
if let actingOne: Latte = myCoffee as? Latte {
print("This is Latte")
} else {
print(coffee.description)
}
// This is Americano
// myCoffee는 Americano 클래스 타입인데
// Coffee 타입이 가능하다면 if 구문을 실행시켜라. 와 같은 의미입니다.
if let actingOne: Coffee = myCoffee as? Coffee {
print("This is just Coffee")
} else {
print(coffee.description)
}
//This is just Coffee
20. 프로토콜
프로토콜은 특정 역할을 하기 위한 메서드, 프로퍼티, 기타 요구사항 등의 청사진을 정의합니다.
프로토콜은 정의를 하고 제시를 할 뿐이지 스스로 기능을 구현하지는 않습니다.
프로토콜의 프로퍼티 요구사항은 항상 var 키워드를 사용한 변수 프로퍼티로 정의합니다.
protocol SomeProtocol {
var settableProperty: String { get set }
var notNeedToBeSettableProperty: String { get }
}
protocol AnotherProtocol {
static var someTypeProperty: Int { get set }
static var anotherTypeProperty: Int { get }
}
protocol Sendable {
var from: String { get }
var to: String { get }
}
class Message: Sendable {
var sender: String
var from: String {
return self.sender
}
var to: String
init(sender: String, receiver: String) {
self.sender = sender
self.to = receiver
}
}
class Mail: Sendable {
var from: String
var to: String
init(sender: String, receiver: String) {
self.from = sender
self.to = receiver
}
}
Sendable 프로토콜에서 원한건 읽기가 가능한 from과 to 변수 였지만 읽기 쓰기 둘 모두가 가능해도 괜찮습니다.
메서드 요구
뭔가 굉장히 어려운 예제다.
from, to에 클래스 인스턴스가 들어가서 헷갈려보이지만, 천천히 보면 그렇게 어려운 예제는 아니다.
주고 받는 mail이 있는데 to 값을 세팅해주고, to에 있는 receiver 메서드를 호출해준다.
다만, 이런걸 어디에서 사용하고 실제로 구현하고자하면 머리가 아플것같다...
// 무언가를 수신받을 수 있는 기능
protocol Receiveable {
func received(data: Any, from: Sendable)
}
// 무언가를 발신할 수 있는 기능
protocol Sendable {
var from: Sendable { get }
var to: Receiveable? { get }
func send(data: Any)
static func isSendableInstance(_ instance: Any) -> Bool
}
// 수신, 발신이 가능한 Message 클래스
class Message: Sendable, Receiveable {
// 발신은 발신 가능한 객체, 즉 Sendable 프로토콜을 준수하는 타입의 인스턴스여야 합니다.
var from: Sendable {
return self
}
// 상대방은 수신가능한 객체, 즉 Receiveable 프로토콜을 준수하는 타입의 인스턴스여야합니다.
var to: Receiveable?
// 메시지를 발신합니다.
func send(data: Any) {
guard let receiver: Receiveable = self.to else {
print("Message has no receiver")
return
}
// 수신 가능한 인스턴스의 received 메서드를 호출합니다.
receiver.received(data: data, from: self.from)
}
// 메시지를 수신합니다.
func received(data: Any, from: Sendable) {
print("Message received \(data) from \(from)")
}
// class 메서드이므로 상속이 가능합니다.
class func isSendableInstance(_ instance: Any) -> Bool {
if let sendableInstance: Sendable = instance as? Sendable {
print(sendableInstance.to != nil)
return sendableInstance.to != nil
}
print(false)
return false
}
}
// 수신, 발신이 가능한 Mail 클래스
class Mail: Sendable, Receiveable {
var from: Sendable {
return self
}
var to: Receiveable?
func send(data: Any) {
guard let receiver: Receiveable = self.to else {
print("Mail has no receiver")
return
}
receiver.received(data: data, from: self.from)
}
func received(data: Any, from: Sendable) {
print("Mail received \(data) from \(from)")
}
// static 메서드이므로 상속이 불가능합니다.
static func isSendableInstance(_ instance: Any) -> Bool {
if let sendableInstance: Sendable = instance as? Sendable {
print(sendableInstance.to != nil)
return sendableInstance.to != nil
}
print(false)
return false
}
}
// 두 Message 인스턴스를 생성합니다.
let myPhoneMessage: Message = Message()
let yourPhoneMessage: Message = Message()
// 아직 수신받을 인스턴스가 없습니다.
myPhoneMessage.send(data: "Hello")
// Mesasge 인스턴스는 발신과 수신이 모두 가능하므로 메시지를 주고 받을 수 있습니다.
myPhoneMessage.to = yourPhoneMessage
myPhoneMessage.send(data: "Hello")
// 두 Mail 인스턴스를 생성합니다.
let myMail: Mail = Mail()
let yourMail: Mail = Mail()
myMail.send(data: "Hi") // MAil has no receiver
myMail.to = yourMail
myMail.send(data: "Hi") // Mail received Hi from __lldb_expr_428.Mail
myMail.to = myPhoneMessage
myMail.send(data: "Bye") // Message received Bye from __lldb_expr_433.Mail
// String은 Sendable 프로토콜을 준수하지 않습니다.
Message.isSendableInstance("Hello") //false
// Mail과 Message는 Sendable 프로토콜을 준수합니다.
Message.isSendableInstance(myPhoneMessage) //true
// yourPhoneMessage는 to 프로퍼티가 설정되지 않아서 보낼 수 없는 상태입니다.
Message.isSendableInstance(yourPhoneMessage) //false
Message.isSendableInstance(myPhoneMessage) //true
Message.isSendableInstance(myMail) //true
가변메서드 요구
// 메서드가 인스턴스의 내부의 값을 변경할 필요가 있을 떄 사용
// 인스턴스 내부의 값을 변경해야하는 메서드를 요구하려면 mutating 키워드를 명시행햐합니다.
protocol Resettable {
mutating func reset()
}
class Person: Resettable {
var name: String?
var age: Int?
func reset() {
self.name = nil
self.age = nil
}
}
struct Point: Resettable {
var x: Int = 0
var y: Int = 0
mutating func reset() {
self.x = 0
self.y = 0
}
}
enum Direction: Resettable {
case east, west, south, north, unknown
mutating func reset() {
self = Direction.unknown
}
}
프로토콜 상속과 클래스 전용 프로토콜
프로토콜은 하나 이상의 프로토콜을 상속받아 기존 프로토콜 요구사항보다 더 많은 요구사항을 추가할 수 있습니다.
protocol Readable {
func read()
}
protocol Writeable {
func write()
}
protocol ReadSpeakable: Readable {
func speak()
}
protocol ReadWriteSpeakable: Readable, Writeable {
func speak()
}
class SomeClass: ReadWriteSpeakable {
func read() {
print("Read")
}
func write() {
print("Write")
}
func speak() {
print("Speak")
}
}
위임을 위한 프로토콜
위임(Delegation)은 클래스나 구조체가 자신의 책임이나 임무를 다른 타입의 인스턴스에게 위임하는 디자인 패턴입니다.
위임 패턴(Delegation Pattern)은 애플의 프레임워크에서 사용하는 주요한 패턴 중 하나입니다.
예를들어, 애플의 프레임워크에서는 0000Delegate라는 식의 이름으로 다양한 프로토콜이 정의되어있습니다.
UITableView 타입의 인스턴스가 해야하는 일을 위임받아 처리하는 인스턴스는 UITableViewDelegate프로토콜을 준수하면 됩니다.
UITableViewDelegate 프로토콜을 준수하는 인스턴스는 UITableView의 인스턴스가 해야하는 일을 대신 처리해줄수있습니다.
21. 익스텐션
익스텐션(Extension)은 스위프트의 강력한 기능 중 하나로 구조체, 클래스, 열거형, 프로토콜 타입에 새로운 기능을 추가할수있습니다.
익스텐션은 타입에 새로운 기능을 추가할 수는 있지만, 기존에 존재하는 기능을 재정의할 수는 없습니다.
상속과 익스텐션을 비교하면서 무슨의미인지 감이잡혔습니다.
자바스크립트의 프로토타입에 추가하는 느낌과 유사한 것 같습니다.
나중에 사용할 일이 있다면 다시 살펴보겠습니다.
22. 제네릭
제네릭을 이용해 코드를 구현하면 어떤 타입에도 유연하게 대응할수있습니다.
코드의 중복을 줄일 수 있고 깔끔하고 추상적인 표현이 가능하게 됩니다.
Array, Dictionary, Set 등의 타입은 모두 제네릭 컬렉션입니다.
코드를 보면 전달받는 인자를 <T>로 뭉뚱그려서 받는데, 이렇게 되면 String타입도, Int형 타입도 받을 수 있습니다.
어떻게 보면 자바스크립트에서는 타입을 미리 선언하지 않으니 자동으로 제네릭이 적용되었다고 볼 수 있을 것 같습니다.
func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
let temporaryA: T = a
a = b
b = temporaryA
}
var numberOne: Int = 5
var numberTwo: Int = 10
var stringOne: String = "a"
var stringTwo: String = "v"
swapTwoValues(&numberOne, &numberTwo)
swapTwoValues(&stringOne, &stringTwo)
print(numberOne, numberTwo )
print(stringOne, stringTwo)
23. 프로토콜 지향 프로그래밍
애플은 스위프트는 프로토콜 지향언어라고 말했습니다.
프로토콜 지향언어는 무슨 뜻일까요?
프로토콜, 익스텐션, 제네릭이 어떻게 조화를 이룰 수 있는지 살펴보자.
익스텐션을 이용하면 프로토콜에 기능을 구현 할 수 있게된다.
중복되는 코드를 extension을 이용해 미리 구현해놓는 것인데, 내가 굳이 쓸 이유가 있을까 싶다..
이렇게 프르토콜의 요구사항을 익스텐션을 통해 구현하는 것을 프로토콜 초기구현(Protocol IMplementations)이라고 한다.
맘에 안들면 클래스에서 재정의하면된다.
protocol Receiveable {
func received(data: Any, from: Sendable)
}
extension Receiveable {
// 메시지를 수신합니다.
func received(data: Any, from: Sendable) {
print("\(self) received \(data) from \(from)")
}
}
protocol Sendable {
var from: Sendable { get }
var to: Receiveable? { get }
func send(data: Any)
static func isSendableInstance(_ instance: Any) -> Bool
}
extension Sendable {
var from: Sendable {
return self
}
func send(data: Any) {
guard let receiver: Receiveable = self.to else {
print("Message has no receiver")
return
}
receiver.received(data: data, from: self.from)
}
static func isSendableInstance(_ instance: Any) -> Bool {
if let sendableInstance: Sendable = instance as? Sendable {
return sendableInstance.to != nil
}
return false
}
}
class Message: Sendable, Receiveable {
var to: Receiveable?
}
class Mail: Sendable, Receiveable {
var to: Receiveable?
}
// 두 Message 인스턴스를 생성합니다.
let myPhoneMessage: Message = Message()
let yourPhoneMessage: Message = Message()
// 아직 수신받을 인스턴스가 없습니다.
myPhoneMessage.send(data: "Hello")
// Mesasge 인스턴스는 발신과 수신이 모두 가능하므로 메시지를 주고 받을 수 있습니다.
myPhoneMessage.to = yourPhoneMessage
myPhoneMessage.send(data: "Hello")
// 두 Mail 인스턴스를 생성합니다.
let myMail: Mail = Mail()
let yourMail: Mail = Mail()
myMail.send(data: "Hi") // MAil has no receiver
myMail.to = yourMail
myMail.send(data: "Hi") // Mail received Hi from __lldb_expr_428.Mail
myMail.to = myPhoneMessage
myMail.send(data: "Bye") // Message received Bye from __lldb_expr_433.Mail
// String은 Sendable 프로토콜을 준수하지 않습니다.
Message.isSendableInstance("Hello") //false
// Mail과 Message는 Sendable 프로토콜을 준수합니다.
Message.isSendableInstance(myPhoneMessage) //true
// yourPhoneMessage는 to 프로퍼티가 설정되지 않아서 보낼 수 없는 상태입니다.
Message.isSendableInstance(yourPhoneMessage) //false
Message.isSendableInstance(myPhoneMessage) //true
Message.isSendableInstance(myMail) //true
어느정도 스위프트 문법을 살펴본 것 같다.
이제는 실제로 ios 앱 개발을 하면서 swift 문법에 익숙해져야겠다.
프로젝트를 진행하면서 모르는 부분이 있으면 찾아보면서 익히도록하자.
'Swift > Swift 기본문법' 카테고리의 다른 글
Swift 기본 문법 - 4. 딕셔너리(dictionary) (0) | 2022.01.13 |
---|---|
Swift 기본 문법 - 4. 컬렉션형(배열) (0) | 2022.01.13 |
Swift 기본 문법 - 3. 데이터 타입 (0) | 2022.01.12 |
Swift 기본 문법 - 2. 주석 (0) | 2022.01.11 |
Swift 기본 문법 - 1. console log (0) | 2022.01.10 |