Search
Duplicate

Swift 기본 문법 (2)

생성일
2023/06/22 03:19
태그
Grammar

Swift 기본 문법 (2)

18) 클로저 기본

코드의 블럭
일급 시민 (first-citizen)
변수, 상수 등으로 저장, 전달 인자로 전달이 가능
함수 : 이름이 있는 클로저

정의

{ (매개변수 목록) -> 반환타입 in 실행코드 }
Swift
복사
함수를 사용했을 때
func sumFunction(a: Int, b: Int) -> Int { return a+b { var sumResult: Int = sumFunction(a: 1, b: 2) print(sumResult) // 3
Swift
복사
클로저 사용했을 때
var sum: (Int, Int) -> Int = { (a: Int, b: Int) -> Int in return a+b } sumResult = sum(1, 2) print(sumResult) // 3 // 함수는 클로저의 일종이므로, sum 변수에는 당연히 함수도 할당할 수 있다. sum = sumFunction(a:b:) sumResult = sum(1, 2) print(sumResult) // 3
Swift
복사

함수의 전달인자로서의 클로저

let add: (Int, Int) -> Int add = { (a: Int, b: Int) -> Int in return a + b } let substract: (Int, Int) -> Int substract = { (a: Int, b; Int) -> Int in return a - b } let divied: (Int, Int) -> Int divide = { (a: Int, b: Int) -> Int in return a / b } func calculate(a: Int, b: Int, method: (Int, Int) -> Int) -> Int { return method(a, b) } var calculated: Int calculated = calculate(a: 50, b: 10, method: add) print(calculated) // 60 calculated = calculate(a: 50, b: 10, method: substract) print(calculated) // 40 calculated = calculate(a: 50, b: 10, method: divide) print(calculated) // 5 calculated = calculate(a: 50, b: 10, method: { (left: Int, right: Int) -> Int { return left * right }) print(calculated) // 500
Swift
복사

19) 클로저 고급

후행 클로저
반환타입 생략
단축인자 이름
암시적 반환 표현
import Swift func calculate(a: Int, b: Int, method: (Int, Int) -> Int in return method(a, b) } var result: Int
Swift
복사

후행 클로저

클로저가 함수의 마지막 전달인자라면, 마지막 매개변수 이름을 생략한 후, 함수 소괄호 외부에 클로저를 구현할 수 있다.
result = calculate(a: 10, b: 10 { (left: Int, right: Int) -> Int in return left + right }) print(result) // 20
Swift
복사

반환타입 생략

calculate 함수의 method 매개변수는 Int 타입을 반환할 것이라는 사실을 컴파일러도 알기 때문에, 굳이 클로저에서 반환타입을 명시해 주지 않아도 된다.
대신 in 키워드는 생략할 수 없다.
result = calculate(a: 10, b: 10, method: { (left: Int, right: Int) in return left + right }) print(result) // 20 // 후행 클로저도 함꼐 사용 가능하다. result = calculate(a: 10, b: 10) { (left: Int, right: Int) in return left + right }
Swift
복사

단축인자 이름

클로저의 매개변수 이름이 굳이 불필요하다면 단축인자 이름을 활용할 수 있다.
단축인자 이름은 클로저의 매개변수 순서대로 $0, $1, … 처럼 표현한다.
result = calculate(a: 10, b: 10, method: { return $0 + $1 }) print(result) // 20 // 당연히 후행 클로저도 함꼐 사용할 수 있다. result = calculate(a: 10, b: 10) { return $0 + $1 } print(result) // 20
Swift
복사

암시적 반환 표현

클로저가 반환하는 값이 있다면, 클로저의 마지막 줄의 결과값은 암시적 반환값으로 취급한다.
result = calculate(a: 10, b: 10) { $0 + $1 } print(result) // 20 // 간결하게 한줄로 표현 가능하다 result = calculate(a: 10, b: 10) { $0 + $1 } print(result) // 20
Swift
복사

축약하지 않은 클로저 문법과 축약 후의 클로저 문법 비교

// 전 result = calculate(a: 10, b: 10, method: { (left: Int, right: Int) -> Int in return left + right }) // 후 result = calculate(a: 10, b: 10) { $0 + $1 }
Swift
복사
→ 적당히 신중하게 축약하여 사용!! → 사람이 볼 것이기 때문에

20) Property (프로퍼티)

저장 프로퍼티 (stored property)
연산 프로퍼티 (computed property)
인스턴스 프로퍼티 (instance property)
타입 프로퍼티 (type property)
프로퍼티는 구조체, 클래스, 열거형 내부에 구현할 수 있다.
다만, 열거형 내부에는 연산 프로퍼티만 구현할 수 있다.
연산 프로퍼티는 var로만 선언할 수 있다.

정의

import Swift struct Student { // 인스턴스 저장 프로퍼티 var name: String = "" var `class`: String = "Swift" var koreanAge: Int = 0 // 인스턴스 연산 프로퍼티 var westernAge: Int { get { return koreanAge - 1 } set(inputValue) { koreanAge = inputValue + 1 } } // 타입 저장 프로퍼티 static var typeDescription: String = "학생" // 인스턴스 메서드 func selfIntroduce() { print("저는 \(self.class)\(name)입니다") } // 읽기전용 인스턴스 연산 프로퍼티 var selfIntroduction: String { get { return "저는 \(self.class)\(name)입니다" } } // 타입 메서드 static func selfIntroduce() { print("학생 타입입니다") // 읽기전용 타입 연산 프로퍼티 // 읽기전용에서는 get을 생략할 수 있다. static var selfIntroduction: String { return ("학생타입입니다") } }
Swift
복사

사용

// 타입 연산 프로퍼티 사용 print(Student.selfIntroduction) // 학생타입입니다 // 인스턴스 생성 var yagom: Student = Student() yagom.koreanAge = 10 // 인스턴스 저장 프로퍼티 사용 yagom.name = "yagom" print(yagom.name) // yagom // 인스턴스 연산 프로퍼티 사용 print(yagom.selfIntroduction) // 저는 Swift반 yagom입니다 print("제 나이는 \(yagom.koreanAge)살이고, 미국 나이는 \(yagom.westernAge)살입니다.")
Swift
복사

응용

struct Money { var currentcyRate: Double = 1100 var dollar: Double = 0 var won: Double { get { return dollar * currencyRate } set { // set에 특별히 매개변수 이름을 써주지 않으면 newValue가 암시적으로 들어오게 된다. dollar = newValue / currencyRate } } } var moneyInMyPocket = Money() moneyInMyPocket.won = 11000 print(moneyInMyPocket.won) // 11000 moneyInMyPocket.dollar = 10 print(moneyInMyPocket.won) // 11000
Swift
복사

지역변수, 전역변수

저장 프로퍼티와 연산 프로퍼티의 기능은 함수, 메서드, 클로저, 타입 등의 외부에 위치한 지역/전역변수에도 모두 사용이 가능하다.
var a: Int = 100 var b: Int = 200 var sum: Int { return a + b } print(sum) // 300
Swift
복사

21) Property Observer (프로퍼티 감시자)

프로퍼티 감시자를 사용하면 프로퍼티 값이 변경될 때 원하는 동작을 수행할 수 있다.

정의

struct Money { // 프로퍼티 감시자 사용 var currencyRate: Double = 1100 { willSet(newRate) { print("환율이 \(currencyRate)에서 \(newRate)로 변경될 예정입니다") } didSet(oldRate) { print("환율이 \(oldRate)dptj \(currencyRate)로 변경되었습니다") } } // 프로퍼티 감시자 사용 var dollar: Double = 0 { // willSet의 암시적 매개변수 이름 newValue willSet { print("\(dollar)달러에서 \(newValue)달러로 변경될 예정입니다") } didSet { print("\(oldValue)달러에서 \(dollar)달러로 변경되었습니다") } } // 연산 프로퍼티 var won: Double { get { return dollar * currencyRate } set { dollar = newValue / currencyRate } // 프로퍼티 감시자와 연산 프로퍼티 기능을 동시에 사용할 수는 없다. // willSet, didSet은 저장되는 값이 변경될 때, 호출이 되는 것이기 때문! /* willSet { } */ } }
Swift
복사
사용
var moneyInMyPocket: Money = Money() // 환율이 1100.0에서 1150.0으로 변경될 예정입니다 moneyInMyPocket.currenyRate = 1150 // 환율이 1100.0에서 1150.0으로 변경되었습니다 // 0.0달러에서 10.0달러로 변경될 예정입니다 moneyInMyPocket.dollar = 10 // 0.0달러에서 10.0달러로 변경되었습니다 print(moneyInMyPocket.won) // 11500.0
Swift
복사
프로퍼티 감시자의 기능은 함수, 메서드, 클로저, 타입 등의 외부에 위치한 지역/전역 변수에도 모두 사용 가능하다.
var a: Int = 100 { willSet { print("\(a)에서 \(newValue)로 변경될 예정입니다") } didSet { print("\(oldValue)에서 \(a)로 변경되었습니다") } } // 100에서 200로 변경될 예정입니다 a = 200 // 100에서 200로 변경되었습니다
Swift
복사

22) Inheritance (상속)

스위프트의 상속은 클래스, 프로토콜 등에서 가능하다.
열거형, 구조체는 상속이 불가능하다.
스위프트는 다중 상속을 지원하지 않는다.
클래스의 상속에 대해서 알아보도록 하자!

클래스 상속과 재정의

class 이름 : 상속받을 클래스 이름 { 구현부 }
Swift
복사
class Person { var name: String = "" func selfIntroduce() { print("저는 \(name)입니다") } // final 키워드를 사용하여 재정의를 방지할 수 있다 final func sayHello() { print("hello") } // 타입 메서드 // 재정의 불가 타입 메서드 - static static func typeMethod() { print("type method - static") } // 재정의 가능한 타입 메서드 - class class func classMethod() { print("type method - class") } // 재정의 가능한 class 메서드라도, final 키워드를 사용하면 재정의 할 수 없다. // 메서드 안의 `static`과 `final class`는 똑같은 역할을 한다 final class func finalClassMethod() { print("type method - final class") } }
Swift
복사
예제)
class Student: Person { // var name: String = "" var major: String = "" override func selfIntroduce() { print("저는 \(name)이고, 전공은 \(major)입니다") // super.selfIntroduce() // 부모 클래스의 selfIntroduce 호출 } override class func classMethod() { print("overriden type method - class") } // static을 사용한 타입 메서드는 재정의 할 수 없다. // override static func typeMethod() { } // Error // final 키워드를 사용한 메서드, 프로퍼티는 재정의 할 수 없다. // override func sayHello() { } // Error // override class func finalClassMethod() { } let yagom: Person = Person() let hana: Student = Student() yagom.name = "yagom" hana.name = "hana" hana.major = "Swift" yagmo.major // 프로퍼티를 찾을 수 없다. yagom.selfIntroduce() // 저는 yagom입니다 hana.selfIntroduce() // 저는 hana이고, 전공은 Swift입니다 Person.classMethod() // type method - class Person.typeMethod() // type method - static Person.finalClassMethod() // type method - final class Student.classMethod() // overriden type method - class Student.typeMethod() // type method - static Student.finalClassMethod() // type method - final class
Swift
복사

23) 인스턴스의 생성과 소멸 init, deinit

init - 이니셜라이저
deinit - 디이니셜라이저

프로퍼티 기본값

스위프트의 모든 인스턴스는 초기화와 동시에 만든 프로퍼티에 유효한 값이 할당되어 있어야 한다.
프로퍼티에 미리 기본값을 할당해두면 인스턴스가 생성됨과 동시에 초기값을 지니게 된다.
class PersonA { // 모든 저장 프로퍼티에 기본값 할당 var name: String = "unknown" var age: Int = 0 var nickName: String = "nick" } let jason: PersonA = PersonA() jason.name = "jason" jason.age = 30 jason.nickname = "j"
C++
복사
→ 우리는 초기화와 동시에 프로퍼티의 값들을 할당해주고 싶다!

이니셜라이저 (init)

프로퍼티 기본값을 지정하기 어려운 경우에는 이니셜라이저를 통해 인스턴스가 가져야 할 초기값을 전달할 수 있다.
class PersonB { var name: String var age: Int var nickname: String // 이니셜라이저 init(name: String, age: Int, nickname: String) { self.name = name self.age = age slef.nickname = nickname } } let hana: PersonB(name: "hana", age: 20, nickname: "하나") let yujin: PersonB(name: "yujin", age: 18, nickname: "") // 비어있다면??
Swift
복사

프로퍼티의 초기값이 꼭 필요없을 때, 옵셔널을 사용하자!

class personC { var name: String var age: Int var nickname: String init(name: String, age: Int, nickname: String) { self.name = name self.age = age self.nickname = nickname } /* convenience init(name: String, age: Int, nickname: String) { self.init(name: name, age: age) self.nickname = nickname } */ init(name: String, age: Int) { self.name = name self.age = age } } let jenny: PersonC = PersonC(name: "jenny", age: 10) let mike: PersonC = PersonC(name: "mike", age: 15, nickname: "m")
Swift
복사

실패 가능한 이니셜라이저

이니셜라이저 매개변수로 전달되는 초기값이 잘못된 경우, 인스턴스 생성에 실패할 수 있다.
인스턴스 생성에 실패하면 nil을 반환한다.
그래서, 실패 가능한 이니셜라이저의 반환타입은 옵셔널 타입으로 설정해준다!
class PersonD { var name: String var age: Int var nickname: String? // nil을 반환할 수도 있기 때문에 옵셔널을 사용! init?(name: String, age: Int) { if (0...120).contains(age) == false { return nil } if name.character.count == 0 { return nil } self.name = name self.age = age } } let john: PersonD = PersonD(name: "john", age: 23) // Error let john: PersonD? = PersonD(name: "john", age: 23) let joker: PersonD? = PersonD(name: "joker", age: 123) let batman: PersonD? = PersonD(name: "", age: 10) print(joker) // nil print(batman) // nil
Swift
복사

디이니셜라이저

deinit은 클래스의 인스턴스가 메모리에서 해제되는 시점에 호출된다.
인스턴스가 해제되는 시점에 해야할 일을 구현할 수 있다.
class PersonE { var name: String var pet: Puppy? var child: PersonC init(name: String, child: PersonC) { self.name = name self.child = child } deinit { if let petName = pet?.name { print("\(name)\(child.name)에게 \(petName)를 인도합니다") } } } var donald: PersonE? = PersonE(name: "donald", child: jenny) donald?.pet = happy donald = nil // donald 인스턴스가 더이상 필요 없으므로 메모리에서 해제된다 // donaldrk jenny에게 happy를 인도합니다
Swift
복사

24) 옵셔널 체이닝과 nil 병합 연산자

Optional Chaining & nil-coalescing operator
옵셔널 체이닝은 옵셔널 요소 내부의 프로퍼티로 또다시 옵셔널이 연속적으로 연결되는 경우 유용하게 사용이 가능하다.
import Swift class Person { var name: String var job: String? var home: Apartment? init(name: String) { self.name = name } } class Apartment { var buildingNumber: String var roomNumber: String var `guard`: Person? var owner: Person? init(dong: String, ho: String) { buildingNumber = dong roomNumber = ho } } let yagom: Person? = Person(name: "yagom") let apart: Apartment? = Apartment(dong: "101", ho: "202") let superman: Person? = Person(name: "superman")
Swift
복사
옵셔널 체이닝이 실행 후 결과값이 nil일 수 있으므로, 결과 타입도 옵셔널이다.
// 만약 우리집 경비원의 직업이 궁금하다면? // 옵셔널 체이닝을 사용하지 않았을 때 func guardJob(owner: Person?) { if let owner = owner { if let home = owner.home { if let `guard` = home.guard { if let guardJob = `guard`.job { print("우리집 경비원의 직업은 \(guardJob)입니다") } else { print("우리집 경비원은 직업이 없어요") } } } } } // 굉장히 복잡.. // 옵셔널 체이닝을 사용했을 때 func guardJobWithOptionalChaining(owner: Person?) { if let guardJob = owner?.home?.guard?.job { print("우리집 경비원의 직업은 \(guardJob)입니다") } else { print("우리집 경비원은 직업이 없어요") } } guardJobWithOptionalChaining(owner: yagom) // 우리집 경비원은 직업이 없어요 yagom?.home?.guard?.job // nil yagom?.home = apart yagom?.home // Optional(Apartment) yagom?.home?.guard // nil yagom?.home?.guard = superman yagom?.home?.guard // Ooptional(Person) yagom?.home?.guard?.name // superman yagom?.home?.guard?.job // nil yagom?.home?.guard?.job = "경비원"
Swift
복사

nil 병합 연산자

nil이 나올 때 기본값 같은 것들을 실제 활용할 때 쓰고 싶을 때
var guardJob: String guardJob = yagom?.home?.guard?.job ?? "슈퍼맨" print(guardJob) // 경비원 yagom?.home?.guard?.job = nil guardJob = yagom?.home?.guard?.job ?? "슈퍼맨" print(guardJob) // 슈퍼맨
Swift
복사