学Flutter就和学iOS一样,先学基本语言语法的使用,再学习搭UI,iOS是OC和Swift,Flutter么就是Dart,语言学多了语法都差球不多,特别是这些新的语言,很多东西都是相似的,但是长时间不用容易忘记,与其每次去网上瞎jb找,不如自己总结一些,以后看自己写的东西就行了。文中个别结论是我自己总结出来的,不能保证准确性,看到的同学仅供参考。
本章提纲:
1.异步
2.泛型
3.库的使用
异步
Dart 库中包含许多返回 Future 或 Stream 对象的函数。这些函数在设置完耗时任务后, 就立即返回了,不会等待耗任务完成。 使用 async
和 await
关键字实现异步编程。 可以让你像编写同步代码一样实现异步操作。
Future
Future的字面意思就是在未来的某一个时刻让你拿到结果。
Future有两种状态:
- 状态一:未完成状态(uncompleted)
执行Future内部的操作时(在上面的案例中就是具体的网络请求过程,我们使用了延迟来模拟),我们称这个过程为未完成状态 - 状态二:完成状态(completed)
当Future内部的操作执行完成,通常会返回一个值,或者抛出一个异常。
这两种情况,我们都称Future为完成状态。
mport "dart:io";
main(List<String> args) {
print("main function start");
print(getNetworkData());
print("main function end");
}
Future<String> getNetworkData() {
return Future<String>(() {
sleep(Duration(seconds: 3));
return "network data";
});
}
// 打印
// main function start
// Instance of 'Future<String>'
// main function end
使用.then
来拿到Future的回调或者使用.catchError(error)
来捕获异常,whenComplete
指所有任务完成后的回调函数,类似于finally:
import "dart:io";
main(List<String> args) {
print("main function start");
var future = getNetworkData();
future.then((value) {
print(value);
}).catchError((error) { // 捕获出现异常时的情况
print(error);
}).whenComplete(() {
print("whenComplete")//所有任务完成后的回调函数;
});
print(future);
print("main function end");
}
Future<String> getNetworkData() {
return Future<String>(() {
sleep(Duration(seconds: 3));
future.then((value) {
print(value);
});
// 不再返回结果,而是出现异常
// return "network data";
// throw Exception("网络请求出现错误");
});
}
Future.then
链式调用避免了回调地狱问题,这个感觉就跟OC里的Block的链式调用很相似:
void main() {
login('tong', '1234556').then((id){
print('登录成功,用户id为${id}');
return getUserInfo(id);
}).then((userInfo){
print('获取用户信息成功,结果为${userInfo}');
return saveUserInfo(userInfo);
}).then((data){
print('保存用户信息成功');
});
Future.value(value)
可以直接获取一个完成的Future,该Future会直接调用then的回调函数:
main(List<String> args) {
print("main function start");
Future.value("哈哈哈").then((value) {
print(value);
});
print("main function end");
}
// 打印
// main function start
// main function end
// 哈哈哈
这块为啥直接获取到完成的Future,哈哈哈还是最后才打印呢,因为.then回调中的事件被加到eventLoop中了,简单说下自己对这个eventLoop的理解吧,这玩意儿就和iOS里的runLoop差不多,本质上都是一套事件循环机制,只不过iOS里的runLoop有主线程和子线程之分,但在Dart里我看文章里讲的似乎只有一条线程,Dart中的异步操作也并不像iOS中可以去新开一条线程去执行一些耗时操作,这里的Future其实是个非阻塞调用的感觉,就是说我当前线程要执行一些耗时操作,那我把调用完这个返回Future的耗时操作方法以后就不管了,“主线程”不会在这边等待结果,继续执行下边的操作,至于结果什么时候回来,我就通过.then去挂一个监听,等回调回来的时候,.then里面的方法会被加到当前这个eventLoop的队列里等待执行,所以上面的哈哈哈就是被放在eventLoop里,当main function end打印完后就打印哈哈哈,大概就这么个意思,这点像我之前接触了iOS的多线程机制还不太能理解单线程是怎么执行异步操作的,其实这个单线程也就是个“伪单线程”,只不过耗时操作在哪个线程执行交给CPU去调度了,我们只需要关注什么时候拿到结果就OK了,看完这块其实也加深了对OC中async和sync的理解,async严格意义上就是个非阻塞调用方式,只不过在OC里这种非阻塞调用的方式可以创建一条新的线程而已,目前是这么理解的,可能不一定对,后面讲到Dart其实也可以自己去创建一个新的线程,只不过他这个不单单是一条线程,他叫Isolate,包含了线程和evetLoop,后面再讲讲。
await、async
使用 await
可以让当前操作等待后面异步函数的执行结果,使用 await
时代码必须在使用 async
标记的函数中,使用async
标记的函数,必须返回一个Future对象,这个对象并不需要我们手动包装,直接返回正常的数据,返回值会默认被包装在一个Future对象中,这个特性好像Swift哪个语法点里也有,我记得可以自动包装来着,完了看Swift的时候再看看吧。
Future<String> getNetworkData() async {
var result = await Future.delayed(Duration(seconds: 3), () {
return "network data";
});
return "请求到的数据:" + result;//自动包装成Future
}
虽然异步函数可能会执行耗时的操作, 但它不会等待这些操作。 相反,异步函数只有在遇到第一个 await 表达式时才会执行。 也就是说,它返回一个 Future 对象, 仅在await表达式完成后才恢复执行。
Dart关于异步的基本用法目前就这些,还有些高级用法后面用到的时候再看吧。
try / catch / finally
和Java中的try/catch
类型,在OC也有捕获异常的机制,就是平时开发的时候没怎么用过:
try {
breedMoreLlamas();
} on OutOfLlamasException {
// 一个具体异常
buyMoreLlamas();
} on Exception catch (e) {
// 任意一个异常
print('Unknown exception: $e');
} catch (e) {
// 非具体类型
print('Something really unknown: $e');
} finally {
print('Something complete');
}
泛型
OC应该是从Swift2.0的时候开始为了更好的兼容Swift,引入了泛型,但我比较lowb,对OC泛型的使用还是仅仅停留在声明集合类型对象的时候,其他用法涉猎不多,慢慢的接触Swift和Dart的时候才接触到泛型的其他使用场景。
为什么使用泛型
在类型安全上通常需要泛型支持, 它的好处不仅仅是保证代码的正常运行:
- 正确指定泛型类型可以提高代码质量。
- 使用泛型可以减少重复的代码。
常见的用法就是用泛型来限制集合存储元素的类型,这样编译器可以进行静态类型检查,从数组中取出的对象也不再是Dart中应该是Dynamic或者Object类型,类似于OC里的id类型:
var names = List<String>();
names.addAll(['Seth', 'Kathy', 'Lars']);
names.add(42); // 错误
另外一个使用泛型的原因是减少重复的代码。 泛型可以在多种类型之间定义同一个实现, 同时还可以继续使用检查模式和静态分析工具提供的代码分析功能。 例如,假设你创建了一个用于缓存对象的接口:
abstract class ObjectCache {
Object getByKey(String key);
void setByKey(String key, Object value);
}
后来发现需要一个相同功能的字符串类型接口,因此又创建了另一个接口:
abstract class StringCache {
String getByKey(String key);
void setByKey(String key, String value);
}
后来,又发现需要一个相同功能的数字类型接口 … 这里你应该明白了。
泛型可以省去创建所有这些接口的麻烦。 通过创建一个带有泛型参数的接口,来代替上述接口:
abstract class Cache<T> {
T getByKey(String key);
void setByKey(String key, T value);
}
泛型类型的构造函数
稍微回顾一下Swift中使用泛型创建一个数组:
var shoppingList: [String] = ["Eggs", "Milk"]
Dart中的写法:
var names = <String>['Seth', 'Kathy', 'Lars'];
List<String> names1 = ['Seth', 'Kathy', 'Lars'];
运行时中的泛型集合
Dart 中泛型类型是 固化的,也就是说它们在运行时是携带着类型信息的。 例如, 在运行时检测集合的类型:
var names = List<String>();
names.addAll(['Seth', 'Kathy', 'Lars']);
print(names is List<String>); // true
提示: 相反,Java中的泛型会被 擦除 ,也就是说在运行时泛型类型参数的信息是不存在的。 在Java中,可以测试对象是否为 List 类型, 但无法测试它是否为 List<String> 。
限制泛型类型
使用泛型类型的时候,可以使用 extends
实现参数类型的限制,参数必须是extends
类或其子类类型。
class Location<T extends num> {
T x;
T y;
Location(this.x, this.y);
}
main(List<String> args) {
Location l2 = Location<int>(10, 20);
print(l2.x.runtimeType);
// 错误的写法, 类型必须继承自num
Location l3 = Location<String>('aaa', 'bbb');
}
使用泛型函数
最初,Dart仅仅在类中支持泛型。后来一种称为泛型方法的新语法允许在方法和函数中使用类型参数。具体用法到时候在网上再查查,这玩意儿对我来说就是个新鲜事物。
main(List<String> args) {
var names = ['why', 'kobe'];
var first = getFirst(names);
print('$first ${first.runtimeType}'); // why String
}
T getFirst<T>(List<T> ts) {
T tmp = ts[0];
return tmp;
}
这里的 first (<T>) 泛型可以在如下地方使用参数 T :
- 函数的返回值类型 (T).
- 参数的类型 (List<T>).
- 局部变量的类型 (T tmp).
库的使用
Dart中任何一个dart文件都是一个库,即使你没有用关键字library
声明。import
语句用来导入一个库,后面跟一个字符串形式的Uri来指定表示要引用的库,语法如下:
import 'dart:html';
使用相对路径导入的库,通常指自己项目中定义的其他dart文件:
import 'lib/student/student.dart';
Pub包管理工具管理的一些库,包括自己的配置以及一些第三方的库,通常使用前缀package:
//Pub包管理系统中有很多功能强大、实用的库,可以使用前缀 package:
import 'package:flutter/material.dart';
库文件中内容的显示和隐藏
如果希望只导入库中某些内容,或者刻意隐藏库里面某些内容,可以使用show
和hide关键字:
show关键字:可以显示某个成员(屏蔽其他)
hide关键字:可以隐藏某个成员(显示其他)
import 'lib/student/student.dart' show Student, Person;
import 'lib/student/student.dart' hide Person;
当库中内容和当前文件中的名字冲突的时候,可以使用as关键字来使用命名空间:
import 'lib/student/student.dart' as Stu;
Stu.Student s = Stu.Student();
库的定义
library关键字
通常在定义库时,我们可以使用library
关键字给库起一个名字:
library math;
export关键字
将不同的库分散在不同的dart文件中,通过export关键字暴露给外界使用:
library utils;
export "mathUtils.dart";
export "dateUtils.dart";
//使用时
//import "lib/utils.dart";
Dart主要语法基本告一段落了,现在去项目里看代码的时候应该会好很多了。
参考资料:
coderwhy-Flutter之搞定Dart系列
Dart编程语言中文网