컴바인에 대한 여러 개념적인 이야기들을 써오긴 했는데 역시 개념 설명 글은 와 닿는 것이 없는 것 같습니다. 아무래도 현실적으로 현업에서 쓸 만한 예제는 아니니깐요. 그래서 이번에는 조금은 더 실용성이 있어 보이는 컴바인 예제 투성이라는 주제로 글을 써봅니다.
기본 자료구조의 퍼블리셔
스위프트(Swift)는 다채로운 자료구조를 제공합니다. 예를 들어 기본 타입도 있겠지만 배열(array)이나 사전형(dictionary) 같은 복잡한 자료구조도 있지요.
대표적으로 Sequence.publisher
를 살펴보겠습니다. 이 타입은 보통 배열(array) 혹은 리스트(list)라고도 불리며 굉장히 자주 쓰이는 타입이지요?
let publisher = [1, 2, 3, 4, 5].publisher
publisher
.filter { $0 % 2 == 0 }
.map { $0 * $0 }
.sink { (value) in
print(value)
}
위 예제의 첫 줄에서 .publisher
라는 프로퍼티를 통해 간단하게 해당 리스트의 퍼블리셔를 구해와서 체인을 구성하는 모습을 볼 수 있습니다. 결과는 다들 파악하시겠지만 4와 16이 표시됩니다. filter를 통해 짝수만 다음 체인으로 넘어가며, map을 통해 제곱 값을 체인으로 밀어 넣습니다. 그래서 그 결과가 sink를 통해 출력되지요.
.assign 컨슈머
서브스크라이버로 sink 컨슈머를 주로 써 왔습니다만 이번에는 다른 컨슈머인 assign을 살펴봅시다. 이 컨슈머는 키 패스(Key Path)를 통해 최종 결과를 전달하는 데 최적화된 서브스크라이버입니다.
아래 예제는 위 예제에서 sink 대신 assign을 쓰기 위해 좀 수정한 코드입니다.
class Dumper {
var value = 0 {
didSet {
print("value was updated to \(value)")
}
}
}
let dumper = Dumper()
let publisher = [1, 2, 3, 4, 5].publisher
publisher
.filter { $0 % 2 == 0 }
.map { $0 * $0 }
.assign(to: \.value, on: dumper)
Dumper
라는 클래스를 만들었는데, 이 클래스의 value
프로퍼티에 값을 지정하면 그 값을 콘솔에 표시하는 기능을 가지고 있습니다.
그리고 앞의 예제와 동일한 리스트의 퍼블리셔 체인 끝에는 assign을 이용해 dumper 오브젝트의 value 프로퍼티에 결과를 전달하도록 구현했습니다. 이 코드가 실행되면 콘솔에는 "value was updated to 4", "value was updated to 16"라는 두 줄의 문자열이 찍힙니다. Dumper 클래스를 통해 출력된 결과지요.
살짝 정리하자면 sink가 '이벤트 체인'의 끝을 장식한다면 assign은 '데이터 체인'의 끝을 장식한다고 볼 수도 있겠네요.
@Published
스위프트의 기본 타입을 사용한다면 @Published
라는 Property Wrapper를 이용해 퍼블리셔를 구성할 수도 있습니다. 제약 사항으로 클래스(class)의 프로퍼티에만 이 형식을 사용할 수 있다는 점을 우선 알아둡시다.
class SomeModel {
@Published var name = "Test"
}
위의 SomeModel
클래스는 name
이라는 프로퍼티를 가지고 있는데 이걸 퍼블리셔로 쓸 수 있습니다. 아래와 같은 식이지요.
let model = SomeModel()
let publisher = model.$name
publisher
.filter { $0.count > 0 }
.sink(receiveValue: { value in
print("name is \(value)")
})
두 번째 라인에 .$name
라는 것을 볼 수 있는데 이것이 바로 퍼블리셔입니다. 당연히 달러마크($)는 오타가 아닙니다. 이렇게 해서 name
프로퍼티를 퍼블리셔로 써서 체인을 만들었습니다.
이 코드가 실행되고 난 후 name
의 값이 변경되면 이 퍼블리셔의 체인에 데이터가 들어가게 됩니다. 그런데 초기값도 이 체인을 타기 때문에 최초 한번 아래와 같은 내용이 콘솔에 표시됩니다.
"name is Test"
위 코드 이후에 연달아서 아래와 같은 코드를 추가해 봅시다. 그리고 이벤트에 따른 동작이 발생하는지 파악해 봅시다.
model.name = ""
model.name = "John"
위 코드가 실행되면 아래 한 줄이 콘솔에 표시됩니다.
"name is John"
filter를 통해 name이 빈 문자열이면 걸러내도록 했기 때문에 당연히 결과는 하나만 표시되지요.
이렇게 클래스 프로퍼티를 퍼블리셔로 간결하게 사용할 수 있는 방법이 있습니다.
파운데이션에서 찾아보기
Swift 5.1에서는 파운데이션(Foundation)이나 UIKit, AppKit 등 기본 프레임워크의 다양한 기능들이 컴바인에 대응하고 있습니다. 이번에는 그 예로 URLSession.dataTaskPublisher
를 살펴보겠습니다. 이름에서 알 수 있겠지만 인터넷에서 자료를 다운로드하는 기능을 제공하는 URLSession
의 Data Task 퍼블리셔입니다.
let p = URLSession
.shared
.dataTaskPublisher(for: URL(string: "https://seorenn.tistory.com")!)
.filter({ (data, response) -> Bool in
guard let r = response as? HTTPURLResponse,
200..<300 ~= r.statusCode else {
return false
}
return true
})
.sink(receiveCompletion: { (completion) in
// do nothing... :)
}) { (data, response) in
guard let html = String(data: data, encoding: .utf8)
else { return }
if let s = html.range(of: "<title>"),
let e = html.range(of: "</title>") {
let range = s.upperBound..<e.lowerBound
print(html[range])
}
}
위 코드가 실행되면 제 블로그의 인덱스 페이지를 로딩해서 filter를 통해 리퀘스트가 성공하면 최종 sink로 데이터가 전달됩니다. 그리고 sink에서는 <title>...</title>
사이의 문자열을 부분적으로 추출(substring)해서 콘솔에 표시합니다. 즉 현재 시점(2020년 1월)에는 아래의 내용이 콘솔에 표시됩니다.
"SEORENN NOTEBOOK"
궁금하다면 사용하는 기본 프레임워크의 레퍼런스 문서에서 publisher와 관련된 것이 있는지 찾아봅시다. 예를 들어 NotificationCenter.publisher
등등 굉장히 많습니다.
관련 글
'기술적인 이야기 > 애플 플랫폼 개발' 카테고리의 다른 글
2020년 4월부터 소셜 로그인 지원 시 애플 인증 강제 (0) | 2020.02.18 |
---|---|
Combine과 SwiftUI (1) | 2020.01.28 |
Combine 이벤트 체인이 왜 필요할까? (0) | 2020.01.23 |
Combine Framework는 어떤 녀석일까요? (0) | 2020.01.15 |
[Swift 5.1] Identifiable (SE-0261) (0) | 2019.11.12 |
댓글