Swift, 객체간 비동기 데이터 전송하기
01 Aug 2017
통신을 하는 iOS 앱을 만드는 경우 다음과 같은 흐름을 통해 데이터를 주고 받습니다. 대표적으로 데이터를 받아오는 경우
- 네트워크를 다루는 객체를 통해 비동기 방식으로 데이터를 전달 받고
- 해당 데이터를 알맞은 모델 객체로 저장하여
- 컨트롤러에서 모델에 기반하여 데이터를 적절히 보여줍니다.

이 경우, 우리는 네트워크를 다루는 객체에서 컨트롤러 객체에 데이터를 넘겨주어야 합니다. 단, 네트워크에서 받는 데이터는 비동기 방식으로 처리되어야 합니다. 여기서는 3가지 방법을 통해 이를 구현하는 방법을 알아보겠습니다.
NotificationCenter 활용하기
NotificationCenter는 observer 패턴을 활용하여 객체에 알람을 전송하는 메커니즘입니다.
NotificationCenter는 특정 객체에 이름을 지닌 observer를 달아서, 앱 어딘가에서 자신을 호출하는 post 메소드가 호출되면 selector가 실행되는 형태를 가지고 있습니다.
class NetworkModule1 {
// 2
static func getData() {
let defaultSession = URLSession(configuration: .default)
guard let url = URL(string: "\(BASEURL)") else {
print("URL is nil")
return
}
// Request
let request = URLRequest(url: url)
// dataTask
var dataTask: URLSessionDataTask?
dataTask = defaultSession.dataTask(with: request) { data, response, error in
// getting Data Error
guard error == nil else {
print("Error occur: \(String(describing: error))")
return
}
if let myData = data, let response = response as? HTTPURLResponse, response.statusCode == 200 {
// data update
// self.updateData(index: index, data: data)
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "getData"),
object: nil, userInfo: myData)
}
}
dataTask?.resume()
}
}class SomeViewController: UIViewController {
// 1
func viewDidload() {
super.viewDidload()
NotificationCenter.default.addObserver(self,
selector: #selector(self.getDataFinished),
name: NSNotification.Name(rawValue: "getData"),
object: nil)
}
// 3
func getDataFinished(_ notification: Notification) {
// userInfo를 통해 데이터 전달
// update instance here
DispatchQueue.main.async {
// update tableView or collectionView
// self.tableView.reloadData()
}
}
}
let viewController = SomeViewController()
viewController.viewDidload()
NetworkModule1.getData()- 가장 먼저
viewDidload()메소드를 호출하여viewController에NotificationCenter의 observer를 달아줍니다. 이 때selector를 통해 해당 observer가 호출되면 실행되는 메소드를getDataFinished로 지정하였습니다. - 다음으로
NetworkModule1.getData()를 호출하는데 이 때NotificationCenter로 post를 전달합니다. 이 때userInfo파라미터를 통해서 딕셔너리 형태의 데이터를 전달할 수 있으며, 이는selector에서 지정된 함수에서 받을 수 있습니다. - 받아 온 데이터를 GCD를 통해
Main큐에서 뷰를 업데이트 합니다.
이와 같이 NotificationCenter를 통해 데이터를 받아오고 난 이후에 post를 전달하는 형태로 코드를 작성하면 데이터가 전달된 후 다음 작업을 하는 것이 보장되기 때문에 비동기 데이터를 처리할 수 있습니다. 다만, NotificationCenter는 observer도 달고, selector 함수도 별도로 작성해야 하기 때문에 코드가 다소 길어지는 경향이 있습니다. 그렇기 때문에 다수의 observer로부터 값을 주고 받는 경우에 사용하는 것이 좋습니다.
Escaping Closure 활용하기
Escaping closure를 사용하면 해당 함수가 실행된 후 클로저가 실행되는 것을 보장 할 수 있습니다.
class NetworkModule2 {
// 2
static func getDataFromClosure(completion: @escaping ([myData]) -> Void) {
let defaultSession = URLSession(configuration: .default)
guard let url = URL(string: "\(BASEURL)") else {
print("URL is nil")
return
}
// Request
let request = URLRequest(url: url)
// dataTask
var dataTask: URLSessionDataTask?
dataTask = defaultSession.dataTask(with: request) { data, response, error in
// getting Data Error
guard error == nil else {
print("Error occur: \(String(describing: error))")
return
}
if let data = data, let response = response as? HTTPURLResponse, response.statusCode == 200 {
// 3
// data update
// self.updateData(index: index, data: data)
DispatchQueue.main.async {
completion(myDatas)
}
}
}
dataTask?.resume()
}
}
class SomeViewController: UIViewController {
func viewDidload() {
super.viewDidload()
// 1
NetworkHandler2.getDataFromClosure() { [weak self] results in
// 4
// update tableView or collectionView
// self?.tableView.reloadData()
}
}
}- 가장 먼저
getDataFromClosure(completion:)함수를 호출합니다. getDataFromClosure(completion:)은 파라미터로escaping closure를 받습니다. 그렇기 때문에completion은 해당 함수가 실행된 이후에 실행됩니다.- 받아온 데이터를 serialize하고
completion클로저를 호출합니다. 이 때, 일반적으로completion은 받아온 데이터들을 화면에 적절히 뿌려주는 작업을 하기 때문에, main 쓰레드에서 클로저를 호출합니다. completion클로저의 body 부분으로escaping closure형태로completion이 작성되어 있기 때문에 항상getDataFromClosure()가 실행된 이후 실행되는 것이 보장됩니다.
Delegate 패턴 활용하기
참고자료
- Apple Inc. The Swift Programming Language (Swift 3.1)
- What do mean @escaping and @nonescaping closures in Swift?
- Completion handlers in Swift 3.0