Swift 문법 훑어보기

냐옹·2024년 7월 8일
0

IOS

목록 보기
1/32

본 작성글은 swift 공식 문서를 참고하였습니다.

Swift에 대해

  • 변수는 항상 사용 전에 초기화되어야 한다.
  • 배열인덱스는 범위초과에러 out-of-bounds 에러에 대해 검사되어야 한다.
  • 정수오버플로우에 대해 검사되어야 한다.
  • 옵셔널nil값이 명시적으로 처리되도록 한다.
    ( nilnull과 비슷한 개념 )
  • 메모리는 자동으로 관리된다.
  • 에러처리를 통해 예기치 않은 오류를 처리할 수 있다.

Swift는 강력한 기능으로 타입 추론패턴매칭을 다른 유명한 언어를 개발하는 개발자들에게 친숙한 현대적이고 가벼운 구문으로 결합하여 복잡한 것도 명확하고 간결한 방식으로 표현할 수 있다. 그 결과로 코드는 읽고, 쓰고, 유지하기 쉽다.

버전호환성

  • 불투명한 타입 opaque type을 반환하는 함수는 Swift 5.1런타임이 필요하다.
  • try? 표현식은 이미 옵셔널 optional을 반환하는 표현식에 추가로 옵셔널 표현식을 도입하지 않는다.
  • 큰정수 리터럴 초기화 표현식은 올바른 정수타입으로 추론한다. 예를 들어 UInt64(0xffff_ffff_ffff_ffff)는 오버플로우가 아닌 올바른 값이다.
  • 동시성 concurrency)Swift5언어모드와 동시성 타입을 제공하는 Swift 표준 라이브러리의 버전이 필요하다.
  • Swift6으로 작성된 타겟은 Swift5, Swift4.2 또는 Swift4로 작성된 타겟에 따라 달라질 수 있고, 그 반대의 경우도 마찬가지이다. 즉, ,여러 프레임워크로 분할된 대규모 프로젝트가 있는 경우 코드를 새로운 언어 버전으로 한번에 하나씩 프레임워크로 마이그레이션할 수 있다.

Swift 둘러보기

간단한 값

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)
  • 문자열 string 값으로 포함하는 더 간단한 방법은 소괄호 안에 값을 작성하고 소괄호 전에 역슬래시\를 포함하면 된다. 예시로
let apples = 3
let oranges = 5
let appleSummary = "I have \(apples) apples."
let fruitSummary = "I have \(apples + oranges) pieces of fruit"
  • 여러줄의 문자열에 대해서는 쌍따옴표 3개"""를 사용하면 된다.
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 🎉"
  • iflet을 사용하여 누락될 수 있는 값에 대해 사용할 수 있다. 이러한 값은 옵셔널 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)를 체크한다.
  • 여기서 let 상수에 옵셔널로 할당된 값을 할당함으로서 옵셔널을 벗기는 작업을 한다.
  • 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는 모든 종류의 데이터와 다양한 비교작업을 지원한다.
  • 스위치정수동등성 비교로 제한되지 않는다.
  • whereswitch 안에서의 조건 추가에 사용한다.
  • 일치하는 스위치 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()
  • 저장된 단순 프로퍼티외에도 프로퍼티는 gettersetter를 가질 수 있다.
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 클래스의 초기화 구문은 세가지 단계가 있다.

  1. 하위 클래스가 선언한 프로퍼티의 값을 설정한다.
  2. 상위 클래스의 초기화 구문을 호출한다.
  3. 상위 클래스에 의해서 정의된 프로퍼티의 값을 변경한다. 메서드, getter 또는 setter를 사용하는 추가 설정 작업도 이 시점에서 수행할 수 있다.
  • 프로퍼티를 계산할 필요는 없지만, 새로운 값을 설정하기 전과 후에 실행되는 코드를 제공하려면, willSetdidSet을 사용하면 된다.
  • 이렇게 제공한 코드는 초기화 구문의 외부에서 값이 변경될 때마다 실행된다.
  • 예를 들어서 아래의 클래스는 삼각형의 측면의 길이가 항상 사각형의 측면의 길이와 같다는 것을 확인합니다.
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가지 방법을 살펴보자.
    1. hearts상수에 값을 할당할 때 명시적으로 타입을 지정하지 않았으므로, 열거형 케이스 Suit.hearts 전체이름으로 참조된다.
    1. 스위치에서 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()
}

동시성 Concurrency

  • 여기서부터 잘 봐야
  • 비동기적으로 실행되는 함수를 나타내기 위해서 async를 사용한다.
  • asyncfunction 키워드 앞에 붙이는 점이 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 비동기 함수로 초기화하는 변수 선언

  • 비동기 함수를 호출하기 위해서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)"
}

do-catch

  • swift에서의 do-catchjavascript에서의 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 객체
  • JSONSWIFT 객체디코딩/인코딩을 거치지 않고서는 서로 호환되지 않는다.

  • 디코딩/인코딩에 들어가기 전 Codable을 알아야하는데 이는 데이터 인코딩과 디코딩을 쉽게 처리할 수 있게 해주는 프로토콜이다.

  • Codable은 실제로 EncodableDecodable프로토콜의 type alias이다. 주요특징은 다음과 같다.
    ㄴ 1. 자동구현
    대부분의 경우에 swift 컴파일러가 자동으로 인코딩 / 디코딩 로직을 생성한다.
    ㄴ 2. JSON 변환
    JSON과 swift 객체 간의 변환을 쉽게 할 수 있다.
    ㄴ 3. 사용자 정의
    필요한 경우 인코딩 / 디코딩 프로세스를 커스터마이징할 수 있다.
    ㄴ 4. 다양한 데이터 형식 지원
    JSON 뿐만 아니라 PropertyList 등 다양한 형식을 지원한다.

  • Codable 예시코드
  1. JSON -> SWIFT object (디코딩)
// 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)")
}
  1. SWIFT object -> JSON (인코딩)
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절에서 오류를 암시적으로 바인딩할 수 있다.
  • 몇가지 형태가 있는데 다음과 같다.
  1. 일반적인 catch
catch{
	print("Decoding error : \(error)")
  	// error는 암시적으로 바인딩된 오류 객체이다.
}
  1. 특정 오류 타입을 명시적으로 잡는 catch
catch let decodingError as DecodingError{
	print("Specific decoding error : \(decodingError)")
}
  1. 패턴 매칭을 사용한 catch
catch DecodingError.keyNotFound(let key, _){
	print("Missing key : \(key)")
}
  • 3번이 살짝 이해가 안가서 조금 더 알아보겠다.
    3번을 살펴보면 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)")
}
  • 아래 2개를 안하고서는 샘플 코드를 이해하기가 힘들다....

guard let , if let

  • if letguard let은 옵셔널 값을 확인하고 해당 값을 일시적으로 변수상수바인딩하는 기능을 수행한다.

  • 그럼 옵셔널이란
    Swift에서 옵셔널은 값이 존재할 수도, 존재하지 않을 수도 있는 상황을 표현하는 타입이다.
    예시로 Int?를 들 수 있음
    옵셔널을 선언할 때 초기값을 지정하지 않으면 자동으로 nil이 할당된다.
    nil이 할당된 값에 접근하는 경우에 런타임 에러가 발생하기 때문에 옵셔널 바인딩, 옵셔널 체이닝, 옵셔널기본값 사용 등을 통해 nil값에 대한 처리가 필요하다.

  • 옵셔널 바인딩
    옵셔널 바인딩이란 강제로 옵셔널을 여는게 아니라 안전하게 확인을 해보고 나서 언래핑하는 방법이다.
    즉 if문을 이용하여 옵셔널에 할당된 값을 임시변수 또는 상수에 할당을 해주는 방식이다.
    ㄴ 1. 강제로 언래핑 : !를 써서 강제로 옵셔널 추출
    ㄴ 2. 옵셔널 바인딩 (안전한 언래핑) : 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)
}
  • 옵셔널 체이닝
    언래핑없이 옵셔널 값에 접근한다.
  1. 예를 들어서
struct TestStruct{
	wrapper : TestInnerStruct?
}

struct TestInnerStruct{
	innerValue : Int? = 10
}

라고 한다.

이제 여기에서

let result = TestStruct?.wrapper?.innerValue;

를 했다고 쳐보자.
1. result의 type은 일단 옵셔널<맨 뒤의 타입이다. 여기서는 innerValue>
2. 만약에 중간에 wrapper가 nil이었다. 그러면?
런타임에러는 발생하지 않고, resultnil이 된다.
ㄴ 이어지는 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
guard let
  • 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은 전역변수로써 사용이 가능하다. 

in , 익명함수

swift에서 익명함수를 만들기 위해서 다음과 같은 절차를 따른다.

    1. 함수 선언 없이 중괄호로 감싼 함수 몸체만 만들고
    1. 파라미터 목록이나 반환타입 설정이 필요한 경우에, 중괄호 영역 첫째줄에 선언하고 in 키워드를 입력한다.

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)")
}
  • 익명함수를 활용하는 방법 1 : 인자를 줄때 아예 거기서 선언
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)")
                            }
  • 익명함수는 한번 더 축약할 수 있다. OTL..
    1. 반환타입을 생략할 수 있다.
UIView.animateWithDuration(0.4, animation : {
                           		() in
  								~~~본문
                           }, completion : {})
    1. 파라미터가 없으면 in 라인을 생략할 수 있다.
UIView.animateWithDuration(0.4, animation : {
                           		~~~본문
                           }, completion : {})
    1. 파라미터 타입을 생략할 수 있다.
UIView.animateWithDuration(0.4, animation : {
                           		(testValue) -> () in 
  								~~~본문
                           }, completion : {})
                           
// 파라미터 타입과 반환값 생략
UIView.animateWithDuration(0.4, animation : {
                           		(testValue) in 
  								~~~본문
                           }, completion : {})
    1. 중괄호를 생략할 수 있다.
UIView.animateWithDuration(0.4, animation : {
                           		testValue in
                           		~~~ 본문
                           }, completion : {})
    1. 파라미터가 있는 때에도 in 라인을 생략할 수 있다.
UIView.animateWithDuration(0.4, animation : {
                           		print("first param : \($0)")
                           }, completion : {})
    1. 파라미터 이름을 생략할 수 있다. (파라미터가 필요없는 동작만 할 경우)
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 }

`

REST API에 대한 GET/POST요청

  • url을 아예 URLComponent라는 객체를 찍어내서 안의 속성을 수정하는 방법도 있고... (이건 뭔가 C# 스럽다)
  • 클래스형 개발에서 함수형 개발로 넘어가는 과정에 있는 듯한 느낌..
  • 다음은 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)")
      	// 암시적 바인딩
    }
}
  • 참고로 json이 저장되어있는 데이터의 생성은
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를 쓰는 것이 안전하다. 
  • post 요청 예시
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"
}
  • 에러를 처리하는 방법은 여러가지가 있다.
  1. do-catch
do{
	let printerResponse = try send(job : 1040, toPrinter : "Bi Sheng")
  	// 앞에서 send 함수에 'throws'를 명시해줬기 때문에 try 키워드를 앞에다가 붙여야 한다. 
}catch{
  	print(error)
}
// JobSent
  1. 특정 에러를 처리하는 여러개의 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"
  1. try?
  • 함수에서 에러가 발생하면 특정 에러는 버려지고 결과는 nil이다.
  • 그렇지 않으면 결과는 함수가 반환한 옵셔널 값을 포함한다.
let printerSuccess = try? send(job : 1884, toPrinter : "Mergenthaler")

let printerFailure = try? send(job : 1885, toPrinter: "Never Has Toner")
  • defer : 함수를 반환하기 직전에 함수의 다른 모든 코드 다음에

rest api를 사용하면서 마주칠 수 있는 Errors

1. URLSession 관련 에러
  • URLSessionError : URLSession에서 발생하는 일반적인 에러로 네트워크 요청이 실패했을 때 나타난다.

  • <세부에러>

  • URLError.notConnectedToInternet
    인터넷 연결이 없는 경우

  • URLError.timedOut
    요청이 타임아웃된 경우

  • URLError.cannotFindHost
    호스트를 찾을 수 없는 경우

  • URLError.cannotConnectToHost
    호스트에 연결할 수 없는 경우

  • URLError.networkConnectionLost
    네트워크 연결이 도중에 끊어진 경우

  • URLError.badServerResponse
    서버로부터 잘못된 응답을 받은 경우

  • URLError.unsupportedURL
    지원되지 않는 URL을 사용하는 경우

2. HTTP 상태코드 에러

이건 패스

3. JSON Parsing Error
  • 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)")
}
4. Custom Error
  • 개발자가 정의한 커스텀 에러.
enum NetworkError : Error{
	case invalidURL
  	case noData
  	case decodingFailed
  	case custom(message : String)
}
  • @escaping 키워드는 클로저가 함수의 실행이 끝난 후에도 호출될 수 있음을 나타내는 속성이다.
  • 클로저가 함수 외부에서도 사용되거나 저장될 수 있음을 명시하는데 사용된다.
  • 기본적으로 클로저는 함수 내에서 실행되고 함수가 종료되면 사라진다.
  • 그러나, 비동기 작업이나 콜백 함수에서 클로저가 함수의 실행이 끝난 후에 호출되어야 하는 경우가 있는데, 이 경우에 클러저에 @escaping 속성을 사용한다.
  • * 이렇게 설명을 들었는데도, 솔직하게 @escaping이 이해가 안된다.

  • @escaping이 필요한 이유는 클로저가 함수의 실행이 끝난 후에도 호출될 수 있음을 명시적으로 나타내기 위해서이다.

  • 구체적으로, 클로저가 함수 외부에서 참조되거나 저장되어 함수가 반환된 이후에도 클로저가 사용될 때 @escaping이 필요하다. 이를 통해서 swift는 메모리 관리와 관련된 최적화를 올바르게 수행할 수 있다.

  1. 비동기작업
  • 비동기 작업에서는 함수가 종료된 후에도 클로저가 실행될 수 있다.

  • 예를 들어서, 네트워크 요청의 결과를 처리하는 클로저는 네트워크 응답이 도착할 때까지 대기하기 때문에 함수가 종료된 후에 실행된다.

  • (참고) 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)")
}
try? 와 try!
  • 함수를 구현할때 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!
    쓰지마라

0개의 댓글