티스토리 뷰

iOS

[iOS] URLSession, URLSessionDataTask 톺아보기

국산 앨런 2019. 9. 5. 07:30

iOS 내에서 HTTP/HTTPS 를 통해 데이터를 주고받기 위해 API를 제공하는 URLSession과

 

이 작업을 나타내는 URLSessionDataTask에 대해서 톺아보겠습니다.

 

URLSession

URLSession은 HTTP/HTTPS를 통해 콘텐츠(데이터)를 주고받는 API를 제공하는 클래스입니다.

 

이 API는 인증 지원을 위한 많은 델리게이트 메서드를 제공하며, 애플리케이션이 실행 중이지 않거나 일시 중단된 동안 백그라운드 작업을 통해 콘텐츠를 다운로드하는 것을 수행하기도 합니다. 

 

URLSession API를 사용하기 위해 애플리케이션은 세션을 생성합니다. 해당 세션은 관련된 데이터 전송작업 그룹을 조정합니다.

 

예를 들면 웹 브라우저를 사용 중인 경우 탭 당 하나의 세션을 만들 수 있습니다. 각 세션 내에서 애플리케이션은 작업을 추가하고, 각 작업은 특정 URL에 대한 요청을 나타냅니다.

 

 

Session 의 유형

URLSession API는 세가지 유형의 세션을 제공합니다. 이 타입은 URLSession 객체가 소유한 configuration 프로퍼티 객체에 의해 결정됩니다.

1. 기본 세션 (Default Session)

기본 세션은 URL 다운로드를 위한 다른 파운데이션 메서드와 유사하게 동작합니다. 디스크에 저장하는 방식입니다.

2. 임시 세션 (Ephemeral Session)

기본 세션과 유사하지만, 디스크에 어떤 데이터도 저장하지 않고, 메모리에 올려 세션과 연결합니다. 따라서 애플리케이션이 세션을 만료시키면 세션과 관련한 데이터가 사라집니다.

3. 백그라운드 세션 (Background Session)

백그라운드 세션은 별도의 프로세스가 모든 데이터 전송을 처리한다는 점을 제외하고는 기본 세션과 유사합니다.

 

Task

URLSessionTask는 세션 작업 하나를 나타내는 추상 클래스입니다.

하나의 세션 내에서 URLSession 클래스는 세 가지 작업 유형, 즉 데이터 작업(Data Task), 업로드 작업(Upload Task), 다운로드 작업(Download Task)을 지원합니다.

1. URLSessionDataTask

HTTP의 각종 메서드를 이용해 서버로부터 응답 데이터를 받아서 Data 객체를 가져오는 작업을 수행합니다.

2. URLsessionUploadTask

애플리케이션에서 웹 서버로 Data 객체 또는 파일 데이터를 업로드하는 작업을 수행합니다. 주로 HTTP의 POST 혹은 PUT 메서드를 이용합니다.

3. URLSessionDownloadTask

서버로부터 데이터를 다운로드 받아서 파일의 형태로 저장하는 작업을 수행합니다. 애플리케이션의 상태가 대기 중이거나 실행 중이 아니라면 백그라운드 상태에서도 다운로드가 가능합니다.

 

데이터 작업은 서버로부터 어떤 응답이라도 Data 객체의 형태로 전달받을 때 사용하며, 업로드 작업 및 다운로드 작업은 단순한 바이너리 파일의 전달에 목적을 둔다고 볼 수 있습니다.


JSON, XML, HTML 데이터 등 단순한 데이터의 전송에는 주로 데이터 작업을 사용하며, 용량이 큰 파일의 경우 애플리케이션이 백그라운드 상태인 경우에도 전달할 수 있도록 업로드(다운로드) 작업을 주로 사용합니다.

 

https://randomuser.me/api/?results=20&inc=name,email,picture

 

바로 예제로 넘어가 볼까융

 

Example) 서버에서 사용자 정보 받아오기

 

 

 

 

오픈 API 를 사용하여 데이터를 받아올 것이구요

 

https://randomuser.me/api/?results=20&inc=name,email,picture

 

이 주소값으로 데이터를 요청하면 됩니당

 

이 예제의 주요 기능은 json 형식의 데이터를 비동기적으로 받아오고,  디코딩 해서 데이터를 뿌려줍니다.

 

뿌려줄 데이터에는 이미지 정보가 담긴 주소값도 있는데 이또한 비동기적으로 처리할 것입니다~

 

 

요렇게요 !

 

자 하나씩 만들어 보겠습니다.

 

뷰 컨트롤러는 심플하게... 

 

테이블 뷰 하나와 셀 하나를 가진 형태 입니다.

 

//ViewController.swift

@IBOutlet weak var tableView: UITableView!
private var users: [User] = []
private let url = "https://randomuser.me/api/?results=20&inc=name,email,picture"

override func viewDidLoad() {
    super.viewDidLoad()
    
    tableView.delegate = self
    tableView.dataSource = self
    
    getUsers()
}

 

테이블뷰의 delegate, datasource를 모두 채택해주고

 

우리가 테이블뷰 셀에 넣어줄 struct의 list 인 users 입니다.

 

getUsers() 를 통해서 서버와의 통신을 할텐데 그전에 User 의 데이터 타입부터 보고 갑시다

 

/*
{
	"results":[
    	{
		"name":
        	{
				"title":"mr",
				"first":"stanley",
				"last":"kelley"
			},
		"email":"stanley.kelley@example.com",
		"picture":
			{
				"large":"https://randomuser.me/api/portraits/men/3.jpg",
				"medium":"https://randomuser.me/api/portraits/med/men/3.jpg",
				"thumbnail":"https://randomuser.me/api/portraits/thumb/men/3.jpg"
			}
		}
		...
	]
}
*/

struct UserResponse: Codable {
    var results: [User]
}

struct User: Codable {
    let name: Name
    let email: String
    let picture: Picture
    
    struct Name: Codable {
        let title: String
        let first: String
        let last: String
        
        var full: String {
            return "\(self.title.capitalized). \(first.capitalized) \(last.capitalized)"
        }
    }
    
    struct Picture: Codable {
        let large: String
        let medium: String
        let thumbnail: String
    }
}

 

Api 의 response json 에 맞게 struct를 구성하였습니다

 

이제 서버에 요청을 보내고 데이터를 받아와 볼까용

 

아까 넘어갔던 getUsers() 함수를 채워나가보겠습니다.

 

private func getUsers() {
	//URLSession의 싱글턴 객체
    let session = URLSession.shared
    guard let requestURL = URL(string: url) else {return}

	// 네트워킹 시작
    session.dataTask(with: requestURL) { data, response, error in
        guard error == nil else {
            print(error?.localizedDescription)
            return
        }
        
        if let data = data, let response = response as? HTTPURLResponse, response.statusCode == 200 {
            do {
            	//Json타입의 데이터를 디코딩
                let userResponse = try JSONDecoder().decode(UserResponse.self, from: data)
                self.users = userResponse.results
                DispatchQueue.main.async {
                	//UI작업은 꼭 main 스레드에서 !!
                    self.tableView.reloadData()
                }
            } catch(let err) {
                print("Decoding Error")
                print(err.localizedDescription)
            }
        }
    }.resume()
}

데이터는 잘 받아오는데요~

 

이제 UITableViewCell을 정의해봅시다

 

class UserProfileCell: UITableViewCell {
    @IBOutlet weak var profileImageView: UIImageView!
    @IBOutlet weak var usernameLabel: UILabel!
    @IBOutlet weak var useremailLabel: UILabel!
    
    func setupView(model: User) {
    	//이미지 리소스 받아오는 부분
        DispatchQueue.global(qos: .userInitiated).async {
            guard let imageURL = URL(string: model.picture.thumbnail) else {
                return
            }
            
            let request = URLRequest(url: imageURL)
            // 역시나 싱글턴 이용
            URLSession.shared.dataTask(with: request, completionHandler: { (data, response, error) in
                if let data = data, let response = response, ((response as? HTTPURLResponse)?.statusCode ?? 500) < 300, let image = UIImage(data: data) {
                	// 테이블뷰 리로드 하는 것과 마찬가지로 UI작업은 main 스레드에서 해줍니다
                    DispatchQueue.main.async {
                        self.transition(toImage: image)
                    }
                }
            }).resume()
        }
        
        usernameLabel.text = model.name.full
        useremailLabel.text =  model.email
    }
    
    // 이미지로딩이 자연스럽도록 설정
    func transition(toImage image: UIImage?) {
        UIView.transition(with: self, duration: 0.3,
                          options: [.transitionCrossDissolve],
                          animations: {
                            self.profileImageView.image = image
        },
                          completion: nil)
    }
}

 

마지막으로 테이블뷰에 셀을 적용시키겠습니다.

 

extension ViewController: UITableViewDelegate, UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return users.count
    }
    
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return 80
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        guard let cell = tableView.dequeueReusableCell(withIdentifier: "UserProfileCell", for: indexPath) as? UserProfileCell else {
            return UITableViewCell()
        }
        
        cell.setupView(model: users[indexPath.row])
        return cell
    }
}

 

테이블뷰에 관련해서 모르는 게 있으시다면 일로  [iOS] UITableView 톺아보기 - 1

 

아래는 전체 코드입니다~ 복붙 편하시도록 한 클래스에 담아보았어요 ㅎㅎ

import UIKit

//{"results":[{"name":{"title":"mr","first":"stanley","last":"kelley"},"email":"stanley.kelley@example.com","picture":{"large":"https://randomuser.me/api/portraits/men/3.jpg","medium":"https://randomuser.me/api/portraits/med/men/3.jpg","thumbnail":"https://randomuser.me/api/portraits/thumb/men/3.jpg"}}

struct UserResponse: Codable {
    var results: [User]
}

struct User: Codable {
    let name: Name
    let email: String
    let picture: Picture
    
    struct Name: Codable {
        let title: String
        let first: String
        let last: String
        
        var full: String {
            return "\(self.title.capitalized). \(first.capitalized) \(last.capitalized)"
        }
    }
    
    struct Picture: Codable {
        let large: String
        let medium: String
        let thumbnail: String
    }
}


class ViewController: UIViewController {
    @IBOutlet weak var tableView: UITableView!
    private var users: [User] = []
    private let url = "https://randomuser.me/api/?results=20&inc=name,email,picture"

    override func viewDidLoad() {
        super.viewDidLoad()
        
        tableView.delegate = self
        tableView.dataSource = self
        
        getUsers()
    }
    
    private func getUsers() {
        let session = URLSession.shared
        guard let requestURL = URL(string: url) else {return}
        session.dataTask(with: requestURL) { data, response, error in
            guard error == nil else {
                print(error?.localizedDescription)
                return
            }
            
            if let data = data, let response = response as? HTTPURLResponse, response.statusCode == 200 {
                do {
                    let userResponse = try JSONDecoder().decode(UserResponse.self, from: data)
                    self.users = userResponse.results
                    DispatchQueue.main.async {
                        self.tableView.reloadData()
                    }
                } catch(let err) {
                    print("Decoding Error")
                    print(err.localizedDescription)
                }
            }
        }.resume()
    }
}

extension ViewController: UITableViewDelegate, UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return users.count
    }
    
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return 80
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        guard let cell = tableView.dequeueReusableCell(withIdentifier: "UserProfileCell", for: indexPath) as? UserProfileCell else {
            return UITableViewCell()
        }
        
        cell.setupView(model: users[indexPath.row])
        return cell
    }
}

class UserProfileCell: UITableViewCell {
    @IBOutlet weak var profileImageView: UIImageView!
    @IBOutlet weak var usernameLabel: UILabel!
    @IBOutlet weak var useremailLabel: UILabel!
    
    func setupView(model: User) {
        DispatchQueue.global(qos: .userInitiated).async {
            guard let imageURL = URL(string: model.picture.thumbnail) else {
                return
            }
            
            let request = URLRequest(url: imageURL)
            URLSession.shared.dataTask(with: request, completionHandler: { (data, response, error) in
                if let data = data, let response = response, ((response as? HTTPURLResponse)?.statusCode ?? 500) < 300, let image = UIImage(data: data) {
                    DispatchQueue.main.async {
                        self.transition(toImage: image)
                    }
                }
            }).resume()
        }
        
        usernameLabel.text = model.name.full
        useremailLabel.text =  model.email
    }
    
    func transition(toImage image: UIImage?) {
        UIView.transition(with: self, duration: 0.3,
                          options: [.transitionCrossDissolve],
                          animations: {
                            self.profileImageView.image = image
        },
                          completion: nil)
    }
}

 

 

참고 : https://www.edwith.org/boostcourse-ios/lecture/16863/

댓글
공지사항