Swift/Swift 기본기

[Swift] for-in과 forEach를 알아보자.

여성일 2024. 1. 10. 13:48
728x90

프로젝트를 하다보면 Collection Type을 순회해야할 때가 있는데, 둘의 차이점을 잘 알지 못한 채 지금까지 사용했었다.

이번 글을 통해 for-in과 forEach과 둘의 차이점에 대해 자세히 알아보겠다.

 

반복문

for-in과 forEach를 알아보기 전에 반복문에 대해 다시 한번 짚어보자.

 

반복문은 주어진 조건에 의해 특정 코드 블럭을 반복적으로 실행할 수 있게 해주는 구문이다.

 

1️⃣ for문 

- 횟수에 의한 반복

2️⃣ while문

- 조건에 의한 반복

 

for-in
for 루프상수 in 순회대상 {
	실행구문
}

✅ 설정한 범위만큼 반복이 일어난다.

✅ Collection에 저장되어있는 요소 수만큼 반복이 일어난다.

✅ 루프상수의 이름은 원하는 대로 작성할 수 있다. ex)index, num, k ...

let intArray: [Int] = [1,2,3,4,5]

for num in intArray {
	print(num)
}

// 1
// 2
// 3
// 4
// 5

for-in문을 이용하여 intArray 요소 값을 print하는 간단한 예제이다. 

첫번째 반복, print(num)에서 num이라는 루프상수에 intArray[0] 대입

두번째 반복, print(num)에서 num이라는 루프상수에 intArray[1] 대입

...

다섯번째 반복, print(num)에서 num이라는 루프상수에 intArray[5] 대입

 

이런 식으로 배열 요소의 수만큼 반복하기 때문에, for문은 횟수에 의한 반복이라고 할 수 있다.

즉, for-in 반복문을 사용하여 배열의 모든 요소에 루프상수로 접근이 가능하다.

 

✅ 배열뿐만 아니라 Dictionary, Set 또한 for-in문을 이용하여 접근할 수 있다.

let capitalDic: [String : String] = ["Korea" : "Seoul", "Japan" : "Totyo"]

for (key, value) in capitalDic {
	print("\(key)의 수도는 \(value)입니다.")
}

// Japan의 수도는 Totyo입니다.
// Korea의 수도는 Seoul입니다.

for key in capitalDic.keys {
	print(key)
}

// Korea
// Japan

for value in capitalDic.values {
	print(value)
}

// Totyo
// Japan

📚 Dictionary요소들의 순서 없이 Key-Value 쌍으로 이루어진 Collection Type이다.

 

첫번째 for-in문과 같이 튜플 상수를 각각 선언해서 사용하는 것이 일반적이지만,

key 또는 value값만 반복하고 싶다면 두번째, 세번째 방법처럼 사용하면 된다.

 

let intSet: Set<Int> = [1,2,3,4,5]

for num in intSet {
	print(num)
}

📚 Set요소들의 순서와 중복 없이 이루어진 Colletion Type이다.

 

set은 배열하고 동일하게 for-in문을 사용하면 된다.

다만, 요소들의 순서가 없기 때문에 print 출력 값은 매번 다를 것이다.

 

forEach
순회대상.forEach {
	실행구문
}

✅ 기본적인 원리는 for-in과 동일하다.

✅ 반복 처리 할 작업은 클로저로 작성해서 파라미터로 넘긴다. 

✅ Collection에 저장된 요소를 클로저가 반복 실행될 때마다 클로저 상수로 넘겨준다.

 

let intArray: [Int] = [1,2,3,4,5]

intArray.forEach {
	print($0)
}

// 1
// 2
// 3
// 4
// 5

내가 전달한 반복 처리(print)를 intArray 요소의 개수 만큼 반복한다.

 

intArray.enumerated().forEach {
	print("index: \($0), num: \($1)")
}

intArray.indices.forEach {
	print("index: \($0), num: \(nums[$0])")
}

index를 알고 싶다면 enumerated 메소드나 indices 메소드를 사용하면 된다.

 

✅ 배열뿐만 아니라 Dictionary, Set 또한 forEach문을 이용하여 접근할 수 있다.

let capitalDic: [String : String] = ["Korea" : "Seoul", "Japan" : "Totyo"]

capitalDic.forEach {
	print("\(key)의 수도는 \(value)입니다.")
}

// Japan의 수도는 Totyo입니다.
// Korea의 수도는 Seoul입니다.

capitalDic.keys.forEach {
	print($0)
}

// Korea
// Japan

capitalDic.values.forEach {
	print($0)
}

// Totyo
// Japan

 

let intSet: Set<Int> = [1,2,3,4,5]

intSet.forEach {
	print($0)
}

//1
//2
//3
//4
//5

 

for-in과 forEach의 차이점

1️⃣ continue, break

for-in문은 개발자가 직접 구현하는 "반복문"이다.

forEach는 개발자가 반복하고 싶은 구문을 forEach라는 함수의 인자로 "클로저"로 작성해서 넘겨주는 것이다.

 

그렇기 때문에, 반복문 안에서만 사용할 수 있는 continue와 break는 for-in에서는 사용 가능하지만,

forEach에서는 사용이 불가능하다.

 

for num in intArray {
    break // ⭕️Succ
    continue // ⭕️Succ
}

intArray.forEach {
    break // ❌err
    continue // ❌err
}

 

2️⃣ return문

func printForIn() {
    var intArray: [Int] = [1,2,3,4,5]
        
    for num in intArray {
        print(num)
        return
    }
}

// 1

for-in문의 경우 Collection을 순회하다가 return을 만나면 반복을 종료함.

 

func printForEach() {
    var intArray: [Int] = [1,2,3,4,5]
    
    intArray.forEach {
    	print($0)
        return
    }
}

// 1
// 2
// 3
// 4
// 5

하지만 forEach의 경우 반복문이 아닌 반복하고자 하는 내용을 익명 함수(클로저)로 전달하기 때문에 return을 만난다는 것은 전달한 클로저를 종료하는 것을 뜻한다.

 

첫번째 반복, print($0) 1을 print ➡️ return 

두번째 반복, print($0) 2를 print ➡️ return 

...

다섯번째 반복, print($0) 5를 print ➡️ return 

 

위와 같이 첫번째 반복 때는 1을 출력하고 클로저를 return하여 바로 다음 두번째 반복 클로저가 실행된다.

즉, 반복 횟수에 영향을 주지 않는다.

 

내가 짠 코드
func fetchData(codes: String, onCompleted: @escaping ([ExchangeRateModel]) -> Void) {
    repository.getExchangeRateData(codes: codes) { entity in
        var model = [ExchangeRateModel]()
            
        entity.forEach {
            for (key, value) in countryModelItem {
                if $0.currencyCode == key {
                    let item = ExchangeRateModel(country: value.countryName, currencyName: value.currencyUnit, currencyCode: $0.currencyCode, basePrice: $0.basePrice, changePrice: $0.changePrice, signedChangeRate: $0.signedChangeRate, date: $0.date, time: $0.time)
                    model.append(item)
                    break
                }
            }
        }
        onCompleted(model)
    }
}

현재 개발하고 있는 "바꿔조" 프로젝트의 ViewModel 로직 중 일부이다.

repository의 getExchangeRateData 메소드를 통해 Entity를 fetch하여 ExchangeRateModel로 가공하는 메소드이다.

서버에서 받아 온 원형 데이터(entity)를 순회하며 entity의 currencyCode값과 countryModelItem Dictionary의 키 값과 일치하면 원하는 entity의 값을 추출하여 ExchangeRateModel로 만들어 model에 저장하는 로직이다.

 

 entity와 countryModelItem 두 CollectionType을 모두 순회하며 비교해야하기 때문에 이중 구조로 작성하였다.