【iOS】—— 初识RAC响应式编程

news2024/11/16 13:44:21

RAC(ReactiveCocoa)

文章目录

  • RAC(ReactiveCocoa)
    • 响应式编程和函数式编程的区别
      • 函数式编程
      • 响应式编程
    • 响应式编程的优点
    • RAC操作
      • 1.利用button点击实现点击事件和传值
      • 2.RACSignal用法
        • RACSignal总结:
      • 3.对于label的TapGesture和KVO测试
      • 4.对textField的监听即过滤操作
      • 5.RAC过滤操作
      • 6.RAC映射操作
      • 7.RAC组合操作
      • 8.RAC定时器实现倒计时
    • RAC的源码分析
      • 1.信号产生
      • 2.订阅信号
      • 3.发送信号
      • 4. 销毁

ReactiveCocoa(简称为RAC),响应式框架,是由Github开源的一个应用于iOS和OS开发的新框架,Cocoa是苹果整套框架的简称,因此很多苹果框架喜欢以Cocoa结尾。
在我们iOS开发过程中,经常会响应某些事件来处理某些业务逻辑,例如按钮的点击,上下拉刷新,网络请求,属性的变化(通过KVO)或者用户位置的变化(通过CoreLocation)。但是这些事件都用不同的方式来处理,比如action、delegate、KVO、callback(回调)等。
其实这些事件,都可以通过RAC处理,ReactiveCocoa为事件提供了很多处理方法,而且利用RAC处理事件很方便,可以把要处理的事情,和监听的事情的代码放在一起,这样非常方便我们管理,就不需要跳到对应的方法里。非常符合我们开发中高聚合,低耦合的思想。

响应式编程和函数式编程的区别

函数式编程

1.如果想再去调用别的方法,那么就需要返回一个对象;
2.如果想用()去执行,那么需要返回一个block;
3.如果想让返回的block再调用对象的方法,那么这个block就需要返回一个对象(即返回值为一个对象的block)。
4.高阶函数:在函数式编程中,把函数当参数来回传递,而这个,说成术语,我们把他叫做高阶函数。在oc中,blocks是被广泛使用的参数传递,它实际上是匿名函数。

响应式编程

1.响应式编程是一种和事件流有关的编程模式,关注导致状态值改变的行为事件,一系列事件组成了事件流。
2.一系列事件是导致属性值发生变化的原因。FRP非常类似于设计模式里的观察者模式。
3.FRP与普通的函数式编程相似,但是每个函数可以接收一个输入值的流,如果其中,一个新的输入值到达的话,这个函数将根据最新的输入值重新计算,并且产生一个新的输出。这是一种”数据流”编程模式。

响应式编程的优点

1) 开发过程中,状态以及状态之间依赖过多,RAC更加有效率地处理事件流,而无需显式去管理状态。在OO或者过程式编程中,状态变化是最难跟踪,最头痛的事。这个也是最重要的一点。
2) 减少变量的使用,由于它跟踪状态和值的变化,因此不需要再申明变量不断地观察状态和更新值。
3) 提供统一的消息传递机制,将oc中的通知,action,KVO以及其它所有UIControl事件的变化都进行监控,当变化发生时,就会传递事件和值。
4) 当值随着事件变换时,可以使用map,filter,reduce等函数便利地对值进行变换操作。

RAC操作

1.利用button点击实现点击事件和传值

//  MainViewController.m
- (void)addButton {   //button点击和传值
    self.firstButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
    self.firstButton.frame = CGRectMake(0, 0, 100, 100);
    [self.firstButton setTitle:@"未获" forState:UIControlStateNormal];
    self.firstButton.backgroundColor = [UIColor purpleColor];
    [self.view addSubview:self.firstButton];
    [[self.firstButton rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(id x) {
        SecondViewController *secondViewController = [[SecondViewController alloc] init];
        secondViewController.modalPresentationStyle = UIModalPresentationFullScreen;
        secondViewController.subject = [RACSubject subject];
        [secondViewController.subject subscribeNext:^(id x) {
            NSLog(@"%@",x);
            [self.firstButton setTitle:x forState:UIControlStateNormal];
        }];
        [self presentViewController:secondViewController animated:YES completion:nil];
    }];
}

//  SecondViewController.m
self.backButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
    self.backButton.backgroundColor = [UIColor redColor];
    self.backButton.frame = CGRectMake(0, 0, 100, 100);
    [self.backButton setTitle:@"返回" forState:UIControlStateNormal];
    [self.view addSubview:self.backButton];
    [[self.backButton rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(id x) {
        [self.subject sendNext:@"已获"];
        [self dismissViewControllerAnimated:YES completion:nil];
    }];![请添加图片描述](https://img-blog.csdnimg.cn/d4292465d216430497d5920fa5dc6342.png)

效果展示:
请添加图片描述
请添加图片描述

2.RACSignal用法

// 1.创建信号
    RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        // 3.发送信号
        [subscriber sendNext:@"牛逼"];
        [subscriber sendNext:@"牛逼2"];
        // 4.取消信号,如果信号想要被取消,就必须返回一个RACDisposable
        // 信号什么时候被取消:1.自动取消,当一个信号的订阅者被销毁的时候机会自动取消订阅,2.手动取消,
        //block什么时候调用:一旦一个信号被取消订阅就会调用
        //block作用:当信号被取消时用于清空一些资源
        return [RACDisposable disposableWithBlock:^{
            NSLog(@"取消订阅");
        }];
    }];
    // 2. 订阅信号
    // subscribeNext
    // 把nextBlock保存到订阅者里面
    // 只要订阅信号就会返回一个取消订阅信号的类
    RACDisposable *disposable = [signal subscribeNext:^(id x) {
        // block的调用时刻:只要信号内部发出数据就会调用这个block
        NSLog(@"======%@", x);
    }];
    // 取消订阅
    [disposable dispose];

输出:
在这里插入图片描述

RACSignal总结:

一.核心:
   1.核心:信号类
   2.信号类的作用:只要有数据改变就会把数据包装成信号传递出去
   3.只要有数据改变就会有信号发出
   4.数据发出,并不是信号类发出,信号类不能发送数据
二.使用方法:
   1.创建信号
   2.订阅信号
三.实现思路:
   1.当一个信号被订阅,创建订阅者,并把nextBlock保存到订阅者里面。
   2.创建的时候会返回 [RACDynamicSignal createSignal:didSubscribe];
   3.调用RACDynamicSignal的didSubscribe
   4.发送信号[subscriber sendNext:value];
   5.拿到订阅者的nextBlock调用

3.对于label的TapGesture和KVO测试

- (void)addKVOandTapGesture {   //对于label的TapGesture和KVO测试
    self.firstLabel = [[UILabel alloc] init];
    self.firstLabel.text = @"未点击";
    self.firstLabel.frame = CGRectMake(0, 100, 100, 100);
    [self.view addSubview:self.firstLabel];
    UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] init];
    self.firstLabel.userInteractionEnabled = YES;
    [self.firstLabel addGestureRecognizer:tap];
    [tap.rac_gestureSignal subscribeNext:^(__kindof UIGestureRecognizer * _Nullable x) {
        self.firstLabel.text = [NSString stringWithFormat:@"%d", arc4random()];
    }];
    
    //KVO
    [RACObserve(self, self.firstLabel.text) subscribeNext:^(id  _Nullable x) {
        NSLog(@"%@", x);
    }];
    [[self.firstLabel.text rac_valuesAndChangesForKeyPath:@"text" options:NSKeyValueObservingOptionNew observer:nil] subscribeNext:^(id x) {
        NSLog(@"%@", x);
    }];
}

在这里插入图片描述

4.对textField的监听即过滤操作

self.firstTextField = [[UITextField alloc] init];
    self.firstTextField.backgroundColor = [UIColor orangeColor];
    [self.view addSubview:self.firstTextField];
    self.firstTextField.frame = CGRectMake(100, 100, 200, 30);
    [self.firstTextField.rac_textSignal subscribeNext:^(NSString * _Nullable x) {
        NSLog(@"%@", x);
    }];
    
    // 只有当文本框的内容长度大于5,才获取文本框里的内容
    [[self.firstTextField.rac_textSignal filter:^BOOL(id value) {
        // value 源信号的内容
        return [value length] > 5;
        // 返回值 就是过滤条件。只有满足这个条件才能获取到内容
    }] subscribeNext:^(id x) {
        NSLog(@"已过滤-----%@", x);
    }];

请添加图片描述

输出:
在这里插入图片描述

5.RAC过滤操作

#pragma mark RACSubject过滤

- (void)skipRACSubject {
    // 跳跃 : 如下,skip传入2 跳过前面两个值
    // 实际用处: 在实际开发中比如 后台返回的数据前面几个没用,我们想跳跃过去,便可以用skip
    RACSubject *subject = [RACSubject subject];
    [[subject skip:2] subscribeNext:^(id x) {
        NSLog(@"%@", x);
    }];
    [subject sendNext:@1];
    [subject sendNext:@2];
    [subject sendNext:@3];
}

- (void)distinctUntilChangedRACSubject {
    //distinctUntilChanged:-- 如果当前的值跟上一次的值一样,就不会被订阅到
    RACSubject *subject = [RACSubject subject];
    [[subject distinctUntilChanged] subscribeNext:^(id x) {
        NSLog(@"%@", x);
    }];
    // 发送信号
    [subject sendNext:@1];
    [subject sendNext:@2];
    [subject sendNext:@2]; // 不会被订阅
}

- (void)takeRACSubject {
    // take:可以屏蔽一些值,去前面几个值---这里take为2 则只拿到前两个值
    RACSubject *subject = [RACSubject subject];
    [[subject take:2] subscribeNext:^(id x) {
        NSLog(@"%@", x);
    }];
    // 发送信号
    [subject sendNext:@1];
    [subject sendNext:@2];
    [subject sendNext:@3];
}

- (void)takeLastRACSubject {
    //takeLast:和take的用法一样,不过他取的是最后的几个值,如下,则取的是最后两个值
    //注意点:takeLast 一定要调用sendCompleted,告诉他发送完成了,这样才能取到最后的几个值
    RACSubject *subject = [RACSubject subject];
    [[subject takeLast:2] subscribeNext:^(id x) {
        NSLog(@"%@", x);
    }];
    // 发送信号
    [subject sendNext:@1];
    [subject sendNext:@2];
    [subject sendNext:@3];
    [subject sendCompleted];
}

- (void)ignoreRACSubject {
    //ignore:忽略一些值
    //ignoreValues:表示忽略所有的值
    // 1.创建信号
    RACSubject *subject = [RACSubject subject];
    // 2.忽略一些值
    RACSignal *ignoreSignal = [subject ignore:@2]; // ignoreValues:表示忽略所有的值
    // 3.订阅信号
    [ignoreSignal subscribeNext:^(id x) {
        NSLog(@"%@", x);
    }];
    // 4.发送数据
    [subject sendNext:@2];

}

在这里插入图片描述

6.RAC映射操作

- (void)map {
    // 创建信号
    RACSubject *subject = [RACSubject subject];
    // 绑定信号
    RACSignal *bindSignal = [subject map:^id(id value) {
        
        // 返回的类型就是你需要映射的值
        return [NSString stringWithFormat:@"ws:%@", value]; //这里将源信号发送的“123” 前面拼接了ws:
    }];
    // 订阅绑定信号
    [bindSignal subscribeNext:^(id x) {
        NSLog(@"%@", x);
    }];
    // 发送信号
    [subject sendNext:@"123"];

}

- (void)flatMap {
    // 创建信号
    RACSubject *subject = [RACSubject subject];
    // 绑定信号
    RACSignal *bindSignal = [subject flattenMap:^RACStream *(id value) {
        // block:只要源信号发送内容就会调用
        // value: 就是源信号发送的内容
        // 返回信号用来包装成修改内容的值
        return [RACReturnSignal return:value];
        
    }];
    
    // flattenMap中返回的是什么信号,订阅的就是什么信号(那么,x的值等于value的值,如果我们操纵value的值那么x也会随之而变)
    // 订阅信号
    [bindSignal subscribeNext:^(id x) {
        NSLog(@"%@", x);
    }];
    
    // 发送数据
    [subject sendNext:@"123"];
}

- (void)flattenMap2 {
    // flattenMap 主要用于信号中的信号
    // 创建信号
    RACSubject *signalofSignals = [RACSubject subject];
    RACSubject *signal = [RACSubject subject];
    
    // 订阅信号
    //方式1
    //    [signalofSignals subscribeNext:^(id x) {
    //
    //        [x subscribeNext:^(id x) {
    //            NSLog(@"%@", x);
    //        }];
    //    }];
    // 方式2
    //    [signalofSignals.switchToLatest  ];
    // 方式3
    //   RACSignal *bignSignal = [signalofSignals flattenMap:^RACStream *(id value) {
    //
    //        //value:就是源信号发送内容
    //        return value;
    //    }];
    //    [bignSignal subscribeNext:^(id x) {
    //        NSLog(@"%@", x);
    //    }];
    // 方式4--------也是开发中常用的
    [[signalofSignals flattenMap:^RACStream *(id value) {
        return value;
    }] subscribeNext:^(id x) {
        NSLog(@"%@", x);
    }];
    
    // 发送信号
    [signalofSignals sendNext:signal];
    [signal sendNext:@"123"];
}

在这里插入图片描述

7.RAC组合操作

#pragma mark 组合
// 把多个信号聚合成你想要的信号,使用场景----:比如-当多个输入框都有值的时候按钮才可点击。
// 思路--- 就是把输入框输入值的信号都聚合成按钮是否能点击的信号。
- (void)combineLatest {
    
    RACSignal *combinSignal = [RACSignal combineLatest:@[self.firstTextField.rac_textSignal, self.secondTextField.rac_textSignal] reduce:^id(NSString *account, NSString *pwd){ //reduce里的参数一定要和combineLatest数组里的一一对应。
        // block: 只要源信号发送内容,就会调用,组合成一个新值。
        NSLog(@"%@ %@", account, pwd);
        return @(account.length && pwd.length);
    }];
    
//        // 订阅信号
//        [combinSignal subscribeNext:^(id x) {
//            self.combinationButton.enabled = [x boolValue];
//        }];    // ----这样写有些麻烦,可以直接用RAC宏
    RAC(self.combinationButton, enabled) = combinSignal;
}

- (void)zipWith {
    //zipWith:把两个信号压缩成一个信号,只有当两个信号同时发出信号内容时,并且把两个信号的内容合并成一个元祖,才会触发压缩流的next事件。
    // 创建信号A
    RACSubject *signalA = [RACSubject subject];
    // 创建信号B
    RACSubject *signalB = [RACSubject subject];
    // 压缩成一个信号
    // **-zipWith-**: 当一个界面多个请求的时候,要等所有请求完成才更新UI
    // 等所有信号都发送内容的时候才会调用
    RACSignal *zipSignal = [signalA zipWith:signalB];
    [zipSignal subscribeNext:^(id x) {
        NSLog(@"%@", x); //所有的值都被包装成了元组
    }];
    
    // 发送信号 交互顺序,元组内元素的顺序不会变,跟发送的顺序无关,而是跟压缩的顺序有关[signalA zipWith:signalB]---先是A后是B
    [signalA sendNext:@1];
    [signalB sendNext:@2];
}

// 任何一个信号请求完成都会被订阅到
// merge:多个信号合并成一个信号,任何一个信号有新值就会调用
- (void)merge {
    // 创建信号A
    RACSubject *signalA = [RACSubject subject];
    // 创建信号B
    RACSubject *signalB = [RACSubject subject];
    //组合信号
    RACSignal *mergeSignal = [signalA merge:signalB];
    // 订阅信号
    [mergeSignal subscribeNext:^(id x) {
        NSLog(@"%@", x);
    }];
    // 发送信号---交换位置则数据结果顺序也会交换
    [signalB sendNext:@"下部分"];
    [signalA sendNext:@"上部分"];
}

// then --- 使用需求:有两部分数据:想让上部分先进行网络请求但是过滤掉数据,然后进行下部分的,拿到下部分数据
- (void)then {
    // 创建信号A
    RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        // 发送请求
        NSLog(@"----发送上部分请求---afn");
        
        [subscriber sendNext:@"上部分数据"];
        [subscriber sendCompleted]; // 必须要调用sendCompleted方法!
        return nil;
    }];
    
    // 创建信号B,
    RACSignal *signalsB = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        // 发送请求
        NSLog(@"--发送下部分请求--afn");
        [subscriber sendNext:@"下部分数据"];
        return nil;
    }];
    // 创建组合信号
    // then;忽略掉第一个信号的所有值
    RACSignal *thenSignal = [signalA then:^RACSignal *{
        // 返回的信号就是要组合的信号
        return signalsB;
    }];
    
    // 订阅信号
    [thenSignal subscribeNext:^(id x) {
        NSLog(@"%@", x);
    }];

}

// concat----- 使用需求:有两部分数据:想让上部分先执行,完了之后再让下部分执行(都可获取值)
- (void)concat {
    // 组合
    
    // 创建信号A
    RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        // 发送请求
        //        NSLog(@"----发送上部分请求---afn");
        
        [subscriber sendNext:@"上部分数据"];
        [subscriber sendCompleted]; // 必须要调用sendCompleted方法!
        return nil;
    }];
    
    // 创建信号B,
    RACSignal *signalsB = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        // 发送请求
        //        NSLog(@"--发送下部分请求--afn");
        [subscriber sendNext:@"下部分数据"];
        return nil;
    }];
    
    
    // concat:按顺序去链接
    //**-注意-**:concat,第一个信号必须要调用sendCompleted
    // 创建组合信号
    RACSignal *concatSignal = [signalA concat:signalsB];
    // 订阅组合信号
    [concatSignal subscribeNext:^(id x) {
        NSLog(@"%@",x);
    }];

}

请添加图片描述
输出:
在这里插入图片描述

8.RAC定时器实现倒计时

#pragma mark 定时器
- (void)countdown {
    self.timeButton = [UIButton buttonWithType:UIButtonTypeCustom];
    self.timeButton.frame = CGRectMake(200, 600, 100, 50);
    self.timeButton.backgroundColor = [UIColor yellowColor];
    [self.timeButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
    [self.timeButton setTitle:@"发送验证码" forState:UIControlStateNormal];
    [self.view addSubview:self.timeButton];

    [[self.timeButton rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(id x) {
        self.count = 10;
        self.timeButton.enabled = NO;
        [self.timeButton setTitle:[NSString stringWithFormat:@"%d", self.count] forState:UIControlStateNormal];
        self.disposable = [[RACSignal interval:1.0 onScheduler:[RACScheduler mainThreadScheduler]]subscribeNext:^(id x) {
            if (self.count == 1) {
                [self.timeButton setTitle:[NSString stringWithFormat:@"重新发送"] forState:UIControlStateNormal];
                self.timeButton.enabled = YES;
                [self.disposable dispose];
            } else {
                self.count--;
                [self.timeButton setTitle:[NSString stringWithFormat:@"%d", self.count] forState:UIControlStateNormal];
                NSLog(@"%d",self.count);
            }
        }];
    }];

}

在这里插入图片描述

RAC的源码分析

1.信号产生


//1. RACSignal
+ (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe {
    //把block封装进来 利用多态原理返回 RACDynamicSignal
	return [RACDynamicSignal createSignal:didSubscribe];//传入的参数就是一个block
}

//2.RACDynamicSignal
+ (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe {
	RACDynamicSignal *signal = [[self alloc] init];
    // 绑定block,在订阅信号的时候调用
	signal->_didSubscribe = [didSubscribe copy];
    
	return [signal setNameWithFormat:@"+createSignal:"];
}



2.订阅信号


// 1.RACSignal
- (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock {
	NSCParameterAssert(nextBlock != NULL);
	//产生一个订阅者 绑定nextBlock 
	RACSubscriber *o = [RACSubscriber subscriberWithNext:nextBlock error:NULL completed:NULL];
    //这是self 是RACDynamicSignal 是本方法的调用者
	return [self subscribe:o];
}


// 2.RACSubscriber
- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber {
	NSCParameterAssert(subscriber != nil);
// 产生 RACCompoundDisposable : 核心销毁者
	RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable];
    
    // RACPassthroughSubscriber 核心订阅者
    /*
     subscriber : 订阅者
     signal : RACDynamicSignal 信号
     disposable : 销毁者
    传入者三个参数后 分别用三个属性保存住
    三个属性保存住 是为了后续使用
    */
	subscriber = [[RACPassthroughSubscriber alloc] initWithSubscriber:subscriber signal:self disposable:disposable];

	if (self.didSubscribe != NULL) {
        // RACScheduler(封装了一个GCD)
		RACDisposable *schedulingDisposable = [RACScheduler.subscriptionScheduler schedule:^{
            //把核心订阅者传出去了,  这里的subscriber 就是create信号时block中的参数,订阅者
            //执行创建信号时保存的didSubscribeBlock
			RACDisposable *innerDisposable = self.didSubscribe(subscriber);
            //添加我的要销毁者的对象 把要销毁的对象都装进去 根据情况统一销毁
			[disposable addDisposable:innerDisposable];
		}];

		[disposable addDisposable:schedulingDisposable];
	}
	
	return disposable;
}



  • 订阅信号,产生了信号的一个订阅者,绑定nextBlock ,在下一步发送信号的时候调用这个nextBlock .
  • 产生一个核心销毁者(RACCompoundDisposable),产生一个核心订阅者(RACPassthroughSubscriber),核心订阅者保存了销毁者,1中创建的订阅者,以及信号.执行didSubscribe(subscriber)

3.发送信号

- (void)sendNext:(id)value {
	if (self.disposable.disposed) return;

	if (RACSIGNAL_NEXT_ENABLED()) {
		RACSIGNAL_NEXT(cleanedSignalDescription(self.signal), cleanedDTraceString(self.innerSubscriber.description), cleanedDTraceString([value description]));
	}
//真正的订阅 
	[self.innerSubscriber sendNext:value];
}
//RACSubscriber
- (void)sendNext:(id)value {
    //加锁
	@synchronized (self) {
		void (^nextBlock)(id) = [self.next copy];
		if (nextBlock == nil) return;

		nextBlock(value);
	}

4. 销毁

在订阅信号的时候,产生了一个核心订阅者(RACPassthroughSubscriber)和一个核心销毁者(RACCompoundDisposable),
而核心订阅者内部持有了订阅者(subscriber),信号(signal),以及核心销毁者(disposable),
核心销毁者(disposable)可以被添加其他的销毁者,存入内部的一个数组中,在自身被释放的时候,会把数组中的也全部释放.
比如订阅者被self持有

[subscriber sendNext:@"123"];
self.subscriber = subscriber;

要想释放掉信号相关的内存,就必须self.subscriber = nil.
当订阅者被释放,在dealloc方法中

- (void)dealloc {
//核心销毁者开始执行dispose方法
	[self.disposable dispose];
}

核心销毁者的dispose

- (void)dispose {
	#if RACCompoundDisposableInlineCount
    //C 数组 type * a[2]
	RACDisposable *inlineCopy[RACCompoundDisposableInlineCount];
	#endif

	CFArrayRef remainingDisposables = NULL;

	pthread_mutex_lock(&_mutex);
	{
		_disposed = YES;

		#if RACCompoundDisposableInlineCount
		for (unsigned i = 0; i < RACCompoundDisposableInlineCount; i++) {
			inlineCopy[i] = _inlineDisposables[i];
            //当前的销毁数组,一个个的清理 数组移除
			_inlineDisposables[i] = nil;
		}
		#endif

		remainingDisposables = _disposables;
		_disposables = NULL;
	}
	pthread_mutex_unlock(&_mutex);

	#if RACCompoundDisposableInlineCount
	// Dispose outside of the lock in case the compound disposable is used
	// recursively.
	for (unsigned i = 0; i < RACCompoundDisposableInlineCount; i++) {
		[inlineCopy[i] dispose];//存入的disposable各自调用各自的dispose方法
	}
	#endif

	if (remainingDisposables == NULL) return;

	CFIndex count = CFArrayGetCount(remainingDisposables);
	CFArrayApplyFunction(remainingDisposables, CFRangeMake(0, count), &disposeEach, NULL);
	CFRelease(remainingDisposables);
}

存进核心销毁者的各个disposable调用dispose方法
[RACDisposable dispose]

- (void)dispose {
	void (^disposeBlock)(void) = NULL;
//遍历去找销毁对象
	while (YES) {
		void *blockPtr = _disposeBlock;
//        OSAtomicCompareAndSwapPtrBarrier(v1,v2,v3)
        /*
         v1 与 v3 匹配 相同就返回yes
         然后把v2 赋值给v3 也就是 = null
         */
		if (OSAtomicCompareAndSwapPtrBarrier(blockPtr, NULL, &_disposeBlock)) {
			if (blockPtr != (__bridge void *)self) {
				disposeBlock = CFBridgingRelease(blockPtr);
			}

			break;
		}
	}
//    持有的block,成为了临时变量,执行完,block也就释放了
	if (disposeBlock != nil) disposeBlock();
}

大佬博客

RACdemo地址

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/381347.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Java | IO 模式之 JavaNIO 应用

文章目录NIO1 Java NIO 基本介绍2 NIO 和 BIO 的比较3 NIO 三大核心原理示意图3.1 Buffer缓冲区3.2 Channel&#xff08;通道&#xff09;3.3 Selector选择器3.4 总结4 NIO核心一&#xff1a;缓冲区(Buffer)4.1 缓冲区&#xff08;Buffer&#xff09;4.2 Buffer 类及其子类4.3 …

泛型与Map接口

Java学习之道 泛型 泛型这种参数类型可以用在类、方法和接口中&#xff0c;分别被称为泛型类&#xff0c;泛型方法&#xff0c;泛型接口 参数化类型&#xff1a;将类型由原来的具体的类型参数化&#xff0c;在使用/调用时传入具体的类型JDK5引入特性提供了安全检测机制&#xf…

[oeasy]python0097_苹果诞生_史蒂夫_乔布斯_沃兹尼亚克_apple_I

苹果诞生 回忆上次内容 上次时代华纳公司 凭借手中的影视ip和资本吞并了雅达利公司 此时 雅达利公司 曾经开发过pong的 优秀员工 乔布斯 还在 印度禅修 寻找自我 看到游戏行业 蓬勃发展 乔布斯 也想有自己的 一番天地 可是 他的机会在哪里呢?&#x1f914; Jobs 跟着 Wozn…

顺序表——重置版

本期我们来实现数据结构的顺序表&#xff08;这个之前写过一次&#xff0c;不过本期和之前可能会略有不同&#xff0c;但大体相同&#xff09;&#xff0c;大家可以看一下我们之前完成的顺序表 (6条消息) 顺序表及其多种接口的实现_顺序表类中实现接口方法_KLZUQ的博客-CSDN博客…

Linux环境下验证python项目

公司大佬开发的python rpa跑数项目&#xff0c;Windows运行没问题后&#xff0c;需要搭建一个linux环境进行验证&#xff0c;NOW START&#xff01; Install VMware官网 下载好之后打开按步骤安装 最后一步会让填许可证&#xff08;密钥&#xff09;&#xff0c;这里自行百…

Meta开放小模型LLaMA,性能超过GPT-3

论文地址&#xff1a;https://research.facebook.com/file/1574548786327032/LLaMA--Open-and-Efficient-Foundation-Language-Models.pdf 介绍 LLaMA&#xff0c;是Meta AI最新发布的一个从7B到65B参数的基础语言模型集合。在数以万亿计的token上训练模型&#xff0c;并表明…

运动跑步耳机哪种最好、5款最好用的运动耳机推荐

而作为一名运动爱好者&#xff0c;我非常喜欢在运动时听音乐&#xff0c;简直不要太轻松&#xff01;不过在换了多款蓝牙耳机之后&#xff0c;我终于找到了几款非常适合运动的耳机&#xff0c;戴着它们运动&#xff0c;不仅不会出现不适感&#xff0c;还能享受清晰动听音乐&…

多个AOP修饰同一个方法

1、背景 之前的文章中&#xff0c;有网友提出了一个问题&#xff0c;同一个方法用多个AOP修饰&#xff0c;执行顺序是怎样的&#xff1f; 好问题&#xff0c;之前没有关注过&#xff0c;这里写一个demo跑一下看看 同时有一个衍生问题&#xff0c;多个AOP修饰&#xff0c;会生…

Linux常用命令--进程和计划任务管理

一、程序和进程的关系 1、程序 ①保存在硬盘、光盘等介质中的可执行代码和数据 ②静态保存的代码 2、进程 ①在cpu及内存中运行及进程代码 ②动态执行的代码 ③父&#xff08;fork&#xff09;、子进程&#xff0c;每个程序可以创建一个或多个进程 父进程和子进程的区别&am…

springboot如何获取websocket的header头信息

websocket协议与http协议类似&#xff0c;也有属于自己的头信息&#xff0c;如下图所示&#xff0c;为postman在连接时自定义的header&#xff1a; 那么在后端中&#xff0c;如何像http的HttpServletRequest一样来获取这个头信息的内容呢? 自定义一个WebSocket配置类&#xff…

【LeetCode】剑指 Offer 19. 正则表达式匹配 p124 -- Java Version

题目链接&#xff1a;https://leetcode.cn/problems/zheng-ze-biao-da-shi-pi-pei-lcof/ 1. 题目介绍&#xff08;19. 正则表达式匹配&#xff09; 请实现一个函数用来匹配包含. 和*的正则表达式。模式中的字符.表示任意一个字符&#xff0c;而’*表示它前面的字符可以出现任意…

图解LeetCode——剑指 Offer 21. 调整数组顺序使奇数位于偶数前面

一、题目 输入一个整数数组&#xff0c;实现一个函数来调整该数组中数字的顺序&#xff0c;使得所有奇数在数组的前半部分&#xff0c;所有偶数在数组的后半部分。 二、示例 2.1> 示例&#xff1a; 【输入】nums [1,2,3,4] 【输出】[1,3,2,4] 【注】[3,1,2,4] 也是正确的…

基于vscode创建SpringBoot项目,连接postgresql数据库

1、Vue下载安装步骤的详细教程(亲测有效) 1_水w的博客-CSDN博客 2、Vue下载安装步骤的详细教程(亲测有效) 2 安装与创建默认项目_水w的博客-CSDN博客 3、基于vscode开发vue项目的详细步骤教程_水w的博客-CSDN博客 4、基于vscode开发vue项目的详细步骤教程 2 第三方图标库FontAw…

最新的Windows docker安装方法

什么是Docker&#xff1f;关于Docker的相关概述&#xff0c;请看&#xff1a;Docker_面向架构编程的博客-CSDN博客在Windows10 or Windows11中安装docker主要就两步&#xff1a;1.安装wsl22. 安装docker一、安装WSL2安装wslwsl --install然后重启一下电脑在cmd窗口可以查看自己…

如何使用CVE-Tracker随时获取最新发布的CVE漏洞信息

关于CVE-Tracker CVE- Tracker是一款功能强大的CVE漏洞信息收集和更新工具&#xff0c;该工具基于自动化ps脚本实现其功能&#xff0c;可以帮助广大研究人员轻松获取到最新发布的CVE漏洞信息。 CVE-Tracker采用PowerShell开发&#xff0c;可以在操作系统启动的时候自动运行Mi…

计算机组成原理4小时速成5:系统总线,总线分类,数据总线,地址总线,控制总线,总线传输率

计算机组成原理4小时速成5&#xff1a;系统总线&#xff0c;总线分类&#xff0c;数据总线&#xff0c;地址总线&#xff0c;控制总线&#xff0c;总线传输率 2022找工作是学历、能力和运气的超强结合体&#xff0c;遇到寒冬&#xff0c;大厂不招人&#xff0c;可能很多算法学…

华为6面,测试岗报价11k,HR说我不尊重华为,他们没有那么低薪资岗位?

在不知道一个公司的普遍薪资水平的时候&#xff0c;很多面试者不敢盲目的开价&#xff0c;但就因为这样可能使得面试官怀疑你的能力。一位网友就在网上诉说了自己的经历&#xff0c;男子是一位测试员&#xff0c;已经有九年的工作经历了&#xff0c;能力自己觉得还不错。 因为…

matlab-初学

初次学习了解matlab的相关知识&#xff0c;跟着b站博主学习的&#xff0c;仅此记录学习过程的笔记与感悟。命令行小常识1.命令行给变量赋值&#xff0c;回车即运行一行。2.逗号反馈这一行所有的变量结果&#xff1b;分号则不反馈&#xff0c;单实际变量值已经改变(只是不在下方…

22- estimater使用 (TensorFlow系列) (深度学习)

知识要点 estimater 有点没理解透 数据集是泰坦尼克号人员幸存数据. 读取数据&#xff1a;train_df pd.read_csv(./data/titanic/train.csv) 显示数据特征&#xff1a;train_df.info() 显示开头部分数据&#xff1a;train_df.head() 提取目标特征&#xff1a;y_train tr…

Web前端:四大Web应用开发趋势和技术

就像其他行业一样&#xff0c;web应用程序开发每年都会经历巨大的变化。就像人们说的&#xff0c;变化是技术中唯一不变的东西。因此&#xff0c;我们这里有一些你可以期待的市场变化。Web应用开发趋势和技术1.市场对聊天机器人和人工智能寄予厚望已经说过很多次&#xff0c;也…