일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- Observable
- collectionview
- Xcode
- swift documentation
- 리펙토링
- Protocol
- ios
- HIG
- uiscrollview
- Clean Code
- 리팩토링
- SWIFT
- clean architecture
- ribs
- Refactoring
- map
- RxCocoa
- combine
- MVVM
- 애니메이션
- 리펙터링
- 클린 코드
- uitableview
- tableView
- UICollectionView
- swiftUI
- UITextView
- Human interface guide
- 스위프트
- rxswift
- Today
- Total
김종권의 iOS 앱 개발 알아가기
[RxSwift] 4. Observables and Subjects 실전 적용 본문
* 실습할 프로젝트의 내용은 여기를 참조하여 다운로드
네비게이션 버튼 중 "+"버튼을 통해 이미지를 추가하며, save버튼을 추가 할 수 있도록 하는 것
RxSwift접근(BehaviorRelay와 PublishSubject이용)하여 구현해야될 내용
- "+"버튼을 누를 시 앨범으로 이동하는 기능, 선택시 뷰에 반영
- save버튼은 이미지가 있는 경우만 활성화
- 이미지를 선택할 때마다, 네비게이션 아이템 타이틀에 현재 입력한 총 이미지 갯수 표현
- save버튼을 누르면 저장되게끔
1. dispose bag
view controller이 dispose bag을 소유하고 있기 때문에, dispose의 ARC가 0이 될때, observable subscription들은 같이 disposed됨
(Rx subscription메모리 관리가 쉬운 장점, 단 해당 뷰 컨트롤러는 루트 뷰 컨트롤이기 때문에 앱이 종료되어야 사라지므로 다른 방법 모색)
<코드를 뷰 컨트롤러 전역에 작성>
// MainViewController.swift
private let bag = DisposeBag()
2. BehaviorRelay
- MainViewController에서 "+"버튼 기능
- BehaviorRelay변수에 추가하는 기능
- BehaviorRelay변수에 추가 된다면 이미지가 보여질 뷰에 삽입
1) BehaviorRelay객체에 구독요청 설정
보여질 이미지 업데이트, collage(size:)함수는 미리 내부적으로 정의해놓은 메소드
// MainViewController.swift, viewDidLoad()
images
.subscribe(onNext: { [weak imagePreview] photos in
guard let preview = imagePreview else { return }
preview.image = photos.collage(size: preview.frame.size)
})
.disposed(by: bag)
2) +버튼 클릭시, 이미지가 추가 되게끔 하는 기능 구현
<subject 객체를 전역에 선언>
- 초기화할 땐, BehaviorRelay<Type>(value : [])로 초기화
// MainViewController.swift
private let images = BehaviorRelay<[UIImage]>(value: [])
<"+"버튼 클릭시 subject에 이미지 추가>
- 기존 객체에 추가 : subject객체.value + 추가할 객체
- 추가를 subejct에게 알림 : subject객체.accept([])
// MainViewController.swift, addAction
let newImages = images.value
+ [UIImage(named: "IMG_1907.jpg")!]
images.accept(newImages)
3) 이미지가 홀수개이면 이미지가 짤리므로, 짝수일 경우만 저장할 수 있게끔 구현
최대 6개 이미지만 추가 가능하드록, 6개가 되면 "+"버튼 비활성화
<구독요청 코드 삽입>
- updateUI는 +버튼과 save버튼 제약을 주는 메소드
// MainViewController.swift, viewDidLoad()
images.asObservable()
.subscribe(onNext: { [weak self] photos in
self?.updateUI(photos: photos)
})
.disposed(by: bag)
* subject객체.asObservable()이란?
- subject는 observer와 observable 둘의 역할을 다 하는데, 외부에서 observer에 접근하지 못하도록 설정하며 observable에만 접근할 수 있도록 나눠서 접근 가능하도록 하기위함
private let subject = BehaviorRelay<[UIImage]>(value : [])
open let observable = subject.asObservable()
<updateUI메소드 작성>
private func updateUI(photos: [UIImage]) {
buttonSave.isEnabled = photos.count > 0 && photos.count % 2 == 0
buttonClear.isEnabled = photos.count > 0
itemAdd.isEnabled = photos.count < 6
title = photos.count > 0 ? "\(photos.count) photos" : "Collage"
}
3. 앨범에서 사진을 택하는 뷰 컨트롤러로 화면전환
- PhotosVIewController는 미리 정의된 뷰 컨트롤러
let photosViewController = storyboard!.instantiateViewController(
withIdentifier: "PhotosViewController") as! PhotosViewController
navigationController!.pushViewController(photosViewController, animated: true)
4. PublishSubject
구독된 순간 새로운 이벤트 수신을 알림
- collection view로 이루어진 PhotoViewController에서 이미지 선택하면 이벤트 발생()
<PublishSubject객체 생성>
// PhotosViewController.swift
// subject의 observer를 외부에서 접근하는 것 방지
private let selectedPhotosSubject = PublishSubject<UIImage>()
// Observable만 외부에서 접근할 수 있도록 internal 접근제한자로 선언
var selectedPhotos: Observable<UIImage> {
return selectedPhotosSubject.asObservable()
}
<PublishSubject구독 요청>
MainViewController에서 이벤트를 처리해야 하므로(이미지 업데이트), push하기 전에 subscribe에게 구독요청
// MainViewController.swift, actionAdd()
// (...중략...)
photosViewController.selectedPhotos
.subscribe(
onNext: { [weak self] newImage in
guard let images = self?.images else { return }
images.accept(images.value + [newImage])
},
onDisposed: {
print("completed photo selection")
}
)
.disposed(by: bag)
navigationController!.pushViewController(photosViewController, animated: true)
<컬렉션뷰에서 아이템을 선택하면 이미지 추가>
collectionView(_:didSelectItemAt)에 subject.onNext(_:)로 이벤트 발생
imageManager.requestImage(for: asset, targetSize: view.frame.size, contentMode: .aspectFill, options: nil, resultHandler: { [weak self] image, info in
guard let image = image, let info = info else { return }
// 이 부분만 주목 : .onNext(_:)
if let isThumbnail = info[PHImageResultIsDegradedKey as NSString] as? Bool, !isThumbnail {
self?.selectedPhotosSubject.onNext(image)
}
})
<할 일을 끝내고 뷰 컨트롤러가 사라질 때 메모리에 남아있는 subject관련 메모리 종료>
// PhotosViewController.swift, viewWillDisappear(_:)
selectedPhotosSubject.onCompleted()
<save버튼 활성화 - 버튼 누르는 부분>
- 성공과 실패만이 관심사이기 때문에 Observable의 Traits중 single()사용 ... Traits관련 내용은 밑에서 추가로 설명
@IBAction func actionSave() {
guard let image = imagePreview.image else { return }
// PhotoWriter.save(_:)는 observable sequence를 반환하므로 바로 구독요청
PhotoWriter.save(image)
.asSingle()
.subscribe(
onSuccess: { [weak self] id in
self?.showMessage("Saved with id: \(id)")
self?.actionClear()
},
onError: { [weak self] error in
self?.showMessage("Error", description: error.localizedDescription)
}
)
.disposed(by: bag)
}
<save버튼 활성화 - 이미지를 업데이트 시키는 부분>
- save함수에서 리턴된 값은 sequence값인 것이 핵심
- 여기서는 이벤트만 발생(onNext, onCompleted, onError)
static func save(_ image: UIImage) -> Observable<String> {
// Observable.create() : 리턴되는 값은 observable sequence (바로 이 객체를 통해 subscribe가능)
return Observable.create({ observer in
var savedAssetId: String?
// PHPhotoLibrary는 사용자가 공유하고 있는 photo library를 관리하는 공유 클래스
PHPhotoLibrary.shared().performChanges({
// PHAssetChangeRequest를 통해 이미지 수정을 요청
let request = PHAssetChangeRequest.creationRequestForAsset(from: image)
savedAssetId = request.placeholderForCreatedAsset?.localIdentifier
}, completionHandler: { success, error in
//
DispatchQueue.main.async {
if success, let id = savedAssetId {
observer.onNext(id)
observer.onCompleted()
} else {
observer.onError(error ?? Errors.couldNotSavePhoto)
}
}
})
return Disposables.create()
})
}
5. Observable의 Traits
1) Single(파일 쓰기와 같은 곳에 사용)
두 가지의 완전한 상태밖에 없는 것 : .success(value), .error(value)
2) Maybe
세 가지의 상태 모두가 있는 것
* success와 completed의 차이점
- completed는 성공하지 못해도 언제나 호출되지만, success는 어떤 일을 성공적으로 마친 경우만 호철
- completed는 파라미터가 없음(value를 emit하지 않음)
3) Completable(일을 잘 마무리 했는지 검사할 때 사용 ... alert)
*참조 : RxSwift: Reactive Programming with Swift, by raywenderlich team's book
'RxSwift > RxSwift 기본' 카테고리의 다른 글
[RxSwift] 6.Filtering Operators 실습 (0) | 2020.05.29 |
---|---|
[RxSwift] 5.Filtering Operators (0) | 2020.05.26 |
[RxSwift] 3. Subjects (0) | 2020.05.22 |
[RxSwift] 2. Observables (0) | 2020.05.21 |
[RxSwift] 1. RxSwift의 개념 (0) | 2020.05.20 |