JSON: Placeholder
JSON: Placeholder (https://jsonplaceholder.typicode.com/) là một trang web cung cấp API REST để thử nghiệm. Dưới đây, chúng ta sẽ sử dụng RxSwift và Alamofire để gọi các API này và lấy dữ liệu dạng chuỗi và JSON.
- GET /posts/1
- GET /posts
- POST /posts
- PUT /posts/1
- DELETE /posts/1
Tất cả các API GET đều trả về dữ liệu JSON với định dạng (JSON-Schema) như sau:
{
"type": "object",
"properties": {
"userId": {"type": "integer"},
"id": {"type": "integer"},
"title": {"type": "string"},
"body": {"type": "string"}
}
}
Tạo Dự Án
Mở Xcode, chọn File / New / Project... Trong hướng dẫn tạo dự án, chọn iOS / Single View App. Điền tên sản phẩm: RxExample. Chọn thư mục lưu trữ và nhấn Create. Đóng dự án đã tạo.
Cấu hình Pods
Tạo tệp Podfile trong thư mục chứa dự án với nội dung sau:
workspace 'RxExample'
use_frameworks!
target 'RxExample' do
project 'RxExample'
pod 'CodableAlamofire'
pod 'RxAlamofire'
end
Mở terminal và chạy lệnh pod install trong thư mục dự án:
$ cd RxExample
$ pod install
...
Installing Alamofire (4.7.3)
Installing CodableAlamofire (1.1.0)
Installing RxAlamofire (4.2.0)
Installing RxSwift (4.2.0)
...
Mở tệp RxExample.xcworkspace bằng Xcode.
RxCodableAlamofire
Thêm tệp RxCodableAlamofire.swift vào dự án RxExample với nội dung sau:
import Foundation
import RxSwift
import Alamofire
import RxAlamofire
import CodableAlamofire
class RxCodableAlamofire {
public static func object<T: Decodable>(_ method: Alamofire.HTTPMethod, _ url: URLConvertible, parameters: [String: Any]? = nil, encoding: ParameterEncoding = URLEncoding.default, headers: [String: String]? = nil, queue: DispatchQueue? = nil, keyPath: String? = nil, mapToObject object: T? = nil, decoder: JSONDecoder = JSONDecoder()) -> Observable<T> {
return SessionManager.default.rx.object(method, url, parameters: parameters, encoding: encoding, headers: headers, queue: queue, keyPath: keyPath, mapToObject: object, decoder: decoder)
}
}
extension Reactive where Base: SessionManager {
public func object<T: Decodable>(_ method: Alamofire.HTTPMethod, _ url: URLConvertible, parameters: [String: Any]? = nil, encoding: ParameterEncoding = URLEncoding.default, headers: [String: String]? = nil, queue: DispatchQueue? = nil, keyPath: String? = nil, mapToObject object: T? = nil, decoder: JSONDecoder = JSONDecoder()) -> Observable<T> {
return request(method, url, parameters: parameters, encoding: encoding, headers: headers).flatMap { $0.rx.object(queue: queue, keyPath: keyPath, mapToObject: object, decoder: decoder) }
}
}
extension Reactive where Base: DataRequest {
public func object<T: Decodable>(queue: DispatchQueue? = nil, keyPath: String? = nil, mapToObject object: T? = nil, decoder: JSONDecoder = JSONDecoder()) -> Observable<T> {
return result(queue: queue, responseSerializer: DataRequest.DecodableObjectSerializer(keyPath, decoder))
}
}
Tìm và mở tệp DataRequest+Decodable.swift trong Pods (Pods/CodableAlamofire) và thay đổi độ khả dụng của DecodableObjectSerializer từ private thành public (nhấn Unlock khi được nhắc).
RestApi
Thêm tệp RestApi.swift vào dự án RxExample với nội dung sau:
import Foundation
import Alamofire
import RxSwift
import RxAlamofire
class StringEncoding: ParameterEncoding {
let body: String;
public init(body: String) {
self.body = body
}
public func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest {
var request = try urlRequest.asURLRequest()
request.httpBody = body.data(using: .utf8, allowLossyConversion: false)
return request
}
}
extension Encodable {
public func toJSONString(prettyPrint: Bool) throws -> String? {
let encoder = JSONEncoder()
if prettyPrint { encoder.outputFormatting = .prettyPrinted }
let data = try! encoder.encode(self)
let jsonString = String(data: data, encoding: .utf8)
return jsonString
}
}
class RestApi {
static func getObject<T: Decodable>(url: String, keyPath: String? = nil) -> Observable<T> {
return RxCodableAlamofire.object(.get, url, keyPath: keyPath)
}
static func getArray<T: Decodable>(url: String, keyPath: String? = nil) -> Observable<[T]> {
return RxCodableAlamofire.object(.get, url, keyPath: keyPath)
}
static func update(url: String, body: String) -> Observable<String> {
return RxAlamofire.string(.put, url, encoding: StringEncoding(body: body))
}
static func create(url: String, body: String) -> Observable<String> {
return RxAlamofire.string(.post, url, encoding: StringEncoding(body: body))
}
static func delete(url: String) -> Observable<String> {
return RxAlamofire.string(.delete, url)
}
static func getString(url: String) -> Observable<String> {
return RxCodableAlamofire.string(.get, url)
}
}
Bài viết
Thêm tệp Post.swift vào dự án RxExample với nội dung sau:
import Foundation
import RxSwift
struct Post: Codable {
let userId: Int
let id: Int
let title: String
let body: String
var description: String {
return "Post {userId = \(userId), id = \(id), title = \"\(title)\", body = \"\(body.replacingOccurrences(of: "\n", with: "\\n"))\"}";
}
static let baseURL = "https://jsonplaceholder.typicode.com/"
static func getPostAsString() -> Observable<String> {
return RestApi.getString(url: "\(baseURL)posts/1")
}
static func getPostAsJson() -> Observable<Post> {
return RestApi.getObject(url: "\(baseURL)posts/1")
}
static func getPosts(n: Int) -> Observable<Post> {
return RestApi.getArray(url: "\(baseURL)posts").flatMap { Observable.from($0) }.take(n)
}
static func createPost() -> Observable<String> {
let post = Post(userId: 101, id: 0, title: "test title", body: "test body")
return RestApi.create(url: "\(baseURL)posts", body: try! post.toJSONString(prettyPrint: false)!)
}
static func updatePost() -> Observable<String> {
let post = Post(userId: 101, id: 1, title: "test title", body: "test body")
return RestApi.update(url: "\(baseURL)posts/1", body: try! post.toJSONString(prettyPrint: false)!)
}
static func deletePost() -> Observable<String> {
return RestApi.delete(url: "\(baseURL)posts/1")
}
}
- getPostAsString: Lấy bài viết đầu tiên và trả về dưới dạng chuỗi.
- getPostAsJson: Lấy bài viết đầu tiên và trả về dưới dạng đối tượng Post.
- getPosts: Lấy n bài viết đầu tiên và trả về n đối tượng Post.
- createPost: Tạo một bài viết mới và trả về chuỗi.
- updatePost: Cập nhật bài viết đầu tiên và trả về chuỗi.
- deletePost: Xóa bài viết đầu tiên và trả về chuỗi.
AppDelegate
Thêm import RxSwift vào AppDelegate.swift và thêm một instance của DisposeBag vào AppDelegate.
import RxSwift
class AppDelegate {
// ...
let disposeBag = DisposeBag()
// ...
}
Trong phương thức applicationDidFinishLaunching, thêm đoạn mã sau:
Post.getPostAsString().do(onNext: { print($0) }).subscribe().disposed(by: disposeBag)
Post.getPostAsJson().do(onNext: { print($0.description) }).subscribe().disposed(by: disposeBag)
Post.getPosts(n: 2).do(onNext: { print($0.description) }).subscribe().disposed(by: disposeBag)
Post.createPost().do(onNext: { print($0) }).subscribe().disposed(by: disposeBag)
Post.updatePost().do(onNext: { print($0) }).subscribe().disposed(by: disposeBag)
Post.deletePost().do(onNext: { print($0) }).subscribe().disposed(by: disposeBag)
Kết quả Đầu Ra
{
"userId": 1,
"id": 1,
"title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
"body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"
}
Post {userId = 1, id = 1, title = "sunt aut facere repellat provident occaecati excepturi optio reprehenderit", body = "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"}
Post {userId = 1, id = 2, title = "qui est esse", body = "est rerum tempore vitae\nsequi sint nihil reprehenderit dolor beatae ea dolores neque\nfugiat blanditiis voluptate porro vel nihil molestiae ut reiciendis\nqui aperiam non debitis possimus qui neque nisi nulla"}
Post {userId = 1, id = 1, title = "sunt aut facere repellat provident occaecati excepturi optio reprehenderit", body = "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"}
{}
{
"{\"body\":\"test body\",\"id\":0,\"title\":\"test title\",\"userId\":101}": "",
"id": 101
}
{
"id": 1
}