Build base request network with Alamofire, Rxswift and SwiftyJSON. Using CRUD & Authorization Alamofire
Bài đăng này đã không được cập nhật trong 6 năm
Giới thiệu
Nếu các bạn đã đọc bài này của mình giới thiệu về các Library thì hôm nay mình sẽ đi sâu hơn về building một base networking sử dụng mô hình CRUD and Authorization mình tự building thông qua tham khảo document của alamofire và dự án mình đang làm.
Using MVVM pattern design.
Các thư viện sử dụng
- Thư viện request network Alamofire
- Function programing RxSwift và RxCocao
- Format json swift to model SwiftyJson
Các bước thực hiện
Xây UserModel
import Foundation
import SwiftyJSON
class SessionModel {
var userID: Int?
var acccessToken: String?
init(json: JSON) {
self.userID = json["user_id"].intValue
self.acccessToken = json["auth_token"].stringValue
}
}
Xây dựng class BaseService.
Trong các dự án thường sẽ được build các tác vụ request networking bên trong một thư mục. Mình sẽ build trong thư mục Networking (Tự tạo nhé :p)
Mình sẽ sử dụng design pattern Singleton để tổ chức cho class BaseService của mình.
import Foundation
import Alamofire
import RxSwift
import SwiftyJSON
class BaseService {
static let instance = BaseService()
var sessionManager = SessionManager.default
private var headers = SessionManager.defaultHTTPHeaders
private var configure = URLSessionConfiguration.default
private init() {
configHeader()
}
func configHeader() {
headers["Content-Type"] = "application/json"
configure.httpAdditionalHeaders = headers
sessionManager = Alamofire.SessionManager(configuration: configure)
}
}
Advanced using của Alamofire. Các bạn có thể đọc thêm ở document của alamofire. Advanced Usage
Ở đây mình sử dụng SessionManager.default để thiết lập một số config riêng biệt ngoài các config default của Alamofire. Nếu request của bạn không có config thì có thể sử dụng mặc định request của Alamofire là Alamofire.request(...) thay vì sử dụng self.sessionManager.request(...) như mình ở function sau nhé.
Xây dựng baseAPI để thực hiện request chung cho các request của chúng ta.
Đặt dưới func configHeader()
Mình chú thích ở sau các hàm. Các bạn chú ý đọc điểu hiểu hàm. Code nó không format comment nên hơi khó đọc xíu
[...]
func baseAPI(router: Router) -> Observable<Result<JSON>> {
return Observable.create({ observer -> Disposable in # Chúng ta sẽ tạo một Observable để phục vụ cho việc thu thập tín hiệu phát ra cho các tác vụ sau đó.
self.sessionManager
.request(router) # Router thì chúng ta sẽ set ở func phía dưới nhé.
.authenticate(user: "account", password: "password") # Nếu app của bạn sử dụng Basic Auth thì thêm cái này không thì có thể bỏ qua. Điền tài khoản của basic Auth vào. Cái này mình cũng chưa rõ lắm nên các bạn tự tìm hiểu thêm nhé.
.responseJSON { response in
switch response.result {
case .success(let value):
let json = JSON(value) # Xử lý về dạn json thông qua SwiftyJson
observer.onNext(Result.success(json)) # observer phát ra tín hiệu trả về dữ liệu
case .failure(let error):
observer.onNext(Result.error(error)) # observer phát ra tín hiệu trả về lỗi
}
observer.onCompleted() # observer phát ra tín hiệu hoàn thành request
}
return Disposables.create()
})
}
[...]
Thiết lập Enum Router.
Router này sẽ giúp chúng ta điều hướng URL PATH và URL METHOD để request lên server.
DEMO JSON PARAM truyền lên với demo LOGIN request
{
"session": {
"mailaddress": "user_test_1@gmail.com",
"password": "Abc@1234"
}
}
enum Router: URLRequestConvertible { # URLRequestConvertible class của Alamofire để thiết lập config
static let baseURLString = "http://example.com/api/" # Base URL đến server của bạn.
case login(email: String, password: String) # Mình sẽ demo một request login
var method: HTTPMethod { # Setup method cho request.
switch self {
case .login:
return .post
default:
return .get
}
}
var path: String { # Setup path url cho request
switch self {
case .login:
return "sessions"
}
func asURLRequest() throws -> URLRequest { # Xử lý endcode url path và method thành urlRequest.
let url = try Router.baseURLString.asURL()
var urlRequest = URLRequest(url: url.appendingPathComponent(path))
urlRequest.httpMethod = method.rawValue
# 3 Lệnh trên là tao sẽ tạo một URL REQUEST để thực hiện request.
var headerParameter: Parameters? = nil # Khởi tạo parameter cho requet của chúng ta.
switch self {
case .login(let email, let password):
headerParameter = ["session": ["mailaddress": email, "password": password]] as [String : Any] # Tạo parameter với tham số truyền vào là email và password. ứng với parameter dạng json ở bên trên.
default:
break
}
urlRequest = try URLEncoding.default.encode(urlRequest, with: headerParameter) # Encode request của chúng ta ra URL REQUEST.
return urlRequest
}
}
Thiết lập access token cho các request sau khi login.
Với thiết lập này thì sau khi login. Các request sau các bạn không phải thêm thủ công một param là token gửi lên server nữa.
class AccessTokenAdapter2: RequestAdapter {
private let accessToken: String
init(accessToken: String) {
self.accessToken = accessToken
}
func adapt(_ urlRequest: URLRequest) throws -> URLRequest {
var urlRequest = urlRequest
urlRequest.setValue(accessToken, forHTTPHeaderField: "AccessToken")
return urlRequest
}
}
Hàm helper set Access token.
Mình sử dụng Extension để thêm func vào class BaseService hoặc bạn có thể gọi trực tiếp AlamofireSetup.instance.sessionManager.adapter = AccessTokenAdapter2(accessToken: accessToken) sau khi login thành công hoặc register tài khoản thành công.
extension BaseService {
static func setToken(accessToken: String) {
AlamofireSetup.instance.sessionManager.adapter = AccessTokenAdapter2(accessToken: accessToken)
}
}
Demo login request.
extension AlamofireSetup {
func login(email: String, password: String) -> Observable<Result<SessionModel>> {
return baseAPI(router: Router.login(email: email, password: password))
.flatMapLatest { resultJson -> Observable<Result<SessionModel>> in
switch resultJson {
case .success(let json):
let userData = SessionModel(json: json)
return Observable.just(Result.success(userData))
case .error(let error):
return Observable.just(Result.error(error))
}
}
}
}
Result này sẽ giúp xử lý lỗi dễ dàng hơn. Bạn có thể tự build theo các khác. Vì mình cũng chưa tối ưu được phần này.
enum Result<T> {
case success(T)
case error(Error)
}
Login button tap send request với mô hình MVVM
Ở Login controller
# Khởi tạo viewModel ở **override func viewDidLoad()**
# Truyền vào là 2 textField là mail và password và button login.
viewModel = LoginViewModel(
inputs: (
email: getObservableTextField(emailTextField),
password: getObservableTextField(passwordTextField),
loginTaps: loginButton.rx.tap.asObservable()
)
)
Xử lý ở viewModel
import Foundation
import RxSwift
import SwiftyJSON
import RxCocoa
class LoginViewModel: BaseViewModel {
// MARK: - Outputs
var loginResult: Observable<Result<SessionModel>>!
init(inputs: (
email: Observable<String>,
password: Observable<String>,
loginTaps: Observable<Void>)) {
super.init()
let emailAndPassword = Observable.combineLatest(inputs.email, inputs.password) { (email: $0, password: $1) }
loginResult = inputs.loginTaps
.withLatestFrom(emailAndPassword)
.flatMapLatest({ [unowned self] loginInfo -> Observable<Result<SessionModel>> in
return AlamofireSetup.instance.login(email: loginInfo.email, password: loginInfo.password)
})
}
}
Xử lý Lắng nghe ở viewController
viewModel.loginResult
.asObservable()
.subscribe(onNext: { [weak self] result in
guard let `self` = self else {
return
}
switch result {
case .success(let userModel):
print("LOGIN SUCCESS")
# Redirect to Home Page
BaseService.setToken(accessToken: userModel.accessToken)
case .error(let error):
print("LOGIN FAIL")
self.showAlertDialog(message: error.localizedDescription)
}
})
.disposed(by: disposeBag)
Kết luận
Follow cơ bản sẽ là vậy. Còn thiếu xót gì mong các bạn comment góp ý. Nhiều code mình sửa đổi tên class để phù hợp với bài viết. Có gì sửa thiếu các bạn thông cảm.
All rights reserved