Optional & Closure

Baek Dong Hyun·2024년 4월 16일
1

야곰 유튜브와 제가 따로 보는 인강 chat GPT 설명을 인용하여 정리해보았습니다.

Optional

let num: Int
num = 123

print(num)

let num2: Int
// num = 123

print(num2) // Constant 'num2' used before being initialized

Constant 'num2' used before being initialized
초기화를 하지않고 num2 를 사용할 수 없다.

Q : 그냥 항상 값을 저장하면 안됨?

A : 예를 들어 파일에 어떤 숫자가 있고 파일을 읽어서 값을 저장한다고 생각해봄
지정한 위치에 파일이 있고 파일안에 필요한 값이 필요한 형태로 저장되어있다면 문제가 없다.
하지만 이중에 하나라도 조건이 맞지않는다면 값을 저장할 수 없게된다.
이번엔 서버에 값이 저장되있다고 생각해봄
서버가 정상적으로 실행되고 있고, 아이폰도 네트워크에 연결되어있고, 전달되는 데이터도 올바른 형태로 되어있다면 문제 없다. 하지만 하나라도 조건에 맞지않다면 값을 저장할 수 없다.
어쨌든 값을 저장하지않고 초기화하는 수단이 필요한데 그게 바로 옵셔널이다.

Optional: 선택적인 , 의무적이지 않은

여기서는 값을 저장하지 않아도 되는 이라고 해석한다.

let num: Int? // Optional Int
num = nil // nil 을 저장하는 건 값을 저장하지않는 것과 같다.

이렇게 type 뒤에 ? 를 작성하면 된다.

값을 저장해도되고 안해도 된다.

let count: Int? = nil // count가 없다
let count: Int? = 0 // count가 0이다

이 차이가 중요하다고 함 (0도 값이고 숫자니까)

let num: Int = 123
print(num) // 123

let optionalNum: Int? = 123
print(optionalNum) // Optional(123)

Optional Type 은 값이 포장되어있다고 생각하면 편하다. 그래서 Unwrapping(추출) 을 해줘야한다.

이제 저 포장되어있는 값을 추출해야하는데

print(optionalNum!) // 강제 추출 , Forced Unwrapping

이렇게하면된다. 근데 이건 값이 확실히 있을 때 한다. 만약 값이 없다면

let optionalNum: Int? = nil
print(optionalNum!) // 강제 추출 , Forced Unwrapping

Fatal error: Unexpectedly found nil while unwrapping an Optional value
옵셔널값을 unwrapping 하는 동안 예상치 못하게 nil 을 발견했다.

Optional Binding

if let name: Type = OptionalExperssion {
	statements
}

while let name: Type = OptionalExperssion {
	statements
}

guard let name: Type = OptionalExpression else {
	statements
}
1. 
let str: String? = "123"

// optional binding
if let str = str { // if let 바인딩할 상수의 이름 = 앞에서 선언했던 옵셔널상수의 이름
  if let num = Int(str) { // 여기에 들어있는 str 은 바인딩할 상수의 이름 이거임
    print(num)
  } else {
    print("타입 컨버전 실패")
  }
} else {
  print("옵셔널 바인딩 실패")
}

// 123

2.
let str: String? = nil

// optional binding
if let str = str { // if let 바인딩할 상수의 이름 = 앞에서 선언했던 옵셔널상수의 이름
  if let num = Int(str) { // 여기에 들어있는 str 은 바인딩할 상수의 이름 이거임
    print(num)
  } else {
    print("타입 컨버전 실패")
  }
} else {
  print("옵셔널 바인딩 실패")
}

// 옵셔널 바인딩 실패

3.
let str: String? = "nil"

// optional binding
if let str = str { // if let 바인딩할 상수의 이름 = 앞에서 선언했던 옵셔널상수의 이름
  if let num = Int(str) { // 여기에 들어있는 str 은 바인딩할 상수의 이름 이거임
    print(num)
  } else {
    print("타입 컨버전 실패")
  }
} else {
  print("옵셔널 바인딩 실패")
}

// 타입 컨버전 실패

Closure

함수는 반복적으로 사용할 수 있는 코드 블럭이다. 클로져도 마찬가지로 코드 블럭이다. 함수가 반복적으로 사용하는 것에 포커스가 맞춰져있다면 클로져는 코드 블럭을 다른 곳으로 전달하는데에 포커스가 맞춰져있다.

Function > Named Closure
Closure > Unnamed Closure

클로저는 함수와 비슷하게 동작하지만, 보다 간결하고 유연한 문법을 제공해서 코드를 작성할 때 많은 편의성을 제공한다.
일급 시민(first-citizen) 이고 변수 , 상수 등으로 저장 , 전달인자로 전달이 가능하다.

Closure 정의 문법

{ (매개변수 목록) -> 반환타입 in 
	실행코드
}

함수를 사용한다면

func sumFunction(a: Int, b: Int) -> Int {
	return a + b
}

var sumResult: Int = sumFunction(a: 1, b: 2)

print(sumResult) // 3

클로져의 사용

var sum: (Int, Int) -> Int = { (a: Int, b: Int) in
	return a + b
}

sumResult = sum(1, 2)
print(sumResult) // 3

당연히 함수는 클로져의 일종이고 위에 만든 sum변수에는 함수도 할당할 수 있다.

sum = sumFunction(a:b:)

sumResult = sum(1, 2)
print(sumResult) // 3

함수의 전달인자로서의 클로져

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 divide: (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 in
	return left * right
})

print(calculated) // 500

후행 클로져

클로져가 함수의 마지막 전달인자라면 마지막 매개변수 이름을 생략한 후 함수 소괄호 외부에 클로져를 구현할 수 있다.

var result: Int

result = calculate(a: 10, b: 10) { (left: Int, right: Int) -> Int in
	return left + right
}

print(result) // 20

반환타입 생략

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
}

print(result) // 20

단축 인자이름

클로져의 매개변수 이름이 굳이 불필요하다면 단축 인자이름을 활용할 수 있다.
단축 인자이름은 클로저의 매개변수의 순서대로 $0 , $1 … 처럼 표현한다

result = calculate(a: 10, b: 10, method: { in
	return $0 + $1
})

print(result) // 20

당연히 후행 클로져와 함께 사용할 수 있다.

result = calculate(a: 10, b: 10) { in
	return $0 + $1
}

print(result) // 20

암시적 반환 표현

클로져가 반환하는 값이 있다면 클로져의 마지막 줄의 결과 값은 암시적으로 반환 값으로 취급한다.

result = calculate(a: 10, b: 10) { in
	$0 + $1
}

print(result) // 20

당연하게도 간결하게 한 줄로 표현해 줄 수 있다.

result = calculate(a: 10, b: 10) { $0 + $1 }

print(result) // 20

축약하지 않은 클로져 문법과 축약 후 문법 비교

result = calculate(a: 10, b: 10, method: { (left: Int, right: Int) -> Int in
	return left + right
})

result = calculate(a: 10, b: 10) { $0 + $1 }

print(result) // 20

이렇게나 줄일 수 있다.

또다른 예

숫자 배열을 정렬하는 간단한 함수를 작성한다고 가정해보면, 일반적으로 이와같이 작성이 가능하다.

let numbers = [5, 2, 8, 1, 9]

func ascending(_ a: Int, _ b: Int) -> Bool {
    return a < b
}

let sortedNumbers = numbers.sorted(by: ascending)
print(sortedNumbers) // [1, 2, 5, 8, 9]

클로져를 사용하게 되면

let numbers = [5, 2, 8, 1, 9]

let sortedNumbers = numbers.sorted(by: { (a: Int, b: Int) -> Bool in
    return a < b
})
print(sortedNumbers) // [1, 2, 5, 8, 9]

클로저는 중괄호로 둘러싸여 있으며, sorted(by:) 메서드에 직접 전달된다. 이 클로저는 두 개의 매개변수 ab를 취하고, bool 값을 반환한다. 클로저의 내용은 함수와 동일하지만, 함수 이름이 없다.

Swift는 타입 추론을 통해 매개변수 및 반환 유형을 자동으로 추측할 수 있기 때문에, 종종 이러한 유형 선언을 생략할 수 있다.

let sortedNumbers = numbers.sorted(by: { a, b in
    return a < b
})

단일 표현식을 사용하는 클로저의 경우 return 카워드를 생략할 수 있다.

let sortedNumbers = numbers.sorted(by: { a, b in a < b })

여기서 더 간단하게 하자면

let sortedNumbers = numbers.sorted(by: <)

클로저는 코드를 간결하고 읽기 쉽게 만들어주며, Swift에서 함수형 프로그래밍을 지원하는 중요한 도구이다.

profile
안녕하세요.

0개의 댓글