Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
Tags
- UICollectionView
- swift documentation
- 리펙토링
- rxswift
- combine
- Protocol
- 애니메이션
- ribs
- tableView
- ios
- SWIFT
- clean architecture
- uitableview
- collectionview
- Xcode
- Refactoring
- 리팩토링
- UITextView
- swiftUI
- Observable
- Clean Code
- uiscrollview
- Human interface guide
- MVVM
- RxCocoa
- 리펙터링
- 클린 코드
- map
- HIG
- 스위프트
Archives
- Today
- Total
김종권의 iOS 앱 개발 알아가기
[iOS - swift] TableView 캐시를 이용한 효율적인 이미지 로딩 방법 (async, cache) 본문
iOS 응용 (swift)
[iOS - swift] TableView 캐시를 이용한 효율적인 이미지 로딩 방법 (async, cache)
jake-kim 2021. 8. 10. 23:41* URLSession 개념 참고
* NSCache 개념 참고
TableView에서 refresh시 데이터 요청
- dataSource는 [AnyObject]형태
- title과 같은 것은 dataSource안에 포함 되어 있지만 이미지같은 경우는 dataSource중 url link를 통해 이미지 획득
- url link를 통해 이미지를 획득할때 시간이 오래걸리므로 cellForRowAt에서 cache와 async방법으로 접근
class ViewController: UIViewController {
lazy var refreshControl: UIRefreshControl = {
let control = UIRefreshControl()
control.addTarget(self, action: #selector(refreshTableView), for: .valueChanged)
return control
}()
var dataSource: [AnyObject] = []
var session: URLSession = URLSession.shared
lazy var cache: NSCache<AnyObject, UIImage> = NSCache()
@IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
tableView.refreshControl = refreshControl
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "GameCell")
}
@objc
func refreshTableView(){
let url:URL! = URL(string: "https://itunes.apple.com/search?term=flappy&entity=software")
session.downloadTask(with: url, completionHandler: { (location: URL?, response: URLResponse?, error: Error?) -> Void in
if location != nil {
let data:Data! = try? Data(contentsOf: location!)
do {
let dic = try JSONSerialization.jsonObject(with: data, options: .mutableLeaves) as AnyObject
self.dataSource = dic.value(forKey : "results") as! [AnyObject]
DispatchQueue.main.async {
self.tableView.reloadData()
self.refreshControl.endRefreshing()
}
} catch {
print("something went wrong, try again")
}
}
}).resume()
}
}
extension ViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.dataSource.count
}
}
cellForRowAt에서 async와 cache를 이용하여 이미지 획득
- cell의 text는 바로 표출, image는 placeholder이미지를 삽입
extension ViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.dataSource.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "GameCell", for: indexPath)
let dictionary = self.dataSource[(indexPath as NSIndexPath).row] as! [String:AnyObject]
cell.textLabel!.text = dictionary["trackName"] as? String
cell.imageView?.image = #imageLiteral(resourceName: "placeholder")
}
}
- cache에 이미지 데이터가 있으면 해당 데이터 사용
extension ViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
...
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
...
if (cache.object(forKey: (indexPath as NSIndexPath).row as AnyObject) != nil) {
/// 해당 row에 해당되는 부분이 캐시에 존재하는 경우
cell.imageView?.image = cache.object(forKey: (indexPath as NSIndexPath).row as AnyObject)
} else {
/// 해당 row에 해당되는 부분이 캐시에 존재하지 않는 경우
}
return cell
}
}
- cache에 데이터가 없는 경우 서버 통신
...
else {
/// 해당 row에 해당되는 부분이 캐시에 존재하지 않는 경우
let artworkUrl = dictionary["artworkUrl100"] as! String
let url:URL! = URL(string: artworkUrl)
session.downloadTask(with: url, completionHandler: { (location, response, error) -> Void in
if let data = try? Data(contentsOf: url){
/// 이미지가 성공적으로 다운 > imageView에 넣기 위해 main thread로 전환 (주의: background가 아닌 main thread)
}
}).resume()
}
- 이미지가 다운로드된 경우 UI update를 위해 main thread와 async하게 접근
/// 이미지가 성공적으로 다운 > imageView에 넣기 위해 main thread로 전환 (주의: background가 아닌 main thread)
DispatchQueue.main.async {
}
- cell을 불러와서 받은 이미지 업데이트
- 핵심: 한번에 모든 cell에 이미지를 표출하지 않고 cellForRow(at:) 메소드를 이용하여 해당 indexPath에 위치하는 cell이 현재 화면에 보이는 경우만 cell획득 (보이지 않으면 nil 반환)
/// 이미지가 성공적으로 다운 > imageView에 넣기 위해 main thread로 전환 (주의: background가 아닌 main thread)
DispatchQueue.main.async {
/// 해당 셀이 보여지게 될때 imageView에 할당하고 cache에 저장
/// 이미지를 업데이트하기전에 화면에 셀이 표시되는지 확인 (확인하지 않을경우, 스크롤하는 동안 이미지가 각 셀에서 불필요하게 재사용)
if let updateCell = tableView.cellForRow(at: indexPath) {
let img:UIImage! = UIImage(data: data)
updateCell.imageView?.image = img
self.cache.setObject(img, forKey: (indexPath as NSIndexPath).row as AnyObject)
}
}
- cellForRowAt 전체 코드
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "GameCell", for: indexPath)
let dictionary = self.dataSource[(indexPath as NSIndexPath).row] as! [String:AnyObject]
cell.textLabel!.text = dictionary["trackName"] as? String
cell.imageView?.image = #imageLiteral(resourceName: "placeholder")
if (cache.object(forKey: (indexPath as NSIndexPath).row as AnyObject) != nil) {
/// 해당 row에 해당되는 부분이 캐시에 존재하는 경우
cell.imageView?.image = cache.object(forKey: (indexPath as NSIndexPath).row as AnyObject)
} else {
/// 해당 row에 해당되는 부분이 캐시에 존재하지 않는 경우
let artworkUrl = dictionary["artworkUrl100"] as! String
let url:URL! = URL(string: artworkUrl)
session.downloadTask(with: url, completionHandler: { (location, response, error) -> Void in
if let data = try? Data(contentsOf: url){
/// 이미지가 성공적으로 다운 > imageView에 넣기 위해 main thread로 전환 (주의: background가 아닌 main thread)
DispatchQueue.main.async {
/// 해당 셀이 보여지게 될때 imageView에 할당하고 cache에 저장
/// 이미지를 업데이트하기전에 화면에 셀이 표시되는지 확인 (확인하지 않을경우, 스크롤하는 동안 이미지가 각 셀에서 불필요하게 재사용)
if let updateCell = tableView.cellForRow(at: indexPath) {
let img:UIImage! = UIImage(data: data)
updateCell.imageView?.image = img
self.cache.setObject(img, forKey: (indexPath as NSIndexPath).row as AnyObject)
}
}
}
}).resume()
}
return cell
}
* source code: https://github.com/JK0369/cache_tableView
* 참고
'iOS 응용 (swift)' 카테고리의 다른 글
[iOS - swift] SwiftLint 적용 방법 (0) | 2021.08.17 |
---|---|
[iOS - swift] Moya를 사용한 network 구조 설계 (무료 API 테스트, REST) (2) | 2021.08.16 |
[iOS - swift] NSCache 개념, 이미지 캐시 (Image cache), (memory cache, disk cache) (2) | 2021.08.08 |
[iOS - swift] tableView cell 간 간격 설정, cell 선택 UI (contentView.frame.inset, setSelected) (8) | 2021.08.06 |
[iOS - swift] UILabel에서 특정 문자열의 font, 색상 변경 방법 (attributedText) (2) | 2021.08.05 |
Comments