iOS 공부하는 감자

SeSac_1, 2주차 과제 피드백 정리 본문

☘️ SeSac_iOS

SeSac_1, 2주차 과제 피드백 정리

DongTaTo 2022. 7. 19. 02:27
반응형

1. forEach VS for-in

둘 다 Collection에 들어있는 요소를 순환하며 처리하고자 할 때 사용한다.

사실 지금까지.. 동작 구조에 대한 이해 없이, 단순 작성이 편하다는 이유로 forEach문을 자주 사용했었다..!!

@IBOutlet var labelList: [UILabel]!

labelList.forEach({ ... })

이런 식으로 IBOutletCollection 배열을 forEach문으로 순환하면서 필요한 작업을 했었는데, 멘토님께서 구조적인 차이를 알고 사용하면 좋을 것 같다고 하셨다.! (보통 편해서 forEach문을 사용하는 경우가 많아서 차이점을 찾아보는 것을 권장해 주셨다.)

 

 

for-in

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

for i in someCollection {
    print(i)
}

 

Collection의 요소를 하나씩 루프 상수로 할당하여 반복한다.

Collection에 저장된 요소의 개수만큼 반복한다.

 

 

forEach

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

someCollection.forEach {
    print($0)
}

 

Collection에 저장된 요소의 개수만큼 반복하여 처리하는 건 for-in문과 동일하다.

단, 반복해서 수행할 코드를 매개변수(클로저)로 입력받고, Collection의 요소는 클로저 상수(매개변수)로 전달된다.

==> Collection의 요소를 클로저 상수로 받아서, Collection 요소의 개수만큼 매개변수로 전달받은 클로저를 반복 실행하는 것

 

 

비슷해 보이지만.. 핵심은

for-in : 반복문

forEach : 고차함수

라는 것이다.

 

[차이점]

1. continue, break

continue는 현재 실행 중인 반복문 스코프의 Loop를 중지하고, 다음 Loop를 실행한다.

break는 현재 실행 중인 반복문 스코프를 중지시킨다.

두 제어 전송 구문은 반복문에서만 사용 가능하기 때문에 고차함수인 forEach문에서는 사용할 수 없다.

 

2. return문의 영향

return은 함수에서 사용된다.

  1. 반환값이 있다면 반환값을 리턴하고, 함수를 종료한다.
  2. 반환값이 없다면 그냥 함수를 종료한다.

즉, 함수가 아닌 단일 반복문에서는 return키워드를 내부에 작성하면 오류가 발생한다. (사용할 수 없다)

 

 

그리고, return을 포함한 제어 전송문 등등은 가장 가까운 스코프에 영향을 미치기 때문에

두 구문을 함수 내부에서 사용했을 때, 차이가 발생한다.

 

 

1. for-in

func someFunction() {
    
    let numbers: [Int] = [1, 2, 3, 4, 5]
    
    for n in numbers {
        print(n, terminator: " ")
        return
    }
}
// 실행결과 : 1

 

위 코드에서 반복문 내부에 작성된 return은 가장 가까운 스코프의 함수를 종료시킨다.

즉, 첫 번째 반복문 Loop를 실행하다가 return을 만나면서 someFunction()함수 자체를 종료시키고

함수 내부에서 동작하는 반복문 Loop 또한 종료된다.

 

 

2. forEach

func someFunction() {
    
    let numbers: [Int] = [1, 2, 3, 4, 5]
    
    numbers.forEach {
        print($0, terminator: " ")
        return
    }
}
// 실행결과 : 1 2 3 4 5

forEach문은 Collection의 요소를 클로저 상수로 사용하여 매개변수로 전달한 클로저(익명함수)를 요소의 개수만큼 반복 실행한다.

따라서 return키워드를 통해 종료되는 함수는 매개변수로 전달되는 클로저(익명함수)이기 때문에

위 코드에서는 모든 Collection의 요소가 문제없이 잘 출력된다.

 

 

⭐️ 중요한 건 for-in문은 반복문, forEach문은 고차함수라는 것. 근본적인 구조가 다름을 기억하면 사용법이 헷갈리지 않을 것 같다!

ex- 특정 조건에서 반복 실행을 종료시켜야 하는 경우 break를 사용할 수 있는 for-in문을 사용하기 등등..

 

 

 

 


 

 

2. cornerRadius와 clipsToBounds(masksToBounds)

지금까지 cornerRadius - 둥글게 처리를 할 때, clipsToBounds 속성을 true로 항상 변경했었다.

어떤 속성인지 제대로 이해하지 않고 그냥 습관처럼 묶어서 사용했는데.. 이번 피드백을 계기로 살짝 찾아보니까 CALayer, Frame, Bounds 등 연관돼서 공부할 내용이 줄줄이 나왔다..!

이 부분은 주말에 날 잡고 정리해야겠다...!

 

clipsToBount : UIView의 프로퍼티로, subView가 자신(view)를 넘어선 경우, 나 자신(view) 너머로 subView의 넘치는 부분을 그릴지 말지 한계를 설정한다.

 

masksToBouns : CALayer의 프로퍼티로, 자신의 영역을 넘어서는 부분을 그릴지 말지 한계를 설정한다.

 

(피드백) 알아볼 내용 ⭐️

  • UIImageView, UIButton은 clipsToBounds 속성이 false여도 cornerRadius가 잘 작동하는 반면, UILabel은 clipsToBounds 속성이 true인 경우에만 cornerRadius가 작동한다. 왜 그럴까??
  • shadow도 연관해서 학습

 

 

 


 

 

3. 클로저 구문에서 weak self를 옵셔널 바인딩으로 처리해보기

[수정 전]

let alertAction = UIAlertAction(title: "확인", style: .default) { [weak self] _ in
	self?.searchTextField.text = nil
	self?.searchResultLabel.text = nil
}

 

 

[수정 후]

let alertAction = UIAlertAction(title: "확인", style: .default) { [weak self] _ in
	guard let searchVC = self else { return }
	searchVC.searchTextField.text = nil
	searchVC.searchResultLabel.text = nil
}

 

옵셔널 바인딩으로 처리함으로써 얻는 이점 (추측)

바인딩에 실패하면 바로 return 하므로, 불필요한 코드 실행을 방지할 수 있다. => 성능 개선(?)

 

 

 


 

 

4. 생명주기 함수 재정의할 때 super 메서드 호출 누락

 

ViewController 생명주기 함수를 재정의하여 사용하면서, super 메서드를 누락했었다...

실수하지 않게 잘 기억하기...ㅠ

override func viewDidDisappear(_ animated: Bool) {
    // ⭐️⚠️ 생명주기 메서드 재정의할 때, super 메서드 호출하기!!!!!
    super.viewDidDisappear(animated)
        
    // ...
}

 

 

 

 


 

 

5. 데이터를 처리하는 시점 변경

UserDefaults를 사용한 데이터 저장 시점

수정 전 : viewWillDisappear

수정 후 : viewDidDisappear

override func viewDidDisappear(_ animated: Bool) {
    super.viewDidDisappear(animated)
        
    // UserDefaults에 데이터를 저장하는 코드
    for (key, emotionType) in zip(EmotionType.allCases, emotionList) {
        UserDefaults.standard.set(emotionType.emotionCount, forKey: key.rawValue)
    }
}

 

[피드백]

보통 데이터 처리는 화면이 완벽하게 사라지거나 나타나는 시점이 끝난 후 처리한다.

=> willDisappear 메서드가 호출되는 시점에 데이터가 변경되고 있을 수 있어서, 화면이 완전히 넘어간 후 안정화(?)된 데이터를 처리하는 게 좋은 것 같다.

=> UserDefaults 코드를 작성한 부분이(데이터를 처리하는 부분이) 경우에 따라서는 viewDidDisappear()에서만 정상적으로 동작하는 경우가 있다. 

 

해당 내용 기억해 두었다가 더 많은 데이터를 처리해야 할 시점에 잘 적용하기! ⭐️

 

 

 

 


 

 

 

6. 트레일링 클로저 (후행 클로저) 학습

함수의 마지막 매개변수가 클로저인 경우, 소괄호를 생략하고 클로저 구문을 뒤에 작성하여 가독성을 올려줄 수 있다.

생성자 함수(init)에서도 사용할 수 있다.

 

함수를 호출할 때 마지막 매개변수(클로저) 위치에서 enter를 누르면 자동으로 트레일링 클로저 형식으로 변환된다.

 

1. 매개변수가 1개인 경우

  • 매개변수로 클로저 1개만 받아서 사용하는 경우, 소괄호를 완전히 지워서 사용할 수 있다.
// 후행 클로저 사용 X
someArray.forEach({
    print($0)
})


// 후행 클로저 사용
someArray.forEach {
    print($0)
}

 

2. 매개변수가 여러 개인 경우

func someFunction(a: Int, b: Int, c: () -> Void) {
    print("some function")
}

// 후행 클로저 사용 X
someFunction(a: 1, b: 2, c: {
    print("")
})


// 후행 클로저 사용
someFunction(a: 1, b: 2) {
    print("")
}

 

 

 


 

 

7. Date <==> String 상호변환 메서드 연산 프로퍼티로 개선해보기

[작성했던 코드]

 

// Date -> String 타입 변환
func dateToString(_ date: Date) -> String {
    return dateFormatter.string(from: date)
}
    
    
// String -> Date 타입 변환
func stringToDate(_ string: String) -> Date? {
    return dateFormatter.date(from: string)
}


// 메서드를 사용하여 Label text 업데이트
after100DateLabel.text = dateToString(addDay(date, additional: 100))

 

 

[연산 프로퍼티로 개선해본 코드]

var after100Date: Date {
    get {
        if let date = dateFormatter.date(from: after100DateLabel.text!) {
            return date
        }else {
            return Date()
        }
    }
    set {
        // 입력된 Date를 문자열로 변환하여 Label.text 업데이트
        after100DateLabel.text = dateFormatter.string(from: newValue)
    }
}

after100Date = addDay(date, additional: 100)

 

 

 

 

 


 

 

 

수업 내용 추가

IBOutletCollection은 어떤 경우에 사용하면 좋을까?

  • Collection에 포함된 뷰객체들의 UI 설정을 동일하게 가져가고 싶은 경우 (반복문으로 처리)

 

 

만약 Collection에 포함된 뷰객체 각각에 필요한 설정이 구분된다면??

Collection의 Index로 접근하여 처리해줄 수 있다.

outletCollection[0].text = "첫번쨰 텍스트"
outletCollection[1].text = "두번쨰 텍스트"

 

하지만, 위 방법은 다음과 같은 이유로 권장되지 않는다.

  1. 나중에 코드를 다시 확인했을 때, 각 Index가 어떤 뷰객체를 표현하는지 명확하게 알기 어렵다.
  2. OutletCollection의 요소가 추가/삭제/수정되는 경우 코드를 수정하기 까다롭다.

 

 

따라서 여러 뷰객체들을 단순히 UI 설정만 동일하게 가져가기 위해 연결한다면 OutletCollection을 사용하면 좋지만,

만약 UI 설정에 추가로 각 뷰객체들에게 구분되게 처리할 내용이 있다면 번거롭더라도 각각 IBOutlet으로 연결한 후, 공통된 부분을 처리할 때만 배열을 만들어서 사용하는 게 좋다.

@IBOutlet weak var dateLabel = UILabel!
@IBOutlet weak var date2Label = UILabel!

...

// 공통된 처리를 위한 배열 생성
let labelArray = [dateLabel, date2Label]

for i in labelArray {
	i?.font = .boldSystemFont(ofSize: 20)
	i?.textColor = .brown
}
      
// 서로 다른 처리가 필요한 부분은 직접 설정
dateLabel.text = "첫번쨰 텍스트"
date2Label.text = "두번쨰 텍스트"

 

 

 

 

 

 

 

 

 

 

 

반응형