学swift也挺久了,但是版本一直在迭代,所以也就看看基础的,刚好这些天拿到了公司苹果机的使用权且swift也升级到3.0,那就认真地学习一下。
当我想写一个ios app时,我在想ios架构应该用什么架构,于是乎Google了一把--->MVVM。再加上之前学习RxAndroid的时候搜索到了一篇RxAcdroid和RxSwift的对比文章,所以就催生了这篇文章,顺便我自己也整理记录一下。欢迎交流学习。
1,RxAndroid和RxSwift是Rx系列的不同语言的实现,这里就不介绍了。
2,Retrofit和Moya两者也很相似,可以说Moya是Swift版的Retrofit。两者都要定义一个BaseUrl,然后定义方法(get,post等),路径,参数。不同方法的路径和参数不同。只是Retrofit不在同一个文件中进行设置。
3,mvp,和mvvm。mvvm是mvp的进一步的产物。据我个人认知android这边架构流行mvp,Swift这边架构流行mvvm,所以Android架构用的是mvp,Swift用的是mvvm。
首先来看下目录架构:
运行效果:
先从相似点入手:
1.Android和Swift都有Model,但是Android的Model是处理网络请求的,它的功能是提供数据。而Swift的Model则记录一些属性。在Android这边,记录属性的是Bean对象,所以Bean对应Model。
2.Presenter和ViewModel:Presenter处理业务逻辑,从Model中获取数据。ViewModel则两者都有。
Android Model
public class MainModelImpl implements MainContract.Model{
RetrofitService retrofitService;
public MainModelImpl() {
// Retrofit retrofit = new Retrofit.Builder() //生成实例
// .baseUrl("http://192.168.191.1:3000/") //基础url,会拼接NetService中的参数
// .addConverterFactory(GsonConverterFactory.create()) //使用Gson解析
// .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) //加入RxJava的适配器
// .build();
retrofitService = RetrofitServiceInstance.getInstance();
}
// 访问网络
@Override
public Observable loginAPP(String username, String password) {
return retrofitService.loginAPP(username,password)
.compose(RxHelper.handleResult());
}
// 自己构造数据,模拟访问
@Override
public Observable loginLocal(final String username, final String password) {
L.e("========>"+username);
L.e("========>"+password);
return Observable.create(new Observable.OnSubscribe() {
@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();
}
// 可以自己判断一些出现错误的情况
// subscriber.onError(new Exception("错误!"));
}
});
}
}
在这里用到了一些Rx的封装,是从鸿洋大神的微信公众号中引用过来的,这里贴一下原作者链接
原文连接
Android Presenter
public class MainPresenterImpl extends BasePresenter implements MainContract.Presenter {
MainContract.Model model;
public MainPresenterImpl() {
this.model = new MainModelImpl();
}
@Override
public void loginApp(String username, String password) {
// model.loginAPP(username, password)//这里我注释掉了需要网络的方法并隐藏了我个人的测试接口,
// 如果懂得后台语言的话,也可以自己写个接口
model.loginLocal(username, password)
.flatMap(new Func1>() {
@Override
public Observable call(final LoginBean loginBean) {
L.d("进行保存token的操作==>" + loginBean.getToken());
StringBuilder statue = new StringBuilder();
statue.append("该帐号的身份是:");
if (loginBean.getStatue().equals("0")) {
statue.append("管理员") ;
} else {
statue.append("普通用户") ;
}
return Observable.just(statue.toString());
}
})
.subscribeOn(Schedulers.io()) //发布者在后台线程中运行
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber() {
@Override
public void onCompleted() {
L.e("=========>onCompleted");
}
@Override
public void onError(Throwable e) {
getMyView().setResult("该帐号登录失败!");
L.e("=========>onError");
}
@Override
public void onNext(String s) {
L.e("=========>onNext");
getMyView().setResult(s);
}
});
}
}
Swift ViewModel
这里文章和源码有点不同,做了部分修改
import Foundation
import RxSwift
import Moya
import RxDataSources
class ViewModel {
private let provider = RxMoyaProvider()
func login(username:String,password:String) -> Observable {
return provider.request(.LoginAPP(username: username, password: password))
.mapJSON()
.mapObject(type: BaseModel.self)
.flatMap{ (value : BaseModel) -> Observable in
print("进行保存token的操作==>\(value.data!["token"]!)")
let str:String
guard "\(value.data!["statue"]!)" != "0" else{
return Observable.just("登录错误")
}
if "\(value.data!["statue"]!)" == "0"{
str = "该账号的身份是:管理员"
}else{
str = "该账号的身份是:普通用户"
}
return Observable.just(str)
}
}
func loginLocal(username:String,password:String) -> Observable {
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 in
print("进行保存token的操作==>\(value.data!["token"]!)")
let str:String
if "\(value.data!["statue"]!)" == "0"{
str = "该账号的身份是:管理员"
}else{
str = "该账号的身份是:普通用户"
}
return Observable.just(str)
}
}
}
3.View,在Android这边Activity充当这View的角色,在Swift中ViewController充当这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的生命周期,避免持续的引用,造成内存泄漏,这里了我没有贴出来,在源码查看。
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)//这里我注释了需要网络的方法接口,改用本地的
viewModel.loginLocal(username: tfUsername.text!, password: ftPassword.text!)
.asDriver(onErrorJustReturn: "")//帮你保证在UI线程中执行代码 等
.drive(lbResult.rx.text)
.addDisposableTo(disposeBag)//自动销毁相关的订阅
}
view这里Swift也用了Rx,在学swift的时候看到一种观点:ViewController 不应该更改数据。
这里我参考了这个观点。
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
服务端使用nodejs的Express框架编写的,这里进行了简单的判断,返回json数据
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"}});
}
});
源码在评论区,哈哈
参考文章:
https://github.com/devxoul/RxTodo/blob/master/README.md
http://blog.dianqk.org/2016/07/06/learn-rxtodo/
http://www.jianshu.com/p/178b6e24ba7e
我的公众号
公众号内容描述
zone7 公众号,与您一起学习分享后端知识。本公众号涉及的知识点将会有:nodejs,python,docker,kubernetes,后端架构等