1. url 주소는 일단 가져와 있습니다.
2. 이제 이 주소에서 디스크,캐쉬에 미리 저장한는 방법을 알아봐야 합니다.
error 체크 추가하자.
이미지를 테이블 뷰에 보여주는 시점을 어떻게 정하지?, 전부 가져오고 보여줘야 되나, 아니면 한개씩 보여줘야되나?
이 메소드는 콜렉션뷰와 테이블뷰에서 사용되는 메소드이다. 다시 불러온다는게, 다시 그린다는 거 같진 않고, 넣고자 하는 데이터를 다시 넣어본다는 거 같다, datasource가 다시 호출되려나??
reloadData()
Reloads the rows and sections of the table view.
[https://developer.apple.com/documentation/uikit/uitableview/1614862-reloaddata]
이게 오버헤드가 얼마나 될까?
이걸 어디서 호출해야 할지 모르겠다.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
여기서 호출하면 재귀적으로 작동하는거 같다. 리로드 데이타가 이 메소드를 호출하므로?
그렇다면 변경된 데이터를 어디서 그려줘야 하는가?
내가 원하는건 사실 데이터 받아오고 다시 그려주는건데, 사실 그려준다기 보다, 받아온 데이터를 다시 넣어주는거지. 이건 그냥 내가 구현했다.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell : DetailCollectionViewCell = collectionView.dequeueReusableCell(withReuseIdentifier: "DetailCollectionViewCell", for: indexPath) as! DetailCollectionViewCell
let imageUrl : String? = Gallery.GalleryInstance.getGalleryImages().urls[indexPath.row]
let Title = "not set yet"
guard let Url = imageUrl else {
print("not instance at CV cell")
return cell
}
let group : DispatchGroup = DispatchGroup()
print("1")
cell.settingDetailCell(group : group, imageUrl: Url, imageTitle: Title)
//클로저를 레퍼런스 타입으로 메모리에 차지 하고 있는다는게 이말이였구나!
group.notify(queue: .main , execute: { [unowned self] in
print("6")
//is this ok to call use?
cell.cellReSet(imageUrl: Url)
})
//return이 위에 클로져를 기다려 주지 않고 바로 되버린다. ㅋㅋㅋㅋㅋㅋㅋ 그로므로,모든 데이터가 전송되어진 후 cellReSet으로 셀을 다시 셀의 뷰에 넣어주면 된다.
print("7")
return cell
}
결국 나의 해결책은 이것인데, collectionView가 내부의 클로저를 기다려주지 않는다는데 문제가 있었다.
return이 위에 클로져를 기다려 주지 않고 바로 되버린다. ㅋㅋㅋㅋㅋㅋㅋ 그로므로,모든 데이터가 전송되어진 후 cellReSet으로 셀을 다시 셀의 뷰에 넣어주면 된다.
순서가 123 7 이 떠버림
1
2
3
7
1
2
3
7
1
2
3
7
4
5
6
4
5
6
아 해결책을 찾은거 같다. lazy를 사용하자.
[https://www.youtube.com/watch?v=pIMAZL0mp0w]
근데 자고 생각해보니 해결책이 아닌거 같다. [https://baked-corn.tistory.com/45] 레이지는 사용되기 전까지 연산이 미뤄진다는 건데, 내 경우는 일단 셀에 이미지를 넣을 수 밖에 없다. 테이블이 일단 만들어지긴 해야 되니까.
이미지 받고, 캐슁
[https://codingwarrior.com/2018/02/05/ios-display-images-in-uicollectionview/]
NSCache로 Image 캐쉬 구현하기
[https://ontheswift.tistory.com/24]
이 방법은 메모리에 저장하는 방식이라고 한다, 디스크에 저장하기 위해서는, FileManager를 사용해야 한다.
let JpgCache = NSCache<NSString,UIImage>()
public func settingDetailCell(imageUrl : String, imageTitle : String){
if let JpgCacheImage = JpgCache.object(forKey: imageUrl as NSString){
self.galleryImageView.image = JpgCacheImage
print("cache image used")
}else{
if let url = URL(string : imageUrl){
//global의 의미?
DispatchQueue.global().async {
//weak vs unowned?
[weak self] in
if let data = try? Data(contentsOf: url){
if let image = UIImage(data : data){
//set to cache
self?.JpgCache.setObject(image, forKey: imageUrl as NSString)
DispatchQueue.main.async {
print("uiImage loaded")
self?.galleryImageView.image = image
}
}else{
print("UIImage not found, load")
}
}
}
}//else
}
//self를 안붙이면 어떻게 되지??
self.galleryImageTitleLB.text = imageTitle
}
//is this ok to use?, 목적은 로딩된 이후에 다시 그려주기 위해서이다.
public func cellReSet(imageUrl : String){
if let JpgCacheImage = DetailCollectionViewCell.self.JpgCache.object(forKey: imageUrl as NSString){
self.galleryImageView.image = JpgCacheImage
print("cache image used")
}
}
NSCache
[https://developer.apple.com/documentation/foundation/nscache]
이건 뭔데 자세하게 설명했지.....?[https://developer.apple.com/library/archive/documentation/Performance/Conceptual/ManagingMemory/Articles/CachingandPurgeableMemory.html]
iOS Memory Footprint 분석 방법?
[https://hcn1519.github.io/articles/2018-09/wwdc2018session416]
이 글을 보고 처음 알게 된게, OS X의 작동 방식에 대해서 어느 정도 알 수 있다는 점이다.
os공부를 꼭 다시하자.
FileManager
[https://hcn1519.github.io/articles/2017-07/swift_file_manager] 이분 글을 따라가면 기본을 구현할 수 있겠다.
[https://developer.apple.com/documentation/foundation/filemanager] 애플 공식 문서이다.
공식 문서 보다가 흥미로운 부분인데,
Threading Considerations
The methods of the shared FileManager object can be called from multiple threads safely. However, if you use a delegate to receive notifications about the status of move, copy, remove, and link operations, you should create a unique instance of the file manager object, assign your delegate to that object, and use that file manager to initiate your operations.
디스크 캐쉬는 딱히, 다중 스레드 상황에서 걱정할 필요가 없다한다, 반면에 NSCache는 걱정해야 될거 같은데, 메모리에서 공유하는 거니까, 근데 왜 디스크는 고려안하지, 그건 잘 모르겠다.
음 근데 예상이랑 다르게, You can add, remove, and query items in the cache from different threads without having to lock the cache yourself. 캐쉬를 락할 필요 없이, 서로 다른 스레드에 락을 걸 필요가 없다네??ㅋㅋ 왜지...
iOS - how does NSCache ensure thread safe
답변에 스레드 세이프를 보장할 수 있는 5가지 방법을 알려준다.
1. phtread의 사용
2. GCD library의 serial queue
3. GCD library의 concurrent queue
4. Foundation lock의 NSLock
5. objective C의 @synchronized()
음 1,4,5는 대충 뭔지 알겠는데, GCD가 뭐지? Grand Central Dispatch (GCD or libdispatch)
collectionView(_:cellForItemAt:)
[https://developer.apple.com/documentation/uikit/uicollectionviewdatasource/1618029-collectionview]
Return Value
A configured cell object. You must not return nil from this method.
Discussion
Your implementation of this method is responsible for creating, configuring, and returning the appropriate cell for the given item. You do this by calling the dequeueReusableCell(withReuseIdentifier:for:) method of the collection view and passing the reuse identifier that corresponds to the cell type you want. That method always returns a valid cell object. Upon receiving the cell, you should set any properties that correspond to the data of the corresponding item, perform any additional needed configuration, and return the cell.
You do not need to set the location of the cell inside the collection view’s bounds. The collection view sets the location of each cell automatically using the layout attributes provided by its layout object.
If isPrefetchingEnabled on the collection view is set to true then this method is called in advance of the cell appearing. Use the collectionView(_:willDisplay:forItemAt:) delegate method to make any changes to the appearance of the cell to reflect its visual state such as selection.
This method must always return a valid view object.
UITableViewDataSource Prefetching
정말 중요한 개념이다. 꼭 보자. 이 기법들을 알아야 UICollectionView를 구현할 수 있다.
함수 사용법을 모르겠었는데, 위 블로그에서 밑에 부분을 보고 알았다. 근접한 인덱스들의 배열을 넘겨주는 것이였다.
4. How prefetching works
다음은 애플이 정확하게 언급하지 않은 프리페치로 몇 행의 데이터가 불러오는지에 대한 저자(원작자)가 개인적으로 관찰한 것을 정리한 내용이다.
- tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) 함수는 스크롤을 하지 않는 이상 화면에 보여지지 않는 행을 불러오지 않는다.
- 초기 보여질 수 있는 행이 가시화되면 tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath])가 호출되고 indexPath 변수는 대략 10개의 보여질 수 있는 영역 근처의 행들을 포함하게 된다.
- 스크롤 속도에 따라 프리페치 함수의 indexPath 변수가 포함하게 되는 행의 갯수는 달라진다. 보통의 스크롤 속도에는 보통 1개의 행을 포함한다. 만일 빠른 속도로 스크롤하게 되면 다수의 행을 포함하게 된다.
func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) {
print("prefetching row of \(indexPaths)")
}
func tableView(_ tableView: UITableView, cancelPrefetchingForRowsAt indexPaths: [IndexPath]) {
print("cancel prefetch row of \(indexPaths)")
}
애플 공식 문서 [https://developer.apple.com/documentation/uikit/uicollectionviewdatasourceprefetching/prefetching_collection_view_data] 그리고 여기서 중요 포인트 복붙 했다.
Load Data Asynchronously
You use data prefetching when loading data is a slow or expensive process—for example, when fetching data over the network. In these circumstances, perform data loading asynchronously. In this sample, the AsyncFetcher class is used to fetch data asynchronously, simulating a network request.
네트워크 요청 같이 데이터 로딩에 오래걸리는 작업을 미리 하려고 사용한다. 셀이 만들어질때 데이터를 갖고 오자니 느려서 생긴 방법 같다.
근데 내 앱이 느린 부분이, https에서 jpg를 찾는 과정이 느린거 같다. url에서 셀에 데이터를 넣는 부분보다.
[https://brunch.co.kr/@tilltue/28]
[https://fluffy.es/prefetching/#loadall]
내 코드인데 셀을 리턴하는 방식이 아니라, 근접한 셀에 대해 미리 넣는 방식같다.
extension MainVC : UICollectionViewDataSourcePrefetching{
func collectionView(_ collectionView: UICollectionView, prefetchItemsAt indexPaths: [IndexPath]) {
print("in vc prefatching collectionView")
for indexPath in indexPaths{
let imageUrl : String? = Gallery.GalleryInstance.getGalleryImages().urls[indexPath.row]
guard let Url = imageUrl else {
print("not instance at CV cell")
}
print("preFatching worked")
let Title = "not set yet"
let cell : DetailCollectionViewCell = collectionView.dequeueReusableCell(withReuseIdentifier: "DetailCollectionViewCell", for: indexPath) as! DetailCollectionViewCell
cell.settingDetailCell(imageUrl: Url, imageTitle: Title)
}
}
}
그러니까 , 내가 해볼 일은 (디스크 or 메모리)에 저장하고, 다운로드 되면 preFetching 방식으로 셀에 넣어주는 것이다.
이후에 몇번의 시도로 정리가 좀 되었다.
1. preFetching은 미리 셀에 로딩해줄 데이터를 가져올 수 있게 하는데, 그럴거면 그냥 뷰컨 로딩시 미리 가져와도 될거 같다.
2. 디스크 vs 캐쉬인데, nscache는 메모리 사용량에 부담이 되면 저절로 데이터를 삭제한다. 이게 언제 발생하냐면 내가 겪은건 앱 처음 키면 괜찬은데, 다른 앱도 좀 보고 다시 돌아오면 캐쉬 데이터가 사라져 있다.
그러니까 음 1번은 뷰컨에서 그냥 왠만큼 필요한 데이터 미리 로딩하는 쪽으로 한거고, 2번은 캐쉬에 할거면 다시 로딩하도록 구현해야 한다.