RxSwift + ViewModelを使用したAPIサンプル
import UIKit
import RxSwift
import RxCocoa
final class ViewController : UIViewController {
@IBOutlet weak private var searchBar : UISearchBar !
@IBOutlet weak private var tableView : UITableView !
private let disposeBag = DisposeBag ( )
private let viewModel = WikipediaSearchViewModel ( wikipediaAPI: WkipediaDefaultAPI ( URLSession: . shared) )
override func viewDidLoad( ) {
super. viewDidLoad ( )
let input = WikipediaSearchViewModel . Input ( searchText: searchBar. rx. text. orEmpty. asObservable ( ) )
let output = viewModel. transform ( input: input)
output. searchDescription
. bind ( to: navigationItem. rx. title)
. disposed ( by: disposeBag)
output. wikipediaPages. bind ( to: tableView. rx. items ( cellIdentifier: " Cell " ) ) { index, result, cell in
cell. textLabel? . text = result. title
cell. detailTextLabel? . text = result. url. absoluteString
}
. disposed ( by: disposeBag)
output. error. subscribe ( onNext: { error in
if let error = error as? URLError ,
error. errorCode == URLError . notConnectedToInternet. rawValue {
print ( error)
}
} )
. disposed ( by: disposeBag)
}
}
import Foundation
import RxSwift
import RxCocoa
protocol ViewModelType {
associatedtype Input
associatedtype Output
func transform( input: Input ) -> Output
}
final class WikipediaSearchViewModel {
private let disposeBag = DisposeBag ( )
private let wikipediaAPI : WikipediaAPI
private let scheduler : SchedulerType
init ( wikipediaAPI: WikipediaAPI ,
scheduler: SchedulerType = ConcurrentMainScheduler . instance) {
self . wikipediaAPI = wikipediaAPI
self . scheduler = scheduler
}
}
extension WikipediaSearchViewModel : ViewModelType {
struct Input {
let searchText : Observable < String >
}
struct Output {
let wikipediaPages : Observable < [ WikipediaPage ] >
let searchDescription : Observable < String >
let error : Observable < Error >
}
func transform( input: Input ) -> Output {
let filterdText = input. searchText. debounce ( . milliseconds( 300 ) , scheduler: scheduler) . share ( replay: 1 , scope: . forever)
let sequence = filterdText
. filter { !$0. isEmpty }
. flatMap { [ unowned self] text -> Observable < Event < [ WikipediaPage ] > > in
return self . wikipediaAPI
. search ( from: text)
. materialize ( )
} . share ( replay: 1 , scope: . forever)
let wikipediaPages = sequence. flatMap { $0. element. map ( Observable . just) ?? . empty( ) }
let _searchDescription = PublishSubject < String > ( )
wikipediaPages. withLatestFrom ( filterdText) { ( pages, word) -> String in
return " \( word) \( pages. count) "
}
. bind ( to: _searchDescription)
. disposed ( by: disposeBag)
let error = sequence. flatMap { $0. error. map ( Observable . just) ?? . empty( ) }
return Output ( wikipediaPages: wikipediaPages,
searchDescription: _searchDescription. asObserver ( ) ,
error: error)
}
}
import Foundation
import RxSwift
protocol WikipediaAPI {
func search( from word: String ) -> Observable < [ WikipediaPage ] >
}
final class WkipediaDefaultAPI : WikipediaAPI {
private let host = URL ( string: " https://ja.wikipedia.org " ) !
private let path = " /w/api.php "
private let URLSession : Foundation . URLSession
init ( URLSession: Foundation . URLSession ) {
self . URLSession = URLSession
}
func search( from word: String ) -> Observable < [ WikipediaPage ] > {
var components = URLComponents ( url: host, resolvingAgainstBaseURL: false ) !
components. path = path
let items = [
URLQueryItem ( name: " format " , value: " json " ) ,
URLQueryItem ( name: " action " , value: " query " ) ,
URLQueryItem ( name: " list " , value: " search " ) ,
URLQueryItem ( name: " srsearch " , value: word)
]
components. queryItems = items
let request = URLRequest ( url: components. url!)
return URLSession . rx. response ( request: request)
. map { pair in
do {
let response = try JSONDecoder ( ) . decode ( WikipediaSearchResponse . self, from: pair. data)
return response. query. search
} catch {
throw error
}
}
}
}
import Foundation
struct WikipediaSearchResponse : Decodable {
let query : Query
struct Query : Decodable {
let search : [ WikipediaPage ]
}
}
struct WikipediaPage {
let id : String
let title : String
let url : URL
}
extension WikipediaPage : Decodable {
private enum CodingKeys : String , CodingKey {
case id = " pageid "
case title
}
init ( from decoder: Decoder ) throws {
let container = try decoder. container ( keyedBy: CodingKeys . self)
self . id = String ( try container. decode ( Int . self, forKey: . id) )
self . title = try container. decode ( String . self, forKey: . title)
self . url = URL ( string: " https://ja.wiipedia.org/w/index.php?curid= \( id) " ) !
}
}
extension WikipediaPage : Equatable {
static func == ( lhs: WikipediaPage , rhs: WikipediaPage ) -> Bool {
return lhs. id == rhs. id
}
}