學(xué)swift也挺久了垦写,但是版本一直在迭代古毛,所以也就看看基礎(chǔ)的钦购,剛好這些天拿到了公司蘋果機(jī)的使用權(quán)且swift也升級(jí)到3.0,那就認(rèn)真地學(xué)習(xí)一下褂萧。
當(dāng)我想寫一個(gè)ios app時(shí)押桃,我在想ios架構(gòu)應(yīng)該用什么架構(gòu),于是乎Google了一把--->MVVM导犹。再加上之前學(xué)習(xí)RxAndroid的時(shí)候搜索到了一篇RxAcdroid和RxSwift的對(duì)比文章唱凯,所以就催生了這篇文章,順便我自己也整理記錄一下谎痢。歡迎交流學(xué)習(xí)磕昼。
1,RxAndroid和RxSwift是Rx系列的不同語言的實(shí)現(xiàn)节猿,這里就不介紹了票从。
2,Retrofit和Moya兩者也很相似滨嘱,可以說Moya是Swift版的Retrofit峰鄙。兩者都要定義一個(gè)BaseUrl,然后定義方法(get太雨,post等)吟榴,路徑,參數(shù)囊扳。不同方法的路徑和參數(shù)不同吩翻。只是Retrofit不在同一個(gè)文件中進(jìn)行設(shè)置。
3锥咸,mvp狭瞎,和mvvm。mvvm是mvp的進(jìn)一步的產(chǎn)物她君。據(jù)我個(gè)人認(rèn)知android這邊架構(gòu)流行mvp脚作,Swift這邊架構(gòu)流行mvvm,所以Android架構(gòu)用的是mvp缔刹,Swift用的是mvvm球涛。
首先來看下目錄架構(gòu):
運(yùn)行效果:
先從相似點(diǎn)入手:
1.Android和Swift都有Model,但是Android的Model是處理網(wǎng)絡(luò)請(qǐng)求的校镐,它的功能是提供數(shù)據(jù)亿扁。而Swift的Model則記錄一些屬性。在Android這邊鸟廓,記錄屬性的是Bean對(duì)象从祝,所以Bean對(duì)應(yīng)Model襟己。
2.Presenter和ViewModel:Presenter處理業(yè)務(wù)邏輯,從Model中獲取數(shù)據(jù)牍陌。ViewModel則兩者都有擎浴。
Android Model
public class MainModelImpl implements MainContract.Model{
RetrofitService retrofitService;
public MainModelImpl() {
// Retrofit retrofit = new Retrofit.Builder() //生成實(shí)例
// .baseUrl("http://192.168.191.1:3000/") //基礎(chǔ)url,會(huì)拼接NetService中的參數(shù)
// .addConverterFactory(GsonConverterFactory.create()) //使用Gson解析
// .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) //加入RxJava的適配器
// .build();
retrofitService = RetrofitServiceInstance.getInstance();
}
// 訪問網(wǎng)絡(luò)
@Override
public Observable<LoginBean> loginAPP(String username, String password) {
return retrofitService.loginAPP(username,password)
.compose(RxHelper.<LoginBean>handleResult());
}
// 自己構(gòu)造數(shù)據(jù)毒涧,模擬訪問
@Override
public Observable<LoginBean> loginLocal(final String username, final String password) {
L.e("========>"+username);
L.e("========>"+password);
return Observable.create(new Observable.OnSubscribe<LoginBean>() {
@Override
public void call(Subscriber<? super LoginBean> subscriber) {
if (username.equals("123456") && password.equals("123456")) {
subscriber.onNext(new LoginBean("this token is local", "0"));
subscriber.onCompleted();
} else {
subscriber.onNext(new LoginBean("this token is local", "1"));
subscriber.onCompleted();
}
// 可以自己判斷一些出現(xiàn)錯(cuò)誤的情況
// subscriber.onError(new Exception("錯(cuò)誤贮预!"));
}
});
}
}
在這里用到了一些Rx的封裝,是從鴻洋大神的微信公眾號(hào)中引用過來的契讲,這里貼一下原作者鏈接
原文連接
Android Presenter
public class MainPresenterImpl extends BasePresenter<MainContract.View> implements MainContract.Presenter {
MainContract.Model model;
public MainPresenterImpl() {
this.model = new MainModelImpl();
}
@Override
public void loginApp(String username, String password) {
// model.loginAPP(username, password)//這里我注釋掉了需要網(wǎng)絡(luò)的方法并隱藏了我個(gè)人的測(cè)試接口仿吞,
// 如果懂得后臺(tái)語言的話,也可以自己寫個(gè)接口
model.loginLocal(username, password)
.flatMap(new Func1<LoginBean, Observable<String>>() {
@Override
public Observable<String> call(final LoginBean loginBean) {
L.d("進(jìn)行保存token的操作==>" + loginBean.getToken());
StringBuilder statue = new StringBuilder();
statue.append("該帳號(hào)的身份是:");
if (loginBean.getStatue().equals("0")) {
statue.append("管理員") ;
} else {
statue.append("普通用戶") ;
}
return Observable.just(statue.toString());
}
})
.subscribeOn(Schedulers.io()) //發(fā)布者在后臺(tái)線程中運(yùn)行
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<String>() {
@Override
public void onCompleted() {
L.e("=========>onCompleted");
}
@Override
public void onError(Throwable e) {
getMyView().setResult("該帳號(hào)登錄失敿衿唤冈!");
L.e("=========>onError");
}
@Override
public void onNext(String s) {
L.e("=========>onNext");
getMyView().setResult(s);
}
});
}
}
Swift ViewModel
這里文章和源碼有點(diǎn)不同,做了部分修改
import Foundation
import RxSwift
import Moya
import RxDataSources
class ViewModel {
private let provider = RxMoyaProvider<MoyaAPI>()
func login(username:String,password:String) -> Observable<String> {
return provider.request(.LoginAPP(username: username, password: password))
.mapJSON()
.mapObject(type: BaseModel.self)
.flatMap{ (value : BaseModel) -> Observable<String> in
print("進(jìn)行保存token的操作==>\(value.data!["token"]!)")
let str:String
guard "\(value.data!["statue"]!)" != "0" else{
return Observable.just("登錄錯(cuò)誤")
}
if "\(value.data!["statue"]!)" == "0"{
str = "該賬號(hào)的身份是:管理員"
}else{
str = "該賬號(hào)的身份是:普通用戶"
}
return Observable.just(str)
}
}
func loginLocal(username:String,password:String) -> Observable<String> {
return Observable.create({ (subscribe) -> Disposable in
if username == "123456" && password == "123456"{
let basemodel = BaseModel(code: "200", message: "success", data: ["token" : "this token is local" as AnyObject,"statue":"0" as AnyObject])
subscribe.onNext(basemodel)
}else{
let basemodel = BaseModel(code: "200", message: "success", data: ["token" : "this token is local" as AnyObject,"statue":"1" as AnyObject])
subscribe.onNext(basemodel)
}
subscribe.onCompleted()
return Disposables.create()
})
.flatMap{ (value : BaseModel) -> Observable<String> in
print("進(jìn)行保存token的操作==>\(value.data!["token"]!)")
let str:String
if "\(value.data!["statue"]!)" == "0"{
str = "該賬號(hào)的身份是:管理員"
}else{
str = "該賬號(hào)的身份是:普通用戶"
}
return Observable.just(str)
}
}
}
3.View银伟,在Android這邊Activity充當(dāng)這View的角色你虹,在Swift中ViewController充當(dāng)這View的角色。
Android
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
presenter = new MainPresenterImpl();
presenter.attachView(this);//綁定view
init();
event();
}
private void event() {
btLogin.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
presenter.loginApp(etUsername.getText().toString(), etPassword.getText().toString());
}
});
}
@Override
public void setResult(String result) {
tvResult.setText(result);
}
在Android這邊枣申,Presenter一般需要綁定Activity的生命周期售葡,避免持續(xù)的引用,造成內(nèi)存泄漏忠藤,這里了我沒有貼出來挟伙,在源碼查看。
Swift
let disposeBag = DisposeBag()
let viewModel = ViewModel()
@IBOutlet weak var tfUsername: UITextField!
@IBOutlet weak var ftPassword: UITextField!
@IBOutlet weak var lbResult: UILabel!
@IBAction func btClick(_ sender: UIButton) {
// viewModel.loginLocal(username: tfUsername.text, password: ftPassword.text)//這里我注釋了需要網(wǎng)絡(luò)的方法接口模孩,改用本地的
viewModel.loginLocal(username: tfUsername.text!, password: ftPassword.text!)
.asDriver(onErrorJustReturn: "")//幫你保證在UI線程中執(zhí)行代碼 等
.drive(lbResult.rx.text)
.addDisposableTo(disposeBag)//自動(dòng)銷毀相關(guān)的訂閱
}
view這里Swift也用了Rx,在學(xué)swift的時(shí)候看到一種觀點(diǎn):ViewController 不應(yīng)該更改數(shù)據(jù)尖阔。
這里我參考了這個(gè)觀點(diǎn)。
Android Gradle
compile 'com.android.support:appcompat-v7:24.2.1'
compile 'com.squareup.retrofit:retrofit:2.0.0-beta2'
compile 'com.squareup.retrofit:adapter-rxjava:2.0.0-beta2'
compile 'com.squareup.retrofit:converter-gson:2.0.0-beta2'
compile 'io.reactivex:rxandroid:1.2.0'
compile 'io.reactivex:rxjava:1.1.0'
Swift Cocopods
use_frameworks!
target 'Login_rxswift' do
pod 'RxSwift', '~> 3.0.0-beta.1'
pod 'RxCocoa', '~> 3.0.0-beta.1'
pod 'ObjectMapper', '~> 2.2'
pod 'Moya/RxSwift', git: 'https://github.com/Moya/Moya.git', tag: '8.0.0-beta.4'
pod 'RxDataSources', '~> 1.0'
pod 'SnapKit', '~> 3.0.2'
end
post_install do |installer|
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
config.build_settings['SWIFT_VERSION'] = '3.0'
config.build_settings['MACOSX_DEPLOYMENT_TARGET'] = '10.10'
end
end
end
服務(wù)端使用nodejs的Express框架編寫的榨咐,這里進(jìn)行了簡(jiǎn)單的判斷介却,返回json數(shù)據(jù)
router.post('/login2', function (req, res) {
if (req.body.username == "123456" && req.body.password == "123456") {
res.json({"code": "200", "message": "success", "data": {"token": "this token is from server", "statue": "0"}});
} else {
res.json({"code": "200", "message": "success", "data": {"token": "this token is from server", "statue": "1"}});
}
});
源碼在評(píng)論區(qū),哈哈
參考文章:
https://github.com/devxoul/RxTodo/blob/master/README.md
http://blog.dianqk.org/2016/07/06/learn-rxtodo/
http://www.reibang.com/p/178b6e24ba7e
我的公眾號(hào)
公眾號(hào)內(nèi)容描述
zone7 公眾號(hào)块茁,與您一起學(xué)習(xí)分享后端知識(shí)齿坷。本公眾號(hào)涉及的知識(shí)點(diǎn)將會(huì)有:nodejs,python数焊,docker永淌,kubernetes,后端架構(gòu)等