Winter

宏愿纵未了 奋斗总不太晚

0%

函数

可选参数

Dart 的可选参数要用方括号,并在类型后面添加问号

1
2
3
4
5
6
7
String fullName(String first, String last, [String? title]) { 
  if (title != null) {
    return '$title $first %% %%$last';
  } else {
    return '$first $last';
  }
}

默认值

1
2
3
4
5
6
7
8

bool withinTolerance(int value, [int min = 0, int max = 10]) {
  return min <= value && value <= max;
}

// 调用
withinTolerance(9, 7, 11) // true
withinTolerance(9, 7) // true

命名参数

可选参数你要记住这几个参数是什么含义,但是过了一段时间你可能就不知道了,所以有了命名参数
命名参数用大括号,在调用时必须使用参数名称

1
2
3
4
5
6
7
8
9
10

bool withinTolerance(int value, {int min = 0, int max = 10}) {
  return min <= value && value <= max;
}



// 调用, 不需要管顺序
withinTolerance(9, min: 7, max: 11) // true
withinTolerance(9, max: 11, min: 7) // true

命名参数默认也是可选的,所以也可以这样调用

1
2
3
4
withinTolerance(5)           // true
withinTolerance(15)          // false
withinTolerance(5, min: 7)   // false
withinTolerance(15, max: 20) // true

必需参数并命名

可以用大括号配合   required 关键字

1
2
3
4
5
6
7
8

bool withinTolerance({
  required int value,
  int min = 0,
  int max = 10,
}) {
  return min <= value && value <= max;
}

箭头函数

1
2
3
4
5
6
7
8

int add(int a, int b) {
    return a + b;
}


// 箭头函数形式,箭头代替了 大括号和主体,省略了 return, 如果有多行,那么就不能用箭头函数
int add(int a, int b) => a + b;

构造函数

如果没有指定构造函数, Dart 会提供一个默认不带参数的构造函数。

1
2
3
4
5

class Address {
    var value = '';
}

相当于这样写:

1
2
3
4
5
6

class Address {
    Address();
    var value = '';
}

自定义构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14

class User {
  User(int id, String name) {
    this.id = id;
    this.name = name;
  }
  int id = 0;
  String name = '';
  // ...
}

final user = User(42, 'Ray');
print(user);

可以简短一点

1
2
3

User(this.id, this.name);

命名构造函数

命名构造函数的模式:

1
ClassName.identifierName()

例如,假设您希望拥有一个具有预设 ID 和名称的匿名用户。您可以通过创建命名构造函数来做到这一点。

1
2
3
4
5
6
7
8
9

User.anonymous() {
  id = 0;
  name = 'anonymous';
}
// 使用
final anonymousUser = User.anonymous();
print(anonymousUser);

转发构造函数

因为 User 本来有一个自定义构造函数可以传 id 和 name,那么 anonymous 这个命名构造函数可以调用这个自定义的构造函数,会更方便一点:

1
2
3

User.anonymous() : this(0, 'anonymous');

还可以结合命名参数做一次简化

1
2
3
4
5
6
7
8
9
10
11
12
13
14

class User {

  // unnamed constructor
  User({this.id = 0, this.name = 'anonymous'});

  // named constructor
  User.anonymous() : this();

  int id;

  String name;
}

初始化列表

如果你不希望其他人改掉 id 和 name 属性,那么就要改成私有属性,加个 _ 就是私有属性了(在同一个文件还是可以访问),: 后面的就是初始化列表

1
User({int id = 0, String name = 'anonymous'}) : _id = id, _name = name;

这个构造函数也是有主体的,会指定初始化列表的内容再指定主体

1
2
3
4

User({int id = 0, String name = 'anonymous'}) : _id = id, _name = name {
  print('User name is $_name'); // 是有值的
}

常量构造函数

如果你不希望其他人改掉 id 和 name 属性,除了改成私有属性,还可以让属性变为不可变,用 finalconst

但是,由于编译器直到运行时才知道属性值是什么,因此唯一的选择是使用 final

1
2
final String _name;
final int _id;

让类不可变,在构造函数添加 const 可以保证所有实例都是常量

1
2
3
4
5
6
7
8
9
const User({int id = 0, String name = 'anonymous'}) : _id = id, _name = name;

const User.anonymous() : this();

// 下面两个 user 和 user 里面的属性都是不可变的,所以创建多少次都是同一个对象。
const user = User(id: 42, name: 'Ray');
const user1 = User(id: 42, name: 'Ray');
print("${identical(user, user1)}");
const anonymousUser = User.anonymous();

工厂构造函数

上面的构造函数只能创建类分身的实例,但是工厂构造函数还可以创建子类的实例。如果想要对使用类的代码隐藏类的实现细节时,非常有用。

工厂构造函数以 factory 开头并返回类类型对象的方法。

最常见的使用工厂构造函数来实现

1
2
3
4
5
6
7
8
9
factory User.fromJson(Map<String, Object> json) {
  final userId = json['id'] as int;
  final userName = json['name'] as String;
  return User(id: userId, name: userName);
}

// 使用
final map = {'id': 10, 'name': 'Sandra'};
final sandra = User.fromJson(map);

静态成员

static 关键字,放在成员变量或者方法前面就是类方法和类变量。

1
2
3
4
5
6
7
8
9
10
11
class SomeClass {

  static int myProperty = 0;
  static void myMethod() {
    print('Hello, Dart!');
  }
}

// 访问
final value = SomeClass.myProperty;
SomeClass.myMethod();

颜色代码实例:

1
2
3
4
5
class Colors {
  static const int red = 0xFFD13F13;
  static const int purple = 0xFF8107D9;
  static const int blue = 0xFF1432C9;
}

单例

1
2
3
4
5
6
7
class MySingleton {
  MySingleton._();
  static final MySingleton instance = MySingleton._();
}

// 访问单例
final MySingleton = Mysingleton.instance;

还可以使用工厂构造函数实现单例

1
2
3
4
5
6
class MySingleton {
  MySingleton._();
  static final MySingleton _instance = MySingleton._();
  factory MySingleton() => _instance;
}

Null

nullNull 类的唯一实例。用 null 调用其他没有实现的方法会报错。

可选类型

1
2
3
4
5
6
7
8
9
10
11

int? myInt = null;

double? myDouble = null;

bool? myBool = null;

String? myString = null;

User? myUser = null;

?? 和 ??=

?? 是一个条件操作符,??= 是一个赋值操作符。

1
2
3

String a = b ?? 'hello';
b ??= 'hellol'; // 如果 b 为空,那么就将 hello 的值赋给 b

..

.. 是级联运算符,它允许您在同一对象上调用多个方法或设置多个属性。如果您知道该对象不可为空,则可以使用级联运算符:

1
2
3
4
5
6
class User {
    String? name;
    int? id;
}

User user = User()..name = 'Ray'..id = 42;

如果对象是可选对象,User? user; 那么久可以使用 ?..

1
user?..name = 'Ray'?..id = 42;

Late 关键字

有点像 Swift 的 lazy 懒加载。

您有一个名为 _secretNumber 的不可为空字段。您希望根据 _calculateSecret 实例方法中复杂算法的返回值来初始化它。但你有一个问题,因为 Dart 不允许你在初始化期间访问实例方法。这可以用 late 关键字来解决这个问题。

1
late final int _secretNumber = _calculateSecret();

使用 late 意味着 Dart 不会立即初始化变量。它仅在您第一次访问时对其进行初始化。这也称为延迟初始化。
也能这样使用

1
2
3
4
5
6
7
8
9
10
11
class User {

  User(this.name) {
    _secretNumber = _calculateSecret();

  }
 
  late final int _secretNumber;
  // ...
}

Abstract 抽象类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
abstract class Animal {

    bool isAlive = true;

    void eat();

    void move();

    @override
    String toString() {
return "I am a $runtimeType";
    }
}

抽象类的方法都必须实现,也能有具体的字段,比如 isAlive ,默认值为 true

具体子类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Platypus extends Animal {

  @override
  void eat() {
    // TODO: implement eat
  }

  @override
  void move() {
    // TODO: implement move
  }

  // 抽象子类自己的方法
  void layEggs() {

  }

}

使用抽象子类

1
2
3
4
5
6
final platypus = Platypus();
print(platypus.isAlive);
platypus.eat();
platypus.move();
platypus.layEggs();
print(platypus);

Interfaces 接口

接口可以隐藏具体实现细节,如果你需要一个接口中的功能,刚好你用的类实现了这个接口,那么直接用就行了,不需要知道这个类是怎么实现的

在 Dart 中接口没有关键字,可以使用任何类作为接口。由于只有字段和方法名称很重要,因此大多数接口都是由不包含逻辑的抽象类组成的。

抽象接口类

1
2
3
4
5
6
7
8
9
10
11
abstract class DataRepository {
double? fetchTemperature(string city);
}

// 实现接口
class FakeWebServer implements DataRepository {
  @override
  double? fetchTemperature(String city) {
    return 42.0;
  }
}

使用工厂构造函数,这样业务逻辑和实现细节可以分离

1
2
3
4
5
6
7
8
9
abstract class DataRepository {
// 未命名的构造函数
factory DataRepository() => FakeWebServer();
double? fetchTemperature(string city);
}

// 在业务中使用(这在编译器角度就不知道 repository 具体返回什么类型了)
final repository = DataRepository();
final temperature = repository.fetchTemperature('Manila');

Mixins 混入

假设有一群鸟,创建一个抽象类来代表它们

1
2
3
4
5
abstract class Bird {
    void fly();
    void layEggs();
}

然后创建一个子类

1
2
3
4
5
6
7
8
9
10
11
12
class Robin extends Bird {
  @override
  void fly() {
print('Swoosh swoosh');
  }

  @override
  void layEggs() {
print('Plop plop');
  }

}

注意,layEggs 和 Platypus 类中的 layEggs 是一样的实现,如果要改的话可能要改两遍,这是不太好的,如果 fly 的实现也是通用的话,那么也可以使用 Mixins 来实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

mixin EggLayer {
  void layEggs() {
    print('Plop plop');
  }
}

mixin Flyer {
  void fly() {
    print('Swoosh swoosh');
  }
}

// 重构 Robin
class Robin extends Bird with EggLayer, Flyer {}

// 重构 Platypus
class Platypus extends Animal with EggLayer {
  @override
  void eat() {
    print('Munch munch');
  }

  @override
  void move() {
    print('Glide glide');
  }
}

Extension Methods 扩展方法

可以给系统库或者第三方的库中的类添加方法,也能添加 gettersetter、和运算符
扩展的语法:

1
2
3
extension on SomeClass {

}

还可以给扩展本身命名:

1
2
3
extension YourExtensionName on SomeClass {

}

Generics 泛型

泛型可以防止代码重复

1
2
3
4
5
6
7

class Node<T> {
    T value;
    Node<T>? leftChild;
    Node<T>? rightChild; 
}

指定子类型的泛型

1
2
3
4
class BinarySearchTree<E extends Comparable<E>> {
  Node<E>? root;
}

Enhanced Enums 增强枚举

普通的枚举

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
enum TrafficLight {
  green,
  yellow,
  red,
}

final color = TrafficLight.green;

switch (color) {
  case TrafficLight.green:
    print('Go!');
    break;

  case TrafficLight.yellow:
    print('Slow down!');
    break;

  case TrafficLight.red:
    print('Stop!');
    break;
}

增强枚举版本:

1
2
3
4
5
6
7
enum TrafficLight {
  green('Go!'),
  yellow('Slow down!'),
  red('Stop!');
  const TrafficLight(this.message);
  final String message;
}

有一个枚举构造函数,枚举构造函数都是用 constgreenyellowred 都是调用这个构造函数的枚举实例,最后一个实例用 ; 隔开。使用起来就非常方便了。

1
2
final color = TrafficLight.green;
print(color.message);

增强枚举也可以重载运算符,添加方法,实现接口,还可以添加混入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
enum Fruit with Describer {
  cherry,
  peach,
  banana,
}

enum Vegetable with Describer {
  carrot,
  broccoli,
  spinach,
}

mixin Describer on Enum {
  void describe() {
    print('This $runtimeType is a $name.');
  }
}

在 mixin 中使用 on 关键字可以访问 Enum 类的 name 属性,使用 mixin

1
2
3
4
final fruit = Fruit.banana;
final vegi = Vegetable.broccoli;
fruit.describe();
vegi.describe();

使用泛型

假设您有一些默认值想要与每个枚举值关联。默认字体是“roboto”,一个 String ;默认大小为 17.0,a double ;默认权重为 400,即 int 。每个枚举值都是 Enum 的不同实例。当不同的实例使用不同类型的值时,您需要泛型来处理它们。

1
2
3
4
5
6
7
8
9
enum Default<T extends Object> {
  font<String>('roboto'),
  size<double>(17.0),
  weight<int>(400);

  const Default(this.value);
  final T value;
}

Error Handling 错误处理

捕获异常

try catch

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import 'dart:convert';

void main() {
  const json = 'abc';

  try {
    dynamic result = jsonDecode(json);
    print(result);
  } catch (e) {
    print('There was an error.');
    print(e);
  }

}

处理特定异常

1
2
3
4
5
6
7
8
9
const json = 'abc';

try {
  dynamic result = jsonDecode(json);
  print(result);
} on FormatException {
  print('The JSON string was invalid.');
}

处理多个异常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
void main() {

  const numberStrings = ["42", "hello"];

  try {

    for (final numberString in numberStrings) {

      final number = int.parse(numberString);

      print(number ~/ 0);

    }

  } on FormatException {

    handleFormatException();

  } on UnsupportedError {

    handleDivisionByZero();

  }

}



void handleFormatException() {

  print("You tried to parse a non-numeric string.");

}



void handleDivisionByZero() {

  print("You can't divide by zero.");

}

Finally

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
void main() {

  final database = FakeDatabase();

  database.open();

  try {

    final data = database.fetchData();

    final number = int.parse(data);

    print('The number is $number.');

  } on FormatException {

    print("Dart didn't recognize that as a number.");

  } finally {

    database.close();

  }

}

class FakeDatabase {

  void open() => print('Opening the database.');

  void close() => print('Closing the database.');

  String fetchData() => 'forty-two';

}

自定义异常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class ShortPasswordException implements Exception {

  ShortPasswordException(this.message);

  final String message;

}

class NoNumberException implements Exception {

  NoNumberException(this.message);

  final String message;

}

class NoUppercaseException implements Exception {

  NoUppercaseException(this.message);

  final String message;

}

class NoLowercaseException implements Exception {

  NoLowercaseException(this.message);

  final String message;

}

抛出异常,用 throw

1
2
3
4
5
6
7
8
9
10
11
void validateLength(String password) {

  final goodLength = RegExp(r'.{12,}');

  if (!password.contains(goodLength)) {

    throw ShortPasswordException('Password must be at least 12 characters!');

  }

}

处理异常,可以用 on catch 组合来访问异常类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
const password = 'password1234';

try {

  validatePassword(password);

  print('Password is valid');

} on ShortPasswordException catch (e) {

  print(e.message);

} on NoLowercaseException catch (e) {

  print(e.message);

} on NoUppercaseException catch (e) {

  print(e.message);

} on NoNumberException catch (e) {

  print(e.message);

}

Concurrency 并发

Dart 是一种单线程语言。

并行的问题:比如多个线程访问相同内存的值,如果一个线程在内存中保存一个值,并期待该线程程稍后检查该值时该值相同。但是,如果第二个线程修改该值,第一个线程就会感到困惑。追踪这些类型的错误可能是一件令人头疼的事情,因为它们的来源与报告错误的代码完全分开。支持多线程的语言需要设置一个锁系统,这样值就不会在错误的时间改变。设计、实现和调试多线程系统的认知负担可能很重。

Dart 的单线程运行在 isolate。每个 isolate 都有自己分配的内存,确保没有 isolate 可以访问任何其他 isolate 的状态。这意味着不需要复杂的锁系统。这也意味着敏感数据更加安全。这样的系统大大减轻了程序员的认知负担。

但是并发不会很慢吗?

并发版本确实需要更长的时间,但也不会太长。原因是并行线程大部分时间处于空闲状态。单个线程通常足以完成需要完成的事情。

Flutter 必须每秒更新 UI 60 次。每个更新时间片称为一个帧。这样就剩下大约 16 毫秒的时间来在每一帧上重新绘制 UI。通常不会花费那么长时间,让您有时间在线程空闲时执行其他工作。只要这项工作不会阻止 Flutter 更新下一帧的 UI,用户就不会注意到任何问题。诀窍是在线程停机期间安排任务。

Dart 使用 event loop(事件循环)来调度异步任务。

事件循环有两个队列:事件队列和微任务队列,事件队列用于处理诸如用户触摸屏幕或来自远程服务器的数据之类的事件。 Dart 主要在内部使用微任务队列来优先处理某些无法等待事件队列中的任务完成的小任务。

  • 主隔离线程中的同步任务始终立即运行。你不能打断他们。

  • 如果 Dart 发现任何同意推迟的长时间运行的任务,Dart 会将它们放入事件队列中。

  • 当 Dart 完成运行同步任务时,事件循环会检查微任务队列。如果微任务队列有任何任务,事件循环会将它们放在主线程上接下来执行。事件循环不断检查微任务队列,直到它为空。

  • 如果同步任务和微任务队列都为空,事件循环将发送事件队列中的下一个等待任务在主线程上运行。一旦到达那里,代码就会同步执行。与任何其他同步代码一样,启动后没有任何东西可以中断它。

  • 如果新的微任务进入微任务队列,事件循环会在事件队列中的下一个事件之前处理它们。

  • 这个过程一直持续到所有队列都为空为止。

虽然 Dart 是单线程的,但是也会在另一个线程运行任务。例如,当您要求读取系统上的文件时,该工作不会在 Dart 线程上进行。系统正在其自己的进程内完成工作。系统完成工作后,会将结果传递回 Dart,Dart 会调度一些代码来处理事件队列中的结果。 dart:io 库中的许多 I/O 工作都是以这种方式发生的。

另一个方法是创建新的 isolate ,新的 isolate 有自己的内存和线程,并与主 isolate 并行工作,两个 isolate 只能通过 message 来通信,他们无法访问彼此的内存状态。

添加任务到事件队列

1
2
3
4
5
6
7
print('first');

Future(
  () => print('second'),
);

print('third');

事件队列代码必须等到所有同步代码完成才能运行,所以 second 是最后打印

添加任务到微任务队列

除非你在写底层的库,不然不太需要这个东西

1
2
3
4
5
6
7
8
9
10
11
print('first');

Future(
  () => print('second'),
);

Future.microtask(
  () => print('third'),
);

print('fourth');

完成任务后执行同步代码

1
2
3
4
5
6
7
8
9
10
11
12
13
print('first');

Future(
  () => print('second'),
).then(
  (value) => print('third'),
);

Future(
  () => print('fourth'),
);

print('fifth');

延迟任务

1
2
3
4
5
6
7
8
print('first');

Future.delayed(
  Duration(seconds: 2),
  () => print('second'),
);

print('third');

练习

打印顺序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
void main() {

  print('1 synchronous');

  Future(() => print('2 event queue')).then(

    (value) => print('3 synchronous'),

  );

  Future.microtask(() => print('4 microtask queue'));

  Future.microtask(() => print('5 microtask queue'));

  Future.delayed(

    Duration(seconds: 1),

    () => print('6 event queue'),

  );

  Future(() => print('7 event queue')).then(

    (value) => Future(() => print('8 event queue')),

  );

  Future(() => print('9 event queue')).then(

    (value) => Future.microtask(

      () => print('10 microtask queue'),

    ),

  );

  print('11 synchronous');

}

// 1 11 4 5 2 3 7 9 10 8 6

Futures

拿到 Future 结果

1
2
3
4
5
6
7
print('Before the future');
final myFuture = Future<int>.delayed(Duration(seconds: 1),() => 42,)
.then((value) => print('Value: $value'),)
.catchError((Object error) => print('Error: $error'),)
.whenComplete(() => print('Future is complete'),);

print('After the future');

可以用 asyncawait 改造

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
print('Before the future');

try {

final value = await Future<int>.delayed(Duration(seconds: 1),() => 42,);

print('Value: $value');

} catch (error) {

print(error);

} finally {

print('Future is complete');

}

print('After the future');

可以用 Completer 拥有最大的灵活性

1

Streams

Streams 和 Futures 的区别是,Futures 是返回一个值,然后就结束了,Streams 会一直发送值,直到发送结束。

读取文件字符串

1
2
3
4
5
6
7
8
9

import 'dart:io';

Future<void> main() async {
  final file = File('assets/text.txt');
  final contents = await file.readAsString();
  print(contents);
}

如果文件很大,可以作为流来读取,这样可以更快地开始处理数据,不用像上面那样等待读取整个文件完成,下面是按流读取的例子:

1
2
3
4
5
6
7
8
9
10

final file = File('assets/text_long.txt');
final stream = file.openRead();

stream.listen(
  (data) {
    print(data.length); // 65536
  },
);

也可以用 异步 for 循环来读取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

Future<void> main() async {

  final file = File('assets/text_long.txt');

  final stream = file.openRead();

  await for (var data in stream) {

    print(data.length);

  }

}

处理错误

第一种方法是使用 onError

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

final file = File('assets/text_long.txt');

final stream = file.openRead();

stream.listen(

  (data) {

    print(data.length);

  },

  onError: (Object error) {

    print(error);

  },

  onDone: () {

    print('All finished');

  },

);

发生错误后,并不会取消流的监听,会继续接收到数据,listen 还有一个 cancelError 参数,设置 true 就会取消

第二种方法是使用 Try-Catch\

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

try {

  final file = File('assets/text_long.txt');

  final stream = file.openRead();

  await for (var data in stream) {

    print(data.length);

  }

} on Exception catch (error) {

    print(error);

} finally {

  print('All finished');

}



取消流

如果没有错误怎么随时取消监听流?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

import 'dart:async';

import 'dart:io';



void main() {

  final file = File('assets/text_long.txt');

  final stream = file.openRead();

  StreamSubscription<List<int>>? subscription;

  subscription = stream.listen(

    (data) {

      print(data.length);

      subscription?.cancel();

    },

    cancelOnError: true,

    onDone: () {

      print('All finished');

    },

  );

}



调用 listen 返回 StreamSubscription ,它是 dart:async 库的一部分。在 subscription 变量中保留对此的引用可以让您随时取消订阅。在这种情况下,您可以在第一个数据事件后取消它。

转换流

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

import 'dart:convert';

import 'dart:io';



Future<void> main() async {

  final file = File('assets/text.txt');

  final byteStream = file.openRead();

  final stringStream = byteStream.transform(utf8.decoder);

  await for (var data in stringStream) {

    print(data);

  }

}



创建流

有几种方式创建流:

  • 使用 Stream 构造函数
  • 使用异步生成器
  • 使用流控制器

Stream 构造函数

  • Stream.empty: 没有值或错误的流。
  • Stream.value: 单个值的流
  • **Stream.error**:单个错误的流
  • **Stream.fromFuture**:将 future 转为流
  • Stream.fromFutures: 将多个 future 转为流
  • **Stream.fromIterable**:将可迭代集合转为流

下面的示例将演示使用 fromFutures 构造函数构建流。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

final first = Future(() => 'Row');
final second = Future(() => 'row');
final third = Future(() => 'row');
final fourth = Future.delayed(
  Duration(milliseconds: 300),
  () => 'your boat',
);

final stream = Stream<String>.fromFutures([
  first,
  second,
  third,
  fourth,
]);



stream.listen((data) {

  print(data);

});



异步生成器

使用 async* 关键字来创建异步生成器,返回流的函数。 async 是返回 future 的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

Stream<String> consciousness() async* {

  final data = ['con', 'scious', 'ness'];

  for (final part in data) {

    await Future<void>.delayed(Duration(milliseconds: 500));

    yield part;

  }
}

final stream = consciousness();
stream.listen((data) {
  print(data);
});

流控制器

sink 相当于水槽,stream 相当于水管,将数据扔进水槽就像是向流中添加一条数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

import 'dart:async';
Future<void> main() async {

  // 1

  final controller = StreamController<String>();

  final stream = controller.stream;

  final sink = controller.sink;

  // 2

  stream.listen(

    (value) => print(value),

    onError: (Object error) => print(error),

    onDone: () => print('Sink closed'),

  );

  // 3

  sink.add('grape');

  sink.add('grape');

  sink.add('grape');

  sink.addError(Exception('cherry'));

  sink.add('grape');

  sink.close();

}



Isolates

如果有一个非常耗 CPU 的操作,那么使用 Future 是没用(如果知道原理的话),因为还是在主线程执行,还是会阻塞 App。

在创建 isolate 之前,先创建一个 ReceivePort 对象,当成 SendPort 属性的引用,这样新隔离可以通过发送端口将消息发送回主隔离的接收端口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

import 'dart:isolate';



// 1

void playHideAndSeekTheLongVersion(SendPort sendPort) {

  var counting = 0;

  for (var i = 1; i <= 1000000000; i++) {

    counting = i;

  }

  final message = '$counting! Ready or not, here I come!';

  // 2 发送消息,然后退出 isolate

  Isolate.exit(sendPort, message);

}

上面是新隔离要运行的代码,接下来是创建 isolate 本身:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

Future<void> main() async {

  // 1 先创建 receivePort 来监听新 isolate 的消息

  final receivePort = ReceivePort();



  // 2 

  await Isolate.spawn<SendPort>(

    // 3 Isolate.spawn 的第一个参数是入口点函数。该函数必须是顶级函数或静态函数。它还必须采用一个参数。

    playHideAndSeekTheLongVersion,

    // 4 Isolate.spawn 的第二个参数是入口点函数的参数。在本例中,它是一个 SendPort 对象。

    receivePort.sendPort,

  );



  // 5 ReceivePort 实现了 Stream 接口,因此您可以将其视为流。调用 await receivePort.first 等待流中传入的第一条消息,然后取消流订阅。

  final message = await receivePort.first as String;

  print(message);

}



如果要传递多个参数的话需要改成传 List 或者 Map
Dart 还有一种简化的方法来开始新 isolate,这个就是 compute ,这个不用传 sendPort ,传

1
await compute(playHideAndSeekTheLongVersion, 1000000000);

主 isolate 也可以主动退出创建的 isolate,只要约定一致就行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57

void playHideAndSeekTheLongVersion(SendPort sendPort) {

  sendPort.send("OK, I'm counting...");



  var counting = 0;

  for (var i = 1; i <= 1000000000; i++) {

    counting = i;

  }



  sendPort.send('$counting! Ready or not, here I come!');

  sendPort.send(null);

}



final receivePort = ReceivePort();



final isolate = await Isolate.spawn<SendPort>(

  playHideAndSeekTheLongVersion,

  receivePort.sendPort,

);



receivePort.listen((Object? message) {

  if (message is String) {

    print(message);

  } else if (message == null) {

    receivePort.close();

    isolate.kill();

  }

});



监听到 null 的时候再关闭 receivePort 和 kill 掉 isolate

Two-Way Isolate Communication 双向隔离通信

如果你需要保持 isolate 一段时间,比如和游戏服务器进行通信,那么就要双向通讯,双方都需要一个 SendPort 和 ReceivePort。
先定义 Work

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

import 'dart:io';



class Work {

  Future<int> doSomething() async {

    print('doing some work...');

    sleep(Duration(seconds: 1));

    return 42;

  }
 
  Future<int> doSomethingElse() async {

    print('doing some other work...');

    sleep(Duration(seconds: 1));

    return 24;

  }

}

创建一个入口点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75

import 'dart:isolate';
// 1 sendToEarthPart 是主 isolate 创建的接收窗口下的发送窗口引用,创建的 isolate 可以用这个来发送消息给main isolate

Future<void> _entryPoint(SendPort sendToEarthPort) async {

  // 2 给主 isolate 也发送一个 sendPort,用来发送消息给这个新创建的 isolate
  final receiveOnMarsPort = ReceivePort();
 
  sendToEarthPort.send(receiveOnMarsPort.sendPort);
  // 3 在创建的 isolate 进行工作
  final work = Work();
 
  receiveOnMarsPort.listen((Object? messageFromEarth) async {

  // 1

  await Future<void>.delayed(Duration(seconds: 1));

  print('Message from Earth: $messageFromEarth');

  // 2

  if (messageFromEarth == 'Hey from Earth') {

    sendToEarthPort.send('Hey from Mars');

  }

  else if (messageFromEarth == 'Can you help?') {

    sendToEarthPort.send('sure');

  }

  // 3

  else if (messageFromEarth == 'doSomething') {

    final result = await work.doSomething();

    // 4

    sendToEarthPort.send({

      'method': 'doSomething',

      'result': result,

    });

  }

  else if (messageFromEarth == 'doSomethingElse') {

    final result = await work.doSomethingElse();

    sendToEarthPort.send({

      'method': 'doSomethingElse',

      'result': result,

    });

    sendToEarthPort.send('done');

  }

});

}



创建一个新的 isolate

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107

// 1

class Earth {

  // 2

  final _receiveOnEarthPort = ReceivePort();

  SendPort? _sendToMarsPort;

  Isolate? _marsIsolate;

  Future<void> contactMars() async {

    if (_marsIsolate != null) return;

    _marsIsolate = await Isolate.spawn<SendPort>(

    _entryPoint,

    _receiveOnEarthPort.sendPort,

  );



_receiveOnEarthPort.listen((Object? messageFromMars) async {

  await Future<void>.delayed(Duration(seconds: 1));

  print('Message from Mars: $messageFromMars');

  // 1

  if (messageFromMars is SendPort) {

    _sendToMarsPort = messageFromMars;

    _sendToMarsPort?.send('Hey from Earth');

  }

  // 2

  else if (messageFromMars == 'Hey from Mars') {

    _sendToMarsPort?.send('Can you help?');

  }

  else if (messageFromMars == 'sure') {

    _sendToMarsPort?.send('doSomething');

    _sendToMarsPort?.send('doSomethingElse');

  }

  // 3

  else if (messageFromMars is Map) {

    final method = messageFromMars['method'] as String;

    final result = messageFromMars['result'] as int;

    print('The result of $method is $result');

  }

  // 4

  else if (messageFromMars == 'done') {

    print('shutting down');

    dispose();

  }

});



}





  // 3

  void dispose() {

    _receiveOnEarthPort.close();

    _marsIsolate?.kill();

    _marsIsolate = null;

  }

}



使用

1
2
3
4
5
6
7
8
9
10
11

Future<void> main() async {

  final earth = Earth();

  await earth.contactMars();

}



React Hooks 是 React 16.8 的新增特性,它可以让我们在不编写 类组件(class component) 的情况下使用 state 以及其他的 React 特性(例如生命周期),所以在 React Hooks 之前,主要是编写类组件。

类组件存在的问题

  1. 逻辑复杂的组件难以开发和维护,组件的生命周期函数中可能包含各种互不想关的逻辑,或者同样的逻辑需要在不同的生命周期函数中实现,例如:在 componentDidMount 中请求数据,在 componentDidUpdate 中判断状态变化请求数据。
  2. 组件状态复用,在 React Hooks 之前,逻辑复用基本是用高阶组件来实现,这种方式会有嵌套地狱 的问题
1
2
3
4
5
6
7
8
9
export default withA(
  withB (
    withC (
      withD (
        Component
      )
    )
  )
)
  1. this 指向问题,在类组件中要搞清楚 this 的指向问题,增加学习成本
    React Hooks 基本能解决上面的问题,而且同一个业务用函数组件的代码量比类组件要少很多,有一些特性用类组件很难实现,所以现在 React 基本都是编写函数组件。

注意:React Hooks 只能在函数组件中使用,不能在类组件、函数组件之外的地方使用。

useState

useState 可以在组件内添加一个状态变量

1
const [state, setState] = useState(initialState);

参数

  • initialState:初始化值,不设置为 undefined ,**只在第一次渲染会用到!**

返回值

  • state:当前状态的值(第一次渲染为初始化值)
  • setState:设置状态值的函数,调用之后组件重新渲染,并且根据新的值返回DOM结构
    useState 写一个计数器的例子:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19

    import { memo, useState } from "react";

    function Counter(props) {

      const [counter, setCounter] = useState(0)

      return (

        <div>
          <h2>当前计数: {counter}</h2>
          <button onClick={e => setCounter(counter+1)}>+1</button>
          <button onClick={e => setCounter(counter-1)}>-1</button>
        </div>

      )

    }

    如果初始化值需要写一些业务代码的话:
    1
    2
    3
    const [counter, setCounter] = useState(() => {
        return 0
    })

useEffect

useEffect 可以让你完成一些类似类组件生命周期的功能,通常用来执行一些副作用的代码。
副作用是指一个 function 做了和本身运算返回值(渲染)无关的事,类似于网络请求、修改DOM、事件监听等

1
useEffect(setup, dependencies?)

参数

  • setup:是一个回调函数,当组件被添加到 DOM 中会被执行,在这里执行副作用的代码,这个回调函数可以返回另一个回调函数 cleanup(清除函数),当组件在 DOM 中被移除会执行 cleanup
  • dependencies:可选,是一个数组,表示该 useEffect 受哪些 state 的影响,如果有 state 被修改的话,会执行 cleanup,然后再执行 setup。如果不传那么每一次重新渲染就会执行 Effect,如果传空数组 [] 表示不依赖任何 state ,适合只执行一次的操作

可以写多个 useEffect 把不同的逻辑分离,调用顺序是每一个 effect 的顺序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

import React, { memo, useEffect } from 'react'
import { useState } from 'react'

const App = memo(() => {

  const [count, setCount] = useState(0)
  const [message, setMessage] = useState("Hello World")

  useEffect(() => {
    console.log("count:", count)
  }, [count])
 
  useEffect(() => {
    console.log("message:", message)
  }, [message])

  return (

    <div>
      <button onClick={e => setCount(count+1)}>+1({count})</button>
      <button onClick={e => setMessage("Hello")}>修改message({message})</button>
    </div>

  )
})

export default App

根据 useEffect 的机制如果监听某一个属性被改变也可以用

useContext

在 React Hooks 之前如果组件中需要多个 Context 数据时,会有大量的嵌套问题,推荐用 useContext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import React, { memo, useContext } from 'react'
import { UserContext, ThemeContext } from "./context"

const App = memo(() => {

  // 使用 Context
  const user = useContext(UserContext)
  const theme = useContext(ThemeContext)
 
  return (
    <div>
      <h2>User: {user.name}-{user.age}</h2>
      <h2 style={{color: theme.color, fontSize: theme.size}}>Theme</h2>
    </div>
  )
 
})

export default App

useCallback

先了解一下 memo

memo

先简单介绍一下 memo 的机制,如果希望子组件避免没必要的重新渲染,可以用这个高阶组件,只要 props 没有发生变化,那么就不会重新渲染

1
2
3
4
5
6
7
import { memo } from 'react';

const ChildComponent = (props) => {
  // ...
}

export default memo(ChildComponent);

useCallback 运行机制

useCallback 可以在组件渲染之间保存函数定义

1
const cachedFn = useCallback(fn, dependencies)

参数

  • fn:希望被缓存的函数,第一次渲染的时候会返回这个函数(不会调用),下次渲染的时候如果依赖没有改变会返回上一次渲染的函数,如果改变会返回这次传入的函数
  • dependencies:依赖值,一般是 fn  中用到的参数

useCallback 可以做性能优化,让子组件避免没必要的重新渲染

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

import React, { memo, useState, useCallback, useRef } from 'react'

const ChildComponent = memo(function(props) {

  const { increment } = props

  console.log("ChildComponent组件渲染")

  return (
    <div>
      <button onClick={increment}>+1</button>
    </div>
  )

})



const App = memo(function() {

  const [count, setCount] = useState(0)

  const increment = () => {
    setCount(count+1)
  }
 
  return (
    <div>
      <h2>计数: {count}</h2>
      <ChildComponent increment={increment}/>
    </div>
  )
 
})

当按钮添加的时候执行 setCount 导致 App 组件重新渲染,会重新定义一个新的 increment 函数,那么 ChildComponent 的 memo 不会生效,从而使 ChildComponent 组件渲染,但 ChildComponent 重新渲染是没必要的,如果它还有大量子组件的话会导致性能严重。

使用 useCallback 优化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

const ChildComponent = memo(function(props) {

  const { increment } = props

  console.log("ChildComponent组件渲染")

  return (

    <div>
      <button onClick={increment}>+1</button>
    </div>

  )

})

const App = memo(function() {

  const [count, setCount] = useState(0)

  // 返回一个对象,下次渲染的时候也是返回相同的对象
  const ref = useRef()
  ref.current = count
 
  const increment = useCallback(() => {
    setCount(ref.current + 1)
  }, [])

  return (
 
    <div>
      <h2>计数: {count}</h2>
      <ChildComponent increment={increment}/>
    </div>
   
  )

})

useMemo

useMemo 可以在组件渲染之间缓存函数的返回值

1
const cachedValue = useMemo(calculateValue, dependencies)

参数

  • calculateValue:回调函数,这个函数的返回值就是想要缓存的值
  • dependencies:依赖

    返回值

  • cachedValue:在第一次渲染时候会调用  calculateValue ,然后返回  calculateValue  的返回值,下一次渲染的时候,如果 dependencies 没有改变,那么会返回上一次渲染的值,不需要重新计算。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

const App = memo(() => {

  const [count, setCount] = useState(0)
 
  // 不依赖任何的值
  const result = useMemo(() => {
      // ...
      return xxx()
  }, [])

  // 依赖 count
  const result2 = useMemo(() => {
    return xxx(count)
  }, [count])

  return (
    <div>
<A result={result}>
    </div>
  )
})

和 useCallback 的区别

以下写法是等价的:

1
2
const increment = useCallback(fn, [])
const increment2 = useMemo(() => fn, [])

useRef

useRef 返回一个对象,这个对象在组件的整个生命周期保持不变。常见的使用常见

  • 持有 DOM

  • 保存一个数据

持有 DOM 的案例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

import React, { memo, useRef } from 'react'

const App = memo(() => {

  const titleRef = useRef()
  const inputRef = useRef()

  function showTitleDom() {
    console.log(titleRef.current)
    inputRef.current.focus()
  }
 
  return (
    <div>
      <h2 ref={titleRef}>Hello World</h2>
      <input type="text" ref={inputRef} />
      <button onClick={showTitleDom}>查看title的DOM</button>
    </div>
  )

})

export default App

useImperativeHandle

useImperativeHandle 在使用 ref时可以自定义暴露给父组件的函数

1
2
3

useImperativeHandle(ref, createHandle, dependencies?)

参数

  • ref:父组件传过来的 ref 
  • createHandle :回调函数,这个函数返回一个对象,包含想要暴露给 ref 使用的函数
  • dependencies:可选参数,依赖,如果依赖的值改变了 createHandle 会重新调用

使用场景:

  • 限制某一个DOM的操作权限
  • 暴露方法给父组件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

const A = memo(forwardRef((props, ref) => {

  const inputRef = useRef()
 
  useImperativeHandle(ref, () => {
 
    return {
      focus() {
        inputRef.current.focus()
      }
    }
   
  })
 
  return <input type="text" ref={inputRef}/>
 
}))

const App = memo(() => {

  const inputRef = useRef()
 
  function handleDOM() {
    inputRef.current.focus()
  }
 
  return (

    <div>
      <A ref={inputRef}/>
      <button onClick={handleDOM}>focus</button>
    </div>
   
  )

})

export default App

App 组件的 inputRef 并没有直接绑定到 A 组件的 input 元素,所以只能调用 focus 函数

useLayoutEffect

useEffect 类似,不过会在组件渲染之前执行回调函数,**会损耗性能!**
使用场景:

  • 如果渲染的内容不是想要的,可以在这里修改再进行渲染
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

const App = memo(function() {

    const [count, setCount] = useState(0)

    useLayoutEffect(() => {
   
        if (count === 10) {
            setCount(1)
        }

    }, [count])

}

useTransition

useTransition 可以让优先级较低或渲染比较耗时的组件稍后渲染

1
const [isPending, startTransition] = useTransition()

返回值

isPending:是否在过渡状态

  • startTransition:函数,让优先级低的状态更新在这里处理
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54

    import React, { memo, useState, useTransition } from 'react'

    import namesArray from './namesArray'

    const App = memo(() => {

      const [ names, setNames ] = useState(namesArray)
      const [ isPending, startTransition ] = useTransition()

      function valueChangeHandle(event) {

        startTransition(() => {

          const keyword = event.target.value

          const filterNames = namesArray.filter(item => item.includes(keyword))

          setNames(filterNames)

        })

      }

      return (

        <div>

          <input type="text" onInput={valueChangeHandle}/>

          <h2>列表: {isPending && <span>loading</span>} </h2>

          <ul>

            {

              names.map((item, index) => {

                return <li key={index}>{item}</li>

              })

            }

          </ul>

        </div>

      )

    })

    export default App

    有点类似防抖(debounce)和节流(throttle),但是对比这两个有以下优势:
  • 更新协调过程是**可中断的**,渲染引擎不会长时间被阻塞,用户可以及时得到响应
  • 不需要开发人员去做额外的考虑, 整个优化过程交给 react浏览器 就行 

useDeferredValue

useDeferredValue 可以让 UI 延迟更新

1
const deferredValue = useDeferredValue(value)

参数

  • value:想要延迟更新的值

返回值

  • deferredValue:第一次渲染的时候返回 value,在下一次渲染的时候会延迟更新(返回旧值),最终拿到最新值重新渲染
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52

    import React, { memo, useState, useTransition } from 'react'
    import namesArray from './namesArray'

    const App = memo(() => {

      const [ names, setNames ] = useState(namesArray)
      const deferredNames = useDeferredValue(names)

      function valueChangeHandle(event) {

        startTransition(() => {

          const keyword = event.target.value

          const filterNames = namesArray.filter(item => item.includes(keyword))

          setNames(filterNames)

        })

      }

      return (

        <div>
       
          <input type="text" onInput={valueChangeHandle}/>
          <h2>列表: {isPending && <span>loading</span>} </h2>
         
          <ul>

            {

              deferredNames.map((item, index) => {

                return <li key={index}>{item}</li>

              })

            }

          </ul>

        </div>

      )

    })

    export default App

这篇主要是介绍 Facebook 的开源库 fishhook 的原理和源码实现,需要了解 Mach-O 的相关知识,最好先阅读 Mach-O 文件探索,两篇结合来看效果更佳。

使用

首先写一个 demo 来使用 fishhook,这个 demo 来 hook 系统函数 printf ,让它始终打印 damon

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#import <stdio.h>
#import "fishhook.h"

static int (*original_printf)(const char * __restrict, ...);

int new_printf(const char * __restrict s) {
original_printf("damon");
return 0;
}

int main(int argc, char * argv[]) {
struct rebinding printf_rebinding = {
"printf",
new_printf,
(void *)&original_printf
};
rebind_symbols((struct rebinding[1]){ printf_rebinding }, 1);
printf("123");
return 0;
}
阅读全文 »

简介

Mach-O 是Mach object的缩写,常见的Mach-O文件类型有目标文件、可执行文件、Dsym文件等。了解Mach-O文件的格式,对于静态分析、动态调试、自动化测试及安全都很有意义。可以使用可视化工具来查看 Mach-O 文件,推荐使用 MachOView 软件

文件结构

Mach-O主要由三部分组成,分别是 Header、Load Command、Data。

Mach-O 头部(Header)保存了CPU架构、大小端序、文件类型、加载命令数量等一些基本信息。Header的定义:

1
2
3
4
5
6
7
8
9
struct mach_header_64 {
uint32_t magic;
cpu_type_t cputype;
cpu_subtype_t cpusubtype;
uint32_t filetype;
uint32_t ncmds;
uint32_t flags;
uint32_t reserved;
}
  • magic:魔数,用于标识当前设备是大端还是小端,iOS是小端序。对于64位架构的程序,它被定义为常量 MH_MAGIC_64,值为 0xfeedfacf
  • cputype: 标识 CPU 的架构,如ARM、ARM64、X86_64等。
  • cpusubtype: 标识具体的CPU类型,区别不同版本处理器
  • filetype: 表示 Mach-O 的文件类型,如可执行文件、动态库文件、符号文件等
  • ncmds: Load Commands(加载命令)的数量
  • flags: 标志信息,例如是否启动 ASLR
  • reserved: 保留字段
阅读全文 »

TCP是一个面向连接可靠的基于字节流传输层通信协议,负责建立、维护、管理端到端连接。

TCP 数据格式

  1. Source Port / Destination Port:源端口和目标端口,分别用16位来存储。tcp头没有ip地址,这是网络层做的事情。
  2. Sequence Number:序列号,占16位,用来解决网络包乱序问题。假如一个报文段的序号是500,而数据有20个字节,这就表明这个报文段的第一个字节序号是500,最后一个字节的序号是519,下一个报文段的序号要从520开始。如果溢出了就回到0。
  3. Acknowledgement Number:确认号,占16位,用来表示期望收到下一个报文段的第一个字节的序号,用来解决丢包的问题。假如A给B发数据,序列号是500,长度为20个字节(500 ~ 519),如果B都收到了这20个字节,就会在发给A的报文段中确认号为520,表明下一个序号应该是520
  4. Offset:数据偏移,占4为位,表示 TCP 报文首部信息的长度,因为 TCP 首部可能会有选项内容,所以这个长度是不确定的。看图片右边的箭头就知道了,没有选项字段的话,TCP头部长度为20字节。
    阅读全文 »

MJRefresh 是一个功能强大的 iOS 上拉下拉刷新组件。有很多大厂App(抖音、快手、京东、喜马拉雅等)都在使用,所以是一个很值得学习的库。

原理

先简单说明上拉下拉刷新的原理再去详细分析 MJRefresh 框架结构以及具体实现。

header/footer的位置

首先 header/footer 是添加到 UIScrollView 上的,是它的子控件,比如 header 的高度是40,那么它的y值就是 -40 ,footer的y值就是 contentSizeHeight

下拉/上拉悬停

上拉或者下拉到一定程度,松手后就会进入刷新状态,这时候 header/footer 都在显示在可见范围悬停,这是通过设置 UIScrollView 的 contentInset.topcontentInset.bottom 来实现,刷新结束后,再重置回来。

设计

上面介绍了一些上拉下拉的原理,其实很简单, 接下去具体看一下 MJRefresh 是如何做的。

结构设计

MJRefreshComponent 是header和footer 的基类,实际都是使用最后一层的类。接下来具体看每个类的具体职责和实现。

阅读全文 »

开始之前,假设你已经有Django和Django REST framework的一些基础了

mixins,ViewSet和routers配合使用

minxis的类有5种

  • CreateModelMixin
  • ListModelMixin
  • RetrieveModelMixin
  • UpdateModelMixin
  • DestroyModelMixin

他们分别对应了对数据库的增查改删操作,使用它们的好处是不用重复写着相同的业务代码逻辑,因为每个mixins内部都写好了对应的逻辑,只需要设置一下querysetserializer_class就可以了.

ViewSet也有5种,分别是

  • ViewSetMixin
  • ViewSet
  • GenericViewSet
  • ReadOnlyModelViewSet
  • ModelViewSet
阅读全文 »

如果对闭包不了解的同学请移步到这里先, 因为装饰器要通过闭包来实现

前言

刚开始学 Python 的时候,装饰器(decorator)一直是个让人难以理解的东西,所以想通过这篇文章能够带你一步一步来理解Python装饰器的原理

什么是装饰器?

经典的设计模式有 23 种,设计模式其实也就是巨人们常年写代码经验的思想总结,虽然说这是一种思想,但是由于语法的限制没有办法轻易实现(比如说用 C 语言来实现组合模式).在面向对象的设计模式中, decorator 被称为装饰模式,OOP 的装饰模式需要通过继承和组合来实现,而 Python 除了能支持 OOP 的 decorator 外,直接从语法层次支持 decorator。

下面开始介绍装饰器的原理

阅读全文 »

Python基础

Python中,函数也是一个对象,而且函数对象可以被赋值给变量,所以,通过变量也能调用该函数。
例如:

1
2
3
4
5
def now():
print('2017-11-06')

f = now
f() # 打印 2017-11-06

理解什么是闭包?

Python 中,闭包的概念是:
1.首先是个嵌套函数
2.内层函数使用了外层函数的变量或者参数
3.外层函数把内层函数当做返回值进行返回

一段简单的闭包代码示例

1
2
3
4
5
6
7
8
9
10
11
12
def func_outer(): # 外部函数

temp = 666 # 外部函数的变量

def func_inner(): # 嵌套的内部函数
print(temp) # 使用了外部函数的变量

return func_inner # 返回内部函数


test = func_outer()
test() # 打印666

由以上可以看出,test变量其实就是返回的func_innter函数对象,test()就是通过这个变量来调用func_innter函数.

阅读全文 »

在阅读之前,希望先了解 NSURLSession 的使用和 HTTP/HTTPS 的基础知识。

AFNetworking 的结构:

  • Manager(网络通信,处理网络请求)
    • AFHTTPSessionManger 和外界交互的,本身并没有做什么事情,AFURLSessionManager 的子类
    • AFURLSessionManager 主要的实现逻辑是在这个类
  • Serialization(网络消息的序列化和反序列化)
  • Security(网络安全、https)
  • Reachability (网络状态监听)
阅读全文 »