前言
学了点 dart 且写了一段时间的 flutter 后,发现遇到大多数的错误都和null
有关系,
一个不小心if
语句就报null
错误了,为了避免这个错误,不得不写成下面这种形式
if( true == (boolVar ?? false)){
// balabala....
}
为何会这样呢?因为if(null == true)
它会报错的。而你一个不留神就可能导致写出这样的代码,你以为它不会变成null
当它某一个时刻变成了null
而真实的报错在重重wrapper
中丢失了原本的信息,这就导致debug
变成了一件非常痛苦的事情。
Dio 踩坑
最近写的代码埋雷了,如下它的作用是将Dio
请求内容转换为 Debug 记录, 最后通过interceptor
打印出 log 来。
String getResponseDebug(Response resp) {
if (resp == null) return 'getResponseDebug()->null';
//debug
var debug = getRequestDebug(resp.request);
debug += '\nresponse--> ${resp.statusCode} ${resp.statusMessage}';
if (resp.data is Map) {
debug += '\n\tdata-->\n${prettyJson(resp.data)}\n';
} else if (resp.data is List) {
var data = (resp.data as List);
var json = data.length >= 1 ? [data.first] : data;
debug += '\n\tdata-->\n${prettyJson(json)}\n';
} else {
debug += '\n\tdata-->\n${resp.data}\n';
}
if (resp.isRedirect) { // 请注意这一行!!!
debug += '\n\tredirects-->\n${prettyJson(resp.redirects)}\n';
}
return debug;
}
看上去没有啥问题的代码,却在我使用dio_http_cache进行缓存的时候出问题了。
为了定位这个问题,我还顺便弄清除了Dio
的interceptor
的机制,可谓是收获颇丰啊。
好家伙,进去isRedirect
属性一看
/// Whether this response is a redirect.
/// ** Attention **: Whether this field is available depends on whether the
/// implementation of the adapter supports it or not.
final bool isRedirect;
大大的attention
摆在哪里,可惜等我注意到它的时候问题都已经修好了。
下次是不是该留一个心眼,明明这么明显属性,居然会有null
的可能性。只能说明我太想当然了吧(too young,too simple)。
关于 null
最早接触null
的时候,就是 C 语言的NULL
空指针,它在 C 与 C++有着不同的区别。C 语言中它只是一个0
罢了,代表了一个指向地址0
的指针;而在 C++中,它才真的是null
(带类型的)。
所以,后面关于null
我都会把它当初 C 语言中的指针来理解。
而在dart
的language tutorial
中,就有这么一句话
Everything you can place in a variable is an object, and every object is an instance of a class. Even numbers, functions, and null are objects. All objects inherit from the Object class.
在dart
中,任何可以放在变量里面的都是一个Object
, 而这个Object
只是一个类的实例,所有的对象都是继承于Object
,甚至是数值,函数和null
。
//这里nullStr可以理解成一个指向String指针
//声明之后没有赋值它的值是一个null
String nullStr;
为了让nullStr
变得可以使用,我们需要手动让它指向一个对象,例如nullStr="Hello"
,这个时候nullStr
不在为null
可以安全的使用。
所有我可以理解dart
是一门纯粹面向对象语言,你可以发现任何类型都有toString()
这个方法,因为它是继承于Object
里面的方法,也因此你可以将null
赋值给任何一个类型的变量。
这样做的好处不言而喻,个人理解,它减少了程序员的心智负担,再也不用区分函数传参中的按值传递
和按引用传递
,可以将注意力集中在具体的业务上面。
但是实际情况可能不是我想的这样美好,例如下面这段代码
//exchange a and b, does it work?
void exchange(int a,int b){
var c = a;
a = b;
b = c;
}
void exchange2(int a){
a = 100;
}
事实上,它并不能正常工作,我甚至找不到一个办法来实现a,b
之间的交换。
但是下面这段代码可以正常工作
class Student {
String no;
String name;
Student({this.no, this.name});
String toString() {
return '{ no: $no, name: $name }';
}
}
void updateName(Student s, String newName) {
s.name = newName;
}
void main(List<String> args) {
var stu = Student(
no: '1001',
name: 'Jack Li',
);
print('Student: $stu');
updateName(stu, 'New Name');
print('Student: $stu');
}
/* output
Student: { no: 1001, name: Jack Li }
Student: { no: 1001, name: New Name }
*/
从上面的结果可以看到,转递过去的对象可以被修改其内部属性。所以我的理解是按引用传参,也就是函数updateName
的s
和stu
是指向同一个对象的,也就是持有这个对象的引用。如果在函数里面尝试对引用进行交换,则是无用的。因为交换的只是函数内部变量持有的引用,不会产生交换的效果。
对于如int
类型,在其它函数进行修改无非就是让它指向一个新的值罢了,并不会影响之前的那个值,所以它表现得就如同按值传递
这个行为了。
stackoverflow 上有关这个的讨论
- Swapping function in Dart, not swapping the original values
- What is the true meaning of pass-by-reference in modern languages like Dart?
Interceptors
关于 Dio 这个库,最舒服的一点就是它的interceptor
设计, 字面上理解就是拦截器。
通过它,可以实现在请求发送之前进行拦截,修改请求参数或者改变请求的流程直接进入response
。
还可以在数据返回的时候进行拦截,判断返回的数据是否异常等。
例如下面通过规定API返回类型,来统一处理API异常数据。
class ApiResponseInterceptor extends InterceptorsWrapper {
@override
Future onResponse(Response response) async {
//如果数据来自缓存,直接返回
if ((response.headers.value(DIO_CACHE_HEADER_KEY_DATA_SOURCE) ?? '') ==
'from_cache') {
return response;
}
var resp = ApiResponse.fromJson(response.data);
if (resp.code != 200) {
throw DioError(
request: response.request,
response: response,
error: resp,
);
}
response.data = resp.data ?? [];
return response;
}
}
之后的还可以拦截错误,类似catch
的效果。
可以说,有了interceptor
让Dio
库变得非常的好用。
而我在代码中使用的dio-http-cache
就是通过interceptor
来实现对请求进行缓存。
理解了这点,去看dio-http-cache
如何处理response
的数据
Response _buildResponse(
CacheObj obj, int statusCode, RequestOptions options) {
Headers headers;
if (null != obj.headers) {
headers = Headers.fromMap((Map<String, List<dynamic>>.from(
jsonDecode(utf8.decode(obj.headers))))
.map((k, v) => MapEntry(k, List<String>.from(v))));
}
if (null == headers) {
headers = Headers();
options.headers.forEach((k, v) => headers.add(k, v ?? ""));
}
// add flag
headers.add(DIO_CACHE_HEADER_KEY_DATA_SOURCE, "from_cache");
dynamic data = obj.content;
if (options.responseType != ResponseType.bytes) {
data = jsonDecode(utf8.decode(data));
}
return Response(
data: data,
headers: headers,
extra: options.extra..remove(DIO_CACHE_KEY_TRY_CACHE),
statusCode: statusCode ?? 200);
}
很容易发现最后返回Response
的时候并没有处理isRedirect
这个属性,因此这个属性自然而然变成了null
。
最后
经过这番挖掘,对dart
中的null
有了多一点的认识,也意识到平时不认真看文档后果的严重性;更重要的是,发现了阅读别人代码的好处,就是可以更加的理解其实现原理,更好的避免坑。