Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .swiftlint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ included:
- CtaReactiveMasterTests
excluded:
- Tests/SwiftLintFrameworkTests/Resources
disabled_rules:
- identifier_name
opt_in_rules:
- empty_count
- file_header
Expand Down
36 changes: 36 additions & 0 deletions CtaReactiveMaster.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@
D7519A61260E357F000E0B78 /* RxCocoa in Frameworks */ = {isa = PBXBuildFile; productRef = D7519A60260E357F000E0B78 /* RxCocoa */; };
D7519A63260E357F000E0B78 /* RxRelay in Frameworks */ = {isa = PBXBuildFile; productRef = D7519A62260E357F000E0B78 /* RxRelay */; };
D7519A65260E357F000E0B78 /* RxSwift in Frameworks */ = {isa = PBXBuildFile; productRef = D7519A64260E357F000E0B78 /* RxSwift */; };
D773E4A52617288000E710DB /* NewsRepositoryActionCreator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D773E4A42617288000E710DB /* NewsRepositoryActionCreator.swift */; };
D773E4AE2617295000E710DB /* NewsRepositoryDispatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = D773E4AD2617295000E710DB /* NewsRepositoryDispatcher.swift */; };
D773E4B226172A2B00E710DB /* NewsRepositoryStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = D773E4B126172A2B00E710DB /* NewsRepositoryStore.swift */; };
D773E4B626172A9200E710DB /* Flux.swift in Sources */ = {isa = PBXBuildFile; fileRef = D773E4B526172A9200E710DB /* Flux.swift */; };
D773E4BA2617358D00E710DB /* RxProperty.swift in Sources */ = {isa = PBXBuildFile; fileRef = D773E4B92617358D00E710DB /* RxProperty.swift */; };
D794061F25B2B419006B8444 /* Requestable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D794061C25B2B419006B8444 /* Requestable.swift */; };
D794062025B2B419006B8444 /* APIClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = D794061D25B2B419006B8444 /* APIClient.swift */; };
D794062125B2B419006B8444 /* NewsAPIRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = D794061E25B2B419006B8444 /* NewsAPIRequest.swift */; };
Expand Down Expand Up @@ -58,6 +63,11 @@
D71C2B7925A5426B00AE29EE /* NewsTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewsTableViewCell.swift; sourceTree = "<group>"; };
D71C2B7A25A5426B00AE29EE /* NewsTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = NewsTableViewCell.xib; sourceTree = "<group>"; };
D71C2BAA25A7EE6800AE29EE /* NewsAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewsAPI.swift; sourceTree = "<group>"; };
D773E4A42617288000E710DB /* NewsRepositoryActionCreator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewsRepositoryActionCreator.swift; sourceTree = "<group>"; };
D773E4AD2617295000E710DB /* NewsRepositoryDispatcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewsRepositoryDispatcher.swift; sourceTree = "<group>"; };
D773E4B126172A2B00E710DB /* NewsRepositoryStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewsRepositoryStore.swift; sourceTree = "<group>"; };
D773E4B526172A9200E710DB /* Flux.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Flux.swift; sourceTree = "<group>"; };
D773E4B92617358D00E710DB /* RxProperty.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RxProperty.swift; sourceTree = "<group>"; };
D794061C25B2B419006B8444 /* Requestable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Requestable.swift; sourceTree = "<group>"; };
D794061D25B2B419006B8444 /* APIClient.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = APIClient.swift; sourceTree = "<group>"; };
D794061E25B2B419006B8444 /* NewsAPIRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NewsAPIRequest.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -103,6 +113,7 @@
82796DE5256923ED00443E2F /* Sources */ = {
isa = PBXGroup;
children = (
D773E4A22617282E00E710DB /* Flux */,
D711C16625F3317400557BDD /* Utils */,
D7F2C4A225B5759200083499 /* Entities */,
D794062525B2BCC5006B8444 /* Repository */,
Expand Down Expand Up @@ -169,6 +180,7 @@
D711C15725F2611000557BDD /* UIRefreshControl+Extension.swift */,
D711C15B25F2615100557BDD /* UIActivityIndicatorView+Extension.swift */,
D711C16125F2F6D500557BDD /* UIViewController+Extension.swift */,
D773E4B92617358D00E710DB /* RxProperty.swift */,
);
path = Extension;
sourceTree = "<group>";
Expand All @@ -189,6 +201,25 @@
name = Frameworks;
sourceTree = "<group>";
};
D773E4A22617282E00E710DB /* Flux */ = {
isa = PBXGroup;
children = (
D773E4A32617286100E710DB /* NewsRepository */,
D773E4B526172A9200E710DB /* Flux.swift */,
);
path = Flux;
sourceTree = "<group>";
};
D773E4A32617286100E710DB /* NewsRepository */ = {
isa = PBXGroup;
children = (
D773E4A42617288000E710DB /* NewsRepositoryActionCreator.swift */,
D773E4AD2617295000E710DB /* NewsRepositoryDispatcher.swift */,
D773E4B126172A2B00E710DB /* NewsRepositoryStore.swift */,
);
path = NewsRepository;
sourceTree = "<group>";
};
D794061B25B2B419006B8444 /* APIClient */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -390,19 +421,24 @@
D711C16825F3318D00557BDD /* LoadingStatus.swift in Sources */,
D711C16225F2F6D600557BDD /* UIViewController+Extension.swift in Sources */,
D71C2B8D25A5CEF500AE29EE /* HomeViewController.swift in Sources */,
D773E4B226172A2B00E710DB /* NewsRepositoryStore.swift in Sources */,
D7F2C4A425B575AE00083499 /* Category.swift in Sources */,
D711C15825F2611000557BDD /* UIRefreshControl+Extension.swift in Sources */,
D794062025B2B419006B8444 /* APIClient.swift in Sources */,
D848D49C2568190F008CA221 /* AppDelegate.swift in Sources */,
D71C2BAB25A7EE6800AE29EE /* NewsAPI.swift in Sources */,
D794061F25B2B419006B8444 /* Requestable.swift in Sources */,
D773E4B626172A9200E710DB /* Flux.swift in Sources */,
D848D49E2568190F008CA221 /* SceneDelegate.swift in Sources */,
D773E4A52617288000E710DB /* NewsRepositoryActionCreator.swift in Sources */,
D7F2C4AB25B575CC00083499 /* Country.swift in Sources */,
D773E4BA2617358D00E710DB /* RxProperty.swift in Sources */,
D71C2B7B25A5426B00AE29EE /* NewsTableViewCell.swift in Sources */,
D711C14D25F24CA400557BDD /* HomeViewModel.swift in Sources */,
D7F2C4AF25B575DA00083499 /* Language.swift in Sources */,
D711C15C25F2615100557BDD /* UIActivityIndicatorView+Extension.swift in Sources */,
D7F2C4B325B591C500083499 /* JSONDecoder+Extension.swift in Sources */,
D773E4AE2617295000E710DB /* NewsRepositoryDispatcher.swift in Sources */,
D794062125B2B419006B8444 /* NewsAPIRequest.swift in Sources */,
D794062725B2BCDD006B8444 /* NewsRepository.swift in Sources */,
);
Expand Down
98 changes: 98 additions & 0 deletions CtaReactiveMaster/Sources/Extension/RxProperty.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@

//
// Property.swift
// RxProperty
//
// Created by Yasuhiro Inami on 2017-03-11.
// Updated by Kohei Keta on 2021-04-03
// Copyright © 2017 Yasuhiro Inami. All rights reserved.
//

import RxSwift
import RxCocoa

/// A get-only `BehaviorRelay` that works similar to ReactiveSwift's `Property`.
///
/// - Note:
/// From ver 0.3.0, this class will no longer send `.completed` when deallocated.
///
/// - SeeAlso:
/// https://github.com/ReactiveCocoa/ReactiveSwift/blob/1.1.0/Sources/Property.swift
/// https://github.com/ReactiveX/RxSwift/pull/1118 (unmerged)
public final class Property<Element> {

public typealias E = Element

private let _behaviorRelay: BehaviorRelay<E>

/// Gets current value.
public var value: E {
get {
return _behaviorRelay.value
}
}

/// Initializes with initial value.
public init(_ value: E) {
_behaviorRelay = BehaviorRelay(value: value)
}

/// Initializes with `BehaviorRelay`.
public init(_ behaviorRelay: BehaviorRelay<E>) {
_behaviorRelay = behaviorRelay
}

/// Initializes with `Observable` that must send at least one value synchronously.
///
/// - Warning:
/// If `unsafeObservable` fails sending at least one value synchronously,
/// a fatal error would be raised.
///
/// - Warning:
/// If `unsafeObservable` sends multiple values synchronously,
/// the last value will be treated as initial value of `Property`.
public convenience init(unsafeObservable: Observable<E>) {
let observable = unsafeObservable.share(replay: 1, scope: .whileConnected)
var initial: E? = nil

let initialDisposable = observable
.subscribe(onNext: { initial = $0 })

guard let initial_ = initial else {
fatalError("An unsafeObservable promised to send at least one value. Received none.")
}

self.init(initial: initial_, then: observable)

initialDisposable.dispose()
}

/// Initializes with `initial` element and then `observable`.
public init(initial: E, then observable: Observable<E>) {
_behaviorRelay = BehaviorRelay(value: initial)

_ = observable
.bind(to: _behaviorRelay)
// .disposed(by: disposeBag) // Comment-Out: Don't dispose when `property` is deallocated
}

/// Observable that synchronously sends current element and then changed elements.
/// This is same as `ReactiveSwift.Property<T>.producer`.
public func asObservable() -> Observable<E> {
return _behaviorRelay.asObservable()
}

/// Observable that synchronously sends current element and then changed elements as Driver.
/// This is same as `ReactiveSwift.Property<T>.driver`.
public func asDriver() -> Driver<E> {
return _behaviorRelay.asDriver()
}

/// Observable that only sends changed elements, ignoring current element.
/// This is same as `ReactiveSwift.Property<T>.signal`.
public var changed: Observable<E> {
return asObservable().skip(1)
}

}

Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
//

import RxSwift
import RxCocoa
import UIKit

extension Reactive where Base: UIRefreshControl {
Expand Down
24 changes: 24 additions & 0 deletions CtaReactiveMaster/Sources/Flux/Flux.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
//
// Flux.swift
// CtaReactiveMaster
//
// Created by 化田晃平 on R 3/04/02.
//

import Foundation

final class Flux {
static let shared = Flux()

let newsRepositoryActionCreator: NewsRepositoryActionCreator
let newsRepositoryDispatcher: NewsRepositoryDispatcher
let newsRepositoryStore: NewsRepositoryStore

private init(newsRepositoryActionCreator: NewsRepositoryActionCreator = .shared,
newsRepositoryDispatcher: NewsRepositoryDispatcher = .shared,
newsRepositoryStore: NewsRepositoryStore = .shared) {
self.newsRepositoryActionCreator = newsRepositoryActionCreator
self.newsRepositoryDispatcher = newsRepositoryDispatcher
self.newsRepositoryStore = newsRepositoryStore
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
//
// NewsRepositoryActionCreator.swift
// CtaReactiveMaster
//
// Created by 化田晃平 on R 3/04/02.
//

import Foundation
import RxSwift
import RxRelay

final class NewsRepositoryActionCreator {
static let shared = NewsRepositoryActionCreator()

private let repository: NewsRepository
private let dispatcher: NewsRepositoryDispatcher

private let disposeBag = DisposeBag()
private let fetchArticles = PublishRelay<Void>()

private init(dispatcher: NewsRepositoryDispatcher = .shared,
repository: NewsRepository = .init()) {
self.dispatcher = dispatcher
self.repository = repository

let fetchedEvent = fetchArticles
.flatMap {
repository.fetch().asObservable().materialize()
}
.share()

fetchedEvent.flatMap { $0.element.map(Observable.just) ?? .empty() }
.map(\.articles)
.bind(to: dispatcher.articles)
.disposed(by: disposeBag)

fetchedEvent.flatMap { $0.error.map(Observable.just) ?? .empty() }
.bind(to: dispatcher.error)
.disposed(by: disposeBag)

Observable.merge(fetchArticles.map { _ in true },
fetchedEvent.map { _ in false })
.bind(to: dispatcher.isFetching)
.disposed(by: disposeBag)
}

func fetch() {
fetchArticles.accept(())
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//
// NewsRepositoryDispatcher.swift
// CtaReactiveMaster
//
// Created by 化田晃平 on R 3/04/02.
//

import RxSwift
import RxRelay

final class NewsRepositoryDispatcher {
static let shared = NewsRepositoryDispatcher()

let articles = PublishRelay<[Article]>()
let isFetching = PublishRelay<Bool>()
let error = PublishRelay<Error>()

private init() {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
//
// NewsRepositoryStore.swift
// CtaReactiveMaster
//
// Created by 化田晃平 on R 3/04/02.
//

import Foundation
import RxSwift
import RxCocoa

final class NewsRepositoryStore {

static let shared = NewsRepositoryStore()
private let dispatcher: NewsRepositoryDispatcher

private let disposeBag = DisposeBag()

let articles: Property<[Article]>
private let _articles = BehaviorRelay<[Article]>(value: [])

let isFetching: Property<Bool>
private let _isFetching = BehaviorRelay<Bool>(value: false)

//状態として保持しない場合はObservableのみで良い
let error: Observable<Error>

private init(dispatcher: NewsRepositoryDispatcher = .shared) {
self.dispatcher = dispatcher
error = dispatcher.error.asObservable()

articles = Property(_articles)
isFetching = Property(_isFetching)

dispatcher.articles
.bind(to: _articles)
.disposed(by: disposeBag)

dispatcher.isFetching
.bind(to: _isFetching)
.disposed(by: disposeBag)
}
}
13 changes: 7 additions & 6 deletions CtaReactiveMaster/Sources/Utils/LoadingStatus.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@
// Created by 化田晃平 on R 3/03/06.
//

enum LoadingStatus {
case initial
case loading
case loadSuccess
case loadFailed(Error)
}
//enum LoadingStatus {
// case initial
// case loading
// case loadSuccess
// case loadFailed(Error)
//}

Loading