Combine-buffer理解与实验

先来看看官方注释:


image.png

翻译一下注释:

///缓冲从上游发布者接收的元素。
///使用' ' Publisher/buffer(size:prefetch:whenFull:) ' '从上游发布者收集特定数量的元素,
///然后根据您指定的' ' Publishers/BufferingStrategy ' '和' ' Publishers/PrefetchStrategy ' '
///策略将它们重新发布到下游订阅者。
///如果发布者在达到' size '阈值之前完成,它会缓冲元素并在完成之前将它们发布到下游。
/// -参数
/// - size:要存储的最大元素数。
/// - prefetch:初始填充缓冲区的策略。
/// - whenFull:当缓冲区满时采取的动作。
/// -返回:缓冲从上游发布者接收的元素的发布者。

字面意思就是根据策略缓冲上游发布者发送的数据,并重新发布给下游,也就是一个背压的作用。举个例子,就像你拿个杯子边接水边用吸管喝,水龙头(上游发布者)一直不停放水,用杯子(buffer)接住,然后你用吸管(下游订阅者)喝水,你喝水得停下来咽吧(消费赶不上发布),所以这个杯子就起到缓冲的作用。

size很好理解,prefetch和whenFull好像不是字面意思那么简单,先分别看看两个参数的枚举

prefetch参数可设置的枚举有两个:

.keepFull:

在订阅时填充缓冲区,并在订阅后保持缓冲区满。
该策略首先在订阅者第一次连接时从上游发出等于缓冲区大小的请求。之后,它继续从上游请求元素,以尽量保持缓冲区满。

.byRequest:

避免预取,而是按需执行请求的策略。
该策略只是将下游的请求转发给上游的发布者。

whenFull的参数可设置的枚举有三个:
. dropNewest:

当缓冲区满时,丢弃新接收到的元素。

. dropOldest

当缓冲区满时,丢弃缓冲区中最老的元素。

. customError(暂时不讨论这个)

当缓冲区已满时,执行闭包以提供一个自定义错误。

whenFull好理解,字面意思,prefetch就有点搞不懂了,特别是byRequest策略,而且在实际使用过程中,whenFull策略在不同的prefetch策略中也有不同的表现。

于是我写了个demo来测试:

测试思路,通过一个上游发布者按照一定数量持续发送值,然后通过buffer进行缓存,下游使用自定义的订阅者来控制接收需求,比如上游发送10个值,下游请求5个值同时打印输出,来模拟观察buffer的不同策略的表现。

为了方便测试,写了个UI界面,如下:


image.png

定义一个变量,用来保存每次上游发送值的递增的结果

var index = 1;

发布者:

let customSubscriber = CustomSubscriber()

然后是自定义的订阅者:

class CustomSubscriber: Subscriber {
    typealias Input = Int
    typealias Failure = Never
    
    var subscription: Subscription?
    
    func receive(subscription: Subscription) {
        self.subscription = subscription
    }
    
    func receive(_ input: Int) -> Subscribers.Demand {
        print(input)
        return .none
    }
    
    func receive(completion: Subscribers.Completion<Never>) {
        print("---completion")
    }
}

在接受订阅成功的方法里只是持有了subscription,并没有向上游请求数据,以模拟下游阻塞的情况,接收到值也是简单打印出来。

发送按钮的点击响应

 @IBAction func sendOnClick(_ sender: Any) {
        let sendCount: Int = Int(sendCountTF.text!) ?? 0
        for i in index ..< index + sendCount {
            publisher.send(i)
        }
        index += sendCount
    }

这里的作用是读取输入框中的数字为sendCount,sendCount代表需要发送数据的个数,然后从index开始循环发送sendCount数量的整型数据,最后设置下一次发送数据的起点

接收按钮的点击响应:

    @IBAction func receiveOnClick(_ sender: Any) {
        let receiveCount: Int = Int(receiveCountTF.text!) ?? 0
        customSubscriber.subscription?.request(.max(receiveCount))
    }

这里的作用是利用subscription告诉上游(buffer)需要接收多少个数量的数据

开始测试:

通过buffer将发布者和订阅者连接起来,这里设置缓存大小为5,策略分别为byRequest和dropOldest:

    override func viewDidLoad() {
        super.viewDidLoad()
        
        publisher.buffer(size: 5, prefetch: .byRequest, whenFull: . dropOldest)
            .subscribe(customSubscriber)
        
    }

设置发送个数为10,接收个数为3


image.png

步骤1:先点击发送,这个时候发布者会发送 1、2、3......8、9、10这10个整型数据,缓存大小为5,whenFull设置的是dropOldest,那么缓存区留下来的就是6、7、8、9、10这十个数据,
步骤2:然后点击接收,这时自定的订阅者会向buffer发布者请求3个数据,预期打印结果应该是6、7、8这三个数,实际与预期相符:


image.png

接着再点击发送,发布者继续发送10个数据分别是11、12、13......18、19、20,然后再点击接收,请尝试猜测一下打印结果。
image.png

嗯,跟预期差不多,buffer会继续填充缓冲区,满了之后会丢弃掉最早的数据。

我们换个策略,将dropOldest换做dropNewest,并重复上面的步骤

override func viewDidLoad() {
        super.viewDidLoad()
        
        publisher.buffer(size: 5, prefetch: .byRequest, whenFull: .dropNewest)
            .subscribe(customSubscriber)
        
    }

打印结果


image.png

跟预期结果一样,由于策略改为缓存满时丢弃最新的数据,第一次操作缓存区的内容为1、2、3、4、5,6到10的数据被丢弃了,打印的结果为1、2、3,缓冲区剩下4、5两个数,第二次操作由于缓冲区只剩3个位置,所以只缓存了11、12、13这3个数,缓冲区缓存的数据为4、5、11、12、13,其他的都丢弃了,最后订阅者请求三个数的时候只有4、5、11。

接下俩将byRequest改成keepFull,whenFull改为dropOldest

    override func viewDidLoad() {
        super.viewDidLoad()
        
        publisher.buffer(size: 5, prefetch: .keepFull, whenFull: .dropOldest)
            .subscribe(customSubscriber)
        
    }

重复上面的操作:
进行步骤1的时候我发现打印的结果跟我预期的不一样:


image.png

因为设置的whenFull是dropOldest,我的预期是打印8、9、10,实际是打印了1、2、3
然后观察步骤2的打印:


image.png

步骤2打印了4、5、11,根据这个结果可以推测出,步骤1时,发布者发送的10个数据只有前面的5个被缓存了下来,后续的被丢弃了,下游发布者请求了3个数据,最后缓冲区剩下4、5两个数据,缓冲区留有3个空位,进行步骤2时只有11、12、13前3个数据缓存了下来,此时缓冲区的数据为4、5、11、12、13,下游请求3个数据时打印就为4、5、11

把dropOldest改为dropNewest再试试

    override func viewDidLoad() {
        super.viewDidLoad()
        
        publisher.buffer(size: 5, prefetch: .keepFull, whenFull: .dropNewest)
            .subscribe(customSubscriber)
        
    }

直接看打印结果:


image.png

我发现得出的结论跟设置为dropOldest的时候一样,当prefetch设置为keepFull时,whenFull并不影响任何结果

结论:
refetch代表预取值的策略,字面意思是预取,我理解为对上游的需求

当设置为byRequest时,buffer对上游发布者的需求为unlimited,即对上游需求不收限制,上游发布的任何值都会发送给buffer,buffer根据whenFull策略决定缓冲区满时丢弃旧数据还是丢弃新数据

当设置为keepFull时,buffer在连接到上游发布者的同时,会告诉上游发布者自己的需求为size大小,那么上游只会发送size数量的数据给buffer,之后的数据不再发送,所以whenFull设置为何值都不影响结果。

以上结论是根据实现得出来的,如有不符,望指出。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,029评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,238评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,576评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,214评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,324评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,392评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,416评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,196评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,631评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,919评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,090评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,767评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,410评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,090评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,328评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,952评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,979评论 2 351