변수
는 항상 사용 전에 초기화되어야 한다. 배열
인덱스는 범위초과에러 out-of-bounds
에러에 대해 검사되어야 한다. 정수
는 오버플로우
에 대해 검사되어야 한다.옵셔널
은 nil
값이 명시적으로 처리되도록 한다.nil
은 null
과 비슷한 개념 )Swift는 강력한 기능으로 타입 추론
과 패턴매칭
을 다른 유명한 언어를 개발하는 개발자들에게 친숙한 현대적이고 가벼운 구문으로 결합하여 복잡한 것도 명확하고 간결한 방식으로 표현할 수 있다. 그 결과로 코드는 읽고, 쓰고, 유지하기 쉽다.
불투명한 타입 opaque type
을 반환하는 함수는 Swift 5.1
런타임이 필요하다.try?
표현식은 이미 옵셔널 optional
을 반환하는 표현식에 추가로 옵셔널 표현식을 도입하지 않는다.큰정수 리터럴 초기화 표현식
은 올바른 정수
타입으로 추론한다. 예를 들어 UInt64(0xffff_ffff_ffff_ffff)
는 오버플로우가 아닌 올바른 값이다.동시성 concurrency)
은 Swift5
언어모드와 동시성 타입을 제공하는 Swift 표준 라이브러리의 버전이 필요하다.Swift6
으로 작성된 타겟은 Swift5, Swift4.2 또는 Swift4로 작성된 타겟에 따라 달라질 수 있고, 그 반대의 경우도 마찬가지이다. 즉, ,여러 프레임워크로 분할된 대규모 프로젝트가 있는 경우 코드를 새로운 언어 버전으로 한번에 하나씩 프레임워크로 마이그레이션할 수 있다. let
: 상수
초기화
할 필요는 없지만 반드시 한번 할당
해야함.var
: 변수초기화
하면 컴파일러는 자동으로 타입매칭
을 한다.let implicitInteger = 70
let implicitDouble = 70.0
let explicitDouble : Double = 70
let label = "The width is "
let width = 50
let widthLabel = label + String(width)
let apples = 3
let oranges = 5
let appleSummary = "I have \(apples) apples."
let fruitSummary = "I have \(apples + oranges) pieces of fruit"
"""
를 사용하면 된다. let temp : String = """
가나다라
마 바 사
아자차
"""
print(temp)
/*
가나다라
마 바 사
아자차
*/
대괄호[]
를 사용하여 배열
과 딕셔너리
를 생성하고 대괄호에 인덱스 또는 키를 작성하여 해당 요소에 접근할 수 있다.쉼표
도 허용한다.var fruits = ["딸기", "라임", "자몽"]
fruits[1] = "포도"
var occupations = ["Malcolm" : "Captain",
"Kaylee" : "Mechanic"]
occupations["Jayne"] = "Public Relations"
배열
은 요소를 추가함에 따라서 자동으로 크기가 늘어난다.[]
[:]
로 작성한다.ex_array = []
ex_dictionary = [:]
새로운 변수
또는 다른 장소의 타입 정보가 없는 곳에 빈배열 또는 빈 딕셔너리를 할당하려면 타입을 명시해야 한다.let empty_array : [String] = []
let empty_dictionary : [String : Float] = [:]
선택사항
이다. 문 statements
을 둘러싼 중괄호는 필수사항
이다.if
/ switch
=
뒤나 return
뒤에 if
또는 switch
를 작성할 수 있다.let scoreDecoration = if(teamScore > 10){
"🎉"
} else {
""
}
print("Score :", teamScore, scoreDecoration)
// Prints "Score: 11 🎉"
if
와 let
을 사용하여 누락될 수 있는 값에 대해 사용할 수 있다. 이러한 값은 옵셔널 optional
로 표기된다.값을 포함
하거나 값이 없음
을 나타내는 nil
을 포함한다.물음표 ?
를 작성한다.var optionalString : String? = "Hello"
print(optionalString == nil)
// false
var optionalName : String? = "John Appleseed"
var greeting = "Hello!"
if(let name = optionalName){
greeting
}
if let
: 값이 있는 경우와 값이 없는 경우(nil
)를 체크한다.옵셔널
로 할당된 값을 할당함으로서 옵셔널을 벗기는 작업을 한다.if let
에서 선언한 let은 지역변수
로서 해당 스코프{}
를 벗어나지 못한다. var optionalValue : Int?;
optionalValue = 2
print(optionalValue)
// Optional(2)
if let value = optionalValue{
print(value)
} else {
print("값이 없음")
}
// 2
옵셔널
값이 nil
이면 조건은 false
이고, 중괄호 안의 코드는 건너뛴다. nil
이 아니면 옵셔널 값은 언래핑되고,let
뒤의 상수로 할당되어 코드블럭 안에서 언래핑된 값으로 사용할 수 있다.??
연산자를 사용하여 기본값을 제공하는 것이다. 옵셔널
값이 없다면 기본값이 대신 사용된다.let nickname : String? = nil
let fullname : String = "John"
let informalGreeting = "Hi \(nickname ?? fullname)"
// Hi John
더 짧게 같은 이름으로 언래핑된 값을 사용할 수 잇다.
let nickname : String? = nil
if let nickname {
print("Hey, \(nickname)")
}
// 이경우에 아무것도 출력되지 않음
스위치 switch
는 모든 종류의 데이터와 다양한 비교작업을 지원한다.스위치
는 정수
및 동등성 비교
로 제한되지 않는다.where
는 switch
안에서의 조건 추가
에 사용한다.case
문을 실행하고 프로그램은 스위치 문을 종료한다. 다음 케이스로 이어서 실행되지 않기 때문에 각 케이스 코드에 명시적으로 스위치 종료를 할 필요가 없다. let vegetable = "red pepper"
switch vegetable {
case "celery":
print("Add some raisins and make ants on a log.")
case "cucumber", "watercress":
print("That would make a good tea sandwich.")
case let x where x.hasSuffix("pepper"):
print("Is it a spicy \(x)?")
default:
print("Everything tastes good in soup.")
}
// Prints "Is it a spicy red pepper?"
for-in
을 사용하여 각 키-값
쌍에 사용할 이름의 쌍을 제공하여 딕셔너리의 항목을 조회한다. 컬렉션 collection
이므로 키와 값은 임의의 순서로 조회된다.let interestingNumbers = [
"prime" : [2,3,5,7,11,13],
"fibonacci" : [1,1,2,3,5,8],
"square" : [1,4,9,16,25],
]
var largest = 0
// 값만 사용
for( _, numbers) in interestingNumbers{
for number in numbers {
if number > largest {
largest = number
}
}
}
print(largest)
while
을 사용해야한다. 대신 루프의 조건이 끝에 있을 수 있으므로 적어도 한번은 루프가 실행되도록 한다.repeat while
은 다른 언어의 do while
과 같다.var n = 2
while n < 100 {
n *= 2
}
print(n)
var m = 2
repeat{
m *= 2
}while m < 100
print(m)
..<
를 사용하고 포함하려면 ...
를 사용한다. 0...4
는 0부터 4(이하)이다. var total = 0
for i in 0..<4{
total += i
}
print(total)
// 6
var total = 0
for i in 0...4{
total += i
}
print(total)
// 10
func
를 사용한다. 반환타입
에서 파라미터
이름과 타입을 구분하기 위해 ->
을 사용한다.func greet(person : String, day : String) -> String {
return "Hello \(person), today is \(day)."
}
greet(person : "Bob", day : "Tuesday")
파라미터 이름 전에 인수라벨을 작성
하거나, 인수라벨을 사용하지 않으
려면 _
를 작성해야 한다.// 이 경우는 전달인자 레이블과 받는 걸 일치시킨 경우이다.
func greet1(to : String, from : String) -> String {
return "\(from) => \(to)"
}
func greet2( receiver to : String, giver from : String ) -> String{
return "\(to) -> \(from)"
}
// 전달인자로 receiver와 giver가 사용되었다.
func greet3( _ to : String, giver from :String ) -> String {
return ("\(to) -> \(from)")
}
greet1(to : "받는 사람", from : "주는 사람")
greet2(receiver : "받는 사람", giver : "주는 사람")
greet3("받는 사람", giver : "주는 사람")
튜플 tuple
을 사용하여 복합 값을 만든다. 예를 들어서 함수로부터 여러개의 값을 반환할 때 사용한다. func calculateStatistics(scores: [Int]) -> (min : Int, max : Int, sum : Int){
var min = scores[0]
var max = scores[0]
var sum = 0
for score in scores {
if score > max {
max = score
} else if score < min {
min = score
}
sum += score
}
return (min, max, sum)
}
let statictics = calculateStatistics(scores : [5,3,100,3,9])
print(statistics.sum)
// 120
print(statistics.2)
// 120
중첩
될 수 있고, 중첩된 함수는 외부함수에서 선언한 변수에 접근할 수 있다. func returnFifteen() -> Int{
var y = 10
fun add(){
y += 5
}
add()
return y
}
return Fifteen()
1급타입
이고, 이것은 함수가 다른 함수를 값으로 반환할 수 있다는 것이다.func makeIncrementer() -> ((int) -> Int) {
func addOne(number : Int) -> Int {
return 1 + number;
}
return addOne;
}
var increment = makeIncrementer()
increment(7)
인수 argument
중 하나로 가질 수 있다. func hasAnyMatches( list : [Int], condition : (Int) -> Bool ) -> Bool {
for item in list {
if condition(item){
return true
}
}
return false
}
func lessThanTen(number : Int) -> Bool{
return number < 10
}
var numbers = [20, 19, 7, 12]
hasAnyMatches( list : numbers, condition : lessThanTen )
클로저
의 특별한 케이스이다.(named closure
) 클로저
에 있는 코드는 이미 중첩된 함수
의 예제에서 보았듯이 클로저
가 실행될 때 다른 범위에 있더라도, 클로저
가 생성된 범위에서 사용가능한 변수와 함수와 같은 항목에 접근할 수 있다. 중괄호 {}
로 코드를 묶어 이름 없이 클로저를 작성할 수 있다. 본문으로부터 인수와 반환타입을 분리하기 위해서 in
을 사용한다.let numbers : [Int] = [1,2,3,4,5]
let result : [String] = numbers.map({
(number : Int) -> String in
print(number)
return String(number)
})
대리자 delegate
에 대한 콜백과 같이 클로저의 타입을 이미 알고 있다면 파라미터의 타입
, 반환 타입
또는 둘 다 생략 가능하다.값
만 반환한다.let numbers : [Int] = [1,2,3,4,5]
let mappedNumbers = numbers.map({
number in 3*number
})
print(mappedNumbers)
// [1,2,3,4,5]
클로저
에 유용한 접근 방법으로 이름 대신 숫자로 파라미터를 참조할 수 있다.클로저
가 함수의 유일한 인수일 때 소괄호는 생략할 수 있다.let numbers : [Int] = [3,1,4,2,5]
let sortedNumbers = numbers.sorted(){ $0 > $1 }
// sorted 뒤의 ()는 생략 가능하다.
print(sortedNumbers)
// [5,4,3,2,1]
class
뒤에 클래스의 이름을 사용하여 클래스를 생성한다.컨텍스트 context
안에 있다는 점을 제외하고는 상수
또는 변수
를 선언하는 방법과 동일하다.메서드
와 함수
선언도 동일한 방법으로 작성된다.class Shape{
var numberOfSides = 0
func simpleDescription() -> String{
return "A shape with \(numberOfSides) sides"
}
}
new
키워드 들어가지 않음프로퍼티
와 메서드
에 접근하기 위해서 점구문
을 사용한다.var shape = Shape()
shape.numberOfSides = 7
var shapeDescription = shape.simpleDescription()
인스턴스가 생성될 때 클래스를 설정하기 위해서 초기화 문
을 사용할 수 있다.
init
을 사용하여 생성한다.
모든 프로퍼티
는 numberOfSides
와 같이 선언 시나 name
과 같이 초기화 구문에서 값을 할당해야 한다.
객체가 할당해제
되기 전에 어떠한 정리작업이 필요하여 초기화 해제 구문 deinitializer
을 생성하려면 deinit
을 사용한다.
하위클래스 subclass
는 클래스 이름 뒤에 콜론으로 구문하여 상위 클래스 superclass
를 포함한다. 클래스가 모든 표준 루트 클래스를 하위클래스화 할 필요가 없으므로 필요에 따라서 상위클래스를 포함하거나 생략할 수 있다.
상위 클래스의 구현을 재정의하는 하위클래스의 메서드는 override
로 표시된다.
실수로 override
없이 메서드를 재정의하면 에러로 컴파일러에 의해서 감지된다.
컴파일러는 실제로 상위 클래스의 어떤 메서드도 실제로 재정의 하지 않는 override
가 있는 메서드를 감지한다.
class NamedShape{
var numberOfSides : Int = 0
var name : String
init(name : String){
self.name = name
}
func simpleDescription() -> String{
return "A shape with \(numberOfSides) sides."
}
}
class Square : NamedShape{
var sideLength : Double
init(sideLength : Double, name : String){
self.sideLength = sideLength
super.init(name : name)
numberOfSides = 4
}
func area() -> Double{
return sideLength * sideLength
}
override func simpleDescription() -> String{
return "A square with sides of length \(sideLength)."
}
}
let test = Square(sideLength : 5.2, name : "my test square")
test.area()
test.simpleDescription()
단순 프로퍼티
외에도 프로퍼티는 getter
와 setter
를 가질 수 있다.class EquilateralTriangle : NamedShape{
var sideLength : Double = 0.0
init(sideLength : Double, name : String){
self.sideLength = sideLength
super.init(name : name)
numberOfSides = 3
}
var perimeter : Double {
get{
return 3.0 * sideLength
}
set{
return sideLength = newValue / 3.0
}
}
override func simpleDescription() -> String {
return "An equilateral triangle with sides of length \(sideLength)."
}
}
var triangle = EquilateralTriangle(sideLength : 3.1, name : "a triangle");
print(triangle.perimeter)
// 9.3
triangle.perimeter = 9.9
print(triangle.sideLength)
// 3.30000000000000003
perimeter
의 setter에서 새로운 값은 암시적으로 newValue
라는 이름을 가진다. set
이후에 소괄호에서 명시적으로 이름을 제공해줄 수 있다.
EquilateralTriangle
클래스의 초기화 구문은 세가지 단계가 있다.
프로퍼티
의 값을 설정한다.초기화 구문
을 호출한다.메서드
, getter
또는 setter
를 사용하는 추가 설정 작업도 이 시점에서 수행할 수 있다.willSet
과 didSet
을 사용하면 된다. class TriangleAndSquare{
var triangle : EquilateralTriangle{
willSet {
square.sideLength = newValue.sideLength
}
}
var square : Square {
willSet {
triangle.sideLength = newValue<.sideLength
}
}
init(size : Double, name : String){
square = Square(SideLength : size, name : name)
triangle = EquilateralTriangle(sideLength : size, name : name )
}
}
var triangleAndSquare = TriangleAndSquare(size : 10, name : "another test shape")
print(triangleAndSquare.square.sideLength)
// 10.0
print(triangleAndSquare.triangle.sideLength)
// 10.0
triangleAndSquare.square = Square(sideLength : 50, name : "larger square")
print(triangleAndSquare.triangle.sideLength)
// 50.0
옵셔널 값
으로 동작할 때 메서드
, 프로퍼티
, 그리고 서브스크립트
와 같은 동작 전에 ?
를 작성할 수 있다. ?
전의 값이 nil
이면 ?
이후의 모든 것은 무시되고 전체 표현의 값은 nil
이다. 옵셔널 값
은 언래핑
되고, ?
후에 모든 동작은 언래핑된 값
으로 동작한다. 옵셔널 값
이다.let optionalSquare : Square? = Square(sideLength : 2.5, name : "optional Square");
let sideLength = optionalSquare?.sideLength
enum
을 사용한다.열거형
과 좀 다르다. 유용하게 사용할 수 있을 것 같은enum Rank : Int{
case ace = 1
case two, three, four, five, six, seven, eight, nine, ten
case jack, queen, king
func simpleDescription() -> String {
switch self{
case .ace :
return "ace"
case .jack :
return "jack"
case .queen :
return "queen"
case .king :
return "king"
default :
return String(self.rawValue)
}
}
}
let ace = Rank.ace
let aceRawValue = ace.rawValue
enum Month {
case mar, apr, may
case jun, jul, aug
case sep, oct, nov
case dec, jan, feb
func printMessage() {
switch self {
case .mar, .apr, .may:
print("봄!")
case .jun, .jul, .aug:
print("여름!")
...
}
}
}
기본적으로 Swift
는 0을 시작으로 매번 증가하는 원시값 raw Value
을 할당하지만 명시적으로 특정 값으로 이 동작을 변경할 수 있다.
위의 코드를 보면 Ace
는 명시적으로 1
의 값이 주어지고 나머지 원시 값은 순서대로 할당된다.
열거형의 원시타입
으로 문자열
또는 부동소수점
도 사용할 수 있다.
열거형 케이스
의 원시값
에 접근하기 위해서 rawValue
프로퍼티를 사용한다.
원시값으로부터 열거형의 인스턴스를 생성하기 위해 init?(rawValue:)
초기화 구문을 사용한다.
원시값이 일치하는 열거형 케이스나 Rank
에 일치하는 항목이 없으면 nil
을 반환한다.
if let convertedRank = Rank(rawValue : 3){
let threeDescription = convertedRank.simpleDescription()
}
enum Suit{
case spaces, hearts, diamonds, clubs
func simpleDescription() -> String{
switch self{
case .spades:
return "spades"
case .hearts:
return "hearts"
case .diamonds:
return "diamonds"
case .clubs:
return "clubs"
}
}
}
let hearts = Suit.hearts
let heartsDescription = hearts.simpleDescription()
hearts
케이스를 참조하는 2가지 방법을 살펴보자.hearts
상수에 값을 할당할 때 명시적으로 타입을 지정하지 않았으므로, 열거형 케이스 Suit.hearts 전체이름으로 참조된다. self
의 값은 이미 카드(suit)로 알고 있기 때문에 열거형 케이스는 짧은 형식인 .hearts
로 참조된다. 값의 타입을 이미 알고 있다면 언제나 짧은 형식을 사용할 수 있다. 값의 타입
을 알고 있다면 언제나 짧은 형식을 사용가능하다. 열거형
이 원시값을 갖는 경우 선언의 부분으로 결정된다. 이것은 특정 열거형 케이스의 모든 인스턴스는 항상 같은 원시값을 갖는다는 의미이다. 열거형
케이스의 또다른 선택은 케이스와 연관된 값 associated value
을 가지는 것이다. 이러한 값은 인스턴스를 생성할 때 결정되고, 열거형 케이스의 각 인스턴스에 대해 다를 수 있다. 연관된 값 associated value
은 열거형 케이스 인스턴스의 저장된 프로퍼티
처럼 동작한다고 생각할 수 있다. 예를 들어 서버에서 일출과 일몰 시간에 대해 요청한다고 가정해보자.enum ServerResponse{
case result(String, String)
case failure(String)
let success = ServerResponse.result("6:00 am", "8.09 pm")
let failure = ServerResponse.failure("Out of cheese")
switch success{
case let .result(sunrise, sunset):
print("Sunrise is at \(sunrise) and sunset is at \(sunsat).")
case let .failure(message):
print("Failure ... \(message)")
}
}
// "Sunrise is at 6:00 am and sunset is at 8:09 pm."
스위치 케이스
에 대한 값이 일치하는 부분으로 ServerResponse
값에서 일출과 일몰 시간이 어떻게 추출되는지 확인struct
를 사용한다. 구조체는 메서드와 초기화 구문을 포함하여 클래스와 동일한 동작을 많이 지원한다. 구조체
와 클래스
의 가장 큰 차이점은 구조체는 코드에서 전달될 때 항상 복사되지만 클래스는 참조로 전달된다. struct Card{
var rank : Rank
var suit : Suit
func simpleDescription() -> {
return "The \(rank.simpleDescription()) of \(suit.simpleDescription())
}
let threeOfSpades = Card(rank : .three, suit : .spades)
let threeOfSpadesDesctiption = threeOfSpades.simpleDescription()
}
async
를 사용한다.async
를 function
키워드 앞에 붙이는 점이 Javascript
와 다르다.func fetchUserID(from server : String) async -> Int{
// 전달인자레이블을 from으로 지정
// 반환형 Int, 비동기 함수 async
if server == "primary"{
return 97
}
return 501
}
await
을 작성해서 비동기 함수를 호출하는 것을 나타낸다.func fetchUserName(from server : String) async -> String{
let userID = await fetchUserId(from : server)
if userID == 501{
return "John Appleseed"
}
return "Guest"
}
async let
을 사용해서 다른 비동기 코드와 병렬로 실행할 수 있다. await
을 작성하여 반환된 값을 사용한다.func connectUser(to server : String) async{
async let userID = fetchUserId(from : server)
async let userName = fetchUsername(from : server)
let greeting = await "Hello \(userName), user ID \(userID)"
}
swift
에서의 do-catch
는 javascript
에서의 try-catch
와 비슷한 동작을 수행한다.
잠재적으로 예외를 발생할 수 있는 코드를 do 코드블록 안에 삽입하고 catch에서 이를 처리한다.
기본구조의 예시는 다음과 같다.
do{
try 잠재적으로 오류를 발생시킬 수 있는 코드
}catch{
오류를 처리하는 코드
}
enum NetworkError : Error {
case invalidURL
case noData
case decodingError
}
struct User : Codable{
}
중간에 알아야 할 내용들을 먼저 살펴보자면,
1. JSON
- SWIFT object
2. Codable
을 먼저 살펴보겠다.
JSON
과 SWIFT 객체
는 디코딩
/인코딩
을 거치지 않고서는 서로 호환되지 않는다.
디코딩
/인코딩
에 들어가기 전 Codable
을 알아야하는데 이는 데이터 인코딩과 디코딩을 쉽게 처리할 수 있게 해주는 프로토콜이다.
Codable
은 실제로 Encodable
과 Decodable
프로토콜의 type alias
이다. 주요특징은 다음과 같다.
ㄴ 1. 자동구현
대부분의 경우에 swift 컴파일러가 자동으로 인코딩 / 디코딩 로직을 생성한다.
ㄴ 2. JSON 변환
JSON과 swift 객체 간의 변환을 쉽게 할 수 있다.
ㄴ 3. 사용자 정의
필요한 경우 인코딩 / 디코딩 프로세스를 커스터마이징할 수 있다.
ㄴ 4. 다양한 데이터 형식 지원
JSON 뿐만 아니라 PropertyList 등 다양한 형식을 지원한다.
Codable
예시코드// Codable
struct User : Codable {
let id : Int
let name : String
let email : String
}
let jsonString = """
{
"id" : 1,
"name" : "John Doe",
"email" : "john@example.com"
}
// JSON -> swift object
let jsonData = Data(jsonString.utf8)
let decoder = JSONDecoder()
"""
do{
let user = try decoder.decode(User.self, from : jsonData)
print("User ID : \(user.id), Name : \(user.name), Email : \(user.email)")
}catch{
print("Decoding Error : \(error)")
}
struct User : Codable {
let id : Int
let name : String
let email : String
}
let user = User(id : 2, name : "John Doe", email : "jane@example.com")
let endcoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
do{
let jsonData = try encoder.encode(user)
if let jsonString = String(data : jsonData, encoding : .utf8){
print("JSON : \(jsonString)")
}
}catch{
print("Encoding error : \(error)")
}
swift
에서는 catch
절에서 오류를 암시적으로 바인딩할 수 있다.catch
catch{
print("Decoding error : \(error)")
// error는 암시적으로 바인딩된 오류 객체이다.
}
catch
catch let decodingError as DecodingError{
print("Specific decoding error : \(decodingError)")
}
catch DecodingError.keyNotFound(let key, _){
print("Missing key : \(key)")
}
DecodingError
라는 에러 객체를 볼 수 잇는데, 이는 열거형 enum
이다. 이를 대상으로 다시 세부적인 예시코드를 보자. do{
}catch DecodingError.keyNotFound(let key, let context){
// JSON에서 필요한 키를 찾지 못했을 때
// key : 찾지 못한 키의 이름, context : 오류에 대한 추가정보
}catch DecodingError.valueNotFound(let type, let context){
// 예상된 값이 JSON에 없을 때 발생
// type : 찾지 못한 값의 예상타입
}catch DecodingError.typeMismatch(let type, let context){
// JSON의 값 타입이 예상과 다를 때 발생
// type : 예상된 타입
}catch{
print("ERROR: \(error)")
}
if let
와 guard let
은 옵셔널 값을 확인하고 해당 값을 일시적으로 변수
나 상수
에 바인딩
하는 기능을 수행한다.
그럼 옵셔널
이란
Swift
에서 옵셔널
은 값이 존재할 수도, 존재하지 않을 수도 있는 상황을 표현하는 타입이다.
예시로 Int?
를 들 수 있음
옵셔널을 선언할 때 초기값을 지정하지 않으면 자동으로 nil
이 할당된다.
nil
이 할당된 값에 접근하는 경우에 런타임 에러가 발생하기 때문에 옵셔널 바인딩
, 옵셔널 체이닝
, 옵셔널기본값
사용 등을 통해 nil
값에 대한 처리가 필요하다.
옵셔널 바인딩
언래핑
하는 방법이다.!
를 써서 강제로 옵셔널 추출if let
, guard let
을 써서 옵셔널 추출 if let
또는 if var
를 사용하여 옵셔널의 값이 존재하는지 검사하고, 존재하면 그 값을 다른 변수에 대입한다. 만약에 해당 옵셔널의 값이 nil
이면 그냥 넘어가고, nil
이 아니면 코드 블럭 안의 구문을 실행한다. 옵셔널 바인딩
의 예시코드는 다음과 같다. let x : String? = "TEST"
let y : String? = nil
// 초기화해주지 않으면 자동으로 nil로 초기화된다.
if let xx = x{
print("\(xx)는 nil이 아니었음")
}
if let yy = y{
print("\(yy)는 nil이 아니었음")
}
// 출력 : TEST는 nil이 아니었음
강제로 실행
의 예시코드는 다음과 같다.let x : String? = "TEST"
let y : String? = nil
print("\(x!)")
print("\(y!)")
// 첫번째 x는 출력되지만 Y는 널참조로 런타임 에러가 발생한다.
let name1 : String?
let name2 : String?
// 둘다 자동으로 nil로 초기화 된다.
if let name11 = name1,
let name22 = name2{
print(name11, name22)
}
옵셔널 체이닝
언래핑
없이 옵셔널 값
에 접근한다.struct TestStruct{
wrapper : TestInnerStruct?
}
struct TestInnerStruct{
innerValue : Int? = 10
}
라고 한다.
이제 여기에서
let result = TestStruct?.wrapper?.innerValue;
를 했다고 쳐보자.
1. result의 type은 일단 옵셔널<맨 뒤의 타입이다. 여기서는 innerValue
>
2. 만약에 중간에 wrapper가 nil
이었다. 그러면?
ㄴ 런타임에러
는 발생하지 않고, result
는 nil
이 된다.
ㄴ 이어지는 innerValue는 평가하지 않는다
옵셔널 기본값
let testValue1 : Int? = 10
let testValue2 : Int?
// testValue2는 자동으로 nil
let testValue3 = testValue2 ?? 20 // testValue2가 nil이라면 20
let testValue4 :Int?
let testValue5 = testValue2 ?? testValue4 ?? 30 // 둘다 nil이라면 30
if let
과 비슷하지만, guard let
에서는 else
인 부분만 작성이 가능하다.nil
이어서 옵셔널 추출이 되지 않을 때만 어떠한 행동을 취할 수 있다.nil
값이 아닐 걸 확인하고 옵셔널을 성공적으로 추출했다면, guard let
문을 통과하게 된다. (유의)
guard let문을 통과하게 되면 저장된 상수는 전역변수
로써 사용이 가능하다. (유의)
guard let 문의 else 안에는 항상 return
아니면 throw
문이 와야한다.예시코드를 살펴보면
let value1 : Int? = 10
let value2 : Int?
guard let test1 = value1 else{
return print("value1는 optional")
}
print(test1)
guard let test2 = value2 else{
return print("value2는 optional")
}
print(test2)
// 이렇게 되면 출력이 10, value2는 optional이라고 뜨는데
// 여기서 test1은 전역변수로써 사용이 가능하다.
swift
에서 익명함수
를 만들기 위해서 다음과 같은 절차를 따른다.
ex) 예시 함수 (인자가 없는 경우)
func exampleFunc() {
self.Something.x += 20
}
위와 동일한 기능을 하는 익명함수 (이상하게 생김.. 진짜로.. 아이폰은 이쁜데 얘는 왜 이렇게 생겼지..)
{
() -> () in
self.Something.x += 20
}
ex) 예시 함수 ( 인자가 있는 경우 )
func exampleFunc(testValue : Int){
self.Something.x += testValue
}
위와 동일한 기능을 하는 익명함수
{
(testValue : Int) -> () in
print("test value : \(testValue)")
}
UIView.animateWithDuration(0.4, animation : whatToAnimate, completion : whatToDoLater)
위 코드를 익명함수를 사용한다면
UIView.animateWithDuration(0.4,
animation :
{() -> () in
self.myButton.frame.origin.y += 20
}
completion :
{
(finished : Bool) -> () in
println("finished : \(finished)")
}
UIView.animateWithDuration(0.4, animation : {
() in
~~~본문
}, completion : {})
UIView.animateWithDuration(0.4, animation : {
~~~본문
}, completion : {})
UIView.animateWithDuration(0.4, animation : {
(testValue) -> () in
~~~본문
}, completion : {})
// 파라미터 타입과 반환값 생략
UIView.animateWithDuration(0.4, animation : {
(testValue) in
~~~본문
}, completion : {})
UIView.animateWithDuration(0.4, animation : {
testValue in
~~~ 본문
}, completion : {})
UIView.animateWithDuration(0.4, animation : {
print("first param : \($0)")
}, completion : {})
UIView.animateWithDuration(0.4, animation : {
_ in
print(~~)
}, completion : {})
let arr = [2,4,6,8]
func doubleMe(i : Int) -> Int{
return i*2
}
let arr2 = arr.map(doubleMe)
// 익명함수로 축약하면 다음과 같이 가능하다.
let arr3 = arr.map{
element -> Int in
return element * 2
}
let arr4 = arr.map{
return $0 * 2
}
let arr5 = arr.map{ $0 * 2 }
`
GET요청
예시코드이다. func performGetRequest(urlString : string) async throws -> Data{
// 함수선언에 throw키워드를 사용하면 해당 함수가 오류를 던질 수 있음을 나타낸다.
// throws 함수를 호출할때는 try 키워드를 사용해야한다. 호출자에게 오류처리의 책임을 부여한다.
guard let url = URL(string : urlString) else{
// 만약에 URL() 의 결과가 nil이라면
throw URLError(.badURL)
// 예외를 던진다.
}
let (data, response) = try await URLSession.shared.data(from : url)
guard let httpResponse = response as? HTTPURLResponse,
// 2개 검사
(200...299).contains(httpResponse.statusCode) else{
// 만약에 조건을 하나라도 못맞추면
throw URLError(.badServerResponse)
}
return data
}
Task{
do{
let data = try await performGetRequest(urlString : "https// api.....com")
let user = try JSONDecoder().decode(디코딩할 타입, json이 저장되어있는 데이터)
print("data : \(user)")
}catch{
print("error : \(error)")
// 암시적 바인딩
}
}
let jsonString = """
{
"id":"suwon",
"age":"28",
.
.
.
}
"""
// 런타임 널참조 에러가 일어날 가능성이 있는 코드
let jsonData = jsonString.data(using : .utf8)!과 같이 할 수 있기는 한데 이 경우에 jsonString이 nil이면 위험하다.
// guard let으로 에러가 일어날 가능성을 줄인 코드
guard let jsonData = jsonString.data(using: .utf8) else{
throw 에러
}
를 해서 jsonData를 쓰는 것이 안전하다.
func performPOSTRequest(urlString : String, body: [String:Any]) async throws -> Data{
// throws : 이 함수를 호출할 때는 오류처리에 대한 명시를 꼭 해야한다.
// body : key는 String 값은 아무값이나 가능
// async : 비동기
guard let url = URL(string : urlString) else{
throw URLError(.badUrl)
}
var request = URLRequest(url : url)
request.httpMethod = "POST"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
let jsonData = try JSONSerialization.data(withJSONObject : body
}
as?
: 타입캐스팅 연산자
as?
는 swift의 조건부 다운캐스팅 연산자
이다.
한 타입의 인스턴스를 다른 타입의 인스턴스로 취급하려고 할때 사용되고,
캐스팅이 성공
하면 옵셔널 형태로 캐스팅된 값을 반환한다.
캐스팅이 실패
하면 nil
을 반환한다.
contains
컬렉션 메서드
contains
는 swift의 시퀀스
나 컬렉션
에서 사용되는 메서드이다.
특정요소가 컬렉션에 포함
되어있는지 확인한다.
주어진 조건을 만족하는 요소가 하나라도 있으면 true
, 그렇지 않으면 false
반환
(200...299)
는 200부터 299까지의 범위를 나타내는 Range<Int>
타입이다.
throws
를 함수선언에 넣음으로써 오류처리를 강제 했기 때문에 호출하는 코드에서는 다음의 형태가 된다. try
를 의무적으로 사용해서 오류처리를 꼭 해주어야한다.
do {
let data = try await performGetRequest(urlString: "https://api.example.com/data")
// 데이터 처리
} catch {
// 오류 처리
print("Error occurred: \(error)")
}
protocol
키워드를 사용한다.protocol ExampleProtocol{
var simpleDescription : String { get }
mutating func adjust()
}
class SimpleClass : ExampleProtocol{
var simpleDescription : String = "A very simple class."
var anotherProperty : Int = 69105
func adjust(){
// mutating 키워드가 필요하지 않다.
simpleDescription += " Now 100% adjusted."
}
}
var a = SimpleClass()
a.adjust()
let aDescription = a.simpleDescription
struct SimpleStructure : ExampleProtocol{
var simpleDescription : String = "A simple structure"
mutating func adjust(){
simpleDescription += " (adjusted)"
}
}
var b = SimpleStructure()
b.adjust()
let bDescription = b.simpleDescription
클래스의 메서드는 항상 클래스를 수정할 수 있으므로, SimpleClass
의 선언에는 mutating
으로 표시된 메서드가 필요하지 않다.
새로운 메서드와 계산된 프로퍼티가 같이 존재하는 타입에 기능을 추가하려면 확장 extension
을 사용한다.
확장 extension
을 사용하여 다른 곳에 선언된 타입
또는 라이브러리
나 프레임워크
에서 가져온 타입에 프로토콜 준수를 추가할 수 있다.
extension Int : ExampleProtocol{
var simpleDescription : String {
return "The number \(self)"
}
mutating func adjust(){
self += 42
}
}
print(7.simpleDescription)
// Prints "The number 7"
extension Int : ExampleProtocol{
var simpleDescription : String {
return "The number \(self)"
}
mutating func adjust(){
self += 42
}
print(7.simpleDescription)
// Prints "The number 7"
}
Error
프로토콜을 채택하는 모든 타입을 사용하여 에러를 나타낸다.
에러를 던지기 위해서 throw
를 사용하고 에러를 던질 수 있는 함수를 나타내기 위해서 throws
를 사용한다.
함수에서 에러가 발생하면 함수는 즉시 반환되고 함수를 호출한 코드가 에러를 처리한다.
func send( job : Int, toPrinter printerName : String ) throws -> String {
if printerName == "Never Has Toner"{
throw PrinterError.noToner
}
return "Job sent"
}
에러
를 처리하는 방법은 여러가지가 있다.do-catch
do{
let printerResponse = try send(job : 1040, toPrinter : "Bi Sheng")
// 앞에서 send 함수에 'throws'를 명시해줬기 때문에 try 키워드를 앞에다가 붙여야 한다.
}catch{
print(error)
}
// JobSent
catch
블럭 case
이후에 하는 것처럼 catch
이후에 패턴을 작성한다.do{
let printerResponse = try send(job : 1440, toPrinter : "Gutenberg")
}catch PrinterError.onFire{
print("~~~")
}catch let printerError as PrinterError{
print("Printer error : \(printerError)")
}catch{
print(error)
}
// "Job sent"
try?
nil
이다. 옵셔널 값
을 포함한다. let printerSuccess = try? send(job : 1884, toPrinter : "Mergenthaler")
let printerFailure = try? send(job : 1885, toPrinter: "Never Has Toner")
defer
: 함수를 반환하기 직전에 함수의 다른 모든 코드 다음에URLSessionError
: URLSession에서 발생하는 일반적인 에러로 네트워크 요청이 실패했을 때 나타난다.
<세부에러>
URLError
.notConnectedToInternet
인터넷 연결이 없는 경우
URLError
.timedOut
요청이 타임아웃된 경우
URLError
.cannotFindHost
호스트를 찾을 수 없는 경우
URLError
.cannotConnectToHost
호스트에 연결할 수 없는 경우
URLError
.networkConnectionLost
네트워크 연결이 도중에 끊어진 경우
URLError
.badServerResponse
서버로부터 잘못된 응답을 받은 경우
URLError
.unsupportedURL
지원되지 않는 URL을 사용하는 경우
이건 패스
DecodingError
.dataCorrupted
JSON 데이터가 손상된 경우 발생
DecodingError
.keyNotFound
JSON에서 필요한 키를 찾을 수 없는 경우
DecodingError
.typeMismatch
JSON데이터의 타입이 일치하지 않는 경우
DecodingError
.valueNotFound
JSON에서 필요한 값을 찾을 수 없는 경우
예시코드
do{
let result = try ~~api 호출하는 함수
}catch let corruptError = DecodingError.dataCorrupted{
print("Corrupt 에러 : \(corruptError)")
}catch{
print("정의되지 않은 에러 : \(error)")
}
enum NetworkError : Error{
case invalidURL
case noData
case decodingFailed
case custom(message : String)
}
@escaping
키워드는 클로저
가 함수의 실행이 끝난 후에도 호출될 수 있음을 나타내는 속성이다. 클로저
가 함수 외부에서도 사용되거나 저장될 수 있음을 명시하는데 사용된다.@escaping
속성을 사용한다.* 이렇게 설명을 들었는데도, 솔직하게 @escaping
이 이해가 안된다.
@escaping
이 필요한 이유는 클로저가 함수의 실행이 끝난 후에도 호출될 수 있음을 명시적으로 나타내기 위해서이다.
구체적으로, 클로저가 함수 외부에서 참조되거나 저장되어 함수가 반환된 이후에도 클로저가 사용될 때 @escaping
이 필요하다. 이를 통해서 swift는 메모리 관리와 관련된 최적화를 올바르게 수행할 수 있다.
비동기작업
비동기 작업에서는 함수가 종료된 후에도 클로저가 실행될 수 있다.
예를 들어서, 네트워크 요청의 결과를 처리하는 클로저는 네트워크 응답이 도착할 때까지 대기하기 때문에 함수가 종료된 후에 실행된다.
(참고
) swift에서
함수는 Named Closure
익명함수는 Unnamed Closure ( 보통 클로저라고 하면 이를 말한다 )
비동기작업
에서의 예시코드를 한번 보자. func fetchData(
from urlString : String,
completion : @escaping ( (Result<Data, Error>) -> Void ) throws -> Void
// 여기에 escaping을 쓰는 이유는 unnamed closure인 익명함수를 다시 재사용할 것이기 때문이다. 일회용이 아니라고 적어놓은 것
){
guard let url = URL(string : urlString) else{
// 만약에 urlString을 URL에 인자로 준게 NIL이 뜬다면
completion(.failure(error))
return
// guard let은 return을 꼭 해줘야함. switch의 case 문이라고 생각해야댐
}
// guard let을 통과한 상수는 전역 변수로서 사용할 수 있다.
let task = URLSession.shared.dataTask(with : url){
data, response, error in
if let error = error{
// 만약에 error가 발생한다면
completion(.failure(error))
// 에러 처리해주고
return
// 함수 종료
}
guard let httpResponse = response as? HTTPURLResponse,
(200..299).contains(httpResponse.statusCode) else{
// 만약에 response가 nil이거나 statuscode가 200대가 아닐 경우에
completion(.failure(NetworkError.custom(message:"Invalid response from server")))
return
}
guard let data = data else{
// 만약에 data가 안들어오면
completion(.failure(NetworkError.noData))
return
}
completion(.success(data))
}
task.resume()
}
do{
let result = try fetchData(from : "api 주소"){
result in
switch result{
case .success(let data) :
print("Data Received: \(data)")
case .failure(let error) :
print("Error occurred: \(error)")
}
}
}catch{
print("error : \(error)")
}
throws
키워드를 달아놓으면, 그 함수를 호출할 때 try
와 에러에 대한 처리를 강제한다.그러면 다음과 같은 구조가 된다.
do{
const result = try ~~~~
}catch{
...
}
try
, try?
, try!
를 쓸 수 있는데 한번 보자.try
는 일반적인 경우에 사용된다.
try?
는 안전한 에러처리를 위해서 사용된다.
try!
는 에러가 발생하지는 않을 것이라고 확신할때 사용하나, 사용하지말자
(스스로를 믿지마라)
try?
try?
는 에러가 발생하면 nil
을 반환한다. 에러가 발생하지 않으면 옵셔널 타입
으로 값을 반환한다.do-catch
문에서 쓸일은 없을 것 같고 다음과 같이 사용 이후에 if let
에서 사용한다.func someFunctionThatThrows() throws -> String{
// 에러를 던질 가능성이 있는 함수 (throws)
return "Success"
}
let result = try? someFunctionThatThrows()
if let result = result{
print("정상 처리 ~~~\(result)")
}else{
print("에러")
}
try!