Upload Task
Dropbox와 연동해 Upload를 Task를 구현해 본다.
Dropbox의 Developer app console에서 필요한 API Key를 얻을 수 있다.
파일을 쓰는데 문제가 없도록 write 권한을 활성화 한다.
Dropbox/upload
Dropbox의 upload url은 다음과 같다.
https://content.dropboxapi.com/2/files/upload
또한 필요한 HTTPHeader는 다음과 같다.
curl -X POST https://content.dropboxapi.com/2/files/upload \
--header "Authorization: Bearer <AccessToken>" \
--header "Dropbox-API-Arg: {\"autorename\":false,\"mode\":\"add\",\"mute\":false,\"path\":\"/Homework/math/Matrices.txt\",\"strict_conflict\":false}" \
--header "Content-Type: application/octet-stream" \
--data-binary @local_file.txt
이전에 사용한 POST보다 종류가 조금 늘어나고 복잡해 졌을 뿐이지 원리 자체는 동일하다.
Request 생성
var dropboxUploadRequest: URLRequest {
guard let apiUrl = URL(string: "https://content.dropboxapi.com/2/files/upload") else {
fatalError("Invalid URL")
}
var request = URLRequest(url: apiUrl)
request.httpMethod = "POST"
request.setValue("Bearer \(dropBoxAccessToken)", forHTTPHeaderField: "Authorization")
request.setValue("{\"autorename\":false,\"mode\":\"add\",\"mute\":false,\"path\":\"/girlpapico03.mp4\",\"strict_conflict\":false}", forHTTPHeaderField: "Dropbox-API-Arg")
request.setValue("application/octet-stream", forHTTPHeaderField: "Content-Type")
return request
}
총 세 개의 필드를 추가해 전달해야한다.
각각 Authorization, Dropbox-API-Arg, Content-Type의 이름으로,
Authorization에는 자신의 API Token을 저장하고,
Content-Type에는 전송하는 데이터의 종류를 저장한다.
Dropbox-API-Arg는 Dropbox에서 요구하는 데이터 뭉치로,
경로, POST 요청의 종류, 이름 옵션, 알림 여부, 우선순위, 충돌 옵션, 해쉬 옵션을 포함한다.
Upload Task 생성
@IBAction func upload(_ sender: Any) {
guard let resourceUrl = Bundle.main.url(forResource: "girlpapico03", withExtension: "mp4") else {
fatalError("Invalid Resource")
}
guard let data = try? Data(contentsOf: resourceUrl) else {
fatalError("Invalid Data")
}
}
업로드 하기 위한 파일을 url로 지정하고, 이를 사용해 Data 인스턴스를 생성한다.
@IBAction func upload(_ sender: Any) {
guard let resourceUrl = Bundle.main.url(forResource: "girlpapico03", withExtension: "mp4") else {
fatalError("Invalid Resource")
}
guard let data = try? Data(contentsOf: resourceUrl) else {
fatalError("Invalid Data")
}
let task = URLSession.shared.uploadTask(with: dropboxUploadRequest, from: data) { data, response, error in
if let error = error {
self.showErrorAlert(with: error.localizedDescription)
print(error)
return
}
guard let httpResponse = response as? HTTPURLResponse else {
self.showErrorAlert(with: "Invalid Response")
return
}
guard (200...299).contains(httpResponse.statusCode) else {
self.showErrorAlert(with: "\(httpResponse.statusCode)")
return
}
guard let data = data, let str = String(data: data, encoding: .utf8) else {
fatalError("Invalid Data")
}
self.showInfoAlert(with: str)
}
task.resume()
}
Upload Task 메서드는 파라미터로 전송할 데이터를 받는다는 특징이 있다.
간단하게는 지금과 같이 Completion Handler를 사용할 수 있지만,
이 경우 '데이터가 전송 완료 된 다음 한 번'만 호출 되기 때문에 작업이 오래 걸리는 겨웅 진행 상태를 알 수 없다는 단점이 있다.
Upload Task의 상태 확인 하기
Task의 상태를 모니터링 하기 위해선 Completion Handler 방식이 아닌 Delegate 방식을 사용해야 한다.
Session 생성
var uploadTask: URLSessionUploadTask?
lazy var session: URLSession = { [weak self] in
let config = URLSessionConfiguration.default
let session = URLSession(configuration: config, delegate: self, delegateQueue: OperationQueue.main)
return session
}()
Delegate 방식으로 Task 모니터링을 진행하기 위해선 Task 메서드를 사용하는 것이 아닌,
직접 정의하고 설정해 사용해야 할 필요가 있다.
Task를 저장할 변수를 선언하고, URLSession을 정의한다.
Task 생성에 필요한 Session은 Default Session Configuration을 사용한다.
@IBAction func uploadWithProgress(_ sender: Any) {
guard let resourceUrl = Bundle.main.url(forResource: "girlpapico03", withExtension: "mp4") else {
fatalError("Invalid Resource")
}
guard let data = try? Data(contentsOf: resourceUrl) else {
fatalError("Invalid Data")
}
uploadProgressView.progress = 0.0
uploadTask = session.uploadTask(with: dropboxUploadRequest, from: data)
uploadTask?.resume()
}
동일하게 파일의 url을 사용해 Data 인스턴스를 생성하고, 이를 업로드 하는 Upload Task를 생성한다.
이전에 생성한 Session을 사용해 Upload Task를 생성하고 resume 메서드를 사용해 실행하도록 구현했다.
extension UploadTaskViewController: URLSessionTaskDelegate {
func urlSession(_ session: URLSession, task: URLSessionTask, didSendBodyData bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64) {
let current = formatter.string(fromByteCount: totalBytesSent)
let total = formatter.string(fromByteCount: totalBytesExpectedToSend)
sizeLabel.text = "\(current)/\(total)"
uploadProgressView.progress = Float(totalBytesSent) / Float(totalBytesExpectedToSend)
}
}
Task를 직접 생성했으므로 Delegate 방식을 사용할 수 있다.
urlSession(_:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:)
해당 메서드는 Upload Task가 데이터를 업로드 하는 동안 반복해서 호출된다.
세 번째 파라미터로 전송된 데이터의 크기가 전달되고,
네 번째 파라미터로 지금까지 전송한 크기가 전달된다.
다섯번째 파라미터레는 전송하는 파일의 총 크기가 전달된다.
해당 메서드로 전달되는 파라미터 만으로 로딩바의 역할을 할 수 있는 데이터를 만족하기 때문에,
이들을 적절히 이용하는 것으로 간단하게 구현할 수 있다.
extension UploadTaskViewController: URLSessionTaskDelegate {
func urlSession(_ session: URLSession, task: URLSessionTask, didSendBodyData bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64) {
let current = formatter.string(fromByteCount: totalBytesSent)
let total = formatter.string(fromByteCount: totalBytesExpectedToSend)
sizeLabel.text = "\(current)/\(total)"
uploadProgressView.progress = Float(totalBytesSent) / Float(totalBytesExpectedToSend)
}
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
print(error ?? "Done")
}
}
urlSession(_:task:didCompleteWithError:)
해당 메서드는 Upload Task가 완료되거나 오류가 발생한 경우 호출된다.
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
session.invalidateAndCancel()
}
그리고 직접 정의한 Task는 반드시 직접 정리하는 것을 잊으면 안 된다.
결과
'학습 노트 > iOS (2021)' 카테고리의 다른 글
214. Background Download (0) | 2022.08.31 |
---|---|
213. Download Task (0) | 2022.08.30 |
211. Post Request (0) | 2022.08.25 |
210. SessionConfiguration (0) | 2022.08.23 |
209. URL Session Delegate (0) | 2022.08.23 |