야곰 유튜브와 제가 따로 보는 인강 chat GPT 설명을 인용하여 정리해보았습니다.
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 를 사용할 수 없다.
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 을 발견했다.
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("옵셔널 바인딩 실패")
}
// 타입 컨버전 실패
함수는 반복적으로 사용할 수 있는 코드 블럭이다. 클로져도 마찬가지로 코드 블럭이다. 함수가 반복적으로 사용하는 것에 포커스가 맞춰져있다면 클로져는 코드 블럭을 다른 곳으로 전달하는데에 포커스가 맞춰져있다.
Function > Named Closure
Closure > Unnamed Closure
클로저는 함수와 비슷하게 동작하지만, 보다 간결하고 유연한 문법을 제공해서 코드를 작성할 때 많은 편의성을 제공한다.
일급 시민(first-citizen) 이고 변수 , 상수 등으로 저장 , 전달인자로 전달이 가능하다.
{ (매개변수 목록) -> 반환타입 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:)
메서드에 직접 전달된다. 이 클로저는 두 개의 매개변수 a
와 b
를 취하고, 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에서 함수형 프로그래밍을 지원하는 중요한 도구이다.