iOS vs Flutter(语法篇)

1 篇文章 0 订阅

iOS开发者入门Flutter

首先说一下,为什么要关心iOS和Flutter的区别问题。因为移动端开发的业务逻辑设计模式等是一致的,区别可能只在于使用的语言不同,实现逻辑的风格不同而已。所以这里我们先分析一下iOS和Flutter的区别到底有哪些,有利于我们更快地去入门。

生命周期:

页面加载的生命周期:

移动端开发首先要关注的一点肯定要了解一个页面加载的生命周期,就像了解iOS的viewcontroller的生命周期在UI绘制的场景中有多么重要:

iOS:

iOS的设计初衷就是MVC,所以iOS中的核心是Controller。每个Controller都有自己的生命周期:

//类的初始化方法
+ (void)initialize;
//对象初始化方法
- (instancetype)init;
//从归档初始化
- (instancetype)initWithCoder:(NSCoder *)coder;
//加载视图
-(void)loadView;
//将要加载视图
- (void)viewDidLoad;
//将要布局子视图
-(void)viewWillLayoutSubviews;
//已经布局子视图
-(void)viewDidLayoutSubviews;
//内存警告
- (void)didReceiveMemoryWarning;
//已经展示
-(void)viewDidAppear:(BOOL)animated;
//将要展示
-(void)viewWillAppear:(BOOL)animated;
//将要消失
-(void)viewWillDisappear:(BOOL)animated;
//已经消失
-(void)viewDidDisappear:(BOOL)animated;
//被释放
-(void)dealloc;
flutter:

这一点flutter则有所不同,flutter页面加载的核心则是build一棵渲染树的过程。

在这里插入图片描述

如图所示大体上它的生命周期可以分为三个过程:初始化,状态改变,销毁。每一次build都是页面的一次加载。

didUpdateWidget: Flutter中的Widget分为两种:stateful(状态可变)和stateless(状态不可变)。如果是stateful的widget需要实现setState方法。当调用了 setState 将 Widget 的状态被改变时 didUpdateWidget 会被调用,Flutter 会创建一个新的 Widget 来绑定这个 State,并在这个方法中传递旧的 Widget ,因此如果你想比对新旧 Widget 并且对 State 做一些调整,你可以用它,另外如果你的某些 Widget 上涉及到 controller 的变更,要么一定要在这个回调方法中移除旧的 controller 并创建新的 controller 监听。

dispose:某些情况下你的 Widget 被释放了,一个很经典的例子是 Navigator.pop 被调用,如果被释放的 Widget 中有一些监听或持久化的变量,你需要在 dispose 中进行释放。很重要的一个应用是在 Bloc 或 Stream 时在这个回调方法中去关闭 Stream。

可参考博客:https://segmentfault.com/a/1190000015211309

App 的生命周期

iOS中的APP的生命周期在appdelegate里设置,这里不多说。

而在flutter中如果我们想监听 App 级别的生命周期,可以通过向Binding 中添加一个???? Observer,同时要实现didChangeAppLifecycleState来监听指定事件的到来,并且最后还需要在 dispose 回调方法中移除这个监听。但限制是它在iOS平台智能监听三种状态。

class LifeCycleDemoState extends State<MyHomePage> with WidgetsBindingObserver {
     @override
     void initState() {
       super.initState();
       WidgetsBinding.instance.addObserver(this);
     }
     @override
     void didChangeAppLifecycleState(AppLifecycleState state) {
       super.didChangeAppLifecycleState(state);
       switch (state) {
         case AppLifecycleState.inactive:
           print('Application Lifecycle inactive');
           break;
         case AppLifecycleState.paused:
           print('Application Lifecycle paused');
           break;
         case AppLifecycleState.resumed:
           print('Application Lifecycle resumed');
           break;
         default:
           print('Application Lifecycle other');
       }
		}
    @override
    void dispose(){
      WidgetsBinding.instance.removeObserver(this);
      super.dispose();
    }
}

详细使用源码看这里:https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/widgets/binding.dart

布局:

Flutter的布局首先要建立一棵Widget树(分析一下UI图建立一棵控件树),根据树的结构层层嵌套Widget控件。margin和padding等的约束布局设置则有点类似css。

UIView => Widgets

iOS:UIView可变,UIView发生改变本质上是间接调用(官方建议不要显式调用,因为这开销很大)了LayoutSubviews方法进行布局上的重构,drawRect进行显示上的重构,updateConstrains进行约束上的重构。三者的更新会在每次RunLoop之后进行update cycle操作(也可以调用layoutIfNeeded等方法进行立刻更新)。(推荐一篇讲述布局不错的文章:https://juejin.im/post/5a951c655188257a804abf94)

Flutter:Widget不可变,也正是由于不可变性,使得Widget比起UIView来说更轻量,因为它不是控件,只是UI的描述。

  • widget的分为Stateful(状态可变)和Stateless(状态不可变)两种,Stateful本质上也是不可变的,它只是将状态拆分到State中去管理。State会存储Widget的状态数据,并在widget树重建时携带着它,因此状态不会丢失。
  • 即使一个widget是有状态的,包含它的父亲的widget也可以是无状态的,只要父widget本身不响应这些变化即可。
  • 在UI更新这一点上感觉flutter会比iOS要麻烦一点,例如点击按钮导致Text的文字发生变化,iOS只需要简单的target-action就可以,而flutter则需要将无状态的text套在一个StatefulWidget父类里面才可以。
class SampleApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'My App',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: SampleAppPage(),
    );
  }
}

class SampleAppPage extends StatefulWidget {
  SampleAppPage({Key key}) : super(key: key);

  @override
  _SampleAppPageState createState() => _SampleAppPageState();
}

class _SampleAppPageState extends State<SampleAppPage> {
  // Default placeholder text
  String textToShow = "Flutter";
  void _updateText() {
    setState(() {
      // update the text
      textToShow = "Text is changed!";
    });
  }
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("My App"),
      ),
      body: Center(child: Text(textToShow)),
      floatingActionButton: FloatingActionButton(
        onPressed: _updateText,
        child: Icon(Icons.update),
      ),
    );
  }
}
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    UIButton *button = [UIButton buttonWithType:UIButtonTypeSystem];
    button.frame = CGRectMake(300, 600, 80, 40);
    [button setTitle:@"点击" forState:UIControlStateNormal];
    [button addTarget:self action:@selector(onClick) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:button];
    self.textField = [[UITextField alloc] initWithFrame:CGRectMake(100, 300, 200, 60)];
    self.textField.text = @"iOS";
    [self.view addSubview:self.textField];
}

-(void) onClick
{
    self.textField.text = @"text has changed!";
}
  • 在布局方面,iOS分为xib和storyboard还有代码布局的方式,flutter比较统一,通过建立一棵widget树进行布局,同时还有类似Center,Column等Widget进行布局。添加约束的话,iOS通过autolayout来添加约束,而flutter则是通过使用container容器添加padding,margin等属性来添加约束。这点非常类似html和css的布局原理。如下所示:

在这里插入图片描述

  • 父子视图的移除方面,比如说举个常见的例子,一个bool值的改变导致两个视图的切换,iOS需要在父view中调用addSubview()或在子view中调用removeFromSuperView() 来动态添加或移除子view,如果涉及到一些传值问题可能还需要通过观察者模式来观察bool值的更新。而在flutter中则需要向父widget中传入一个返回widget的函数,并用bool来控制子widget的创建。

    class SampleApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Sample App',
          theme: ThemeData(
            primarySwatch: Colors.blue,
          ),
          home: SampleAppPage(),
        );
      }
    }
    
    class SampleAppPage extends StatefulWidget {
      SampleAppPage({Key key}) : super(key: key);
    
      @override
      _SampleAppPageState createState() => _SampleAppPageState();
    }
    
    class _SampleAppPageState extends State<SampleAppPage> {
      bool toggle = true;
      void _toggle() {
        setState(() {
          toggle = !toggle;
        });
      }
    
      _getToggleChild() {
        if (toggle) {
          return Text('View One');
        } else {
          return CupertinoButton(
            onPressed: () {},
            child: Text('View Two'),
          );
        }
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text("Sample App"),
          ),
          body: Center(
            child: _getToggleChild(),
          ),
          floatingActionButton: FloatingActionButton(
            onPressed: _toggle,
            tooltip: 'Update Text',
            child: Icon(Icons.update),
          ),
        );
      }
    }
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        // Do any additional setup after loading the view.
        UIButton *button = [UIButton buttonWithType:UIButtonTypeSystem];
        button.frame = CGRectMake(350, 750, 50, 30);
        [button setTitle:@"切换" forState:UIControlStateNormal];
        [button addTarget:self action:@selector(onClick) forControlEvents:UIControlEventTouchUpInside];
        [self.view addSubview:button];
        self.textField = [[UITextField alloc] initWithFrame:CGRectMake(100, 300, 200, 60)];
        self.textField.text = @"View 1";
        [self.view addSubview: self.textField];
        self.button2 = [UIButton buttonWithType:UIButtonTypeSystem];
        self.button2.frame = CGRectMake(100, 300, 200, 60);
        [self.button2 setTitle:@"View 2" forState:UIControlStateNormal];
    }
    
    -(void) onClick
    {
        if (self.textField.superview) {
            [self.textField removeFromSuperview];
            [self.view addSubview:self.button2];
        }
        else
        {
            [self.button2 removeFromSuperview];
            [self.view addSubview:self.textField];
        }
    
    }
    

导航

UIViewController => Scaffold

Flutter中没有专门用来管理视图而且类似那种和View一对一的Controller类。有类似的Scaffold,其包含控制器的appBar,也可以通过body设置一个widget来做其视图。

页面跳转

iOS有UINavigationController栈进行push,pop操作,其并不负责显示,而是负责各个页面跳转。或者使用模态视图。

同时注意iOS通过navigationController导航时要记得添加NavigationController,可在stroyboard设置,如果代码的话添加如下:

ViewController *vc = [ViewController new];
UINavigationController *navigation = [[UINavigationController alloc] initWithRootViewController:vc];
self.window.rootViewController = navigation;
[self.window makeKeyWindow];
// --------  ViewController  --------
-(NextViewController *)next
{
    if (!_next)
    {
        _next = [NextViewController new];
    }
    return _next;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    self.view.backgroundColor = [UIColor whiteColor];
    UIButton *button = [UIButton buttonWithType:UIButtonTypeSystem];
    button.frame = CGRectMake(350, 750, 50, 30);
    [button setTitle:@"切换" forState:UIControlStateNormal];
    [button addTarget:self action:@selector(onClick) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:button];
}

-(void) onClick
{
    [self.navigationController pushViewController:self.next animated:YES];
    // [self presentViewController:self.next animated:YES completion:nil];
}
// --------  NextViewController  --------
- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor whiteColor];
    // Do any additional setup after loading the view.
    UIButton *button = [UIButton buttonWithType:UIButtonTypeSystem];
    button.frame = CGRectMake(350, 750, 50, 30);
    [button setTitle:@"返回" forState:UIControlStateNormal];
    [button addTarget:self action:@selector(onClick) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:button];
}

-(void) onClick
{
    [self.navigationController popViewControllerAnimated:YES];
    // [self dismissViewControllerAnimated:YES completion:nil];
}

Flutter中可以将MaterialApp理解为iOS的导航控制器,其包含一个navigationBar以及导航栈,这点和iOS是一样的。

Flutter中使用了 NavigatorRoutes。一个路由是 App 中“屏幕”或“页面”的抽象,而一个 Navigator 是管理多个路由的 widget。可以粗略地把一个路由对应到一个 UIViewController。Navigator 的工作原理和 iOS 中 UINavigationController 非常相似,当你想跳转到新页面或者从新页面返回时,它可以 push()pop() 路由。

在页面之间跳转,有如下选择:

  • 具体指定一个由路由名构成的 Map。(MaterialApp)
  • 直接跳转到一个路由。(WidgetApp)

下面是构建一个 Map 的例子:

void main() {
  runApp(MaterialApp(
    home: MyAppHome(), // becomes the route named '/'
    routes: <String, WidgetBuilder> {
      '/a': (BuildContext context) => MyPage(title: 'page A'),
      '/b': (BuildContext context) => MyPage(title: 'page B'),
      '/c': (BuildContext context) => MyPage(title: 'page C'),
    },
  ));
}

通过把路由的名字 push 给一个 Navigator 来跳转:

Navigator.of(context).pushNamed('/b');
页面传值

iOS中页面传值正向直接通过属性传输即可,反向的话可以通过Block,delegate,通知等方式进行传值。

而在Flutter中反向传值就简单了很多,举个例子,要跳转到“位置”路由来让用户选择一个地点,可能要这么做:

Map coordinates = await Navigator.of(context).pushNamed('/location');

之后,在 location 路由中,一旦用户选择了地点,携带结果一起 pop() 出栈:

Navigator.of(context).pop({"lat":43.821757,"long":-79.226392});

多线程

RunLoop VS EventLoop

说起Flutter的多线程前先谈一下Flutter中的Event Loop。我们都知道前端开发框架大都是事件驱动的。意味着程序中必然存在事件循环和事件队列。事件循环会不断地从事件队列中获取和处理各种事件。

iOS:iOS中的类似机制是RunLoop,可以通过一个RunLoopObserver来实现对RunLoop的观察,当遇到Source0,Source1或者Timer等事件会进行处理。下面图可以很清晰的理解整个过程。(关于RunLoop的讲解推荐一篇很详细的文章:https://juejin.im/post/5add46606fb9a07abf721d1d)

在这里插入图片描述

Flutter:Flutter中的Event Loop和JavaScript的基本一样。循环中有两个队列。一个是微任务队列(MicroTask queue),一个是事件队列(Event queue)。这里类似iOS中存在set里面的Source0和Source1。

  • event queue: 主要是外部事件,负责处理I/O事件、绘制事件、手势事件、Timer等
  • microtask queue:可以自己向isolate内部添加事件,事件的优先级比event queue高。

在这里插入图片描述

两个队列是有优先级的,当isolate开始执行后,会先处理microtask的事件,当microtask队列中没有事件后,才会处理event队列中的事件,并按照这个顺序反复执行。当执行microtask的事件时会阻塞event队列,这会导致渲染响应手势等event事件响应延迟。为保证渲染和手势响应,所以应尽量将耗时操作放到event队列中。

线程和协程:

Flutter中的多线程和异步是比较难理解的一块。先看两个语法糖:

  1. Future:

    学过js的童鞋应该很容易理解这块了,Future就是js里的Promise,曾经js里面延时只能层层回调,这样很容易造成回调地狱。所以应运而生了Promise(Future),它是一个链式操作,可以通过追加then方法进行多层处理。

  2. async/await:用法完全沿用自js。

多协程:

我们拆开来看,首先对于异步操作这块,flutter基本上是沿用ES6的async和await这些异步语法糖以及Future。这里涉及到一个和传统意义不一样的概念——协程(https://www.itcodemonkey.com/article/4620.html这篇漫画讲的比较生动,https://www.zhihu.com/question/308641794讲解为什么协程比线程要好),最早接触是在python里有遇到过,在Flutter中,执行到async则表示进入一个协程,会同步执行async的代码块。当执行到await时,则表示有任务需要等待,CPU则去调度执行其他IO。过一段时间CPU会轮询一次查看某个协程是否任务已经处理完成,有返回结果可以被继续执行,如果可以被继续执行的话,则会沿着上次离开时指针指向的位置继续执行。也就是await标志的位置。

iOS中有没有类似的实现呢,其实是有的,个人感觉是串行队列中的dispatch_async操作,遇到耗时操作也不会阻塞,而是放到事件队列中等待当前操作执行完再继续执行。Flutter在当执行到await时,保存当前的上下文,并将当前位置标记为待处理任务,用一个指针指向当前位置,并将待处理任务放入当前线程的队列中。在每个事件循环时都去询问这个任务,如果需要进行处理,就恢复上下文进行任务处理。

多线程:

而async和await是沿用自js的,但有一点要注意的是,js是脚本语言,所以必须是单线程的,到这里就足够了。而flutter是手机框架,很可能我们要进行很耗时的IO操作,这仅仅凭异步是解决不了的,必须要多线程。这里flutter引入了新的方案,叫做isolate。

但isolate和普通的Thread还不同,它具有独立的内存,isolate间的通信通过port来实现,这个port消息传递的过程是异步的。实例化一个isolate的过程也就是实例化isolate这个结构体、在堆中分配线程内存,配置port。

感觉从操作来看其实isolate更像是进程,而实际async则更像是线程操作。

loadData() async {
    // 通过spawn新建一个isolate,并绑定静态方法
    ReceivePort receivePort =ReceivePort();
    await Isolate.spawn(dataLoader, receivePort.sendPort);
    
    // 获取新isolate的监听port
    SendPort sendPort = await receivePort.first;
    // 调用sendReceive自定义方法
    List dataList = await sendReceive(sendPort, 'https://jsonplaceholder.typicode.com/posts');
    print('dataList $dataList');
}

// isolate的绑定方法
static dataLoader(SendPort sendPort) async{
    // 创建监听port,并将sendPort传给外界用来调用
    ReceivePort receivePort =ReceivePort();
    sendPort.send(receivePort.sendPort);
    
    // 监听外界调用
    await for (var msg in receivePort) {
      String requestURL =msg[0];
      SendPort callbackPort =msg[1];
    
      Client client = Client();
      Response response = await client.get(requestURL);
      List dataList = json.decode(response.body);
      // 回调返回值给调用者
      callbackPort.send(dataList);
    }    
}

// 创建自己的监听port,并且向新isolate发送消息
Future sendReceive(SendPort sendPort, String url) {
    ReceivePort receivePort =ReceivePort();
    sendPort.send([url, receivePort.sendPort]);
    // 接收到返回值,返回给调用者
    return receivePort.first;
}

(代码引用自https://lequ7.com/2019/04/26/richang/shen-ru-li-jie-Flutter-duo-xian-cheng/)

网络请求和数据解析

这点不做详述,理解了Dart的多线程以及网络请求的基本原理也很容易搞懂这块(https://flutterchina.club/networking/)。

总结

文章不从性能进行分析,而是仅仅从语法方面进行入门比对。其实如果做过Web前端的童鞋学期Flutter来说应该会比较简单,因为Flutter中的Widget布局方式完全类似于html和css。而Dart语言本身有非常像js,尤其是ES6特性引入很多语法糖后的js,这些语法糖大大简化了代码的复杂度。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值