03 Apr 2021
|
swift
익스텐션
클래스, 구조체, 열거형 혹은 프로토콜 타입에 기능을 추가할 수 있다.
- 계산된 인스턴스 프로퍼티와 계산된 타입 프로퍼티의 추가
- 인스턴스 메소드와 타입 메소드의 추가
- 새로운 이니셜라이저 제공
- 서브스크립트 정의
- 중첩 타입의 선언과 사용
- 특정 프로토콜을 따르는 타입 만들기
익스텐션은 타입에 새 기능을 추가할 수 있지만, 오버라이드는 할 수 없다.
익스텐션 문법
extension 키워드를 사용해 선언한다.
extension SomeType {
...
}
하나 이상의 프로토콜을 따르도록 확장할 수 있다.
extension SomeType: SomeProtocol, AnotherProtocol {
...
}
계산된 프로퍼티
익스텐션을 이용해 존재하는 타입에 인스턴스 프로퍼티와 타입 프로퍼티를 추가할 수 있다.
extension Double {
var km: Double { return self 1_000.0 }
var m: Double { return self }
var cm: Double { return self / 100.0 }
var mm: Double { return self / 1_000.0 }
var ft: Double { return self / 3.28084 }
}
let oneInch = 25.4.mm
print("1인치는 \(oneInch) 미터 입니다.")
let threeFeet = 3.ft
print("3피트는 \(threeFeet) 미터 입니다.")
이니셜라이저
익스텐션을 이용해 존재하는 타입에 새로운 이니셜라이저를 추가할 수 있다.
struct Size {
var width = 0.0, height = 0.0
}
struct Point {
var x = 0.0, y = 0.0
}
struct Rect {
var origin = Point()
var size = Size()
}
extension Rect {
init(center: Point, size: Size) {
let originX = center.x - (size.width / 2)
let originY = center.y - (size.height / 2)
self.init(origin: Point(x: originX, y: originY), size: size)
}
}
let centerRect = Rect(center: Point(x: 4.0, y: 4.0),
size: Size(width: 3.0, height: 3.0))
메소드
익스텐션을 이용해 존재하는 타입에 인스턴스 메소드나 타입 메소드를 추가할 수 있다.
extension Int {
func repetitions(task: () -> Void) {
for _ in 0..<self {
task()
}
}
}
3.repetitions {
print("Hello!")
}
// Hello!
// Hello!
// Hello!
변경 가능한 인스턴스 메소드
익스텐션에서 추가된 인스턴스 메소드는 인스턴스 자신(self)을 변경할 수 있다.
extension Int {
mutating func square() {
self = self * self
}
}
var someInt = 3
someInt.square()
// someInt의 값은 9가 됨
서브스크립트
익스텐션을 이용해 존재하는 타입에 새로운 서브스크립트를 추가할 수 있다.
extension Int {
subscript(digitIndex: Int) -> Int {
var decimalBase = 1
for _ in 0..<digitIndex {
decimalBase *= 10
}
return (self / decimalBase) % 10
}
}
746381295[0] // 5
746381295[1] // 9
746381295[2] // 2
746381295[8] // 7
중첩 타입
익스텐션을 이용해 존재하는 클래스, 구조체, 열거형에 중첩 타입을 추가할 수 있다.
extension Int {
enum Kind: Character {
case negative = "-", zero = "0", positive = "+"
}
var kind: Kind {
switch self {
case 0:
return .zero
case let x where x > 0:
return .positive
default:
return .negative
}
}
}
1.kind.rawValue // +
0.kind.rawValue // 0
-1.kind.rawValue // -
03 Apr 2021
|
swift
중첩 타입
열거형, 클래스, 구조체를 열거형, 클래스, 구조체 안에서 정의할 수 있음
한글로 번역하면 이상한데, nested의 의미를 생각하면 이해하기 편함
Inner Class를 생각하면 됨
struct BlackjackCard {
// struct 안에서 enum 정의
enum Suit: Character {
case spades = "♠", hearts = "♡", diamonds = "♢", clubs = "♣"
}
// struct 안에서 enum 정의
enum Rank: Int {
case two = 2, three, four, five, six, seven, eight, nine, ten
case jack, queen, king, ace
// enum 안에서 struct 정의
struct Values {
let first: Int, second: Int?
}
var values: Values {
switch self {
case .ace:
return Values(first: 1, second: 11)
case .jack, .queen, .king:
return Values(first: 10, second: nil)
default:
return Values(first: self.rawValue, second: nil)
}
}
}
let rank: Rank, suit: Suit
var description: String {
var output = "suit is \(suit.rawValue),"
output += " value is \(rank.values.first)"
if let second = rank.values.second {
output += " or \(second)"
}
return output
}
}
중첩 타입의 언급
중첩 타입을 선언 밖에서 사용하려면 선언된 곳의 시작부터 끝까지 적어줘야 함
let heartsSymbol = BlackjackCard.Suit.hearts.rawValue
// heartsSymbol is "♡"
03 Apr 2021
|
swift
타입캐스팅
인스턴스의 타입을 확인하거나 인스턴스를 같은 계층에 있는 다른 superclass나 subclass로 취급하는 방법.
is 와 as 두 연산자를 사용함
class MediaItem {
var name: String
init(name: String) {
self.name = name
}
}
class Movie: MediaItem {
var director: String
init(name: String, director: String) {
self.director = director
super.init(name: name)
}
}
class Song: MediaItem {
var artist: String
init(name: String, artist: String) {
self.artist = artist
super.init(name: name)
}
}
let library = [
Movie(name: "비포 선라이즈", director: "리처드 링클레이터"),
Song(name: "Slow Dancing In The Dark", artist: "Joji"),
Movie(name: "그린북", director: "피터 패럴리"),
Song(name: "HIGHEST IN THE ROOM", artist: "Travis Scott"),
Song(name: "시공간", artist: "기리보이")
]
// 이 배열의 타입은 [MediaItem]으로 추론됨
타입 확인 is
인스턴스의 타입을 확인할 수 있음. 자바로 치면 instanceof
var movieCount = 0
var songCount = 0
for item in library {
if item is Movie {
movieCount += 1
} else if item is Song {
songCount += 1
}
}
print("\(movieCount) 개의 영화와 \(songCount) 개의 음악이 있습니다.")
다운캐스팅 as?, as!
특정 타입이 맞는지 확신할 수 없을 때 as?
특정 타입이라는 것이 확실한 경우에 as!
for item in library {
if let movie = item as? Movie {
print("영화: \(movie.name), 감독. \(movie.director)")
} else if let song = item as? Song {
print("음악: \(song.name), 아티스트 \(song.artist)")
}
}
03 Apr 2021
|
swift
에러 처리
Swift에서는 런타임 에러 발생 시 처리를 위해 에러의 발생(throwing), 감지(catching), 증식(propagation), 조작(manipulating)을 지원하는 일급 클래스를 제공
주로 Error 프로토콜을 상속받은 열거형으로 정의
enum VendingMachineError: Error {
case invalidSelection
case insufficientFunds(coinsNeeded: Int)
case outOfStock
}
throw VendingMachineError.insufficientFunds(coinsNeeded: 5)
에러 발생
throws 키워드를 사용해 어떤 함수, 메소드가 에러를 발생 시킬 수 있다는 것을 명시,
throws 키워드는 리턴 타입 표시 기호인 -> 전에 적음
func canThrowErrors() throws -> String
func cannotThrowErrors() -> String
오직 throwing function 만이 에러를 발생시킬 수 있음.
struct Item {
var price: Int
var count: Int
}
class VendingMachine {
var inventory = [
"빼빼로": Item(price: 12, count: 7),
"꼬북칩": Item(price: 10, count: 0),
"포카칩": Item(price: 7, count: 11)
]
var coinsDeposited = 0
func vend(_ name: String) throws {
guard let item = inventory[name] else {
throw VendingMachineError.invalidSelection // 에러 발생
}
guard item.count > 0 else {
throw VendingMachineError.outOfStock // 에러 발생
}
guard item.price <= coinsDeposited else {
throw VendingMachineError.insufficientFunds(coinsNeeded: item.price - coinsDeposited) // 에러 발생
}
coinsDeposited -= item.price
var newItem = item
newItem.count -= 1
inventory[name] = newItem
print("Dispensing \(name)")
}
}
에러 처리
do-catch를 이용해 에러를 처리하는 코드 블럭을 작성할 수 있다.
일반적으로 다음과 같이 사용한다.
do {
try expression
statements
} catch pattern1 {
statements
} catch pattern2 where condition {
statements
} catch {
statements
}
var vendingMachine = VendingMachine()
vendingMachine.coinsDeposited = 8
do {
try vendingMachine.vend("꼬북칩")
print("꼬북칩을 구매했다.")
} catch VendingMachineError.invalidSelection {
print("그런 과자는 없습니다.")
} catch VendingMachineError.outOfStock {
print("재고가 없습니다.")
} catch VendingMachineError.insufficientFunds(let coinsNeeded) {
print("돈이 부족합니다. \(coinsNeeded) 개의 동전을 더 넣으세요.")
} catch {
print("알 수 없는 에러: \(error).")
}
// 재고가 없습니다. 출력
에러를 옵셔널 값으로 변환하기
try? 구문을 사용해 에러를 옵셔널 값으로 변환할 수 있다.
try? 표현 내에서 에러 발생 시 그 표현의 값은 nil이 된다.
func someFunction() throws -> Int {
...
}
// try? 구문 사용
let x = try? someFunction()
// do - catch 구문 사용
let y = Int?
do {
y = try somFunction()
} catch {
y = nil
}
위 두 가지 표현은 동일한 결과를 반환함
에러 발생 중지
함수나 메소드에서 에러가 발생하지 않을 것이라고 확신하는 경우 try!를 사용할 수 있음
let x = try! someFunction()
정리 액션 기술
defer 구문을 이용해 함수가 종료된 후 구문을 수행할 수 있음
func proccessFile(filename: String) throws {
if exists(filename){
let file = open(filename)
defer {
close(file)
}
~~~
// 스코프의 마지막에 defer 구문이 실행됨, close(file)
}
}
03 Apr 2021
|
swift
옵셔널 체이닝
nil일 수도 있는 프로퍼티나 메소드 그리고 서브스크립트에 질의(query)를 하는 과정
만약 옵셔널이 값을 가지고 있다면 값을 반환하고, 값이 nil이면 nil을 반환함
강제 언래핑(!)의 대체로서의 옵셔널 체이닝(?)
강제 언래핑 시 그 값이 없으면 런타임 에러가 발생하지만, 옵셔널 체이닝을 사용하면 런타임 에러 대신 nil이 반환
옵셔널 체이닝의 값은 항상 옵셔널 값
class Person {
var residence: Residence?
}
class Residence {
var numberOfRooms = 1
}
let person = Person()
// 강제 언래핑
let roomCount = person.residence!.numberOfRooms // 런타임 에러 발생
// 옵셔널 체이닝
if let roomCount = person.residence?.numberOfRooms {
print("person의 집엔 방이 \(roomCount)개가 있다.")
} else {
print("person은 집이 없다.")
}
// person은 집이 없다. 출력
let person = Person()
person.residence = Residence()
// 옵셔널 체이닝
if let roomCount = person.residence?.numberOfRooms {
print("person의 집엔 방이 \(roomCount)개가 있다.")
} else {
print("person은 집이 없다.")
}
// person의 집엔 방이 1개가 있다. 출력
옵셔널 체이닝을 통한 프로퍼티의 접근
let person = Person()
person.residence?.numberOfRooms = 2 // residence가 nill이기 때문에 아예 실행되지 않음