Closure의 캡처링과 [weak self] 캡처링
ARC랑 캡처리스트는 Swift.org를 참고
공식 문서 지난번에도 봤지만 ^.ㅠ
한번 읽고 뙇!!! 이해하면 천재지.... 🤔
A closure can capture constants and variables from the surrounding context in which it’s defined. The closure can then refer to and modify the values of those constants and variables from within its body, even if the original scope that defined the constants and variables no longer exists.
In Swift, the simplest form of a closure that can capture values is a nested function, written within the body of another function. A nested function can capture any of its outer function’s arguments and can also capture any constants and variables defined within the outer function.
a nested function란? 다른 함수의 body에 쓰여진 함수를 일컬음
값 캡쳐를 할 수 있는 클로저의 가장 단순한 형태가 이 nested function이다!
아래의 예시에서 makeIncrementer라는 함수는 incrementer라는 이름의 nested function을 포함함
nested incrementer() 함수는 주변의 문맥으로부터 2개의 밸류를 캡쳐하는 데
하나는 runningTotal이고 나머지는 amount임
이 2개의 밸류를 캡쳐한 이후, incrementer는 makeIncrementer에 의해 클로저로서 반환됨
이 클로저는 매번 불릴 때 지정해준 amount만큼 runningTotal을 증가시킴
import Foundation
func makeIncrementer(forIncrement amount: Int) -> () -> Int {
var runningTotal = 0
func incrementer() -> Int {
runningTotal += amount
return runningTotal
}
return incrementer
}
let incrementByTen = makeIncrementer(forIncrement: 10)
incrementByTen() // returns a value of 10
incrementByTen() // returns a value of 20
incrementByTen() // returns a value of 30
let incrementBySeven = makeIncrementer(forIncrement: 7)
incrementBySeven() // returns a value of 7
incrementBySeven() // returns a value of 14
incrementBySeven() // returns a value of 21
incrementByTen() // returns a value of 40
여기서 makeIncrementer의 반환 타입은 () -> Int
이게 의미하는 바가 뭐냐?
바로 makeIncrementer가 반환하는 게 함수라는 거다! (단순한 어떤 값이 아니라)
makeIncrementer가 반환하는 함수는 parameter가 없는 Int 정수 값을 반환함
makeIncrementer(forIncrement:) 함수는 runningTotal라는 이름의 integer 변수를 정의함
뭐하려고? to store the current running total of the incrementer that will be returned
incrementer()의 현재의 running total 값을 저장하려고!
(runningTotal은 일단 0으로 초기화된 상태임)
makeIncrementer(forIncrement:) 함수는 한 개의 정수 파라미터를 가지고 있음
forIncrement라는 argument label과 함께 amount라는 이름의 파라미터
(진짜 직독직해 이상하다 🤦♀️)
이 amount라는 파라미터로 전달된 전달인자 값은
runningTotal 변수가 얼마만큼 증감돼야 하는지를 지정해줌
makeIncrementer()은 nested function인 incrementer()을 정의함
→ incrementer()가 실질적인 증감을 수행하는 함수임
incrementer()는 단순히 amount를 runningTotal에 더하고 그 결과값을 리턴함
func incrementer() -> Int {
runningTotal += amount
return runningTotal
}
incrementer()는 어떤 파라미터도 가지고 있지 않음
하지만 이 함수의 바디 내부에서 runningTotal과 amount를 가리킴
이게 어떻게 가능하냐?
(바로 얘네를 둘러싼 주변의 함수로부터) runningTotal과 amount에 대한 참조를 캡쳐
& 그 캡쳐한 참조를 자기 자신의 함수 body 내에서 사용하기 때문!
Capturing by reference를 함으로써
→ (makeIncrementer에 대한 호출이 끝나도) runningTotal과 amount 값이 사라지지 않음
→ 다음에 incrementer 함수가 호출되도 runningTotal이 여전히 available함
NOTE
As an optimization, Swift may instead capture and store a copy of a value if that value isn’t mutated by a closure, and if the value isn’t mutated after the closure is created.
Swift also handles all memory management involved in disposing of variables when they’re no longer needed.
Closures - The Swift Programming Language (Swift 5.5)
결국 혼자 힘으로는 해결을 못할 것 같아서 😣
리뷰어 붱이에게 SOS 요청!!
DispatchQueue.global().async {
guard let imageURL: URL = URL(string: item.thumbnails.first!) else { return }
guard let imageData: Data = try? Data(contentsOf: imageURL) else { return }
DispatchQueue.main.async {
print("Sunny🌞🌞🌞 : \(collectionView.indexPath(for: cell)), indexPath: \(indexPath)")
if let index: IndexPath = collectionView.indexPath(for: cell) {
if index.row == indexPath.row {
cell.itemThumbnail.image = UIImage(data: imageData)
}
}
}
}
DispatchQueue.main.async에 프린트문을 추가해서 확인해봄
테이블뷰는 indexPath 값이 잘 불러와짐
= 이미지도 제대로 들어감
밑에 nil로 찍히는 부분은 아직 스크롤 하기 전
스크롤 밑으로 내리면 화면에 보이는 셀은 이제 indexPath 값이 잘 나온다
같은 방법으로 컬렉션뷰도 확인해보았다.
컬렉션뷰는 처음에는 셀에 대한 indexPath가 잘 나오다가 이후엔 계속 nil값이 찍힘
nil 값이 들어오니 indexPath가 맞는지 확인할 수가 없어서
아래의 if 문을 통과하지 못하니
image를 못 넣어줌 😣
if index.row == indexPath.row {
cell.itemThumbnail.image = UIImage(data: imageData)
}
(왜 안 불리는지는 모름 ^.^)
기존 리퀘스트를 취소하는 방법 고민해보기...!
네비게이션 바 영향은 아닌듯
(스토리보드에서 확인해보면 컬렉션뷰와 범위가 겹치지 않음)
var indexPath: IndexPath?
cell.indexPath = indexPath 추가
DispatchQueue의 메인 스레드에 if 조건문 수정
if cell.indexPath == indexPath {
cell.itemThumbnail.image = UIImage(data: imageData)
}
기존 코드를 수정하지 않고 해결하고 싶다면
네트워크 받아오는 DispatchQueue.main.async에 아래 한줄만 추가해주면 된다.
self.collectionView.isPrefetchingEnabled = false
그럼 이제 nil값이 찍히지도 않고
이미지도 제대로 불러와짐 👍
(isPrefetchingEnabled의 default 값은 true로 돼있음)
UICollectionView cellForItemAt indexPath is skipping row indexes in iOS 10
Thanks to 붱이 & 메이슨 👏👏👏
여기 프로젝트에서 고칠 점
이미지를 필요할 때마다
매번 이미지 url 주소 가서 받아오는 작업을 반복함
이게 비용, 성능적인 측면에서 좋은 방법이 아니다 ^.ㅠ
어차피 같은 이미지 갖고 올거면
매번 다시 다녀오는 것보다
한번 이미지를 받아오면
캐싱을 해두는게
좋지 않을까 ??
그래서 캐싱을 쓰는거 !!!!