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
복사