0%

alloc是我们日常开发中经常遇到,下面我们从源码层面分析一下,alloc方法的底层。

准备工作-获取源码

  • 从苹果开源网站下载objc的源码。
  • 根据这个教程将源码调试成可编译的状态

    开始探究

    我们新建一个类JSPerson
    1
    2
    3
    4
    5
    6
    //.h
    @interface JSPerson : NSObject
    @end
    //.m
    @implementation JSPerson
    @end
    main方法中初始化JSPerson
    1
    2
    3
    4
    5
    6
    7
    int main(int argc, const char * argv[]) {
    @autoreleasepool {
    JSPerson *person = [JSPerson alloc];
    NSLog(@"%@",person);
    }
    return 0;
    }
    JSPerson *person = [JSPerson alloc];这行添加断点运行。
    control键发现断点走到了
    1
    2
    KCObjcBuild`objc_alloc:
    -> 0x100003f44 <+0>: jmpq *0x40be(%rip) ; (void *)0x0000000100003f76
    说明这个时候调用了objc_alloc方法,我们打一个objc_alloc的符号断点,继续执行程序,发现断点来到了objc_alloc的源码部分。
    1
    2
    3
    4
    5
    6
    // Calls [cls alloc].
    id
    objc_alloc(Class cls)
    {
    return callAlloc(cls, true/*checkNil*/, false/*allocWithZone*/);
    }
    我们继续进入callAlloc方法:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    static ALWAYS_INLINE id
    callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
    {
    #if __OBJC2__
    if (slowpath(checkNil && !cls)) return nil;
    if (fastpath(!cls->ISA()->hasCustomAWZ())) {
    return _objc_rootAllocWithZone(cls, nil);
    }
    #endif

    // No shortcuts available.
    if (allocWithZone) {
    return ((id(*)(id, SEL, struct _NSZone *))objc_msgSend)(cls, @selector(allocWithZone:), nil);
    }
    return ((id(*)(id, SEL))objc_msgSend)(cls, @selector(alloc));
    }
    我们继续走断点,发现走到了return ((id(*)(id, SEL))objc_msgSend)(cls, @selector(alloc));即又调用了alloc方法,也就是说callAlloc也会再次调用。
    继续调试这次走到了return _objc_rootAllocWithZone(cls, nil);,断点进入_objc_rootAllocWithZone方法看一下源码:
    1
    2
    3
    4
    5
    6
    7
    id
    _objc_rootAllocWithZone(Class cls, malloc_zone_t *zone __unused)
    {
    // allocWithZone under __OBJC2__ ignores the zone parameter
    return _class_createInstanceFromZone(cls, 0, nil,
    OBJECT_CONSTRUCT_CALL_BADALLOC);
    }
    代码很简单,我们继续跟进到_class_createInstanceFromZone方法:
    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
    static ALWAYS_INLINE id
    _class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone,
    int construct_flags = OBJECT_CONSTRUCT_NONE,
    bool cxxConstruct = true,
    size_t *outAllocatedSize = nil)
    {
    ASSERT(cls->isRealized());
    // Read class's info bits all at once for performance
    bool hasCxxCtor = cxxConstruct && cls->hasCxxCtor();
    bool hasCxxDtor = cls->hasCxxDtor();
    bool fast = cls->canAllocNonpointer();
    size_t size;
    ///获取实例大小
    size = cls->instanceSize(extraBytes);
    if (outAllocatedSize) *outAllocatedSize = size;
    id obj;
    if (zone) {
    obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
    } else {
    //分配内存
    obj = (id)calloc(1, size);
    }
    if (slowpath(!obj)) {
    if (construct_flags & OBJECT_CONSTRUCT_CALL_BADALLOC) {
    return _objc_callBadAllocHandler(cls);
    }
    return nil;
    }
    ///关联isa指针
    if (!zone && fast) {
    obj->initInstanceIsa(cls, hasCxxDtor);
    } else {
    // Use raw pointer isa on the assumption that they might be
    // doing something weird with the zone or RR.
    obj->initIsa(cls);
    }
    if (fastpath(!hasCxxCtor)) {
    return obj;
    }
    construct_flags |= OBJECT_CONSTRUCT_FREE_ONFAILURE;
    return object_cxxConstructFromClass(obj, cls, construct_flags);
    }
    _class_createInstanceFromZone方法有三个关键的点,我们下面分别分析:

    获取实例大小 size = cls->instanceSize(extraBytes)

    内联函数instanceSize的作用是获取实例的大小,对象的大小取决于其ivars(成员变量)的大小。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // May be unaligned depending on class's ivars.
    uint32_t unalignedInstanceSize() const {
    ASSERT(isRealized());
    return data()->ro()->instanceSize;
    }
    inline size_t instanceSize(size_t extraBytes) const {
    if (fastpath(cache.hasFastInstanceSize(extraBytes))) {
    return cache.fastInstanceSize(extraBytes);
    }
    size_t size = alignedInstanceSize() + extraBytes;
    // CF requires all objects be at least 16 bytes.
    if (size < 16) size = 16;
    return size;
    }
    根据if (size < 16) size = 16;可以看出,对象最小大小为16,这个就是内存对齐的概念,上面的alignedInstanceSize()函数,会继续调用内联函数word_align
    1
    2
    3
    4
    5
    // __LP64__
    # define WORD_MASK 7UL
    static inline uint32_t word_align(uint32_t x) {
    return (x + WORD_MASK) & ~WORD_MASK;
    }
    这里就引入一个概念就是字节对齐。可以看到WORD_MASK=7,它的作用是保证字节的大小为8的倍数。

    给对象分配内存空间

    1
    2
    3
    4
    5
    if (zone) {
    obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
    } else {
    obj = (id)calloc(1, size);
    }

    给对象关联isa指针

    1
    2
    3
    4
    5
    6
    7
    if (!zone && fast) {
    obj->initInstanceIsa(cls, hasCxxDtor);
    } else {
    // Use raw pointer isa on the assumption that they might be
    // doing something weird with the zone or RR.
    obj->initIsa(cls);
    }

    总结

    总结来看alloc的流程图即为下图所示:

alloc流程.png

alloc是我们日常开发中经常遇到,下面我们从源码层面分析一下,alloc方法的底层。

准备工作-获取源码

  • 从苹果开源网站下载objc的源码。
  • 根据这个教程将源码调试成可编译的状态

    开始探究

    我们新建一个类JSPerson
    1
    2
    3
    4
    5
    6
    //.h
    @interface JSPerson : NSObject
    @end
    //.m
    @implementation JSPerson
    @end
    main方法中初始化JSPerson
    1
    2
    3
    4
    5
    6
    7
    int main(int argc, const char * argv[]) {
    @autoreleasepool {
    JSPerson *person = [JSPerson alloc];
    NSLog(@"%@",person);
    }
    return 0;
    }
    JSPerson *person = [JSPerson alloc];这行添加断点运行。
    control键发现断点走到了
    1
    2
    KCObjcBuild`objc_alloc:
    -> 0x100003f44 <+0>: jmpq *0x40be(%rip) ; (void *)0x0000000100003f76
    说明这个时候调用了objc_alloc方法,我们打一个objc_alloc的符号断点,继续执行程序,发现断点来到了objc_alloc的源码部分。
    1
    2
    3
    4
    5
    6
    // Calls [cls alloc].
    id
    objc_alloc(Class cls)
    {
    return callAlloc(cls, true/*checkNil*/, false/*allocWithZone*/);
    }
    我们继续进入callAlloc方法:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    static ALWAYS_INLINE id
    callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
    {
    #if __OBJC2__
    if (slowpath(checkNil && !cls)) return nil;
    if (fastpath(!cls->ISA()->hasCustomAWZ())) {
    return _objc_rootAllocWithZone(cls, nil);
    }
    #endif

    // No shortcuts available.
    if (allocWithZone) {
    return ((id(*)(id, SEL, struct _NSZone *))objc_msgSend)(cls, @selector(allocWithZone:), nil);
    }
    return ((id(*)(id, SEL))objc_msgSend)(cls, @selector(alloc));
    }
    我们继续走断点,发现走到了return ((id(*)(id, SEL))objc_msgSend)(cls, @selector(alloc));即又调用了alloc方法,也就是说callAlloc也会再次调用。
    继续调试这次走到了return _objc_rootAllocWithZone(cls, nil);,断点进入_objc_rootAllocWithZone方法看一下源码:
    1
    2
    3
    4
    5
    6
    7
    id
    _objc_rootAllocWithZone(Class cls, malloc_zone_t *zone __unused)
    {
    // allocWithZone under __OBJC2__ ignores the zone parameter
    return _class_createInstanceFromZone(cls, 0, nil,
    OBJECT_CONSTRUCT_CALL_BADALLOC);
    }
    代码很简单,我们继续跟进到_class_createInstanceFromZone方法:
    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
    static ALWAYS_INLINE id
    _class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone,
    int construct_flags = OBJECT_CONSTRUCT_NONE,
    bool cxxConstruct = true,
    size_t *outAllocatedSize = nil)
    {
    ASSERT(cls->isRealized());
    // Read class's info bits all at once for performance
    bool hasCxxCtor = cxxConstruct && cls->hasCxxCtor();
    bool hasCxxDtor = cls->hasCxxDtor();
    bool fast = cls->canAllocNonpointer();
    size_t size;
    ///获取实例大小
    size = cls->instanceSize(extraBytes);
    if (outAllocatedSize) *outAllocatedSize = size;
    id obj;
    if (zone) {
    obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
    } else {
    //分配内存
    obj = (id)calloc(1, size);
    }
    if (slowpath(!obj)) {
    if (construct_flags & OBJECT_CONSTRUCT_CALL_BADALLOC) {
    return _objc_callBadAllocHandler(cls);
    }
    return nil;
    }
    ///关联isa指针
    if (!zone && fast) {
    obj->initInstanceIsa(cls, hasCxxDtor);
    } else {
    // Use raw pointer isa on the assumption that they might be
    // doing something weird with the zone or RR.
    obj->initIsa(cls);
    }
    if (fastpath(!hasCxxCtor)) {
    return obj;
    }
    construct_flags |= OBJECT_CONSTRUCT_FREE_ONFAILURE;
    return object_cxxConstructFromClass(obj, cls, construct_flags);
    }
    _class_createInstanceFromZone方法有三个关键的点,我们下面分别分析:

    获取实例大小 size = cls->instanceSize(extraBytes)

    内联函数instanceSize的作用是获取实例的大小,对象的大小取决于其ivars(成员变量)的大小。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // May be unaligned depending on class's ivars.
    uint32_t unalignedInstanceSize() const {
    ASSERT(isRealized());
    return data()->ro()->instanceSize;
    }
    inline size_t instanceSize(size_t extraBytes) const {
    if (fastpath(cache.hasFastInstanceSize(extraBytes))) {
    return cache.fastInstanceSize(extraBytes);
    }
    size_t size = alignedInstanceSize() + extraBytes;
    // CF requires all objects be at least 16 bytes.
    if (size < 16) size = 16;
    return size;
    }
    根据if (size < 16) size = 16;可以看出,对象最小大小为16,这个就是内存对齐的概念,上面的alignedInstanceSize()函数,会继续调用内联函数word_align
    1
    2
    3
    4
    5
    // __LP64__
    # define WORD_MASK 7UL
    static inline uint32_t word_align(uint32_t x) {
    return (x + WORD_MASK) & ~WORD_MASK;
    }
    这里就引入一个概念就是字节对齐。可以看到WORD_MASK=7,它的作用是保证字节的大小为8的倍数。

    给对象分配内存空间

    1
    2
    3
    4
    5
    if (zone) {
    obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
    } else {
    obj = (id)calloc(1, size);
    }

    给对象关联isa指针

    1
    2
    3
    4
    5
    6
    7
    if (!zone && fast) {
    obj->initInstanceIsa(cls, hasCxxDtor);
    } else {
    // Use raw pointer isa on the assumption that they might be
    // doing something weird with the zone or RR.
    obj->initIsa(cls);
    }

    总结

    总结来看alloc的流程图即为下图所示:

alloc流程.png

RxSwift简介

RxSwiftReactiveX 家族的重要一员, ReactiveXReactive Extensions 的缩写,一般简写为 RxReactiveX 官方给Rx的定义是:Rx是一个使用可观察数据流进行异步编程的编程接口。

1
ReactiveX` 不仅仅是一个编程接口,它是一种编程思想的突破,它影响了许多其它的程序库和框架以及编程语言。它拓展了`观察者模式`,使你能够`自由组合多个异步事件`,而`不需要去关心线程`,`同步,线程安全`,`并发数据以及I/O阻塞

RxSwiftRxSwift 语言开发的一门函数响应式编程语言, 它可以代替iOS系统的 Target Action / 代理 / 闭包 / 通知 / KVO,同时还提供网络数据绑定UI事件处理UI的展示和更新多线程……

Swift为值类型,在传值与方法回调上有影响,RxSwift一定程度上弥补Swift的灵活性

  • RxSwift使得代码复用性较强,减少代码量
  • RxSwift因为声明都是不可变更,增加代码可读性
  • RxSwift使得更易于理解业务代码,抽象异步编程,统一代码风格
  • RxSwift使得代码更易于编写集成单元测试,增加代码稳定性

RxSwift核心流程

RxSwift的api设计非常精简,流程就是:

1、创建序列()

2、订阅序列

3、发送信号

4、销毁

1
2
3
4
5
6
7
8
9
10
11
// 1: 创建序列
_ = Observable<String>.create { (obserber) -> Disposable in
// 3:发送信号
obserber.onNext("RxSwift 研究")
return Disposables.create() // 4、销毁
// 2: 订阅序列
}.subscribe(onNext: { (text) in
print("订阅到:\(text)")
})

// 控制台打印:“订阅到:RxSwift 研究”

创建序列

我们先看Create.swift文件的代码:

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
extension ObservableType {
// MARK: create
/**
Creates an observable sequence from a specified subscribe method implementation.

- seealso: [create operator on reactivex.io](http://reactivex.io/documentation/operators/create.html)

- parameter subscribe: Implementation of the resulting observable sequence's `subscribe` method.
- returns: The observable sequence with the specified implementation for the `subscribe` method.
*/
public static func create(_ subscribe: @escaping (AnyObserver<Element>) -> Disposable) -> Observable<Element> {
AnonymousObservable(subscribe)
}
}

final private class AnonymousObservable<Element>: Producer<Element> {
typealias SubscribeHandler = (AnyObserver<Element>) -> Disposable

let subscribeHandler: SubscribeHandler

init(_ subscribeHandler: @escaping SubscribeHandler) {
self.subscribeHandler = subscribeHandler
}
override func run<Observer: ObserverType>(_ observer: Observer, cancel: Cancelable) -> (sink: Disposable, subscription: Disposable) where Observer.Element == Element {
let sink = AnonymousObservableSink(observer: observer, cancel: cancel)
let subscription = sink.run(self)
return (sink: sink, subscription: subscription)
}
}

我们可以看到,可观察序列的创建是利用扩展ObservableType协议的create方法实现的,里面创建了AnonymousObservable(匿名可观察序列) ,这个命名体现了作者的思维,这是一个内部类,具备一些通用特性(具有自己功能的类才会命名)可以总结一下:

  • create方法的时候创建了一个内部对象AnonymousObservable
  • AnonymousObservable保存了外界传入的闭包
  • AnonymousObservable继承了Producer

接下来我们看一下Producer

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
class Producer<Element>: Observable<Element> {
override init() {
super.init()
}
override func subscribe<Observer: ObserverType>(_ observer: Observer) -> Disposable where Observer.Element == Element {
if !CurrentThreadScheduler.isScheduleRequired {
// The returned disposable needs to release all references once it was disposed.
let disposer = SinkDisposer()
let sinkAndSubscription = self.run(observer, cancel: disposer)
disposer.setSinkAndSubscription(sink: sinkAndSubscription.sink, subscription: sinkAndSubscription.subscription)
return disposer
}
else {
return CurrentThreadScheduler.instance.schedule(()) { _ in
let disposer = SinkDisposer()
let sinkAndSubscription = self.run(observer, cancel: disposer)
disposer.setSinkAndSubscription(sink: sinkAndSubscription.sink, subscription: sinkAndSubscription.subscription)
return disposer
}
}
}
func run<Observer: ObserverType>(_ observer: Observer, cancel: Cancelable) -> (sink: Disposable, subscription: Disposable) where Observer.Element == Element {
rxAbstractMethod()
}
}

可以看到Producer有一个很重要的方法subscribe(订阅),subscribe方法最后返回一个Disposable对象。

订阅序列

我们看一下ObservableType拓展(ObservableType+Extensions.swift)的功能,订阅的方法subscribe(注意这个方法和Producersubscribe不是同一个)

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
/**
Subscribes an element handler, an error handler, a completion handler and disposed handler to an observable sequence.
- parameter onNext: Action to invoke for each element in the observable sequence.
- parameter onError: Action to invoke upon errored termination of the observable sequence.
- parameter onCompleted: Action to invoke upon graceful termination of the observable sequence.
- parameter onDisposed: Action to invoke upon any type of termination of sequence (if the sequence has
gracefully completed, errored, or if the generation is canceled by disposing subscription).
- returns: Subscription object used to unsubscribe from the observable sequence.
*/
public func subscribe(
onNext: ((Element) -> Void)? = nil,
onError: ((Swift.Error) -> Void)? = nil,
onCompleted: (() -> Void)? = nil,
onDisposed: (() -> Void)? = nil
) -> Disposable {
let disposable: Disposable
if let disposed = onDisposed {
disposable = Disposables.create(with: disposed)
}
else {
disposable = Disposables.create()
}
#if DEBUG
let synchronizationTracker = SynchronizationTracker()
#endif
let callStack = Hooks.recordCallStackOnError ? Hooks.customCaptureSubscriptionCallstack() : []
///以下重点关注的代码 创建匿名观察者
let observer = AnonymousObserver<Element> { event in
#if DEBUG
synchronizationTracker.register(synchronizationErrorMessage: .default)
defer { synchronizationTracker.unregister() }
#endif
switch event {
case .next(let value):
onNext?(value)
case .error(let error):
if let onError = onError {
onError(error)
}
else {
Hooks.defaultErrorHandler(callStack, error)
}
disposable.dispose()
case .completed:
onCompleted?()
disposable.dispose()
}
}
return Disposables.create(
self.asObservable().subscribe(observer),
disposable
)
}
}

代码说明:

  • ESwift的关联类型,这个如果仔细看过可观察序列的继承链源码应该不难得出:这个E 就是我们的 序列类型,我们这里就是String

    1
    2
    3
    public class Observable<Element> : ObservableType {
    /// Type of elements in sequence.
    public typealias E = Element
  • 创建AnonymousObserver对象,可以类比前面createAnonymousObservable对象,初始化参数为闭包,保存了外界传入的onNext,onError,onComplete,onDisposed的处理回调闭包。

  • self.asObservable()是我们的RxSwift为了保持一致性的写法。

  • self.asObservable().subscribe(observer)其实本质就是self.subscribe(observer),通过可观察序列的继承关系,我们可以快速定位到Producer订阅代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    override func subscribe<Observer: ObserverType>(_ observer: Observer) -> Disposable where Observer.Element == Element {
    if !CurrentThreadScheduler.isScheduleRequired {
    // The returned disposable needs to release all references once it was disposed.
    let disposer = SinkDisposer()
    let sinkAndSubscription = self.run(observer, cancel: disposer)
    disposer.setSinkAndSubscription(sink: sinkAndSubscription.sink, subscription: sinkAndSubscription.subscription)
    return disposer
    }
    else {
    return CurrentThreadScheduler.instance.schedule(()) { _ in
    let disposer = SinkDisposer()
    let sinkAndSubscription = self.run(observer, cancel: disposer)
    disposer.setSinkAndSubscription(sink: sinkAndSubscription.sink, subscription: sinkAndSubscription.subscription)
    return disposer
    }
    }
    }
  • 销毁代码后面在分析

  • self.run这个代码最终由我们生产者Producer(抽象方法找子类)延伸到具体事务代码AnonymousObservable.run

    1
    2
    3
    4
    5
    override func run<Observer: ObserverType>(_ observer: Observer, cancel: Cancelable) -> (sink: Disposable, subscription: Disposable) where Observer.Element == Element {
    let sink = AnonymousObservableSink(observer: observer, cancel: cancel)
    let subscription = sink.run(self)
    return (sink: sink, subscription: subscription)
    }
  • 这里调用了sink.run(self)方法,将业务处理下沉,分工更加明确。

    1
    2
    3
    func run(_ parent: Parent) -> Disposable {
    parent.subscribeHandler(AnyObserver(self))
    }
  • parent是上面传入进来的AnonymousObservable对象

  • 这个地方我们可以看到调用了AnonymousObservable对象的subscribeHandler方法,这里我们清楚了,为什么序列订阅的时候流程会执行我们的序列闭包,然后去执行发送响应

  • 发送响应的代码后面再分析,下面还有个点是AnyObserver(self)

    1
    2
    3
    4
    5
    6
    /// Construct an instance whose `on(event)` calls `observer.on(event)`
    ///
    /// - parameter observer: Observer that receives sequence events.
    public init<Observer: ObserverType>(_ observer: Observer) where Observer.Element == Element {
    self.observer = observer.on
    }
  • 这个构造方法里面,我们创建了一个结构体AnyObserver保存了一个信息AnonymousObservableSink.on函数。注意不是AnonymousObservableSink

发送响应

通过上面的分析,我们清楚了observer.onNext("")本质是AnyObserver.onNext(""),我们发现AnyObserver没有这个方法,顺着思路找父类,找ObserverType

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/// Convenience API extensions to provide alternate next, error, completed events
extension ObserverType {
/// Convenience method equivalent to `on(.next(element: Element))`
///
/// - parameter element: Next element to send to observer(s)
///这里是我们关注的点 AnyObserver.onNext("")实际调用这里
public func onNext(_ element: Element) {
self.on(.next(element))
}
/// Convenience method equivalent to `on(.completed)`
public func onCompleted() {
self.on(.completed)
}
/// Convenience method equivalent to `on(.error(Swift.Error))`
/// - parameter error: Swift.Error to send to observer(s)
public func onError(_ error: Swift.Error) {
self.on(.error(error))
}
}
  • 外界调用的observer.onNext("")再次变形:AnyObserver.on(.next("")),AnyObserver调用了on里面的.next函数,.next函数带有我们最终的参数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    /// Construct an instance whose `on(event)` calls `observer.on(event)`
    /// - parameter observer: Observer that receives sequence events.
    public init<Observer: ObserverType>(_ observer: Observer) where Observer.Element == Element {
    self.observer = observer.on
    }
    /// Send `event` to this observer.
    /// - parameter event: Event instance.
    public func on(_ event: Event<Element>) {
    self.observer(event)
    }
  • self.observer 构造初始化就是:AnonymousObservableSink .on 函数

  • self.observer(event) -> AnonymousObservableSink .on(event) 其中 event = .next("") 最终我们的核心逻辑又回到了 sink

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    final private class AnonymousObservableSink<Observer: ObserverType>: Sink<Observer>, ObserverType {
    func on(_ event: Event<Element>) {
    #if DEBUG
    self.synchronizationTracker.register(synchronizationErrorMessage: .default)
    defer { self.synchronizationTracker.unregister() }
    #endif
    switch event {
    case .next:
    if load(self.isStopped) == 1 {
    return
    }
    self.forwardOn(event)
    case .error, .completed:
    if fetchOr(self.isStopped, 1) == 0 {
    self.forwardOn(event)
    self.dispose()
    }
    }
    }
    }
  • self.forwardOn(event) 这也是执行的核心代码,因为 AnonymousObservableSink 继承 Sink 这里还有封装,请看下面的代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    class Sink<Observer: ObserverType>: Disposable {
    final func forwardOn(_ event: Event<Observer.Element>) {
    #if DEBUG
    self.synchronizationTracker.register(synchronizationErrorMessage: .default)
    defer { self.synchronizationTracker.unregister() }
    #endif
    if isFlagSet(self.disposed, 1) {
    return
    }
    self.observer.on(event)
    }
    }
  • 其中 self.observer 就是我们初始化保存的 观察者:AnonymousObserver

  • 到这里我们得出了发送序列的本质就是AnonymousObserver.on(.next("")),这个逻辑又回到了我们订阅序列时候创建的AnonymousObserver参数闭包的调用。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    let observer = AnonymousObserver<E> { event in
    switch event {
    case .next(let value):
    onNext?(value)
    case .error(let error):
    if let onError = onError {
    onError(error)
    }
    else {
    Hooks.defaultErrorHandler(callStack, error)
    }
    disposable.dispose()
    case .completed:
    onCompleted?()
    disposable.dispose()
    }
    }
  • 判断event进而调用onNext?(value),因为枚举的关联值value="",接下来外界onNext的调用参数。

销毁

我们先看一下创建序列到销毁的执行代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 创建序列
let ob = Observable<Any>.create { (observer) -> Disposable in
observer.onNext("Jason")
return Disposables.create {
print("销毁释放了")
}
}
// 序列订阅
let dispose = ob.subscribe(onNext: { (anything) in
print("订阅到了:\(anything)")
}, onError: { (error) in
print("订阅到了:\(error)")
}, onCompleted: {
print("完成了")
}) {
print("销毁回调")
}
  • 这段代码里面关于销毁相关的代码就是Disposables.create {print("销毁释放了")},所以我们直接定位到Disposables类(AnonymousDisposable.swift文件)。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    extension Disposables {
    /// Constructs a new disposable with the given action used for disposal.
    ///
    /// - parameter dispose: Disposal action which will be run upon calling `dispose`.
    public static func create(with dispose: @escaping () -> Void) -> Cancelable {
    AnonymousDisposable(disposeAction: dispose)
    }

    }
  • 可以看出,这里创建了一个匿名销毁序列AnonymousDisposable,和订阅一样的手法。继续看AnonymousDisposable代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    /// When dispose method is called, disposal action will be dereferenced.
    private final class AnonymousDisposable : DisposeBase, Cancelable {
    // Non-deprecated version of the constructor, used by `Disposables.create(with:)`
    fileprivate init(disposeAction: @escaping DisposeAction) {
    self.disposeAction = disposeAction
    super.init()
    }
    /// Calls the disposal action if and only if the current instance hasn't been disposed yet.
    ///
    /// After invoking disposal action, disposal action will be dereferenced.
    ///销毁核心的逻辑
    fileprivate func dispose() {
    if fetchOr(self.disposed, 1) == 0 {
    if let action = self.disposeAction {
    self.disposeAction = nil
    action()
    }
    }
    }
    }
  • 上我们看到,初始化方法里保存了销毁响应闭包,什么时候调用,我们看下面的dispose()方法。

  • fetchOr(self.disposed, 1)是一个单项标记手段,这里利用了牛逼的算法标记可以降低依赖和更加快速。

  • 主要就是保证只会销毁一次

  • 销毁会首先self.disposeAction = nil,将回调闭包置空

  • 最后调用闭包调用action(),这里是一个局部变量不需要再置空

下面我们看一下dispose()是什么时候调用的

上面的流程,我们再序列的回调闭包:subscriberHandle里面,这个流程之前有一个重要的流程就是订阅subscriber

1
2
3
4
5
if let disposed = onDisposed {
disposable = Disposables.create(with: disposed)
}else {
disposable = Disposables.create()
}
  • 这里保存外界传入的销毁闭包

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    switch event {
    case .next(let value):
    onNext?(value)
    case .error(let error):
    // 响应外界调回闭包
    disposable.dispose()
    case .completed:
    // 响应外界调回闭包
    disposable.dispose()
    }
  • 观察者回调里面调用, 响应外界调回闭包

  • return Disposables.create(self.asObservable().subscribe(observer),disposable) 综合来看,我们的重点必然在这句代码,沟通下面流程的 subscribe, 外界订阅返回的销毁者(可以随时随地进行 dispose.dispose()
  • 上面代码跟进去看到BinaryDisposable(disposable1, disposable2) 原来创建的二元销毁者!
1
2
3
4
5
6
7
8
func dispose() {
if fetchOr(self._isDisposed, 1) == 0 {
self._disposable1?.dispose()
self._disposable2?.dispose()
self._disposable1 = nil
self._disposable2 = nil
}
}
  • 二元销毁者的 dispose 方法也在预料之中,分别销毁

  • 那么我们的重点就应该探索,在 subscribe 这里面创建的关键销毁者是什么?

  • 下面我们进入非常熟悉的:Producer

    1
    2
    3
    4
    5
    6
    let disposer = SinkDisposer()
    let sinkAndSubscription = self.run(observer, cancel: disposer)
    disposer.setSinkAndSubscription(sink: sinkAndSubscription.sink,
    subscription: sinkAndSubscription.subscription)
    // 返回销毁者
    return disposer
  • 看到 SinkDisposer 就熟悉了,普通销毁者:AnonymousDisposable , 关键销毁者: SinkDisposer

  • 先看什么东西进入了 SinkDisposer

  • self.run(observer, cancel: disposer) 证明里面需要用到 SinkDisposer

  • disposer.setSinkAndSubscription 常规操作

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    func setSinkAndSubscription(sink: Disposable, subscription: Disposable) {
    self._sink = sink
    self._subscription = subscription
    // 获取状态
    let previousState = fetchOr(self._state,
    DisposeState.sinkAndSubscriptionSet.rawValue)
    // 如果状态满足就销毁
    if (previousState & DisposeState.disposed.rawValue) != 0 {
    sink.dispose()
    subscription.dispose()
    self._sink = nil
    self._subscription = nil
    }
    }
  • 保存了两个属性 : sinksubscription(就是外界创建序列的闭包的返回销毁者)

  • 取了某一个状态:previousState,判断状态的条件,然后执行 这两个保存属性的销毁和置空释放销毁 : .dispose() + = nil

  • 其实是可以理解,就是我们在加入的东西其实需要销毁的,不应该保留的,那么没必要给它继续保留生命迹象

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    // 创建 sink 保存了销毁者
    let sink = AnonymousObservableSink(observer: observer, cancel: cancel)
    // 省略不相管代码。。。。
    func on(_ event: Event<Element>) {
    switch event {
    case .next:
    self.forwardOn(event)
    case .error, .completed:
    if fetchOr(self._isStopped, 1) == 0 {
    self.forwardOn(event)
    // 关键点:完成和错误信号的响应式必然会直接开启销毁的
    self.dispose()
    }
    }
    }
  • 完成和错误信号的响应式必然会直接开启销毁的 : self.dispose()! 这里也解释了:一旦我们的序列发出完成或者错误就无法再次响应了!

  • 剩下一个问题: 到底我们的销毁的是什么

    1
    2
    3
    4
    5
    6
    7
    8
    9
    func dispose() {
    let previousState = fetchOr(self._state, DisposeState.disposed.rawValue)
    if (previousState & DisposeState.sinkAndSubscriptionSet.rawValue) != 0 {
    sink.dispose()
    subscription.dispose()
    self._sink = nil
    self._subscription = nil
    }
    }
  • 无论我们直接销毁还是系统帮助我们销毁必然会调用:dispose()

  • 我们查看 dispose() 得出: 就是在初始化初期我们保留的两个属性的操作

  • sink.dispose() + self._sink = nil & subscription.dispose() + self._subscription = nil 执行相关释放和销毁

总结一下销毁

第一:内部创建的临时序列和观察者都会随着对外的观察者和序列的生命周期而销毁释放。

第二:外界观察者和序列会随着他们的作用域空间而释放

第三:释放不了只是对象的释放有问题,常规内存管理问题

第四:最为一个再牛逼的框架也不能对程序员写的代码直接管理控制

第五:RxSwift 的观察和序列以及销毁者就是普通对象。

到这里RxSwift从创建序列->订阅序列->发送信号->销毁的源码解析就结束了,里面的精妙还是需要细细品味的。

上传github发现不给上传100m以上文件的错误,按着提示进行了,用到一个叫lfs的工具专门用来上传大文件的!附git-lfs 1.安装 mac

1
2
brew install git-lfs
git lfs install

找出超过100m的文件

1
2
cd 目标项目path
find ./ -size +100M

大文件加入git large file storage上面

1
2
3
git lfs track "name_of_a_giant_file"
#example:
git lfs track ".//huanxin/HXTest/Hyphenate.framework/Hyphenate"

添加到git上

1
2
3
git add path_of_a_giant_file
#example:
git add .//huanxin/HXTest/Hyphenate.framework/Hyphenate

仅供记录以后遇到类似问题查找。

原文地址

Flutter默认是单线程任务处理的,如果不开启新的线程,任务默认在主线程中处理。

事件队列

和iOS应用很像,在Dart的线程中也存在事件循环和消息队列的概念,但在Dart中线程叫做isolate。应用程序启动后,开始执行main函数并运行main isolate

每个isolate包含一个事件循环以及两个事件队列,event loop事件循环,以及event queuemicrotask queue事件队列,eventmicrotask队列有点类似iOS的source0source1

  • event queue:负责处理I/O事件、绘制事件、手势事件、接收其他isolate消息等外部事件。
  • microtask queue:可以自己向isolate内部添加事件,事件的优先级比event queue高。

这两个队列也是有优先级的,当isolate开始执行后,会先处理microtask的事件,当microtask队列中没有事件后,才会处理event队列中的事件,并按照这个顺序反复执行。但需要注意的是,当执行microtask事件时,会阻塞event队列的事件执行,这样就会导致渲染、手势响应等event事件响应延时。为了保证渲染和手势响应,应该尽量将耗时操作放在event队列中。

async、await

在异步调用中有三个关键词,asyncawaitFuture,其中asyncawait需要一起使用。在Dart中可以通过asyncawait进行异步操作,async表示开启一个异步操作,也可以返回一个Future结果。如果没有返回值,则默认返回一个返回值为nullFuture

asyncawait本质上就是Dart对异步操作的一个语法糖,可以减少异步调用的嵌套调用,并且由async修饰后返回一个Future,外界可以以链式调用的方式调用。这个语法是JSES7标准中推出的,Dart的设计和JS相同。

下面封装了一个网络请求的异步操作,并且将请求后的Response类型的Future返回给外界,外界可以通过await调用这个请求,并获取返回数据。从代码中可以看到,即便直接返回一个字符串,Dart也会对其进行包装并成为一个Future

1
2
3
4
5
6
7
8
9
10
Future<Response> dataReqeust() async {
String requestURL = 'https://jsonplaceholder.typicode.com/posts';
Client client = Client();
Future<Response> response = client.get(requestURL);
return response;
}
Future<String> loadData() async {
Response response = await dataReqeust();
return response.body;
}

在代码示例中,执行到loadData方法时,会同步进入方法内部进行执行,当执行到await时就会停止async内部的执行,从而继续执行外面的代码。当await有返回后,会继续从await的位置继续执行。所以await的操作,不会影响后面代码的执行。

Future

Future就是延时操作的一个封装,可以将异步任务封装为Future对象。获取到Future对象后,最简单的方法就是用await修饰,并等待返回结果继续向下执行。正如上面async、await中讲到的,使用await修饰时需要配合async一起使用。

Dart中,和时间相关的操作基本都和Future有关,例如延时操作、异步操作等。下面是一个很简单的延时操作,通过Futuredelayed方法实现。

1
2
3
4
5
6
7
8
9
loadData() {
// DateTime.now(),获取当前时间
DateTime now = DateTime.now();
print('request begin $now');
Future.delayed(Duration(seconds: 1), (){
now = DateTime.now();
print('request response $now');
});
}

Dart还支持对Future的链式调用,通过追加一个或多个then方法来实现,这个特性非常实用。例如一个延时操作完成后,会调用then方法,并且可以传递一个参数给then。调用方式是链式调用,也就代表可以进行很多层的处理。这有点类似于iOS的RAC框架,链式调用进行信号处理。

1
2
3
4
5
6
7
Future.delayed(Duration(seconds: 1), (){
int age = 18;
return age;
}).then((onValue){
onValue++;
print('age $onValue');
});

协程

如果想要了解asyncawait的原理,就要先了解协程的概念,asyncawait本质上就是协程的一种语法糖。协程,也叫作coroutine,是一种比线程更小的单元。如果从单元大小来说,基本可以理解为进程->线程->协程。

任务调度

在弄懂协程之前,首先要明白并发和并行的概念,并发指的是由系统来管理多个IO的切换,并交由CPU去处理。并行指的是多核CPU在同一时间里执行多个任务。

并发的实现由非阻塞操作+事件通知来完成,事件通知也叫做“中断”。操作过程分为两种,一种是CPU对IO进行操作,在操作完成后发起中断告诉IO操作完成。另一种是IO发起中断,告诉CPU可以进行操作。

线程本质上也是依赖于中断来进行调度的,线程还有一种叫做“阻塞式中断”,就是在执行IO操作时将线程阻塞,等待执行完成后再继续执行。但线程的消耗是很大的,并不适合大量并发操作的处理,而通过单线程并发可以进行大量并发操作。当多核CPU出现后,单个线程就无法很好的利用多核CPU的优势了,所以又引入了线程池的概念,通过线程池来管理大量线程。

协程

在程序执行过程中,离开当前的调用位置有两种方式,继续调用其他函数和return返回离开当前函数。但是执行return时,当前函数在调用栈中的局部变量、形参等状态则会被销毁。

协程分为无线协程和有线协程,无线协程在离开当前调用位置时,会将当前变量放在堆区,当再次回到当前位置时,还会继续从堆区中获取到变量。所以,一般在执行当前函数时就会将变量直接分配到堆区,而asyncawait就属于无线协程的一种。有线协程则会将变量继续保存在栈区,在回到指针指向的离开位置时,会继续从栈中取出调用。

async、await原理

asyncawait为例,协程在执行时,执行到async则表示进入一个协程,会同步执行async的代码块。async的代码块本质上也相当于一个函数,并且有自己的上下文环境。当执行到await时,则表示有任务需要等待,CPU则去调度执行其他IO,也就是后面的代码或其他协程代码。过一段时间CPU就会轮训一次,看某个协程是否任务已经处理完成,有返回结果可以被继续执行,如果可以被继续执行的话,则会沿着上次离开时指针指向的位置继续执行,也就是await标志的位置。

由于并没有开启新的线程,只是进行IO中断改变CPU调度,所以网络请求这样的异步操作可以使用asyncawait,但如果是执行大量耗时同步操作的话,应该使用isolate开辟新的线程去执行。

如果用协程和iOS的dispatch_async进行对比,可以发现二者是比较相似的。从结构定义来看,协程需要将当前await的代码块相关的变量进行存储,dispatch_async也可以通过block来实现临时变量的存储能力。

我之前还在想一个问题,苹果为什么不引入协程的特性呢?后来想了一下,awaitdispatch_async都可以简单理解为异步操作,OC的线程是基于Runloop实现的,Dart本质上也是有事件循环的,而且二者都有自己的事件队列,只是队列数量和分类不同。

我觉得当执行到await时,保存当前的上下文,并将当前位置标记为待处理任务,用一个指针指向当前位置,并将待处理任务放入当前isolate的队列中。在每个事件循环时都去询问这个任务,如果需要进行处理,就恢复上下文进行任务处理。

Promise

这里想提一下JS里的Promise语法,在iOS中会出现很多if判断或者其他的嵌套调用,而Promise可以把之前横向的嵌套调用,改成纵向链式调用。如果能把Promise引入到OC里,可以让代码看起来更简洁,直观。

isolate

isolateDart平台对线程的实现方案,但和普通Thread不同的是,isolate拥有独立的内存,isolate由线程和独立内存构成。正是由于isolate线程之间的内存不共享,所以isolate线程之间并不存在资源抢夺的问题,所以也不需要锁。

通过isolate可以很好的利用多核CPU,来进行大量耗时任务的处理。isolate线程之间的通信主要通过port来进行,这个port消息传递的过程是异步的。通过Dart源码也可以看出,实例化一个isolate的过程包括,实例化isolate结构体、在堆中分配线程内存、配置port等过程。

isolate看起来其实和进程比较相似,之前请教阿里架构师宗心问题时,宗心也说过“isolate的整体模型我自己的理解其实更像进程,而asyncawait更像是线程”。如果对比一下isolate和进程的定义,会发现确实isolate很像是进程。

代码示例

下面是一个isolate的例子,例子中新创建了一个isolate,并且绑定了一个方法进行网络请求和数据解析的处理,并通过port将处理好的数据返回给调用方。

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
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;
}

isolate和iOS中的线程还不太一样,isolate的线程更偏底层。当生成一个isolate后,其内存是各自独立的,相互之间并不能进行访问。但isolate提供了基于port的消息机制,通过建立通信双方的sendPortreceiveport,进行相互的消息传递,在Dart中叫做消息传递。

从上面例子中可以看出,在进行isolate消息传递的过程中,本质上就是进行port的传递。将port传递给其他isolate,其他isolate通过port拿到sendPort,向调用方发送消息来进行相互的消息传递。

Embedder

正如其名,Embedder是一个嵌入层,将Flutter嵌入到各个平台上。Embedder负责范围包括原生平台插件、线程管理、事件循环等。

Embedder中存在四个Runner,四个Runner分别如下。其中每个Flutter Engine各自对应一个UI RunnerGPU RunnerIO Runner,但所有Engine共享一个Platform Runner

Runnerisolate并不是一码事,彼此相互独立。以iOS平台为例,Runner的实现就是CFRunLoop,以一个事件循环的方式不断处理任务。并且Runner不只处理Engine的任务,还有Native Plugin带来的原生平台的任务。而isolate则由Dart VM进行管理,和原生平台线程并无关系。

Platform Runner

Platform Runner和iOS平台的Main Thread非常相似,在Flutter中除耗时操作外,所有任务都应该放在Platform中,Flutter中的很多API并不是线程安全的,放在其他线程中可能会导致一些bug。

但例如IO之类的耗时操作,应该放在其他线程中完成,否则会影响Platform的正常执行,甚至于被watchdog干掉。但需要注意的是,由于Embedder Runner的机制,Platform被阻塞后并不会导致页面卡顿。

不只是Flutter Engine的代码在Platform中执行,Native Plugin的任务也会派发到Platform中执行。实际上,在原生侧的代码运行在Platform Runner中,而Flutter侧的代码运行在Root Isolate中,如果在Platform中执行耗时代码,则会卡原生平台的主线程。

UI Runner

UI Runner负责为Flutter Engine执行Root Isolate的代码,除此之外,也处理来自Native Plugin的任务。Root Isolate为了处理自身事件,绑定了很多函数方法。程序启动时,Flutter Engine会为Root绑定UI Runner的处理函数,使Root Isolate具备提交渲染帧的能力。

Root IsolateEngine提交一次渲染帧时,Engine会等待下次vsync,当下次vsync到来时,由Root IsolateWidgets进行布局操作,并生成页面的显示信息的描述,并将信息交给Engine去处理。

由于对widgets进行layout并生成layer treeUI Runner进行的,如果在UI Runner中进行大量耗时处理,会影响页面的显示,所以应该将耗时操作交给其他isolate处理,例如来自Native Plugin的事件。

GPU Runner

GPU Runner并不直接负责渲染操作,其负责GPU相关的管理和调度。当layer tree信息到来时,GPU Runner将其提交给指定的渲染平台,渲染平台是Skia配置的,不同平台可能有不同的实现。

GPU Runner相对比较独立,除了Embedder外其他线程均不可向其提交渲染信息。

IO Runner

一些GPU Runner中比较耗时的操作,就放在IO Runner中进行处理,例如图片读取、解压、渲染等操作。但是只有GPU Runner才能对GPU提交渲染信息,为了保证IO Runner也具备这个能力,所以IO Runner会引用GPU Runnercontext,这样就具备向GPU提交渲染信息的能力

本文地址

闲鱼讲解

上一篇中我们列举了几种状态管理,我们已经知道Provider是对InheritedWidget的包装,只是让InheritedWidget用起来更加简单且高可复用。我们上一篇也提到过InheritedWidget的缺点。

我们这篇主要带着问题,看Provider是如果规避InheritedWidget的缺点,而且Provider提供了dispose回调,你可以在该函数中主动关闭,如何做到的呢。带着这些问题,我们寻找答案。

如何使用

我们先看怎么使用provider。

1、定义一个ChangeNotifier

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Counter with ChangeNotifier, DiagnosticableTreeMixin {
int _count = 0;
int get count => _count;
void increment() {
_count++;
notifyListeners();
}
/// Makes `Counter` readable inside the devtools by listing all of its properties
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(IntProperty('count', count));
}
}

2、用ChangeNotifierProvider来订阅Counter

不难猜出,ChangeNotifierProvider肯定是InheritedWidget的包装类,负责将Counter的状态共享给子Widget。

1
2
3
4
5
6
7
8
9
10
11
12
void main() {
runApp(
/// Providers are above [MyApp] instead of inside it, so that tests
/// can use [MyApp] while mocking the providers
MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => Counter()),
],
child: const MyApp(),
),
);
}

3、接收数据通过Consumer<Counter>

Consumer是个消费者,它负责消费ChangeNotifierProvider生产的数据。

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
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return const MaterialApp(
home: MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
const MyHomePage({Key? key}) : super(key: key);

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Example'),
),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: const <Widget>[
Text('You have pushed the button this many times:'),

/// Extracted as a separate widget for performance optimization.
/// As a separate widget, it will rebuild independently from [MyHomePage].
///
/// This is totally optional (and rarely needed).
/// Similarly, we could also use [Consumer] or [Selector].
Consume<Counter>(
builder:(BuildContext context,Counter value,Widget child){
return Text('${value.count}')
},
),
Count(),
],
),
),
floatingActionButton: FloatingActionButton(
key: const Key('increment_floatingActionButton'),

/// Calls `context.read` instead of `context.watch` so that it does not rebuild
/// when [Counter] changes.
onPressed: () => context.read<Counter>().increment(),
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}

class Count extends StatelessWidget {
const Count({Key? key}) : super(key: key);

@override
Widget build(BuildContext context) {
return Text(

/// Calls `context.watch` to make [Count] rebuild when [Counter] changes.
'${context.watch<Counter>().count}',
key: const Key('counterState'),
style: Theme.of(context).textTheme.headline4);
}
}

通过看Provider的用法例子,可以判断出Provider的封装足够易用,而且Counter作为Model层使用with ChangeNotifier而不是继承,所以说它的侵入性也比较低。下面我们看InheritedWidget的缺点看是否已经规避

1、 容易造成不必要的刷新(看是否已经解决)

我们家两个子Widget进去,排在Consumer的后面,OtherWidget什么都不干,不去订阅Counter,OtherWidget2通过contex.watch<Counter>().count函数监听而不是Consumer,来看一下效果一样不,然后在build函数中都加入print

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class OtherWidget extends StatelessWidget{
@override
Widget build(BuildContext context) {
print('OtherWidget build');
//
return Text('OtherWidget');
}
}
class OtherWidget2 extends StatelessWidget{
@override
Widget build(BuildContext context) {
print('OtherWidget2 build');
//
return Text('OtherWidget2 ${context.watch<Counter>().count}');
}
}

可以看出几个结论

  • Consumer、context.watch都可以监听Counter变化
  • Consumer只会刷新自己
  • context.watch所在的子widget不管是否是const都会被重建后刷新数据
  • OtherWidget并没有被重建,因为它没有订阅Counter

局部刷新确实实现了通过Consumer,第二个问题不支持跨页面router的状态,这个不支持,第三个问题数据是不可变的(只读),经过这个例子可以分辨出数据确实是可变的,如何变化的我们分析源码找本质。

ChangeNotifier和ChangeNotifierProvider是被观察者和观察者的关系。ChangeNotifierProvider和Cosumer是生产者和消费者的关系。

源码分析

ChangeNotifier

在包package:meta/meta.dart下,是flutter sdk的代码,并不属于Provider框架的一部分。通过查看代码可以看出,这是一个标准的观察者模型,而真正的监听者就是typedef VoidCallback = void Function();是dart.ui包下定义的一个函数,没有任何返回参数的函数。ChangeNotifer实现抽象类Listenable,通过源码注释我们看到Listenable是一个转么负责维护监听列表的一个抽象类。

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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
class _ListenerEntry extends LinkedListEntry<_ListenerEntry> {
_ListenerEntry(this.listener);
final VoidCallback listener;//真正的监听者
}
/// A class that can be extended or mixed in that provides a change notification
/// API using [VoidCallback] for notifications.
///
/// It is O(1) for adding listeners and O(N) for removing listeners and dispatching
/// notifications (where N is the number of listeners).
///
/// See also:
///
/// * [ValueNotifier], which is a [ChangeNotifier] that wraps a single value.
class ChangeNotifier implements Listenable {
LinkedList<_ListenerEntry>? _listeners = LinkedList<_ListenerEntry>();

bool _debugAssertNotDisposed() {
assert(() {
if (_listeners == null) {
throw FlutterError(
'A $runtimeType was used after being disposed.\n'
'Once you have called dispose() on a $runtimeType, it can no longer be used.'
);
}
return true;
}());
return true;
}

/// Whether any listeners are currently registered.
///
/// Clients should not depend on this value for their behavior, because having
/// one listener's logic change when another listener happens to start or stop
/// listening will lead to extremely hard-to-track bugs. Subclasses might use
/// this information to determine whether to do any work when there are no
/// listeners, however; for example, resuming a [Stream] when a listener is
/// added and pausing it when a listener is removed.
///
/// Typically this is used by overriding [addListener], checking if
/// [hasListeners] is false before calling `super.addListener()`, and if so,
/// starting whatever work is needed to determine when to call
/// [notifyListeners]; and similarly, by overriding [removeListener], checking
/// if [hasListeners] is false after calling `super.removeListener()`, and if
/// so, stopping that same work.
@protected
bool get hasListeners {
assert(_debugAssertNotDisposed());
return _listeners!.isNotEmpty;
}

/// Register a closure to be called when the object changes.
///
/// This method must not be called after [dispose] has been called.
@override
void addListener(VoidCallback listener) {
assert(_debugAssertNotDisposed());
_listeners!.add(_ListenerEntry(listener));
}

/// Remove a previously registered closure from the list of closures that are
/// notified when the object changes.
///
/// If the given listener is not registered, the call is ignored.
///
/// This method must not be called after [dispose] has been called.
///
/// If a listener had been added twice, and is removed once during an
/// iteration (i.e. in response to a notification), it will still be called
/// again. If, on the other hand, it is removed as many times as it was
/// registered, then it will no longer be called. This odd behavior is the
/// result of the [ChangeNotifier] not being able to determine which listener
/// is being removed, since they are identical, and therefore conservatively
/// still calling all the listeners when it knows that any are still
/// registered.
///
/// This surprising behavior can be unexpectedly observed when registering a
/// listener on two separate objects which are both forwarding all
/// registrations to a common upstream object.
@override
void removeListener(VoidCallback listener) {
assert(_debugAssertNotDisposed());
for (final _ListenerEntry entry in _listeners!) {
if (entry.listener == listener) {
entry.unlink();
return;
}
}
}

/// Discards any resources used by the object. After this is called, the
/// object is not in a usable state and should be discarded (calls to
/// [addListener] and [removeListener] will throw after the object is
/// disposed).
///
/// This method should only be called by the object's owner.
@mustCallSuper
void dispose() {
assert(_debugAssertNotDisposed());
_listeners = null;
}

/// Call all the registered listeners.
///
/// Call this method whenever the object changes, to notify any clients the
/// object may have changed. Listeners that are added during this iteration
/// will not be visited. Listeners that are removed during this iteration will
/// not be visited after they are removed.
///
/// Exceptions thrown by listeners will be caught and reported using
/// [FlutterError.reportError].
///
/// This method must not be called after [dispose] has been called.
///
/// Surprising behavior can result when reentrantly removing a listener (i.e.
/// in response to a notification) that has been registered multiple times.
/// See the discussion at [removeListener].
@protected
@visibleForTesting
void notifyListeners() {
assert(_debugAssertNotDisposed());
if (_listeners!.isEmpty)
return;

final List<_ListenerEntry> localListeners = List<_ListenerEntry>.from(_listeners!);

for (final _ListenerEntry entry in localListeners) {
try {
if (entry.list != null)
entry.listener();
} catch (exception, stack) {
FlutterError.reportError(FlutterErrorDetails(
exception: exception,
stack: stack,
library: 'foundation library',
context: ErrorDescription('while dispatching notifications for $runtimeType'),
informationCollector: () sync* {
yield DiagnosticsProperty<ChangeNotifier>(
'The $runtimeType sending notification was',
this,
style: DiagnosticsTreeStyle.errorProperty,
);
},
));
}
}
}
}

ChangeNotifierProvider

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
class ChangeNotifierProvider<T extends ChangeNotifier>
extends ListenableProvider<T> {
/// Creates a [ChangeNotifier] using `create` and automatically
/// dispose it when [ChangeNotifierProvider] is removed from the widget tree.
///
/// `create` must not be `null`.
ChangeNotifierProvider({
Key key,
@required Create<T> create,
bool lazy,
TransitionBuilder builder,
Widget child,
}) : super(
key: key,
create: create,
dispose: _dispose,
lazy: lazy,
builder: builder,
child: child,
);
/// Provides an existing [ChangeNotifier].
ChangeNotifierProvider.value({
Key key,
@required T value,
TransitionBuilder builder,
Widget child,
}) : super.value(
key: key,
builder: builder,
value: value,
child: child,
);

static void _dispose(BuildContext context, ChangeNotifier notifier) {
notifier?.dispose();
}
}

分析一下构造

  • Create<T> create

    是个通用函数typedef Create<T> = T Function(BuildContext context)用于创建T类,这里负责创建ChangeNotifier

  • bool lazy

    是否是懒加载

  • TransitionBuilder builder

    当builder存在时将不会用child作为子widget,追溯源码实现,根据继承关系找到InheritedProvider:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    @override
    Widget buildWithChild(BuildContext context, Widget child) {
    assert(
    builder != null || child != null,
    '$runtimeType used outside of MultiProvider must specify a child',
    );
    return _InheritedProviderScope<T>(
    owner: this,
    child: builder != null
    ? Builder(
    builder: (context) => builder(context, child),
    )
    : child,
    );
    }
  • Widget child

    builder不存在时就用child。

ChangeNotifierProvider继承 ListenableProvider,我们我们继续看ListenableProvider:

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
class ListenableProvider<T extends Listenable> extends InheritedProvider<T> {
/// Creates a [Listenable] using [create] and subscribes to it.
///
/// [dispose] can optionally passed to free resources
/// when [ListenableProvider] is removed from the tree.
///
/// [create] must not be `null`.
ListenableProvider({
Key key,
@required Create<T> create,
Dispose<T> dispose,
bool lazy,
TransitionBuilder builder,
Widget child,
}) : assert(create != null),
super(
key: key,
startListening: _startListening,
create: create,
dispose: dispose,
lazy: lazy,
builder: builder,
child: child,
);

/// Provides an existing [Listenable].
ListenableProvider.value({
Key key,
@required T value,
UpdateShouldNotify<T> updateShouldNotify,
TransitionBuilder builder,
Widget child,
}) : super.value(
key: key,
builder: builder,
value: value,
updateShouldNotify: updateShouldNotify,
startListening: _startListening,
child: child,
);

static VoidCallback _startListening(
InheritedContext<Listenable> e,
Listenable value,
) {
value?.addListener(e.markNeedsNotifyDependents);
return () => value?.removeListener(e.markNeedsNotifyDependents);
}
}
  • Listenable上面已经分析,它是负责管理观察者列表的抽象
  • 它比子类ChangeNotifierProvider多了一个构造函数dispose,这个函数是 typedef Dispose<T> = void Function(BuildContext context, T value);是个回调,应该是当页面被销毁时出发(需要继续探究源码,目前只是猜测)。

因为ListenableProvider是继承自InheritedProvider,我们继续看InheritedProvider的实现:

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
/// A generic implementation of an [InheritedWidget].
///
/// Any descendant of this widget can obtain `value` using [Provider.of].
///
/// Do not use this class directly unless you are creating a custom "Provider".
/// Instead use [Provider] class, which wraps [InheritedProvider].
///
/// See also:
///
/// - [DeferredInheritedProvider], a variant of this object where the provided
/// object and the created object are two different entity.
class InheritedProvider<T> extends SingleChildStatelessWidget {
/// Creates a value, then expose it to its descendants.
///
/// The value will be disposed of when [InheritedProvider] is removed from
/// the widget tree.
InheritedProvider({
Key key,
Create<T> create,
T Function(BuildContext context, T value) update,
UpdateShouldNotify<T> updateShouldNotify,
void Function(T value) debugCheckInvalidValueType,
StartListening<T> startListening,
Dispose<T> dispose,
this.builder,
bool lazy,
Widget child,
}) : _lazy = lazy,
_delegate = _CreateInheritedProvider(
create: create,
update: update,
updateShouldNotify: updateShouldNotify,
debugCheckInvalidValueType: debugCheckInvalidValueType,
startListening: startListening,
dispose: dispose,
),
super(key: key, child: child);

/// Expose to its descendants an existing value,
InheritedProvider.value({
Key key,
@required T value,
UpdateShouldNotify<T> updateShouldNotify,
StartListening<T> startListening,
bool lazy,
this.builder,
Widget child,
}) : _lazy = lazy,
_delegate = _ValueInheritedProvider(
value: value,
updateShouldNotify: updateShouldNotify,
startListening: startListening,
),
super(key: key, child: child);

InheritedProvider._constructor({
Key key,
_Delegate<T> delegate,
bool lazy,
this.builder,
Widget child,
}) : _lazy = lazy,
_delegate = delegate,
super(key: key, child: child);

final _Delegate<T> _delegate;
final bool _lazy;

/// Syntax sugar for obtaining a [BuildContext] that can read the provider
/// created.
///
/// This code:
///
/// ```dart
/// Provider<int>(
/// create: (context) => 42,
/// builder: (context, child) {
/// final value = context.watch<int>();
/// return Text('$value');
/// }
/// )
///

///
/// is strictly equivalent to:
///
/// dart /// Provider<int>( /// create: (context) => 42, /// child: Builder( /// builder: (context) { /// final value = context.watch<int>(); /// return Text('$value'); /// }, /// ), /// ) ///
///
/// For an explanation on the child parameter that builder receives,
/// see the “Performance optimizations” section of [AnimatedBuilder].
final TransitionBuilder builder;

@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
_delegate.debugFillProperties(properties);
}

@override
_InheritedProviderElement createElement() {
return _InheritedProviderElement(this);
}

@override
Widget buildWithChild(BuildContext context, Widget child) {
assert(
builder != null || child != null,
‘$runtimeType used outside of MultiProvider must specify a child’,
);
return _InheritedProviderScope(
owner: this,
child: builder != null
? Builder(
builder: (context) => builder(context, child),
)
: child,
);
}
}

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

构造函数中多出来的参数:

* T Function(BuildContext context, T value) update,该函数返回数据变更值value,具体实现在\_CreateInheritedProvider类中,说白了InheritedProvider\<T>是无状态的,他要变更的话依赖于\_CreateInheritedProvider类,\_CreateInheritedProvider是\_Delegate的实现类,\_Delegate是一个状态代理类,来看一下\_Delegate的实现

```dart
@immutable
abstract class _Delegate<T> {
_DelegateState<T, _Delegate<T>> createState();

void debugFillProperties(DiagnosticPropertiesBuilder properties) {}
}

abstract class _DelegateState<T, D extends _Delegate<T>> {
_InheritedProviderScopeElement<T> element;

T get value;

D get delegate => element.widget.owner._delegate as D;

bool get hasValue;

bool debugSetInheritedLock(bool value) {
return element._debugSetInheritedLock(value);
}

bool willUpdateDelegate(D newDelegate) => false;

void dispose() {}

void debugFillProperties(DiagnosticPropertiesBuilder properties) {}

void build({@required bool isBuildFromExternalSources}) {}
}

这里用到了委托模式,这里就有点类似StatefulWidget和State的关系,同样的_DelegateState提供了类似生命周期的函数,如willUpdateDelegate更新新的委托,dispose注销等。

  • UpdateShouldNotify<T> updateShouldNotify,
    void Function(T value) debugCheckInvalidValueType,
    StartListening<T> startListening,
    Dispose<T> dispose,这些函数全部交给了委托类。
  • 最关键的实现来了,到目前为止还没看到InheritedWidget的逻辑,他来了Widget buildWithChild(BuildContext context, Widget child),我们传入的Widget就被叫_InheritedProviderScope的类包裹了,看一下源码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class _InheritedProviderScope<T> extends InheritedWidget {
const _InheritedProviderScope({
this.owner,
@required Widget child,
}) : super(child: child);
final InheritedProvider<T> owner;
@override
bool updateShouldNotify(InheritedWidget oldWidget) {
return false;
}
@override
_InheritedProviderScopeElement<T> createElement() {
return _InheritedProviderScopeElement<T>(this);
}
}

至此你有没有发现一个特点,所有的函数都被_Delegate带走了,剩下的只有Widget交给了_InheritedProviderScope,这个设计很好,毕竟InheritedWidget其实也就只能做到数据共享,跟函数并没有关系,唯一有关系的地方,猜测在InheritedWidget提供的Widget中调用。

一个细节:this在buildWithChild函数中,将InheritedProvider本身传递给InheritedWidget,应该是为了方便调用它的_Delegate委托类,用来回调各种函数。

_InheritedProviderScope唯一特殊的地方,我们发现它自己创建了一个Element实现通过覆盖createElement函数,返回_InheritedProviderScopeElement实例,flutter三板斧Widget、Element、RenderObject,该框架自己实现一层Element,我们知道Widget是配置文件,只有build和rebuild以及remove from the tree,而Element作为一层虚拟Dom,主要负责优化,优化页面刷新的逻辑,我们详细看有一下_InheritedProviderScopeElement的源码:

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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
//继承自InheritedElement,因为InheritedWidget对应的Element就是它
//实现InheritedContext,InheritedContext继承自BuildContext,多了个T泛型
class _InheritedProviderScopeElement<T> extends InheritedElement
implements InheritedContext<T> {
//构造函数,将Element对应的Widget传进来
_InheritedProviderScopeElement(_InheritedProviderScope<T> widget)
: super(widget);
//是否需要通知依赖的Element变更
bool _shouldNotifyDependents = false;
bool _debugInheritLocked = false;
//是否允许通知变更
bool _isNotifyDependentsEnabled = true;
//是否第一次构建
bool _firstBuild = true;
//是否更新newWidget的Delegate委托
bool _updatedShouldNotify = false;
//这个变量就是控制的数据变更,在Widget变更和Element依赖变更的时候都会被设置为true
bool _isBuildFromExternalSources = false;
//委托类的状态 this就是为了那到上层的委托类
_DelegateState<T, _Delegate<T>> _delegateState;
@override
_InheritedProviderScope<T> get widget =>
super.widget as _InheritedProviderScope<T>;
@override
void reassemble() {
super.reassemble();
final value = _delegateState.hasValue ? _delegateState.value : null;
if (value is ReassembleHandler) {
value.reassemble();
}
}
@override
void updateDependencies(Element dependent, Object aspect) {
final dependencies = getDependencies(dependent);
// once subscribed to everything once, it always stays subscribed to everything
if (dependencies != null && dependencies is! _Dependency<T>) {
return;
}
if (aspect is _SelectorAspect<T>) {
final selectorDependency =
(dependencies ?? _Dependency<T>()) as _Dependency<T>;

if (selectorDependency.shouldClearSelectors) {
selectorDependency.shouldClearSelectors = false;
selectorDependency.selectors.clear();
}
if (selectorDependency.shouldClearMutationScheduled == false) {
selectorDependency.shouldClearMutationScheduled = true;
SchedulerBinding.instance.addPostFrameCallback((_) {
selectorDependency
..shouldClearMutationScheduled = false
..shouldClearSelectors = true;
});
}
selectorDependency.selectors.add(aspect);
setDependencies(dependent, selectorDependency);
} else {
// subscribes to everything
setDependencies(dependent, const Object());
}
}

@override
void notifyDependent(InheritedWidget oldWidget, Element dependent) {
final dependencies = getDependencies(dependent);

var shouldNotify = false;
if (dependencies != null) {
if (dependencies is _Dependency<T>) {
// select can never be used inside `didChangeDependencies`, so if the
// dependent is already marked as needed build, there is no point
// in executing the selectors.
if (dependent.dirty) {
return;
}

for (final updateShouldNotify in dependencies.selectors) {
try {
assert(() {
_debugIsSelecting = true;
return true;
}());
shouldNotify = updateShouldNotify(value);
} finally {
assert(() {
_debugIsSelecting = false;
return true;
}());
}
if (shouldNotify) {
break;
}
}
} else {
shouldNotify = true;
}
}

if (shouldNotify) {
dependent.didChangeDependencies();
}
}

@override
void performRebuild() {
if (_firstBuild) {
_firstBuild = false;
_delegateState = widget.owner._delegate.createState()..element = this;
}
super.performRebuild();
}

@override
void update(_InheritedProviderScope<T> newWidget) {
assert(() {
if (widget.owner._delegate.runtimeType !=
newWidget.owner._delegate.runtimeType) {
throw StateError('''
Rebuilt $widget using a different constructor.

This is likely a mistake and is unsupported.
If you're in this situation, consider passing a `key` unique to each individual constructor.
''');
}
return true;
}());

_isBuildFromExternalSources = true;
_updatedShouldNotify =
_delegateState.willUpdateDelegate(newWidget.owner._delegate);
super.update(newWidget);
_updatedShouldNotify = false;
}

@override
void updated(InheritedWidget oldWidget) {
super.updated(oldWidget);
if (_updatedShouldNotify) {
notifyClients(oldWidget);
}
}

@override
void didChangeDependencies() {
_isBuildFromExternalSources = true;
super.didChangeDependencies();
}

@override
Widget build() {
if (widget.owner._lazy == false) {
value; // this will force the value to be computed.
}
_delegateState.build(
isBuildFromExternalSources: _isBuildFromExternalSources,
);
_isBuildFromExternalSources = false;
if (_shouldNotifyDependents) {
_shouldNotifyDependents = false;
notifyClients(widget);
}
return super.build();
}

@override
void unmount() {
_delegateState.dispose();
super.unmount();
}

@override
bool get hasValue => _delegateState.hasValue;

@override
void markNeedsNotifyDependents() {
if (!_isNotifyDependentsEnabled) {
return;
}

markNeedsBuild();
_shouldNotifyDependents = true;
}

bool _debugSetInheritedLock(bool value) {
assert(() {
_debugInheritLocked = value;
return true;
}());
return true;
}

@override
T get value => _delegateState.value;

@override
InheritedWidget dependOnInheritedElement(
InheritedElement ancestor, {
Object aspect,
}) {
assert(() {
if (_debugInheritLocked) {
throw FlutterError.fromParts(
<DiagnosticsNode>[
ErrorSummary(
'Tried to listen to an InheritedWidget '
'in a life-cycle that will never be called again.',
),
ErrorDescription('''
This error typically happens when calling Provider.of with `listen` to `true`,
in a situation where listening to the provider doesn't make sense, such as:
- initState of a StatefulWidget
- the "create" callback of a provider

This is undesired because these life-cycles are called only once in the
lifetime of a widget. As such, while `listen` is `true`, the widget has
no mean to handle the update scenario.

To fix, consider:
- passing `listen: false` to `Provider.of`
- use a life-cycle that handles updates (like didChangeDependencies)
- use a provider that handles updates (like ProxyProvider).
'''),
],
);
}
return true;
}());
return super.dependOnInheritedElement(ancestor, aspect: aspect);
}

@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
_delegateState.debugFillProperties(properties);
}
}
  • void update(_InheritedProviderScope<T> newWidget)让页面重新build是在这里,因为InheritedElement继承自ProxyElement,而ProxyElement的update函数调用了两个函数updated(已更新完成),rebuild函数出发重新build逻辑,下面是代码

    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
    abstract class ProxyElement extends ComponentElement {
    /// Initializes fields for subclasses.
    ProxyElement(ProxyWidget widget) : super(widget);
    @override
    ProxyWidget get widget => super.widget as ProxyWidget;
    @override
    Widget build() => widget.child;
    @override
    void update(ProxyWidget newWidget) {
    final ProxyWidget oldWidget = widget;
    assert(widget != null);
    assert(widget != newWidget);
    super.update(newWidget);
    assert(widget == newWidget);
    updated(oldWidget);
    _dirty = true;
    rebuild();
    }
    /// Called during build when the [widget] has changed.
    ///
    /// By default, calls [notifyClients]. Subclasses may override this method to
    /// avoid calling [notifyClients] unnecessarily (e.g. if the old and new
    /// widgets are equivalent).
    @protected
    void updated(covariant ProxyWidget oldWidget) {
    notifyClients(oldWidget);
    }
  • performRebuild()是在update触发真正调用rebuild之后被调用。

  • updateDependencies、notifyDependent处理Element依赖逻辑

  • update、updated处理widget更新逻辑

  • didChangeDependencies当此State对象的依赖项更改时调用,子类很少重写此方法,因为框架总是在依赖项更改后调用build。一些子类确实重写了此方法,因为当他们的依存关系发生变化时,他们需要做一些昂贵的工作(如:网络获取),并且对于每个构建而言这些工作将太昂贵。

  • build()构建需要的widget,Element在调用build的时候也会触发Widget的build

  • void unmount()这里看到了_delegateState.dispose();的调用,当Element从树上移除的时候,回调了dispose函数

来看一个生命周期的图:

widget_lifecycle

  • notifyClients是InheritedElement中实现的函数,通过官方文档了解到,它是通过调用Element.didChangeDependencies通知所有从属Element此继承的widget已更改,此方法只能在构建阶段调用,通常,在重建Inherited widget时自动调用此方法,还有InheritedNotifier,它是InheritedWidget的子类,在其Listenable发送通知时也调用此方法。

  • markNeedsNotifyDependents,如果调用它,会强制build后,通知所有依赖Element刷新widget,看下面代码,发现该函数在InheritedContext中定义,所以我们可以通过InheritedContext上下文来强制页面的构建。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    abstract class InheritedContext<T> extends BuildContext {
    /// The current value exposed by [InheritedProvider].
    ///
    /// This property is lazy loaded, and reading it the first time may trigger
    /// some side-effects such as creating a [T] instance or start a subscription.
    T get value;

    /// Marks the [InheritedProvider] as needing to update dependents.
    ///
    /// This bypass [InheritedWidget.updateShouldNotify] and will force widgets
    /// that depends on [T] to rebuild.
    void markNeedsNotifyDependents();

    /// Wether `setState` was called at least once or not.
    ///
    /// It can be used by [DeferredStartListening] to differentiate between the
    /// very first listening, and a rebuild after `controller` changed.
    bool get hasValue;
    }

小结

我们先回顾一下我们是如何使用InheritedWidget的,为了能让InheritedWidget的子Widget能够刷新,我们不得不依赖于Statefulwidget,并通过State控制刷新Element,调用setState刷新页面,其实是底层调用_element.markNeedsBuild()函数,这样我们明白了,其实最终控制页面的还是Element,那么Provider它也巧妙的封装了自己的_delegateState,是私有的,并没有给我们公开是用,也没有提供类似setState,单可以通过markNeedsNotifyDependents大号和setState一样的调用效果,一样的都是让所有子Widget进行重建,可我们要局部刷新呢,那就是Consumer。

Consumer

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
class Consumer<T> extends SingleChildStatelessWidget {
/// {@template provider.consumer.constructor}
/// Consumes a [Provider<T>]
/// {@endtemplate}
Consumer({
Key key,
@required this.builder,
Widget child,
}) : assert(builder != null),
super(key: key, child: child);

/// {@template provider.consumer.builder}
/// Build a widget tree based on the value from a [Provider<T>].
///
/// Must not be `null`.
/// {@endtemplate}
final Widget Function(BuildContext context, T value, Widget child) builder;

@override
Widget buildWithChild(BuildContext context, Widget child) {
return builder(
context,
Provider.of<T>(context),
child,
);
}
}
  • 这里源码有点绕,widget child传给了父类 SingleChildStatelessWidget,最终通过buildWithChild函数的参数child传递回来,而Builder函数收到了此child,然后再组合child和需要刷新的widget组合一个新的widget给Consumer。一句话就是Consumer的构造函数可以传两个widget,一个是buider,一个是child,最终是通过builder构建最终的widget,如果child不为空,那么你需要自己组织child和builder中返回widget的关系。

  • Provider.of<T>(context)获取共享数据,怎么获取的,继续看代码

    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
    // 调用_inheritedElementOf函数
    static T of<T>(BuildContext context, {bool listen = true}) {
    assert(context != null);
    assert(
    context.owner.debugBuilding ||
    listen == false ||
    debugIsInInheritedProviderUpdate,
    '''
    Tried to listen to a value exposed with provider, from outside of the widget tree.

    This is likely caused by an event handler (like a button's onPressed) that called
    Provider.of without passing `listen: false`.

    To fix, write:
    Provider.of<$T>(context, listen: false);

    It is unsupported because may pointlessly rebuild the widget associated to the
    event handler, when the widget tree doesn't care about the value.

    The context used was: $context
    ''',
    );

    final inheritedElement = _inheritedElementOf<T>(context);

    if (listen) {
    context.dependOnInheritedElement(inheritedElement);
    }

    return inheritedElement.value;
    }

    static _InheritedProviderScopeElement<T> _inheritedElementOf<T>(
    BuildContext context,
    ) {
    assert(context != null, '''
    Tried to call context.read/watch/select or similar on a `context` that is null.

    This can happen if you used the context of a StatefulWidget and that
    StatefulWidget was disposed.
    ''');
    assert(
    _debugIsSelecting == false,
    'Cannot call context.read/watch/select inside the callback of a context.select',
    );
    assert(
    T != dynamic,
    '''
    Tried to call Provider.of<dynamic>. This is likely a mistake and is therefore
    unsupported.

    If you want to expose a variable that can be anything, consider changing
    `dynamic` to `Object` instead.
    ''',
    );
    _InheritedProviderScopeElement<T> inheritedElement;

    if (context.widget is _InheritedProviderScope<T>) {
    // An InheritedProvider<T>'s update tries to obtain a parent provider of
    // the same type.
    context.visitAncestorElements((parent) {
    inheritedElement = parent.getElementForInheritedWidgetOfExactType<
    _InheritedProviderScope<T>>() as _InheritedProviderScopeElement<T>;
    return false;
    });
    } else {
    inheritedElement = context.getElementForInheritedWidgetOfExactType<
    _InheritedProviderScope<T>>() as _InheritedProviderScopeElement<T>;
    }
    if (inheritedElement == null) {
    throw ProviderNotFoundException(T, context.widget.runtimeType);
    }
    return inheritedElement;
    }
  • 通过visitAncestorElements往复机查找_InheritedProviderScope的实现类也就是InheritedWidget,当找到时就返回_InheritedProviderScopeElement,而_InheritedProviderScopeElement正好可以拿到value,这个value也就是_delegateState的value

走到这里只是实现了读取数据,那么数据到底是如何刷新的呢?我们回过头来看下面极端代码:

1、Model数据调用ChangeNotifier提供的函数notifyListeners

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
@protected
@visibleForTesting
void notifyListeners() {
assert(_debugAssertNotDisposed());
if (_listeners!.isEmpty)
return;
final List<_ListenerEntry> localListeners = List<_ListenerEntry>.from(_listeners!);
for (final _ListenerEntry entry in localListeners) {
try {
if (entry.list != null)
entry.listener();
} catch (exception, stack) {
FlutterError.reportError(FlutterErrorDetails(
exception: exception,
stack: stack,
library: 'foundation library',
context: ErrorDescription('while dispatching notifications for $runtimeType'),
informationCollector: () sync* {
yield DiagnosticsProperty<ChangeNotifier>(
'The $runtimeType sending notification was',
this,
style: DiagnosticsTreeStyle.errorProperty,
);
},
));
}
}
}

这个时候遍历所有的监听,然后执行函数listener(),这里其实等于执行VoidCallback的实例,那这个listener到底是哪个函数?

2、在ChangeNotifierProvider父类ListenableProvider的静态函数中,自动订阅了观察者,前面说了观察者就是一个普通函数,而e.markNeedsNotifyDependents就是InheritedContext的一个函数,当你notifyListemers的时候执行的就是它markNeedsNotifyDependents,上面我们知道了markNeedsNotifyDependents类似setState效果,就这样实现了UI的刷新。

1
2
3
4
5
6
7
8
//ListenableProvider 的静态函数
static VoidCallback _startListening(
InheritedContext<Listenable> e,
Listenable value,
) {
value?.addListener(e.markNeedsNotifyDependents);
return () => value?.removeListener(e.markNeedsNotifyDependents);
}

现在还有一个点就是局部刷新,我们继续寻找

1
2
3
4
5
6
7
8
@override
Widget buildWithChild(BuildContext context, Widget child) {
return builder(
context,
Provider.of<T>(context),
child,
);
}

Consumer通过Provider.of<T>(context)这段代码才能监听到数据,而且刷新的内容也只是这一部分,我们再看下它的实现发现另一个细节

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
  static T of<T>(BuildContext context, {bool listen = true}) {
assert(context != null);
assert(
context.owner.debugBuilding ||
listen == false ||
debugIsInInheritedProviderUpdate,
'''
Tried to listen to a value exposed with provider, from outside of the widget tree.

This is likely caused by an event handler (like a button's onPressed) that called
Provider.of without passing `listen: false`.

To fix, write:
Provider.of<$T>(context, listen: false);

It is unsupported because may pointlessly rebuild the widget associated to the
event handler, when the widget tree doesn't care about the value.

The context used was: $context
''',
);

final inheritedElement = _inheritedElementOf<T>(context);

if (listen) {
context.dependOnInheritedElement(inheritedElement);
}
return inheritedElement.value;
}

它调用了BuildContext的dependOnInheritedElement函数,这个函数做了什么

1
2
3
4
5
6
7
8
9
10
11
12
13
InheritedWidget dependOnInheritedElement(InheritedElement ancestor, { Object aspect });
/// Obtains the nearest widget of the given type, which must be the type of a
/// concrete [InheritedWidget] subclass, and registers this build context with
/// that widget such that when that widget changes (or a new widget of that
/// type is introduced, or the widget goes away), this build context is
/// rebuilt so that it can obtain new values from that widget.
///
/// This method is deprecated. Please use [dependOnInheritedWidgetOfExactType] instead.
// TODO(a14n): Remove this when it goes to stable, https://github.com/flutter/flutter/pull/44189
@Deprecated(
'Use dependOnInheritedWidgetOfExactType instead. '
'This feature was deprecated after v1.12.1.'
)

触发updateDependencies,通过setDependencies,将Element缓存到_dependetns Map中,最后通过下面代码更新:

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
@override
void notifyDependent(InheritedWidget oldWidget, Element dependent) {
final dependencies = getDependencies(dependent);
var shouldNotify = false;
if (dependencies != null) {
if (dependencies is _Dependency<T>) {
// select can never be used inside `didChangeDependencies`, so if the
// dependent is already marked as needed build, there is no point
// in executing the selectors.
if (dependent.dirty) {
return;
}
for (final updateShouldNotify in dependencies.selectors) {
try {
assert(() {
_debugIsSelecting = true;
return true;
}());
shouldNotify = updateShouldNotify(value);
} finally {
assert(() {
_debugIsSelecting = false;
return true;
}());
}
if (shouldNotify) {
break;
}
}
} else {
shouldNotify = true;
}
}
if (shouldNotify) {
dependent.didChangeDependencies();
}
}

所以说整体流程是这样,当notifyListeners的时候其实是出发了InheritedWidget的performRebuild,再到build,build后触发notifyClients,notifyClients触发notifyDependent,notifyDependent这个时候通过getDependencies获取缓存好的Element,最终确定是否需要刷新然后调用dependent.didChangeDependencies()更新,只要widget中通过Provider.of函数订阅后,就会被InheritedWidget缓存在一个Map中,然后刷新页面的时候,如果子Widget不在缓存的Map中,就不会走刷新,而且如果shouldNotify变量是false也不会刷新,这个控制可能是虽然widget订阅了,但它自己就是不刷新,可以更加细粒度的控制。

总结

  • Provider通过缓存inheritedElement实现局部刷新。
  • 通过控制自己实现的Element层来更新UI
  • 通过Element提供的unmount函数回调dispose,实现选择性释放。

provider_class_view

转载自

状态管理是什么

我们知道最基本的程序是什么

  • 程序 = 算法 + 数据结构

    数据是程序的中心。数据结构和算法两个概念间的逻辑关系贯穿整个程序世界,首先两者表现为不可分割的关系。flutter也是一个程序。由此得出

  • Flutter = 算法 + 数据结构

    那状态管理是什么?我们也用公式表示一下:

  • Flutter状态管理 = 算法 + 数据结构 + UI绑定

    来看一段代码例子:

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
class ThemeBloc {
final _themeStreamController = StreamController<AppTheme>();
get changeTheTheme => _themeStreamController.sink.add;
get darkThemIsEnABLE => _themeStreamController.stream;
dispose(){
_themStreamController.close();
}
}
final bloc = ThemeBloc();

class AppTheme {
ThemeData themeData;
AppTheme(this.themeData);
}
//绑定到UI
StreamBulder<AppTheme>(
initialData:AppTheme.LIGHT_THEME,
stream:bloc.dartThemeIsEnabled,
builder:(contenxt,AsyncSnapshot<AppTheme> snapshot){
return MaterialApp(
title:"Jason",
theme:snapshot.data.themeData,
home:PageHome(),
routes:<String,WidgetBuilder>{
"/pageChatGroup":(context) => PageChatGroup(),
"/laoYu":(context) => LaoYu(),
}
);
}
)
  • AppTheme 是数据结构
  • changeTheTheme 是算法
  • StreamBuilder 是绑定UI

这样一整套代码的逻辑就是我们所说的Flutter状态管理。算法就是我们如何管理,数据结构是数据状态,状态管理的本质还是如何通过合理的算法管理数据,如何取,如何接收等,最终展示在UI上,通过UI的变更来提现状态的管理逻辑。

为什么需要状态管理

状态管理可以实现组件通信、跨组件数据储存。原生的状态变更是通过具体的组件直接复制,如果页面全部变更,是不是需要每一个都设置一遍,而Flutter的变更就简单粗暴 ,setState搞定,它背后的逻辑是重新build整个页面,发现有变更,再将新的数据复制。其实原生开发与Flutter的本质的区别就是数据与视图完全分离,目前原生也出现了UI绑定框架。说明flutter的设计的先进性。这样设计的弊端是什么?

答案就是页面如何刷新才是Flutter的关键,原生也面临这个问题,页面的重绘导致丢帧问题,为了效果更好,我们很多时候都选择局部刷新,原生已经很明确的告诉UI要刷新什么更新什么,而对于Flutter来说,这一点恨不清晰,虽然Flutter也做了类似虚拟Dom优化重绘逻辑,但这远远不够,如何合理的更新UI才是最主要的,这个时候一大堆的状态管理就出来了,当然状态管理也不是仅仅为了解决更新问题。我们再思考一个问题,如果我有一个widget A,我想在另外一个widget B中改变widget A的一个状态,或者从网络、数据库取到数据,然后刷新它。

一个糟糕的写法是,A widget的 state为全局的,当B widget 需要刷新的时候 直接调用 AState对象的 setState 方法.

这样写有几个问题:

1、违背了封装的概念,其他所有类都可以拿到A的state对象。

2、A和B强耦合了。

3、每次都是重绘整个widget A 性能变差。

4、不利于测试。

如何变好了,这就需要我们选择一种合适的状态管理方式。

状态管理的目标

其实我们做状态管理,不仅仅是因为它的特点,也是更好地架构。

  • 代码层次分明,易维护,易阅读。
  • 可扩展,可以动态替换UI而不影响算法逻辑。
  • 安全可靠,保持数据的稳定伸缩
  • 性能佳,局部刷新。

这不仅仅是状态管理的目的,也是我们做一款优秀应用的基础架构。

状态管理的基本分类

  • 局部管理 官方也称Ephemeral State,意思是短暂的状态,这种状态根本不需要做全局处理,StatefulWidget处理即可完成。
  • 全局管理 官方称App state ,即应用状态,非短暂状态,你要在应用程序的许多部分之间共享,以及希望在用户会话之间保持的状态,就是我们所说的应用程序状态(也称共享状态)。例如:
    • 用户偏好
    • 登录信息
    • 购物车
    • 新闻阅读状态

总之,任何Flutter应用程序中都有两种概念性的状态类型。临时状态可以使用State和setState来事项,并且通常是单个窗口小部件的本地状态。剩下的就是应用的状态。两种类型在任何Flutter应用程序中都有自己的位置,两者之间的划分取决于你自己的喜好和应用程序的复杂性。没有最好的管理方式,只有最合适的管理方式。

底层逻辑

底层逻辑主要讲,Flutter中目前有哪些可以做到状态管理,有什么缺点,适合做什么不适合做什么,完全明白底层逻辑才不会畏惧复杂的逻辑,即使逻辑复杂也能选择合理的方式去管理状态。

  • State

    StatefulWidget、SteamBuilder状态管理方式。

  • InheritedWidget

    专门负责Widget树种数据共享的功能型Widget,如Provider、scoped_model就是基于它开发

  • Notification

    与InheritedWidget正好相反,InheritedWidget是从上往下传递数据,Notification是从下往上,但两者都是在自己的Widget树种传递,无法跨越树传递。

  • Stream

    数据流 如Bloc、flutter_redux、fish_redux等也都基于它来做实现。

下面一一分析

State

State是我们常用而且使用最频繁的一个状态管理类,它必须结合StatefulWidget一起使用,StreamBuilder继承自StatefulWidget,同样使用setState来管理状态。

为什么用State管理状态,而不是Widget本身。Flutter设计时让Widget本身是不变的,类似固定的配置信息,那么就需要一个角色来控制它,State就出现了,但State的任何更改都会强制整个Widget重新构建,当然也可以覆盖必要的方法自己控制逻辑。

注意:setState是整个Widget重新构建(子Widget也会销毁重建),这个点也是为什么不推荐大量使用StatefulWidget的原因。如果页面足够复杂,就会导致严重的性能损耗。建议使用StreamBuilder,它原理上是State,但它做到了子Widget的局部刷新,不会导致整个页面的重建。

State的缺点

  • 无法做到夸组件共享数据(这个夸是无关联的,如果是直接的父子关系,不算是跨组件)。一般我们将State的子类设置为私有,所以无法做到让别的组件调用State的setState函数来刷新。
  • setState会成为维护的难点,因为到处都有。随着页面状态的增多,可能调用的setState的地方会越来越多,不能统一管理。
  • 处理数据逻辑和视图混合在一起,违反代码设计原则。比如网络的数据取出来setState的UI上,这样编写代码,导致状态和UI耦合在一起,不利于测试和复用。

小结

反过来讲,State简单高效,当复杂到需要更好地管理的时候再重构,一个基本原则就是,状态是否需要跨组件使用,如果需要那就用别的方法管理状态而不是State管理。

InheritedWidget

InheritedWidget是一个无私的Widget,它可以把自己的状态数据,无私交给所有的子Widget,所有的子Widget可以无条件的继承它的状态。它的数据是只读的,子widget不能修改。

小结

缺点:

  • 容易造成不必要的刷新。
  • 不支持跨页面(router)的状态,不是同一个树的状态无法获取。
  • 数据不可变,必须结合StatefulWidget、ChangeNotifier或者Steam使用。

比较适合在一个属性Widget中,抽象出公有状态,每一个子Widget或者孙Widget都可以获取该状态,我们可以通过手段控制rebuild的粒度来优化重绘逻辑,但它更适合从上往下传递,如果从下往上传递,如何做,下面解答。

Notification

它是flutter中跨层数据共享的一种机制,它不是widget,它提供了dispatch方法,来让我们沿着context对应的Element节点向上逐层发送通知。

小结:

缺点

  • 不支持跨页面的状态,准确的说不支持NotificationListener同级或者父级Widget的状态通知
  • 本身不支持刷新UI,需要结合State使用
  • 如果结合State,会导致整个UI的重绘,效率低下不科学。

使用起来简单,如果页面复杂度高不推荐。

Stream

Stream其实是一个生产者消费者模型,一端负责生产,一端负责消费,而且是纯Dart的实现,跟Flutter没什么关系,扯上关系的就是StreamBuilder来构建一个Stream通道的Widget,像知名的rxdart、Bloc、flutter_redux全都用到了Stream的api。所以学习它才是我们掌握状态管理的关键。

小结

缺点

  • api生涩,不好理解。
  • 需要定制化,才能满足更复杂的场景。
  • 没有自动dispose逻辑。

总结

以上所有的状态更新都离不开State的支持。

状态管理的使用原则

局部管理优于全局

这个原则来源于,Flutter的性能优化,局部刷新肯定比全局刷新要好很多,那么我们在管理状态的同时,也要考虑该状态到底是局部还是全局,从而编写正确的逻辑。

保持数据安全性

_私有化状态,因为当开发人员众多,当别人看到你的变量的时候,第一反应可能不是找你提供的方法,而是直接对变量操作,那就有可能出现想不到的后果,如果他只能调用你提供的方法,那他就要遵循你方法的逻辑,避免数据被处理错误。

考虑页面重新build带来的影响

很多时候页面的重建都会调用build函数,也就是说,在一个生命周期内,build函数是多次被调用的,所以你就要考虑数据的初始化或者刷新怎么样才能合理。

在Objective-C中,block是一个很常见的东西,说白了就是个匿名函数,网上有很多关于block如何使用的文章,讲的都非常精彩,这里主要探讨下block的实现原理。

先看一个例子

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
void blockFunc1() {
int num = 100;
void (^block)(void) = ^() {
NSLog(@"num = %d\n", num);
};
num = 200;
block();
}

void blockFunc2() {
__block int num = 100;
void (^block)(void) = ^() {
NSLog(@"num = %d\n", num);
};
num = 200;
block();
}

int num = 100;
void blockFunc3() {
void (^block)(void) = ^() {
NSLog(@"num = %d\n", num);
};
num = 200;
block();
}

void blockFunc4()
{
static int num = 100;
void (^block)(void) = ^{
NSLog(@"num = %d", num);
};
num = 200;
block();
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
^{ printf("Hello, World!\n"); } ();
blockFunc1();
blockFunc2();
blockFunc3();
blockFunc4();
}
return 0;
}

打印结果:

1
2
3
4
5
Hello, World!
2021-05-19 11:25:19.532572+0800 OCDemo[60496:2272335] num = 100
2021-05-19 11:25:19.534041+0800 OCDemo[60496:2272335] num = 200
2021-05-19 11:25:19.534166+0800 OCDemo[60496:2272335] num = 200
2021-05-19 11:25:19.534292+0800 OCDemo[60496:2272335] num = 200

我们先简单解释一下:

1、”Hello, World!”应该很简单,执行block直接打印。

2、blockFunc1里面,num是以值传递的方式被block获取,所以尽管后面更改了num的值,但是在block里面还是保持保持原来的值。

3、blockFunc2里面,num由__block修饰,num在block变成了外部的一个引用(后面会通过源码解释),所以在block外部改变num的值时,block里面的num也随着改变。

4、blockFunc3里面,block引用的是一个全局的num,所以,num改变的时候也会改变block内部num的值。

5、blockFunc3里面,block引用的是一个static的num,所以,num改变也会改变block内部的num的值。

源码分析

也许大家看到上面的解释还是不知道为啥会这样,所以接下,我通过源码来分析下其中的缘由,我们先把这段先转换成c++文件,cd到main.m所在的目录,并执行这条命令clang -rewrite-objc main.m

如果上面的命令报错'UIKit/UIKit.h' file not found,可以使用下面命令

clang -x objective-c -rewrite-objc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk main.m

通过这条命令可以把main.m文件转换成cpp文件,里面可以看到block的结构。我们打开这份文件,这个文件比较长,直接拉到最后。可以看到在文件的最后是main函数的入口,代码如下:

1
2
3
4
5
6
7
8
9
10
11
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA)) ();
blockFunc1();
blockFunc2();

blockFunc3();
blockFunc4();
}
return 0;
}

先看第一行代码,构造了一个main_block_impl_0对象,main_block_impl_0是一个结构体。相关代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};

从代码可知,最后block会转化成一个__block_impl对象,而block执行的代码会转化成一个静态函数,__block_impl里面的FuncPtr会指向这个静态函数。在这里printf("Hello, World!\n");这个block转换后的静态函数如下:

1
2
3
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
printf("Hello, World!\n");
}

所以整个过程是这样的:
1、先构造一个__main_block_impl_0对象,构造的时候把__main_block_func_0传进去,当然还有别的参数,这里先不考虑。

2、在__main_block_impl_0的构造方法中,再把__main_block_func_0赋给__block_impl的FuncPtr。

3、调用FuncPtr。

所以,从上面可以看出,block实际上是转化为了一个__block_impl对象,这个对象有isa指针,用来表示block的类型,上面的block的isa指向&_NSConcreteStackBlock。同时block对象还有一个FuncPtr指针,用来指向block执行的方法(转换后的静态函数)。

再来看看blockFunc1相关的内容

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
struct __blockFunc1_block_impl_0 {
struct __block_impl impl;
struct __blockFunc1_block_desc_0* Desc;
int num;
__blockFunc1_block_impl_0(void *fp, struct __blockFunc1_block_desc_0 *desc, int _num, int flags=0) : num(_num) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __blockFunc1_block_func_0(struct __blockFunc1_block_impl_0 *__cself) {
int num = __cself->num; // bound by copy

NSLog((NSString *)&__NSConstantStringImpl__var_folders__s_l_p47fxn36j8d2_n75spyb7c0000gn_T_main_324f63_mi_0, num);
}

static struct __blockFunc1_block_desc_0 {
size_t reserved;
size_t Block_size;
} __blockFunc1_block_desc_0_DATA = { 0, sizeof(struct __blockFunc1_block_impl_0)};
void blockFunc1() {
int num = 100;
void (*block)(void) = ((void (*)())&__blockFunc1_block_impl_0((void *)__blockFunc1_block_func_0, &__blockFunc1_block_desc_0_DATA, num));
num = 200;
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}

在这个函数里面,构造block的时候把num传了进去,而且是普通值传递,这样的话其实是拷贝了一份num。然后在执行block方法的时候,使用的是拷贝的那份num,从int num = __cself->num; // bound by copy可以看出。这个block也是_NSConcreteStackBlock类型的。

再来看看__block修饰过的num在block里面是怎么传递的,我们看看blockFunc2相关的代码:

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
struct __Block_byref_num_0 {
void *__isa;
__Block_byref_num_0 *__forwarding;
int __flags;
int __size;
int num;
};
struct __blockFunc2_block_impl_0 {
struct __block_impl impl;
struct __blockFunc2_block_desc_0* Desc;
__Block_byref_num_0 *num; // by ref
__blockFunc2_block_impl_0(void *fp, struct __blockFunc2_block_desc_0 *desc, __Block_byref_num_0 *_num, int flags=0) : num(_num->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __blockFunc2_block_func_0(struct __blockFunc2_block_impl_0 *__cself) {
__Block_byref_num_0 *num = __cself->num; // bound by ref

NSLog((NSString *)&__NSConstantStringImpl__var_folders__s_l_p47fxn36j8d2_n75spyb7c0000gn_T_main_324f63_mi_1, (num->__forwarding->num));
}
static void __blockFunc2_block_copy_0(struct __blockFunc2_block_impl_0*dst, struct __blockFunc2_block_impl_0*src) {_Block_object_assign((void*)&dst->num, (void*)src->num, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __blockFunc2_block_dispose_0(struct __blockFunc2_block_impl_0*src) {_Block_object_dispose((void*)src->num, 8/*BLOCK_FIELD_IS_BYREF*/);}

static struct __blockFunc2_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __blockFunc2_block_impl_0*, struct __blockFunc2_block_impl_0*);
void (*dispose)(struct __blockFunc2_block_impl_0*);
} __blockFunc2_block_desc_0_DATA = { 0, sizeof(struct __blockFunc2_block_impl_0), __blockFunc2_block_copy_0, __blockFunc2_block_dispose_0};
void blockFunc2() {
__attribute__((__blocks__(byref))) __Block_byref_num_0 num = {(void*)0,(__Block_byref_num_0 *)&num, 0, sizeof(__Block_byref_num_0), 100};
void (*block)(void) = ((void (*)())&__blockFunc2_block_impl_0((void *)__blockFunc2_block_func_0, &__blockFunc2_block_desc_0_DATA, (__Block_byref_num_0 *)&num, 570425344));
(num.__forwarding->num) = 200;
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}

从代码中可以看到,__block修饰的num在内部被包装成一个__Block_byref_num_0的对象,假设叫a,原来num的值100存储在对象a的num字段中,同时这个对象a有一个forwarding字段,指向a本身。当改变num的值的时候(源代码是num = 200;),这段代码变为`(num.forwarding->num) = 200;,也就是说把对象a里面的num字段的值变为了200。同时,在block的执行函数__blockFunc2_block_func_0中,打印出来的取值是从Block_byref_num_0 *num = __cself->num;`取出,也就是取得是改变后的值,所以打印结果是200。这就是为什么用block修饰的变量可以在block内部被修改。

那当num为全局变量的时候,block又是怎样的呢?请看代码:

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
int num = 100;

struct __blockFunc3_block_impl_0 {
struct __block_impl impl;
struct __blockFunc3_block_desc_0* Desc;
__blockFunc3_block_impl_0(void *fp, struct __blockFunc3_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __blockFunc3_block_func_0(struct __blockFunc3_block_impl_0 *__cself) {

NSLog((NSString *)&__NSConstantStringImpl__var_folders__s_l_p47fxn36j8d2_n75spyb7c0000gn_T_main_324f63_mi_2, num);
}

static struct __blockFunc3_block_desc_0 {
size_t reserved;
size_t Block_size;
} __blockFunc3_block_desc_0_DATA = { 0, sizeof(struct __blockFunc3_block_impl_0)};
void blockFunc3() {
void (*block)(void) = ((void (*)())&__blockFunc3_block_impl_0((void *)__blockFunc3_block_func_0, &__blockFunc3_block_desc_0_DATA));
num = 200;
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}

从代码里可以看出,这种情况很简单,block对象根本没有num字段,也就是打印的时候直接取得全局的num。

最后一种情况也很简单,当num时static的时候,构造block对象的时候直接用引用传值的方式把num放到block对象中。所以,当外部改变num的值的时候,也能反映到block内部。代码如下:

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
struct __blockFunc4_block_impl_0 {
struct __block_impl impl;
struct __blockFunc4_block_desc_0* Desc;
int *num;
__blockFunc4_block_impl_0(void *fp, struct __blockFunc4_block_desc_0 *desc, int *_num, int flags=0) : num(_num) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __blockFunc4_block_func_0(struct __blockFunc4_block_impl_0 *__cself) {
int *num = __cself->num; // bound by copy

NSLog((NSString *)&__NSConstantStringImpl__var_folders__s_l_p47fxn36j8d2_n75spyb7c0000gn_T_main_324f63_mi_3, (*num));
}

static struct __blockFunc4_block_desc_0 {
size_t reserved;
size_t Block_size;
} __blockFunc4_block_desc_0_DATA = { 0, sizeof(struct __blockFunc4_block_impl_0)};
void blockFunc4()
{
static int num = 100;
void (*block)(void) = ((void (*)())&__blockFunc4_block_impl_0((void *)__blockFunc4_block_func_0, &__blockFunc4_block_desc_0_DATA, &num));
num = 200;
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}

总结

这篇文章主要从源码的角度讲述了block的实现机制,并针对四种情况分析了block是如何引用外部变量的,分别是:
1、当引用局部变量的时候,如果没有__block修饰,那么在block内部获取的是外部变量的一份拷贝,改变外部变量不影响block内部的那份拷贝。

2、当引用局部变量的时候,同时局部变量用__block修饰,那么在block内部使用的实际上是外部变量的一个引用,所以改变外部变量会影响block内部变量的值。

3、当引用全局变量的时候,block并不持有这个变量。

4、当引用static变量的时候,block会以引用的方式持有这个变量。当在外部修改这个变量的时候,会影响block内部持有的这个变量的值。

多线程是我们开发和面试中都会遇到的一个重要概念,相比于其他编程语言和平台,iOS 的多线程使用起来要比较友好和易用一些。但是对于多线程的基本概念,我们还是需要重视起来,这对于我们探索 pthreadNSThreadGCD 以及 RunLoop 都大有裨益。

前导知识

  • POSIX

POSIX Threads, usually referred to as pthreads, is an execution model that exists independently from a language, as well as a parallel execution model. It allows a program to control multiple different flows of work that overlap in time. Each flow of work is referred to as a thread, and creation and control over these flows is achieved by making calls to the POSIX Threads API.

– POSIX Threads, Wikipedia

译:

POSIX(Portable Operating System Interface of UNIX,可移植操作系统接口)线程,即 pthreads,是一种不依赖于语言的执行模型,也称作并行(Parallel)执行模型。其允许一个程序控制多个时间重叠的不同工作流。每个工作流即为一个线程,通过调用 POSIX 线程 API 创建并控制这些流。

– POSIX 线程,维基百科

  • detached 和 joinable

无论在windows中还是Posix中,主线程和子线程的默认关系是:无论子线程执行完毕与否,一旦主线程执行完毕退出,所有子线程执行都会终止。这时整个进程结束或僵死,部分线程保持一种终止执行但还未销毁的状态,而进程必须在其所有线程销毁后销毁,这时进程处于僵死状态。线程函数执行完毕退出,或以其他非常方式终止,线程进入终止态,但是为线程分配的系统资源不一定释放,可能在系统重启之前,一直都不能释放,终止态的线程,仍旧作为一个线程实体存在于操作系统中,什么时候销毁,取决于线程属性。在这种情况下,主线程和子线程通常定义以下两种关系:

1、可会合(joinable):这种关系下,主线程需要明确执行等待操作,在子线程结束后,主线程的等待操作执行完毕,子线程和主线程会合,这时主线程继续执行等待操作之后的下一步操作。主线程必须会合可会合的子线程。在主线程的线程函数内部调用子线程对象的wait函数实现,即使子线程能够在主线程之前执行完毕,进入终止态,也必须执行会合操作,否则,系统永远不会主动销毁线程,分配给该线程的系统资源也永远不会释放。

2、相分离(detached):表示子线程无需和主线程会合,也就是相分离的,这种情况下,子线程一旦进入终止状态,这种方式常用在线程数较多的情况下,有时让主线程逐个等待子线程结束,或者让主线程安排每个子线程结束的等待顺序,是很困难或不可能的,所以在并发子线程较多的情况下,这种方式也会经常使用。

在任何一个时间点上,线程是可结合(joinable)或者是可分离的(detached),一个可结合的线程能够被其他线程回收资源和杀死,在被其他线程回收之前,它的存储器资源如栈,是不释放的,相反,一个分离的线程是不能被其他线程回收或杀死的,它的存储器资源在它终止时由系统自动释放。

线程的分离状态决定一个线程以什么样的方式来终止自己,在默认的情况下,线程是非分离状态的,这种情况下,原有的线程等待创建的线程结束,只有当pthread_join函数返回时,创建的线程才算终止,释放自己占用的系统资源,而分离线程没有被其他的线程所等待,自己运行结束了,线程也就终止了,马上释放系统资源。

joinable 和 detaced 其实是主线程与子线程之间的一种关系。app 退出后,detached 线程直接进入终止态,其栈空间资源被系统回收,但是 joinable 线程资源不会被回收,所以可以在 app 退出的时候使用 joinable 线程也就是 pthread 来做一些保存资源到磁盘的操作。也就是说在主线程执行完毕要退出之前,会去处理joinable的线程。

线程初探

线程定义

要先了解什么是线程,我们需要先了解什么是进程。对于 iOS 来说,每一个 app 其实就是一个进程,这一点和 Android 有很大的区别。除了单进程之外,在未越狱之前,每个 app 只能访问每个 app 自身的沙盒环境,不能访问之外的内容。得益于这样的设计,iOS 成为了世界上最安全的操作系统(苹果是这么的说的🐶)。

下面给出苹果官方对于线程的定义

Threads are a relatively lightweight way to implement multiple paths of execution inside of an application.

【译】线程是在应用程序内部实现多个执行路径的相对轻量的方法。

From a technical standpoint, a thread is a combination of the kernel-level and application-level data structures needed to manage the execution of code. The kernel-level structures coordinate the dispatching of events to the thread and the preemptive scheduling of the thread on one of the available cores. The application-level structures include the call stack for storing function calls and the structures the application needs to manage and manipulate the thread’s attributes and state.

【译】从技术角度来看,线程是管理代码执行所需的内核级和应用程序级数据结构的组合。内核负责线程事件的分发和线程优先级的调度,应用层则负责存储线程函数中断时的状态和属性的存储方便下次内核切换时再从存储的地方运行。

线程的定义可以总结为以下三点:

  • 线程是进程的基本执行单元,一个进程的所有任务都在线程中执行。
  • 进程想要执行任务,必须得有线程,进程至少要有一条线程。
  • 程序启动会默认开启一条线程,这条线程被称为主线程或 UI 线程。

下面我们看一下进程的定义:

  • 进程是指在系统中正在运行的一个应用程序
  • 每个进程之间是独立的,每个进程均运行在其专用的且受保护的内存空间内

这两句话不难理解,只针对于 iOS 来说,一个 app 就是一个进程,而由于有沙盒机制,每个 app 所对应的进程只能访问当前 app 沙盒所对应的内存空间,是相互独立的。

我们的 app 只有一条进程,而线程的话,默认只有一条主线程。我们可以通过多线程技术来开线程然后启动线程来执行任务。而进程与线程之间的关系可以总结为下列几点:

  • 地址空间:同一进程的线程共享本进程的地址空间,而进程之间则是独立的地址空间。
  • 资源拥有:同一进程内的线程共享本进程的资源如内存、I/O、CPU 等,但是进程之间资源是独立的。
  • 一个线程崩溃后,在保护模式下不会对其它进程产生影响,但是一个线程崩溃后会导致整个进程的崩溃,所以多进程相比多线程更加健壮。
  • 进程切换时,消耗的资源大,效率低。所以涉及到频繁的上下文切换的时候,使用线程要好于进程。同样的,如果要求同时进行且要共享某些变量的并发操作,只能用线程,不能用进程。
  • 执行过程:每个独立的进程有一个程序运行的入口、顺序执行序列和程序入口。但是线程不能独立执行,必须依存于应用程序中,由应用程序提供多个线程执行控制。
  • 线程是 CPU 调度的基本单位,进程不是。

线程相关的术语

在深入讨论线程及其支持技术之前,有必要弄清楚一些基本术语。

如果你熟悉 UNIX 系统,术语 任务 于表示正在运行的进程,但在本文中并不是这样定义的。

本文采用的术语如下:

  • 术语 线程 用于指代代码的独立执行路径。
  • 术语 进程 用于指代一个正在运行的可执行文件,它可以包含多个线程。
  • 术语 任务 用于指代需要执行的工作的抽象概念。

线程的替代方案

手动创建线程会给我们的代码带来一定的不确定性。相对来说线程属于抽象层次比较低且使用起来比较麻烦的一种让应用程序支持并发的方案。如果你对于直接使用线程不熟悉的话,那么很容易遇到线程同步和时序问题,其严重性可能从细微的问题到应用程序崩溃和用户数据损坏。

所以如上图所示,苹果官方给出了以下几种线程的替代方案:

  • Operation Objects: 任务对象(NSOpeartion)是 OS X 10.5 推出的一个特性。通过封装在子线程执行的任务,隐藏底层的线程管理的具体细节,让开发者聚焦于任务执行的本身。通常来说任务对象会与任务对象队列(NSOperationQueue)结合使用,来实现在一个或多个线程上任务的执行。
  • GCD: OS 10.6 正式推出,GCD 是另外一种可以让开发者无需知道线程细节而专注于任务本身的一项技术。通过使用 GCD,你可以定义你需要执行的任务,然后把这个任务加入到一个队列中来让 GCD 选择在一个合适的线程上执行这个任务。队列考虑了可用核心的数量和当前负载,这样与直接使用线程相比可以更有效地执行任务。
  • Idle-time notifications: 空闲时通知,对于优先级和复杂度相对来说不高的任务,空闲时通知技术可以在应用程序空闲时执行任务。Cocoa 使用 NSNotificationQueue 来实现这一技术。为了获得空闲时通知,你需要使用 NSPostWhenIdle 选项来向 NSNotificationQueue 队列发送通知,然后直到 Runloop 空闲时,队列才会执行的通知里面的具体任务。

NSNotificationQueue 官方定义

Whereas a notification center distributes notifications when posted, notifications placed into the queue can be delayed until the end of the current pass through the run loop or until the run loop is idle. Duplicate notifications can be coalesced so that only one notification is sent although multiple notifications are posted.

【译】通知中心收到发出的通知后会向在通知中心注册的观察者分发这些通知,而添加到 NSNotificationQueue 队列中的通知只会在两种情况下分发出去,分别是当前 Runloop 即将退出或者 Runloop 处于空闲状态时。重复的向 NSNotificationQueue 队列中加入通知会导致相同的通知被合并,这样到了发送时机,对于重复的通知只会发送一条出去。

A notification queue maintains notifications in first in, first out (FIFO) order. When a notification moves to the front of the queue, the queue posts it to the notification center, which in turn dispatches the notification to all objects registered as observers.

【译】一个通知队列以先进先出的方式维护着通知。当一个通知移动到了队列的头部,队列会将这个通知发往通知中心,通知中心将通知分派给所有注册为观察者的对象。

Every thread has a default notification queue, which is associated with the default notification center for the process. You can create your own notification queues and have multiple queues per center and thread.

【译】每一个线程都有一个默认的通知队列,并且这个通知队列会与当前进程的默认的通知中心相关联。但是你可以创建自己的通知队列,使得每个通知中心和线程有多条通知队列

关于 NSNotificationQueue 的更多底层细节,可以参考这篇文章 一文全解iOS通知机制

  • Asynchronous functions: 异步的函数。系统自带的 api 包含许多可以为你提供自动并发功能的函数。这些函数的实现你无需关心,它们依托于系统的守护进程和进程或者会创建子线程来执行你所需要的任务。
  • Timers: 定时器。你可以在应用程序的主线程上使用定时器来执行周期性的任务,这些任务虽然琐碎而无法使用线程,但仍需要定期进行维护。
  • Separate processes: 尽管比线程更重,但是在任务仅与应用程序有切线关系的情况下,创建单独的进程可能会很有用。 如果任务需要大量内存或必须使用 root 特权执行,则可以使用进程。 例如,当 32 位应用程序将结果显示给用户时,你可以使用 64 位服务器进程来计算大型数据集。

线程支持

Cocoa 中线程相关的技术
  • Cocoa Threads: Cocoa 使用 NSThread 来实现线程。除此之外,NSObject 还提供了一揽子方法来派生线程和在已存在线程上执行任务。
  • POSIX threads: POSIX 提供了基于 C 语言的一套创建线程的 API。如果不是创建 Cocoa 程序,那么这将是最好的创建线程的方案。POSIX 接口使用起来非常简单,并且为配置线程提供了足够的灵活性
  • Multiprocessing Services: 多处理服务是从旧版 Mac OS 过渡到的应用程序使用的基于 C 的旧接口。此技术仅在 OS X 中可用,任何新开发都应避免使用。

线程启动之后,主要以三种状态运行,分别是:

  • 运行态
  • 就绪态
  • 阻塞态

如果线程当前未在运行态,则它要么被阻塞并等待输入,要么准备运行,但尚未计划这样做。线程继续在这些状态之间来回移动,直到最终退出并进入终止状态。

Runloop

A run loop is a piece of infrastructure used to manage events arriving asynchronously on a thread. A run loop works by monitoring one or more event sources for the thread. As events arrive, the system wakes up the thread and dispatches the events to the run loop, which then dispatches them to the handlers you specify. If no events are present and ready to be handled, the run loop puts the thread to sleep.

【译】一个运行循环是一个处理线程上所接收到的异步的事件的结构。运行循环管理线程上的一个或多个事件源。当事件到达时,系统将唤醒线程并将事件分配给运行循环,然后运行循环将其分配给你指定的处理程序。如果不存在任何事件或有待处理的事件,则运行循环会将线程置于睡眠状态。

You are not required to use a run loop with any threads you create but doing so can provide a better experience for the user. Run loops make it possible to create long-lived threads that use a minimal amount of resources. Because a run loop puts its thread to sleep when there is nothing to do, it eliminates the need for polling, which wastes CPU cycles and prevents the processor itself from sleeping and saving power.

【译】你不需要对你所创建的线程使用运行循环,但是使用运行循环可以提高用户体验。运行循环可以创建使用最少资源的常驻线程。因为当没事做的时候,运行循环会让线程休眠,这样就不许需要通过轮询这种需要消耗 CPU 的低效操作从而节能。

To configure a run loop, all you have to do is launch your thread, get a reference to the run loop object, install your event handlers, and tell the run loop to run. The infrastructure provided by OS X handles the configuration of the main thread’s run loop for you automatically. If you plan to create long-lived secondary threads, however, you must configure the run loop for those threads yourself.

【译】要配置运行循环,你要做的就是启动线程,获取运行循环对象的引用,安装事件处理程序,并告诉运行循环运行。 OS X 提供的基础结构会自动为你处理主线程运行循环的。但是,如果计划创建寿命长的辅助线程,则必须自己为这些线程配置运行循环。

通过上面官方文档的描述,runloop 其实和线程是紧密关联的,通过 runloop 可以让子线程一直存活而不被系统回收。同时,runloop 还能提升用户体验,可以重复的在子线程工作而无需为了执行任务多次开同样工作内容的线程。

线程同步

使用多线程技术会遇到多个线程同时访问同一份资源的情况,而如果这些线程同时尝试使用或修改资源,则会出现严重的问题。解决此问题的办法通常来说有两种,一种是消除共享资源,让每个线程独享其特有的资源进行操作。第二种就是通过 locks(锁), conditions(条件), atomic operations(原子操作)等其它技术。显然第二种方案使用频率更高。

苹果官方给出了几种线程间通信的方式,简单总结一下如下:

  • 直接消息传递: 通过 performSelector 的一系列方法,可以实现由某一线程指定在另外的线程上执行任务。因为任务的执行上下文是目标线程,这种方式发送的消息将会自动的被序列化。
  • 全局变量、共享内存块和对象: 在两个线程之间传递信息的另一种简单方法是使用全局变量,共享对象或共享内存块。尽管共享变量既快速又简单,但是它们比直接消息传递更脆弱。必须使用锁或其他同步机制仔细保护共享变量,以确保代码的正确性。 否则可能会导致竞争状况,数据损坏或崩溃。
  • 条件执行: 条件是一种同步工具,可用于控制线程何时执行代码的特定部分。您可以将条件视为关守,让线程仅在满足指定条件时运行。
  • Runloop sources: 一个自定义的 Runloop source 配置可以让一个线程上收到特定的应用程序消息。由于 Runloop source 是事件驱动的,因此在无事可做时,线程会自动进入睡眠状态,从而提高了线程的效率。
  • Ports and sockets: 基于端口的通信是在两个线程之间进行通信的一种更为复杂的方法,但它也是一种非常可靠的技术。更重要的是,端口和套接字可用于与外部实体(例如其他进程和服务)进行通信。为了提高效率,使用 Runloop source 来实现端口,因此当端口上没有数据等待时,线程将进入睡眠状态。
  • 消息队列: 传统的多处理服务定义了先进先出(FIFO)队列抽象,用于管理传入和传出数据。尽管消息队列既简单又方便,但是它们不如其他一些通信技术高效。
  • Cocoa 分布式对象: 分布式对象是一种 Cocoa 技术,可提供基于端口的通信的高级实现。尽管可以将这种技术用于线程间通信,但是强烈建议不要这样做,因为它会产生大量开销。分布式对象更适合与其他进程进行通信,尽管在这些进程之间进行事务的开销也很高

使用线程的注意点

  • 避免显式的创建线程

手动编写线程创建代码很繁琐,并且可能容易出错,因此应尽可能避免这样做。

OS XiOS 通过其他 API 为并发提供隐式支持。与其自己创建一个线程,不如考虑使用异步 APIGCD 或操作对象来完成工作。这些技术可以在底层为您完成与线程相关的工作,并且可以保证正确进行。此外,GCD 和操作对象等技术旨在根据当前系统负载调整活动线程的数量,从而比您自己的代码更有效地管理线程。

  • 让线程合理的执行任务

如果决定手动创建和管理线程,请记住线程会消耗宝贵的系统资源。您应该尽力确保分配给线程的所有任务都可以长期有效地工作。同时,您不必担心终止花费大部分空闲时间的线程。线程占用的内存非常少,其中一些是 wired memory,因此释放空闲线程不仅有助于减少应用程序的内存占用,还可以释放更多的物理内存供其他系统进程使用。

PS: Mac 中的内存使用可以分为四大类

  • Wired(联动): 系统核心占用的,永远不会从系统物理内存中驱除。
  • Active(活跃): 表示这些内存数据正在使用中,或者刚被使用过。
  • Inactive(非活跃): 表示这些内存中的数据是有效的,但是最近没有被使用。
  • Free(可用空间): 表示这些内存中的数据是无效的,即内存剩余量。

开始终止空闲线程之前,应始终记录一组应用程序当前性能的基准测量值。 尝试更改后,请进行其他测量以确认更改实际上在提高性能,而不是损害性能。

  • 避免共享数据结构

避免与线程相关的资源冲突的最简单和容易的方法是为程序中的每个线程提供所需数据的自己的副本。当您最小化线程之间的通信和资源争用时,并行代码最有效。

  • 线程和用户界面

如果你的应用有图形化的用户界面,强烈建议在应用程序的主线程上接收用户相关的事件和启动界面更新的操作。这种方法有助于避免与处理用户事件和绘制窗口内容相关的同步问题。某些框架(例如 Cocoa)通常需要此行为,但是即使对于那些不需要的框架,在主线程上保留此行为也具有简化管理用户界面的逻辑的优势。

当然,在某些情况下,在非主线程上执行图形操作是有助于提高性能的。例如,您可以使用子线程来创建和处理图像以及执行其他与图像有关的计算操作。但是遇到不确定的图形操作的时候,最好在主线程上执行。

  • 注意线程退出时的问题

进程一直运行到所有非分离线程都退出为止。默认情况下,仅将应用程序的主线程创建为非分离式。当然,你也可以创建非分离的子线程。 当用户退出应用程序时,通常认为立即终止所有分离的线程是适当的行为,因为分离的线程完成的工作被认为是可选的。但是,如果你的应用程序正在使用后台线程将数据保存到磁盘或执行其他关键工作,则可能需要将这些线程创建为非分离线程,以防止在应用程序退出时丢失数据。

将线程创建为非分离线程(也称为可连接线程)需要你进行额外的工作。因为大多数高级线程技术默认情况下都不创建可连接线程,所以你可能必须使用 POSIX API 创建线程。此外,你必须在应用程序的主线程中添加代码,以便在非分离线程最终退出时加入它们。

  • 处理异常

抛出异常时,异常处理机制依赖于当前的调用堆栈来执行任何必要的清除工作。因为每个线程都有自己的调用堆栈,所以每个线程负责捕获自己的异常。在辅助线程中未能捕获异常与在主线程中未能捕获异常会有相同的后果:进程终止。你不能将未捕获的异常抛出到另一个线程进行处理。

如果你需要在当前线程中将异常情况通知另一个线程(例如主线程),则应捕获该异常,并简单地向该另一个线程发送一条消息,指出发生了什么。 根据您的模型和您要执行的操作,捕获到异常的线程可以继续处理(如果可能的话),等待指令或直接退出。

在某些情况下,可能会自动为你创建一个异常处理程序。 例如,Objective-C 中的@synchronized 指令包含一个隐式异常处理程序。

  • 彻底的退出线程

退出线程的最佳方法自然是让线程到达其主入口点例程的末尾。尽管有立即终止线程的功能,但这些功能仅应作为最后的手段使用。在线程到达其自然终点之前终止该线程会阻止该线程自身的清理。如果线程已分配内存,打开文件或获取其他类型的资源,则您的代码可能无法回收这些资源,从而导致内存泄漏或其他潜在问题。

  • 库中的线程安全

尽管应用程序开发人员可以控制应用程序是否使用多线程执行,但库开发人员不能。在开发库时,必须假设调用应用程序是多线程的,或者可以随时切换到多线程的。因此,你应该始终对代码的关键部分使用锁。

对于库开发人员来说,仅当应用程序变成多线程时才创建锁是不明智的。如果你需要在某个时候锁定代码,请在使用库的早期创建 lock 对象,最好是通过某种显式调用来初始化库。尽管你也可以使用静态库初始化函数来创建此类锁,但只有在没有其他方法时才尝试这样做。执行初始化函数会增加加载库所需的时间,并可能对性能产生不利影响。

始终记住在库中平衡互斥锁的加锁和解锁。你还应该记住对库中的数据结构加锁,而不是依赖调用代码来提供线程安全的环境。

如果你正在开发 Cocoa 库,那么如果你希望在应用程序变为多线程时得到通知,则可以注册为 NSWillBecomeMultiThreadedNotification 的观察者。但是,你不应该依赖于接收此通知,因为它可能在调用库代码之前被发送。

线程管理

线程开销

  • 内核层的数据结构:开销为 1KB。这部分用于存储线程数据结构和属性,其中大部分是作为有线内存分配的,因此无法分页到磁盘。
  • 栈空间:iOS 程序主线程开销为 1MB,macOS 程序主线程开销为 8MB,子线程开销为 512KB。
  • 创建时间:大约 90ms,此值反映创建线程的初始调用与线程的入口点例程开始执行之间的时间。这些数字是通过分析在基于英特尔的 iMac 上创建线程时生成的平均值和中值来确定的,iMac 具有一个 2GHz 的双核处理器和一个运行 OSX V10.5 的 1GB RAM。

由于其底层内核支持,Operation 对象通常可以更快地创建线程。它们不是每次都从头开始创建线程,而是使用已经驻留在内核中的线程池来节省分配时间。

创建线程

线程创建出来后必须要执行任务才有意义,下面介绍几种创建线程的方式。

使用 NSThread 创建线程

通过 NSThread 创建线程有两种方式:

  • 使用 detachNewThreadSelector:toTarget:withObject: 类方法来派生新的线程。
  • 创建一个 NSThread 实例对象,然后调用 start 方法。

这两种技术都会在应用程序中创建分离式线程。分离的线程意味着当线程退出时,系统会自动回收该线程的资源。这也意味着您的代码以后不必显式地与线程联接。

下面给出两种方式的实际用法:

1
2
3
4
5
6
[NSThread detachNewThreadSelector:@selector(myThreadMainMethod:) toTarget:self withObject:nil];

NSThread* myThread = [[NSThread alloc] initWithTarget:self
selector:@selector(myThreadMainMethod:)
object:nil];
[myThread start]; // Actually create the thread

这里有一个注意点,采用实例化 NSThread 对象的方式,只有调用 start 方法后,在底层线程才会被创建出来。

使用 initWithTarget:selector:object:method 的另一种方法是将 NSThread 子类化并重写其 main 方法。你可以使用此方法的重写版本来作为线程的主入口点。

如果你有一个已经创建好并且在运行中的 NSThread 线程对象,你可以通过 performSelector:onThread:withObject:waitUntilDone: 方法来向这个线程发送消息。这个方法是 NSObject 的分类中的方法,也就意味着几乎任何对象都能适用。使用这个方法发送的消息将由另一个线程直接执行,作为其正常运行循环处理的一部分。当然,这意味着目标线程必须在其运行循环中运行)。在这过程中你可能还需要适用锁来进行线程同步,当然,这比适用基于 port 的线程间通信要简单一些。

虽然 performSelector:onThread:withObject:waitUntilDone: 方法用起来很简单,但是对于频繁间的线程通信或时间敏感类的任务执行,请不要使用这个方法。

使用 POSIX 创建线程

OS X 和 iOS 为使用 POSIX 线程 API 创建线程提供了基于 C 的支持。这种技术实际上可以用于任何类型的应用程序(包括 Cocoa 和 Cocoa Touch 应用程序),如果您为多个平台编写软件,则可能更方便。用于创建线程的 POSIX 例程被适当地调用为 pthread_create

下面是对于 pthread_create 的简单使用示例:

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
#include <assert.h>
#include <pthread.h>

// 线程要执行的任务
void* PosixThreadMainRoutine(void* data)
{
// Do some work here.

return NULL;
}

// 启动线程
void LaunchThread()
{
// 线程属性
pthread_attr_t attr;
// 线程对象
pthread_t posixThreadID;
// 返回值
int returnVal;

// 初始化线程属性
returnVal = pthread_attr_init(&attr);
assert(!returnVal);
// 设置线程为 detach 状态
returnVal = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
assert(!returnVal);

// 创建线程,传入属性和要执行的任务
int threadError = pthread_create(&posixThreadID, &attr, &PosixThreadMainRoutine, NULL);

// 销毁属性
returnVal = pthread_attr_destroy(&attr);
assert(!returnVal);
if (threadError != 0)
{
// Report an error.
}
}

关于 pthread 详细的使用请参考这篇文章 iOS 多线程技术实践之 pthreads(一)

使用 NSObject 派生线程

所有继承于 NSObject 的对象都可以通过 performSelectorInBackground:withObject: 来在子线程上执行任务。

这个方法底层实际上是调用的 NSThread 类方法 detachNewThreadSelector:toTarget:withObject: 来创建线程并执行任务。

Cocoa 应用程序中使用 POSIX 线程

虽然在 Cocoa 中使用 NSThread 是创建线程的主要方式,但是当需要的时候,还是可以使用 POSIX 线程的。但是需要遵守以下几点:

  • Protecting the Cocoa Frameworks: 保护 Cocoa 中的框架
    • 对于多线程应用程序,Cocoa 框架使用锁和其他形式的内部同步来确保它们的行为正确。但是,为了防止这些锁在单线程情况下降低性能,在应用程序使用 NSThread 类生成其第一个新线程之前,Cocoa 不会创建这些同步的元素。如果只使用 POSIX 线程例程生成线程,Cocoa 将不会收到需要知道应用程序现在是多线程的通知。当这种情况发生时,涉及 Cocoa 框架的操作可能会破坏应用程序的稳定性或崩溃。
    • 要让 Cocoa 知道你打算使用个线程,你只需使用 NSThread 类生成一个线程,并让该线程立即退出。你的线程入口点不需要做任何事情。使用 NSThread 生成一个线程的行为就足以确保 Cocoa 框架所需的锁被放置到位。
    • 如果不确定 Cocoa 是否认为你的应用程序是多线程的,则可以使用 NSThreadisMultiThreaded 方法进行检查。
  • 混合使用 Cocoa 中的锁与 POSIX
    • 在同一个应用程序中混合使用 POSIXCocoa 锁是安全的。Cocoa 锁和条件对象本质上只是 POSIX 互斥锁和条件对象的包装。但是,对于给定的锁,必须始终使用相同的接口来创建和操作该锁。换句话说,不能使用 CocoaNSLock 对象来操作使用 pthread_mutex_init 函数创建的互斥锁对象,反之亦然。

配置线程属性

在线程创建之前或者之后,你可能想要配置一些线程相关的属性,具体内容如下:

配置线程所占栈空间大小

对于你创建的每个新线程,系统都会在进程空间中分配特定数量的内存,以充当该线程的堆栈。

堆栈管理堆栈帧,线程内部声明的局部变量就会存于此处。

要设置线程所占用的栈空间大小,只能在线程创建之前指定。

  • Cocoa

通过实例化 NSThread 对象,然后在调用 start 方法之前调用 setStackSize: 来设置栈空间大小。

  • POSIX

创建 pthread_attr_t 结构体对象,然后将其传入 pthread_attr_setstacksize 方法来设置栈大小,然后将 pthread_attr_t 传入 pthread_create 函数来创建 POSIX 线程。

配置 TLS

每个线程会维护一个键值对的字典,用来在线程执行过程中存储一些内容,这个字典

CocoaPOSIX 以不同的方式存储线程字典,因此你不能混合和匹配对这两种技术的调用。 但是,只要您在线程代码中坚持使用一种技术,最终结果应该是相似的。在 Cocoa 中,你可以使用 NSThread 对象的 threadDictionary 方法来获取 TLS,你可以在该对象中添加线程所需的任何键。在 POSIX 中,使用 pthread_setspecificpthread_getspecific 函数来设置和获取 TLS

设置线程的分离状态

通过 NSThread 创建的线程默认是分离式的,当应用程序退出时,这种类型的线程也将退出,所以为了执行诸如在应用程序退出时保存数据的任务,需要创建 joinable 类型的线程。而目前的方案只有通过 POSIX 来实现,通过 pthread_attr_setdetachstate 方法来设置 pthread_t 的属性来达到非分离式的效果。当然,如果想改变线程的状态,可以通过 pthread_attr_setdetachstate 来将线程设置为分离式的。

设置线程优先级

你所创建的线程都有与之关联默认的优先级,内核在调度线程的时候,优先级高的线程相比于优先级低的线程更有可能性执行。但是优先级高的线程并不能保证有固定的运行时间,只是更可能被调度而已。

It is generally a good idea to leave the priorities of your threads at their default values. Increasing the priorities of some threads also increases the likelihood of starvation among lower-priority threads. If your application contains high-priority and low-priority threads that must interact with each other, the starvation of lower-priority threads may block other threads and create performance bottlenecks

【译】 通常最好将线程的优先级保留为默认值。增加某些线程的优先级也增加了低优先级线程之间出现饥饿的可能性。如果你的应用程序包含必须相互交互的高优先级和低优先级线程,则低优先级线程的饥饿可能会阻塞其他线程并造成性能瓶颈。

这里可以联想到已经不再安全的自旋锁 OSSpinLock,具体内容参见 YYKit 作者的博文 不再安全的 OSSpinLock

如果你确实想修改线程优先级,对于 Cocoa 线程,可以使用 NSThreadsetThreadPriority 类方法设置当前正在运行的线程的优先级。对于 POSIX 线程,使用 pthread_setschedparam 函数。

编写线程入口方法

在大多数情况下,OS X 中线程的入口点例程的结构与其他平台上的相同。你可以初始化数据结构,进行一些工作或有选择地设置运行循环,并在线程代码完成后进行清理。根据你的设计,编写输入例程时可能需要采取一些其他步骤。

  • 创建一个自动释放池

Objective-C 框架中链接的应用程序通常必须在其每个线程中至少创建一个自动释放池。如果应用程序使用 ARC,则自动释放池将捕获从该线程自动释放的所有对象。

下面的代码演示了在 MRC 下需要在线程入口方法里面创建一个自动释放池

1
2
3
4
5
6
7
8
- (void)myThreadMainRoutine
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; // Top-level pool

// Do thread work here.

[pool release]; // Release the objects in the pool.
}

因为顶层的自动释放池直到线程退出之后才会释放对象,所以常驻线程需要创建一些额外的自动释放池来达到经常性的清理效果。例如,使用运行循环的线程可能每次通过该运行循环都会创建并释放一个自动释放池。频繁释放对象可以防止应用程序的内存占用过大,从而导致性能问题。 但是,与任何与性能相关的行为一样,你应该衡量代码的实际性能,并适当调整自动释放池的使用。

  • 设置异常处理

如果在你的应用程序里面捕获并抛出了异常,那么在线程的入口方法中也需要做相应的处理,如果有抛出的异常在线程内部没有被捕获到将会导致程序的退出。你可以使用 C++OC 风格的 final-try&catch 代码块来处理。

  • 设置 RunLoop

在子线程上执行任务的时候,有两种选择,一种是执行完之后线程会自动退出,一种是希望线程可以一直存活来处理任务。第一种方式不需要额外的操作,而第二种方式则需要 runloop 的配合。而 iOSmacOS 中的每个线程都有对应的 runloop 对象,app 的主线程启动之后,其对应的主运行循环也会自动开启,但是对于子线程来说,则需要手动开启。

终止线程

退出线程的建议方法是让其正常退出其入口方法。尽管 CocoaPOSIXMultiprocessing Services 提供了直接杀死线程的例程,但是强烈建议不要使用此类例程。直接杀死线程会防止它自己清理掉从而导致线程分配的内存可能会泄漏,线程当前使用的任何其他资源可能无法正确清理,从而在以后产生潜在问题。

如果你预计需要在操作过程中终止线程,则应从一开始就设计线程以响应取消或退出消息。对于长时间运行的操作,这可能意味着要定期停止工作并检查是否收到此消息。如果确实有消息要求线程退出,则该线程将有机会执行所需的清理并正常退出;否则,它可以简单地返回工作并处理下一个数据块。

下面是通过 runloop 以及 threadDictionary 来实现定时检查是否要退出线程的代码:

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
- (void)threadMainRoutine
{
// 是否还有更多工作要做
BOOL moreWorkToDo = YES;
// 是否要退出线程
BOOL exitNow = NO;
// 获取当前线程对应的 runloop 对象
NSRunLoop* runLoop = [NSRunLoop currentRunLoop];

// 将 exitNow 存入 threadDictionary 中
NSMutableDictionary* threadDict = [[NSThread currentThread] threadDictionary];
[threadDict setValue:[NSNumber numberWithBool:exitNow] forKey:@"ThreadShouldExitNow"];

// 设置 runloop 的输入源
[self myInstallCustomInputSource];

while (moreWorkToDo && !exitNow)
{
// 具体要做的工作
// 完成时改变 moreWorkToDo 的值

// 让 runloop 跑起来,但如果没有要触发的输入源,则立即超时。
[runLoop runUntilDate:[NSDate date]];

// 检查输入源处理程序是否更改了 exitNow 值
exitNow = [[threadDict valueForKey:@"ThreadShouldExitNow"] boolValue];
}
}