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
- swift documentation
- uiscrollview
- Xcode
- 클린 코드
- Clean Code
- ios
- UITextView
- HIG
- Protocol
- 애니메이션
- rxswift
- uitableview
- 리팩토링
- 리펙터링
- swiftUI
- UICollectionView
- map
- RxCocoa
- Refactoring
- 스위프트
- collectionview
- SWIFT
- clean architecture
- 리펙토링
- ribs
- Observable
- combine
- MVVM
- tableView
- Human interface guide
Archives
- Today
- Total
김종권의 iOS 앱 개발 알아가기
[iOS - swift] URLSessionTask, URLSessionDownloadTask (Cancel, Resume, Suspend) - 서버 데이터 다운로드, 일시정지, 취소, 재개 본문
iOS 응용 (swift)
[iOS - swift] URLSessionTask, URLSessionDownloadTask (Cancel, Resume, Suspend) - 서버 데이터 다운로드, 일시정지, 취소, 재개
jake-kim 2021. 10. 27. 01:57
URLSessionTask 개념
- URLsession으로 다운로드나 resource관련한 작업들을 처리하는 모듈
- cancel() 메소드: task를 중지
- resume() 메소드: task가 일시중지되어 있던 경우, 다시 시작
- suspend() 메소드: task를 일시중지 (인스턴스 생성시 초기값은 suspend 상태)
URLSession과 연관된 모듈
- URLSessionDataTask
- 서버로부터 주로 GET으로 다운로드한 데이터를 메모리에 바로 저장하는 경우 사용
- URLSessionUploadTask
- 서버로부터 주로 POST, PUT 메소드를 통해 디스크에서 웹서버로 파일을 전송하는 경우 사용
- URLSessionDownloadTask
- 서버에서 데이터 or 파일을 다운로드 할 경우 사용
상속 관계
다운로드, 일시정지, 취소, 재개 사용 방법
cancel(), resume(), suspend()기능 사용
- 다운로드: URLSessionTask 인스턴스 생성 시 초기상태가 suspend()이므로, resume()으로 시작
- 다운로드: resume()
- 일시정지: cancel(byProducingResumeData:) `byProducingResumeData`클로저에서 진행중인 data를 얻고 이 data를 기록
- 취소: cancel()
- 재개: 기존에 저장해둔 데이터가 있을땐 `urlSession.downloadTask(withResumeData:)`, 없을땐 `urlSession.downloadTask(with:)`
핵심 모듈 설계
- Download
- 데이터 저장 모델
- DownloadService
- `urlSession.downloadTask(with:)`로 URLSessionDownloadTask를 만들어서 resume(), cancel() 호출
Download 모델
- 데이터를 저장할 모델
- URLSessionDownloadTask 프로퍼티가 존재, 이것의 delegate(다운로드 상황 delegate 실행)는 ViewController로 위임
class Download {
var track: Track
init(track: Track) {
self.track = track
}
/// 각 URL마다 하나의 task를 가지고 있고 이 task를 통해서 download, pause, resume, cancel 호출 (Delegate 위임을 외부에서 해야하므로, 생성자를 ViewController에서 만들어서 이곳에 주입)
var task: URLSessionDownloadTask?
/// view에서 isDownloading 플래그값을 보고 버튼을 Pause로 할지, Resume으로 할지 정할 때 사용 (값 set은 DownloadService에서 설정)
var isDownloading = false
/// 사용자가 다운로드 일시 중지한 경우(suspended) 생성된 Data 저장
var resumeData: Data?
/// progressView에서 사용될 progress 정도 저장
var progress: Float = 0.0
}
DownloadService
- startDownload(): URL을 받아서 urlSession.downloadTask(with:) 수행
- pauseDownload(): cancel(byProducingResumeData:)로 진행중인 data를 얻어서 저장, task 취소
- cancelDownload(): cancel()로 task 취소
- resumeDownload(): 기존에 데이터가 있으면 .downloadTask(withResumeData:), 없으면 downloadTask(with:)
class DownloadService {
var urlSession: URLSession!
var activeDownloads: [URL: Download] = [:]
/// dataTask의 resume() 호출
func startDownload(_ track: Track) {
let download = Download(track: track)
download.task = urlSession.downloadTask(with: track.previewURL)
download.task?.resume()
download.isDownloading = true
activeDownloads[track.previewURL] = download
}
/// dataTask를 cancel 후, 진행중인 data를 임시로 저장
func pauseDownload(_ track: Track) {
guard let download = activeDownloads[track.previewURL] else { return }
if download.isDownloading {
download.task?.cancel(byProducingResumeData: { data in
download.resumeData = data
})
download.isDownloading = false
}
}
/// dataTask를 cancel()
func cancelDownload(_ track: Track) {
if let download = activeDownloads[track.previewURL] {
download.task?.cancel()
activeDownloads[track.previewURL] = nil
}
}
/// cancel에서 저장해둔 data를 다시 불러와서 resume (없는 경우 새로 생성)
func resumeDownload(_ track: Track) {
guard let download = activeDownloads[track.previewURL] else { return }
if let resumeData = download.resumeData {
download.task = urlSession.downloadTask(withResumeData: resumeData)
} else {
download.task = urlSession.downloadTask(with: track.previewURL)
}
download.task?.resume()
download.isDownloading = true
}
}
ViewController
- cell로부터 버튼 클릭 이벤트는 delegate를 사용
- tableView의 cellForRowAt 메서드에서 설정
// ViewController.swift
extension ViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "TrackTableViewCell", for: indexPath) as! TrackTableViewCell
let track = tracks[indexPath.row]
cell.model = .init(track: track, downloaded: track.downloaded, download: downloadService.activeDownloads[track.previewURL])
cell.delegate = self
return cell
}
...
}
// TrackTableViewCell.swift
protocol TrackTableViewCellDelegate: AnyObject {
func didTapPauseButton(_ cell: TrackTableViewCell)
func didTapResumeButton(_ cell: TrackTableViewCell)
func didTapCancelButton(_ cell: TrackTableViewCell)
func didTapDownloadButton(_ cell: TrackTableViewCell)
}
class TrackTableViewCell: UITableViewCell {
weak var delegate: TrackTableViewCellDelegate?
...
- cell의 클릭 이벤트마다 downloadService 인스턴스를 통해 호출
// ViewController.swift
extension ViewController: TrackTableViewCellDelegate {
func didTapDownloadButton(_ cell: TrackTableViewCell) {
guard let indexPath = tableView.indexPath(for: cell) else { return }
let track = tracks[indexPath.row]
downloadService.startDownload(track)
tableView.reloadRows(at: [indexPath], with: .none)
}
func didTapPauseButton(_ cell: TrackTableViewCell) {
guard let indexPath = tableView.indexPath(for: cell) else { return }
let track = tracks[indexPath.row]
downloadService.pauseDownload(track)
tableView.reloadRows(at: [indexPath], with: .none)
}
func didTapResumeButton(_ cell: TrackTableViewCell) {
guard let indexPath = tableView.indexPath(for: cell) else { return }
let track = tracks[indexPath.row]
downloadService.resumeDownload(track)
tableView.reloadRows(at: [indexPath], with: .none)
}
func didTapCancelButton(_ cell: TrackTableViewCell) {
guard let indexPath = tableView.indexPath(for: cell) else { return }
let track = tracks[indexPath.row]
downloadService.cancelDownload(track)
tableView.reloadRows(at: [indexPath], with: .none)
}
}
- downloadService안에 있는 urlSession 프로퍼티에 URLSession을 만들때 delegate: self로 설정된 인스턴스를 넘겨주어서 초기화
// ViewController.swift
let downloadService = DownloadService()
lazy var downloadURLSession: URLSession = {
return URLSession(configuration: .default, delegate: self, delegateQueue: nil)
}()
private func initDownloadService() {
downloadService.urlSession = downloadURLSession
}
- 저장할 파일 경로는 ViewController에서 정의
// ViewController.swift
// 저장될 파일을 관리할 directory의 URL 획득
let documentsPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
func localFilePath(for url: URL) -> URL {
return documentsPath.appendingPathComponent(url.lastPathComponent)
}
- 다운로드 현황을 받아볼 수 있는 URLSession delegate 구현은 별도의 파일에서 구현
- ViewController+URLSessionDelegates
- progressView 사용 방법은 여기 참고
// ViewController+URLSessionDelegates.swift
// URLSession(configuration: .default, delegate: self, delegateQueue: nil)
extension ViewController: URLSessionDownloadDelegate {
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
guard let sourceURL = downloadTask.originalRequest?.url else { return }
let download = downloadService.activeDownloads[sourceURL]
downloadService.activeDownloads[sourceURL] = nil
let destinationURL = localFilePath(for: sourceURL)
let fileManager = FileManager.default
try? fileManager.removeItem(at: destinationURL)
do {
try fileManager.copyItem(at: location, to: destinationURL)
download?.track.downloaded = true
} catch {
print("Could not copy file to disk: \(error.localizedDescription)")
}
if let index = download?.track.index {
DispatchQueue.main.async { [weak self] in
self?.tableView.reloadRows(at: [IndexPath(row: index, section: 0)], with: .none)
}
}
}
func urlSession(_ session: URLSession,
downloadTask: URLSessionDownloadTask,
didWriteData bytesWritten: Int64,
totalBytesWritten: Int64,
totalBytesExpectedToWrite: Int64) {
guard let url = downloadTask.originalRequest?.url,
let download = downloadService.activeDownloads[url] else { return }
download.progress = Float(totalBytesWritten) / Float(totalBytesExpectedToWrite)
let totalSize = ByteCountFormatter.string(fromByteCount: totalBytesExpectedToWrite, countStyle: .file)
DispatchQueue.main.async { [weak self] in
if let trackCell = self?.tableView.cellForRow(at: IndexPath(row: download.track.index, section: 0)) as? TrackTableViewCell {
trackCell.updateProgressDisplay(download.progress, totalSize)
}
}
}
}
* 전체 소스 코드: https://github.com/JK0369/ExURLSessionDownloadTask
* 참고
URLSessionTask: https://developer.apple.com/documentation/foundation/urlsessiontask
URLSessionDataTask: https://developer.apple.com/documentation/foundation/urlsessiondatatask
URLSessionUploadTask: https://developer.apple.com/documentation/foundation/urlsessionuploadtask
URLSessionDownloadTask: https://developer.apple.com/documentation/foundation/urlsessiondownloadtask
raywenderlich: https://www.raywenderlich.com/3244963-urlsession-tutorial-getting-started
'iOS 응용 (swift)' 카테고리의 다른 글
[iOS - swift] 이미지 캐시 (ImageCache) 구현 방법, URLSession, NSCache (애플 공식 문서 방법) (0) | 2021.10.28 |
---|---|
[iOS - swift] UIButton underline 설정 방법 (0) | 2021.10.27 |
[iOS - swift] UIProgressView (로딩, 프로그래스 바) (0) | 2021.10.25 |
[iOS - swift] UILabel 라인 간 간격 (spacing, 간격, NSMutableAttributedString, ) (0) | 2021.10.23 |
[iOS - swift] UISheetPresentationController, Bottom Sheet, Sheet Drawer, 애플 지도에서 사용되는 뷰 (2) | 2021.10.21 |
Comments