Swift
의 조금 더 심화된 문법에 대해 알아볼거에요.
guard
문을 통해 옵셔널의 언래핑과 함수의 다양한 기능을 학습할 예정이에요.
마지막으로 1급 객체와 클로저의 개념에 대해 알아볼거에요.
guard
문에 대해 알아보고 if
문과 guard
문의 차이점을 확인해볼 거에요.BMI
판정하는 로직을 다양한 방식으로 정의해서 이해를 도울 예정이에요.first class object
와 first class citizen
, Clousuer
의 개념에 대해 알아볼 거에요.guard
→ function
→ BMI
guard
문은 Swift
에서 변수를 선언할 때, 값이 없을 수도 있는 nil
자료형의 오류를 방지하기 위한 방법 중 하나에요.
옵셔널 없애는 것을 언래핑(unwrapping
)이라고 해요.
먼저, 기존 옵셔널 언래핑에 대해 간단하게 알아볼게요.
optional binding
옵셔널을 언래핑하는 방법 중의 하나로 옵셔널 바인딩을 통해서 옵셔널에 할당된 값을 할당할 수 있어요.
optional binding
if let constantname = optionalName {
// 옵셔널 변수가 값이 있다면 언래핑해서 일반 상수 constantname에 대입하고 if문을 실행해요
// 값이 없다면 if문의 조건이 거짓이 되어 if문을 실행하지 않아요
}
if var variablename = optionalName {
// 옵셔널 변수가 값이 있다면 언래핑해서 일반 변수 variblename에 대입하고 if문을 실행해요
// 값이 없다면 if문의 조건이 거짓이 되어 if문을 실행하지 않아요
}
간단한 예제를 통해서 확인해볼게요.
x
와 x1
을 선언하고 각각 값이 있는 경우와 없는 경우의 결과를 확인할 수 있어요.var x : Int?
x = 10
if let xx = x {
print(x,xx)
//옵셔널 변수 x가 값(10)이 있으므로 언래핑해서 일반 상수 xx에 대입하고 if문을 실행해요
}
else {
print("nil")
}
var x1 : Int?
if let xx = x1 {
print(xx)
//옵셔널 변수 x1이 값이 없어서 if문의 조건이 거짓이 되어 if문 실행하지 않고 else문의 내용을 반환해요
}
else {
print("nil")
}
이렇게 if
문을 활용해서 옵셔널 언래핑을 할 수 있지만, Swift
에서는 guard
문을 지원해요. 어떤 부분이 다른지 알아볼게요.
guard
문의 형태부터 확인해볼게요.
guard
문 정의 방법guard <불리언 표현식> else {
// 표현식이 거짓일 경우에 실행될 부분이에요
<코드 블록을 빠져 나갈 구문>
}
// 표현식이 참일 경우에 실행되는 코드는 이곳에 위치해요
이렇게 guard
문은 조건식이 거짓이면 실행하는 구문이에요.
guard
문의 특징에 대해서 알아볼게요.
else
절에 속한 코드는 현재의 코드 흐름을 빠져나갈 수 있는 구문을 반드시 포함해야 해요.else
코드블록 안에서 호출할 수도 있어요.guard
문은 기본적으로 특정 조건에 맞지 않으면 현재의 함수나 반복문에서 빠져나갈 수 있도록 하는 조기출구(early exit
) 전략을 제공해요.내용이 길고 복잡하다보니 어렵게 느껴질 수 있어요.
func multiplyByTen(value: Int?) {
guard let number = value else { //조건식이 거짓(nil)일 때 else 블록 실행해요
print("nil")
return
}
print(number*10) //조건식이 참일 때 실행해요
// 주의할 점이 number를 guard문 밖인 여기서도 사용 가능해요
}
multiplyByTen(value: 3) // 30
multiplyByTen(value: nil)
multiplyByTen(value: 10) // 100
예제를 보면서 다시 얘기하면 다음과 같아요.
guard
문은 return
, break
, continue
, throw
등 제어문 전환 키워드를 쓸 수 있는 상황이라면 사용할 수 있어요.return
을 써서 해당 함수를 조기에 빠져나오는 조기출구 용도로 사용해요.실제 앱을 만들다 보면 옵셔널 바인딩 때문에 다중 if~else
를 사용해야 하는데, guard~let
을 사용하면 다중루프 없는 훨씬 가독성이 좋은 코드가 가능해서 그런 경우 많이 사용한다고 해요.
guard
문으로 언래핑하는 방법을 알아봤어요.
if
문으로 언래핑이 가능하기 때문에 차이점을 확인해볼게요.func printName(firstName:String, lastName:String?){
// if let 방법
if let lName = lastName { // lastName이 nil이 아닌 경우
print(lName,firstName)
}
else {
print("성이 없네요!")
}
// guard let 방법
guard let lName = lastName else { // lastName이 nil인 경우
print("성이 없네요!")
return // early exit
}
print(lName,firstName)
}
printName(firstName: "길동", lastName:"홍")
printName(firstName: "길동", lastName:nil)
가장 먼저 코드의 길이가 줄어든 것을 확인할 수 있어요.
또한 else
절이 나오는 순서의 차이도 확인할 수 있어요.
조금 더 차이를 체감하기 위해 무비 데이터를 가져오는 코드 일부분을 if
문 방식과 guard
문 방식으로 각각 확인해볼게요.
if
문 방식func getData() {
if let url = URL(string: movieURL) {
let session = URLSession(configuration: .default)
let task = session.dataTask(with: url) { (data, response, error) in
if error != nil {
print(error!)
return
}
if let JSONData = data {
print(JSONData, response!)
let dataString = String(data: JSONData, encoding: .utf8)
print(dataString!)
let decoder = JSONDecoder()
do {
let decodedData = try decoder.decode(MovieData.self, from: JSONData)
print(decodedData.boxOfficeResult.dailyBoxOfficeList[0].movieNm)
print(decodedData.boxOfficeResult.dailyBoxOfficeList[0].audienceCnt)
self.movieData = decodedData
DispatchQueue.main.async {
self.table.reloadData()
}
} catch {
print(error)
}
}
}
task.resume()
}
}
guard
문 방식func getData() {
guard let url = URL(string: movieURL) else { return }
let session = URLSession(configuration: .default)
let task = session.dataTask(with: url) { [self] (data, response, error) in
if error != nil {
print(error!)
return
}
guard let JSONData = data else { return }
let dataString = String(data: JSONData, encoding: .utf8)
print(dataString!)
let decoder = JSONDecoder()
do {
let decodedData = try decoder.decode(MovieData.self, from: JSONData)
print(decodedData.boxOfficeResult.dailyBoxOfficeList[0].movieNm)
print(decodedData.boxOfficeResult.dailyBoxOfficeList[0].audienceCnt)
self.movieData = decodedData
DispatchQueue.main.async {
self.table.reloadData()
}
} catch {
print(error)
}
}
task.resume()
}
if
문과 guard
문의 차이는 주로 코드의 흐름과 가독성에 있기 때문에 두 코드를 비교해볼게요.
if
문if let url = URL(string: movieURL) {
// url이 nil이 아닐 때 실행되는 코드
} else {
// url이 nil일 때 실행되는 코드 (옵션: 처리할 내용이 필요)
}
코드의 흐름: if
문을 사용할 경우, 조건이 만족되지 않으면 해당 블록의 코드가 실행되지 않고 아래 코드 블록(else
)이 실행돼요.
가독성: 각 조건을 처리하기 위해 추가적인 블록을 작성할 수 있기 때문에 코드가 복잡해질 수 있어요. 코드가 분산되기 때문에 가독성이 떨어질 수 있어요.
guard
문guard let url = URL(string: movieURL) else { return }
// url이 nil이 아닐 경우 이 아래 코드가 실행됨
코드의 흐름: guard
문은 조건이 만족하지 않으면 즉시 해당 함수에서 빠져나가기 때문에 흐름이 끊기지 않고 코드를 확인할 수 있어요.
가독성: guard
문을 사용하면 오류 처리 및 조건 검사를 한 곳에서 간단히 할 수 있어요. 이는 코드의 주요 흐름이 더 명확해지고 코드가 더 간결하게 유지될 수 있어요.
따라서 특수한 상황이 아닌 경우 Swift
에서 if
문 보다는 guard
문을 사용하는 것이 코드 분석에 더 도움이 될 수 있어요.
이전 내용은 함수를 정의하는 방법에 대해서만 알아봤어요.
이번에는 Swift
에서만 나타낼 수 있는 함수의 정의 방법에 대해 알아볼게요.
함수를 정의할 때, 인자값(argument
)이 없는 경우가 있을 수 있어요.
이 경우 디폴트 매개변수(default argument
)를 사용해서 초기값을 지정해줄 수 있어요.
func sayHello(count: Int, name: String = "길동") -> String {
return ("\(name), 너의 번호는 \(count)")
}
var message = sayHello(count:10, name: "소프트")
print(message)
message = sayHello(count:100)
print(message)
이전의 함수 정의 방법에서 자료형 뒤에 = <Default Value>
를 추가하면 돼요.
이렇게 디폴트 매개변수를 지정해주면, 호출 시에 해당 매개변수의 값을 지정하지 않아도 정상적으로 코드가 실행되는 것을 확인할 수 있어요.
이전에는 입력값이 여러 개, 반환값은 하나 또는 없는 경우의 함수만 다뤘어요.
이제 여러 값을 반환하는 방법에 대해서 알아볼게요.
여러 값을 반환하는 방법은 반환값을 튜플로 감싸서 반환할 수 있어요.
이해를 돕기 위해 단위 변환 함수의 예제를 통해서 확인해볼게요.
func converter(_ length: Float) -> (yards: Float, centimeters: Float, meters: Float) {
let yards = length * 0.0277778
let centimeters = length * 2.54
let meters = length * 0.0254
return (yards, centimeters, meters)
}
var lengthTuple = converter(10)
print(lengthTuple)
print(lengthTuple.yards)
print(lengthTuple.centimeters)
print(lengthTuple.meters)
함수가 튜플로 반환한 경우 TupleName.<method name>
를 통해 값에 접근할 수 있어요.
또 다른 예제를 통해서 확인해볼게요.
import Foundation
func sss(x : Int, y : Int) -> (sum : Int, sub : Int, mul : Int, div : Double) {
let sum = x+y
let sub = x-y
let mul = x*y
let div = Double(x)/Double(y) //같은 자료형만 연산 가능
return (sum, sub, mul, div)
}
var result = sss(x:10,y:3)
print(result.sum)
print(result.sub)
print(result.mul)
print(String(format: "%.3f", result.div))
print(type(of: sss))
div
의 경우 String(format: "%.3f", result.div)
를 사용해서 소수점 3자리에서 반올림된 값을 출력할 수 있어요.위 내용처럼 여러 값을 매개변수로 받을 수 있지만, 개수를 지정해줘야 하는 단점이 존재해요.
그러나 Swift
에서는 가변 매개변수를 통해서 개수 제한을 두지 않고 함수를 정의할 수 있어요.
매개변수 자료형 부분 뒤에 세 개의 점(...
)을 사용해서 가변 매개변수로 설정해줄 수 있어요.
예제를 통해서 확인해볼게요.
func displayStrings(strings: String...) {
for string in strings {
print(string)
}
}
displayStrings(strings: "일", "이", "삼", "사")
displayStrings(strings: "one", "two")
다른 예제를 통해서 확인해볼게요.
func add(numbers: Int...) {
var sum: Int = 0
for num in numbers {
sum += num
}
print(sum)
}
add(numbers: 1, 2, 3) // 6
add(numbers: 2, 2, 2, 2, 2) // 10
add(numbers: 1, 1, 1, 1, 1, 1, 1, 1, 1, 1) // 10
add(numbers: 1, 1, 1, 1) // 4
Swift
는 기본적으로 call by value
으로 값을 불러오는 방식이에요.
그러나 프로그래밍을 하다보면 주소값을 불러와야 할 때가 있어요.
그럴 때 Swift
에서 call by address
를 구현하는 방법에 대해 알아볼게요.
함수가 값을 반환한 후에도 매개변수에 일어난 변화를 유지하려면, 함수의 선언부에서 매개변수를 입출력 매개변수(inout parameter)로 선언해야 해요.
call by address
구현 예제var myValue = 10
func doubleValue (value: inout Int) -> Int {
value += value
return(value)
}
print(myValue)
print(doubleValue(value : &myValue))
print(myValue)
기존에는 함수 선언부에서 변수명 : 자료형
방식이었지만, call by address
를 구현하기 위해서는 변수명 : inout 자료형
으로 선언 할 경우 구현할 수 있어요.
이제 이전까지의 내용을 통대로 BMI
를 계산하는 프로그램을 작성해볼게요.
if
~ else
방식if
~else
로 구현한 판정 로직이에요.
이전에 사용한 String(format: "%.1f", <변수>)
를 통해서 소수점 한 자리 까지만 출력해요.
import Foundation
let weight = 60.0
let height = 170.0
let bmi = weight / (height*height*0.0001) // kg/m*m
let shortenedBmi = String(format:"%.1f", bmi)
var body = ""
if bmi >= 40 {
body = "3단계 비만"
} else if bmi >= 30 && bmi < 40 {
body = "2단계 비만"
} else if bmi >= 25 && bmi < 30 {
body = "1단계 비만"
} else if bmi >= 18.5 && bmi < 25 {
body = "정상"
} else {
body = "저체중"
}
print("BMI:\(bmi), 판정:\(body)")
import Foundation
func calcBMI(weight :Double,height :Double) -> String{
let bmi=weight / (height*height*0.0001) // kg/m*m
let shortenedBmi = String(format:"%.1f", bmi)
var body = ""
if bmi >= 40 {
body = "3단계비만"
} else if bmi >= 30 && bmi < 40 {
body = "2단계비만"
} else if bmi >= 25 && bmi < 30 {
body = "1단계비만"
} else if bmi >= 18.5 && bmi < 25 {
body = "정상"
} else {
body = "저체중"
}
return "BMI:\(shortenedBmi), 판정:\(body)"
}
print( calcBMI( weight:62.5, height:172.3 ) )
switch
~ case
방식import Foundation
func calcBMI (weight : Double, height : Double) {
let bmi = weight / (height*height*0.0001) // kg/m*m
let shortenedBmi = String(format: "%.1f", bmi)
switch bmi {
case 0.0..<18.5:
print("BMI:\(shortenedBmi),판정:저체중")
case 18.5..<25.0 :
print("BMI:\(shortenedBmi),판정:정상")
case 25.0..<30.0 :
print("BMI:\(shortenedBmi),판정:1단계 비만")
case 30.0..<40.0 :
print("BMI:\(shortenedBmi),판정:2단계 비만")
default :
print("BMI:\(shortenedBmi),판정:3단계 비만")
}
}
calcBMI(weight:62.5, height: 172.3)
switch
~ case
방식import Foundation
func calcBMI(weight : Double, height : Double) -> String {
let bmi = weight / (height*height*0.0001) // kg/m*m
let shortenedBmi = String(format: "%.1f", bmi)
var body = ""
switch bmi {
case 0.0..<18.5:
body = "저체중"
case 18.5..<25.0:
body = "정상"
case 25.0..<30.0:
body = "1단계 비만"
case 30.0..<40.0:
body = "2단계 비만"
default:
body = "3단계 비만"
}
return "BMI:\(shortenedBmi), 판정:\(body)"
}
print( calcBMI( weight:60.0, height: 170.0 ) )
1급 객체(first class object
) 또는 1급 시민(first class citizen
)란 다음과 같은 조건을 만족하는 언어를 의미해요.
이 조건을 충족하는 것을 1급 객체 또는 1급 시민이라 해요.
따라서 Swift
의 함수는 1급 객체에요.
1급 객체는 프로그래밍에서 유연하게 사용할 수 있고, 다양한 방식으로 활용될 수 있다는 의미예요.
그래서 1급 객체는 함수형 프로그래밍이나 고차 함수와 같은 개념에서 매우 중요한 역할을 하게 돼요.
각각의 특성을 나눠서 설명할게요.
Swift
는 함수를 데이터 타입처럼 처리할 수 있어요.func inchesToFeet (inches: Float) -> Float {
return inches * 0.0833333
}
let toFeet = inchesToFeet //함수를 자료형처럼사용
print(inchesToFeet(inches:10))
print(toFeet(10)) //주의 : argument label인 (inches:) 사용X
func inchesToFeet (inches: Float) -> Float {
return inches * 0.0833333
}
let toFeet = inchesToFeet
(Float) -> Float
이에요.(Int, Double) -> String
이에요.func outputConversion(converterFunc: (Float) -> Float, value: Float) { //함수를 매개변수로 사용
let result = converterFunc(value) //toFeet(10)
print("Result = \(result)")
}
outputConversion(converterFunc:toFeet, value: 10) // 피트로 변환하는 inchesToFeet함수 호출
func inchesToFeet (inches: Float) -> Float {
return inches * 0.0833333
}
func inchesToYards (inches: Float) -> Float {
return inches * 0.0277778
}
let toFeet = inchesToFeet
let toYards = inchesToYards
func outputConversion(converterFunc: (Float) -> Float, value: Float) { //함수를 매개변수로 사용
let result = converterFunc(value)
print("Result = \(result)")
}
outputConversion
함수를 호출할 때 선언된 데이터 타입과 일치하는 함수를 전달해요.outputConversion(converterFunc:toYards, value: 10) // 야드로 변환하는 inchesToYards함수 호출
outputConversion(converterFunc:toFeet, value: 10) // 피트로 변환하는 inchesToFeet함수 호출
func decideFunction (feet: Bool) -> (Float) -> Float { //매개변수형 리턴형이 함수형
if feet {
return toFeet
} else {
return toYards
}
}
위에서 언급된 1급 객체의 모든 조건을 코드로 직접 실행해서 확인해볼 수 있어요.
아래 코드는 위 내용을 충족하는 내용의 코드에요.
func up(num: Int) -> Int {
return num + 1
}
func down(num: Int) -> Int {
return num - 1
}
let toUp = up
print(up(num:10))
print(toUp(10))
let toDown = down
func upDown(Fun: (Int) -> Int, value: Int) {
let result = Fun(value)
print("결과 = \(result)")
}
upDown(Fun:toUp, value: 10) //toUp(10)
upDown(Fun:toDown, value: 10) //toDown(10)
func decideFun(x: Bool) -> (Int) -> Int { //매개변수형 리턴형이 함수형
if x {
return toUp
} else {
return toDown
}
}
let r = decideFun(x:true) // let r = toUp
print(type(of:r)) //(Int) -> Int
print(r(10)) // toUp(10)
클로저(Closure
)란 특정 작업(함수)과 그 작업이 일어난 곳(환경 또는 상태)을 모두 기억하고 있는 도구를 뜻해요.
각 언어별로 불리는 이름이 달라요. 아래는 주요 언어에서 불리는 이름이에요.
C
, C++
, Objective-C
의 block
Java
의 Lambda function
C#
의 Delegates
클로저 표현식은 독립적인 코드 블록이에요.
func add(x: Int, y: Int) -> Int {
return(x+y)
}
print(add(x:10, y:20))
let add1 = { (x: Int, y: Int) -> Int in
return(x+y)
}
print(add1(10, 20))
print(type(of:add1))
클로저는 코드에서 볼 수 있듯이 함수를 정의하지 않고 바로 변수에 저장해서 사용할 수 있어요.
이전의 func <이름>(변수:자료형) ...
의 형태가 아닌 변수를 통해 선언하고 호출하는 방식이에요.
클로저 표현식은 매개변수를 받거나, 값을 반환하도록 만들 수도 있어요.
{ (<매개변수 이름>: <매개변수 타입>, ... ) -> <반환 타입> in
// 클로저 표현식 코드
}
예제를 통해서 확인해볼게요.
let multiply = { (val1: Int, val2: Int) -> Int in // 매개변수리턴형
return val1 * val2
} // 여기서 multiply의 자료형은 (Int, Int) -> Int
let result = multiply(10, 20) // 상수를 함수처럼 호출,200
print(result)
이렇게 Swift
의 더 많은 내용을 알아봤어요.
학습해야 하는 내용이 많아지는 만큼 잘 구분하는 능력을 기르는 것이 중요하겠죠?
각각의 특징을 잘 이해하고 구분할 수 있도록 복습하는 것이 중요해요.