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이기 때문에 아예 실행되지 않음