【iOS】—— 初识RAC响应式编程
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的调用时刻:只要信号内部发出数据就会调用这个blockNSLog(@"======%@", x);}];// 取消订阅[disposable dispose];
输出:
RACSignal总结:
一.核心:1.核心:信号类2.信号类的作用:只要有数据改变就会把数据包装成信号传递出去3.只要有数据改变就会有信号发出4.数据发出,并不是信号类发出,信号类不能发送数据
二.使用方法:1.创建信号2.订阅信号
三.实现思路:1.当一个信号被订阅,创建订阅者,并把nextBlock保存到订阅者里面。2.创建的时候会返回 [RACDynamicSignal createSignal:didSubscribe];3.调用RACDynamicSignal的didSubscribe4.发送信号[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 跳过前面两个值// 实际用处: 在实际开发中比如 后台返回的数据前面几个没用,我们想跳跃过去,便可以用skipRACSubject *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事件。// 创建信号ARACSubject *signalA = [RACSubject subject];// 创建信号BRACSubject *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 {// 创建信号ARACSubject *signalA = [RACSubject subject];// 创建信号BRACSubject *signalB = [RACSubject subject];//组合信号RACSignal *mergeSignal = [signalA merge:signalB];// 订阅信号[mergeSignal subscribeNext:^(id x) {NSLog(@"%@", x);}];// 发送信号---交换位置则数据结果顺序也会交换[signalB sendNext:@"下部分"];[signalA sendNext:@"上部分"];
}// then --- 使用需求:有两部分数据:想让上部分先进行网络请求但是过滤掉数据,然后进行下部分的,拿到下部分数据
- (void)then {// 创建信号ARACSignal *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 {// 组合// 创建信号ARACSignal *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封装进来 利用多态原理返回 RACDynamicSignalreturn [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中的参数,订阅者//执行创建信号时保存的didSubscribeBlockRACDisposable *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];#endifCFArrayRef remainingDisposables = NULL;pthread_mutex_lock(&_mutex);{_disposed = YES;#if RACCompoundDisposableInlineCountfor (unsigned i = 0; i < RACCompoundDisposableInlineCount; i++) {inlineCopy[i] = _inlineDisposables[i];//当前的销毁数组,一个个的清理 数组移除_inlineDisposables[i] = nil;}#endifremainingDisposables = _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方法}#endifif (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地址
相关文章:
【iOS】—— 初识RAC响应式编程
RAC(ReactiveCocoa) 文章目录RAC(ReactiveCocoa)响应式编程和函数式编程的区别函数式编程响应式编程响应式编程的优点RAC操作1.利用button点击实现点击事件和传值2.RACSignal用法RACSignal总结:3.对于label的TapGestur…...
Java——面向对象
目录 前言 一、什么是面向对象? 面向过程 & 面向对象 面向对象 二、回顾方法的定义和调用 方法的定义 方法的调用 三、类与对象的创建 类和对象的关系 创建与初始化对象 四、构造器详解 五、创建对象内存分析 六、封装详解 七、什么是继承&#x…...
电影《毒舌律师》观后感
上周看了《毒蛇律师》这部电影,讲述一位’大律师’在法庭为己方辩护,最终赢得辩护的故事。 (1)人之常情 说起法律相关,不禁会让人联想到讲法律相关知识的罗翔老师,平时也会看他相关视频,无论是亲…...
【活学活用掌握trap命令】
trap 命令用于指定在接收到信号后将要采取的动作,常见的用途是在脚本程序被中断时完成清理工作。当 shell 接收到 sigspec 指定的信号时, arg 参数(通常是执行命令)会被读取,并被执行。 1. 命令介绍 开始掌握基本的使用方式和方法 [1] 语法…...
计算机组成原理4小时速成6:输入输出系统,io设备与cpu的链接方式,控制方式,io设备,io接口,并行串行总线
计算机组成原理4小时速成6:输入输出系统,io设备与cpu的链接方式,控制方式,io设备,io接口,并行串行总线 2022找工作是学历、能力和运气的超强结合体,遇到寒冬,大厂不招人,…...
MyBatis源码分析(三)SqlSession的执行主流程
文章目录一、熟悉主要接口二、SqlSession的获取1、通过数据源获取SqlSession三、Mapper的获取与代理1、从SqlSession获取Mapper2、执行Mapper方法前准备逻辑3、SqlCommand的创建4、构造MethodSignature四、执行Mapper的核心方法1、执行Mapper的方法逻辑五、简单SELECT处理过程1…...
环境搭建01-Ubuntu16.04如何查看显卡信息及安装NVDIA显卡驱动
1. 查看显卡型号、驱动 ubuntu-drivers devices2. 安装NVIDIA显卡驱动 (1)验证是否禁用nouveau lsmod | grep nouveau若有输出,没有禁用,进行以下操作禁用。 sudo gedit /etc/modprobe.d/blacklist.conf在文件末尾中添加两条&…...
内网渗透测试理论学习之第四篇内网渗透域的横向移动
文章目录一、IPC二、HashDump三、PTH四、PTT五、PsExec六、WMI七、DCOM八、SPN九、Exchange在内网中,从一台主机移动到另外一台主机,可以采取的方式通常有文件共享、计划任务、远程连接工具、客户端等。 一、IPC IPC(Internet Process Conn…...
20 | k8s v1.20集群搭建master和node
1 单节点master 1.1 服务器整体规划 1.2 单Master架构图 1.3 初始化配置 1.3.1 关闭防火墙 systemctl stop firewalld systemctl disable firewalld1.3.2 关闭selinux sed -i s/enforcing/disabled/ /etc/selinux/config # 永久 setenforce 0 # 临时 1.3.3 关闭swap …...
《商用密码应用与安全性评估》第一章密码基础知识1.1应用概念
密码的概念与作用 概念 密码:采用特定变换的方法对信息进行加密保护、安全认证的技术、产品和服务。 密码技术:密码编码、实现、协议、安全防护、分析破译、以及密钥产生、分发、传递、使 用、销毁等技术。 密码技术核心:密码算法…...
【博学谷学习记录】超强总结,用心分享丨人工智能 深度学习 神经网络基础知识点总结
目录神经网络激活函数引入激活函数原因:sigmoid激活函数tanh 激活函数ReLU 激活函数(最常用)SoftMax如何选择反向传播参数初始化方法优化方法正则化批量归一层网络模型调优的思路神经网络 简单的神经网络包括三层:输入层…...
Python+tkinter添加滚动条
大家好,我是IKUN的真爱粉,有时候我们需要在tkinter上加滚动条,那么怎么制作呢,我们先看下面的视频展示效果,是不是你想要的 展示 感觉制作的略微粗糙,各位可以后期自己慢慢调整 创建滚动条重要的步骤是&a…...
大V龚文祥造谣董明珠恋情被禁言
我是卢松松,点点上面的头像,欢迎关注我哦! 因造谣董明珠与王自如恋情,知名大V龚文祥老师被今日头条禁言。龚文祥说,69岁的董明珠,找了一个小自己34岁的男友,引的网友议论纷纷。 2月26日&#…...
深入浅出Reactjs
深入浅出Reactjs 介绍 React是一个流行的JavaScript库,用于开发复杂的用户界面。它可以帮助开发人员构建灵活、高效和可维护的应用程序。本文将深入浅出地介绍React开发框架。 React的核心概念 React框架的核心概念是组件。组件是一个独立的、可复用的代码块&am…...
《C++ Primer Plus》第18章:探讨 C++ 新标准(1)
本章首先复习前面介绍过的C11功能,然后介绍如下主题: 移动语义和右值引用。Lambda 表达式。包装器模板 function。可变参数模板。 本章重点介绍 C11 对 C 所做的改进。本书前面介绍过多项 C11 功能,本章首先复习这些功能,并详细…...
PCB板漏孔、漏槽怎么办?看工程师避坑“SOP”
本文为大家介绍PCB画板时常见的钻孔问题,避免后续踩同样的坑。钻孔分为三类,通孔、盲孔、埋孔。不管是哪种孔,孔缺失的问题带来的后果是直接导致整批产品不能使用。因此钻孔设计的正确性尤为重要。 案例讲解 问题1:Altium设计的文…...
mysql数据库同步方案:springboot+集成cannal
1授权 -- 使用命令登录:mysql -u root -p -- 创建用户 用户名:canal 密码:Canal123456 create user canal% identified by Canal123456; -- 授权 *.*表示所有库 grant SELECT, REPLICATION SLAVE, REPLICATION CLIENT on *.* to canal% ident…...
oracle 19c 创建物化视图并测试logminer进行日志挖掘
1、创建物化视图 alter session set containerpdb; grant create materialized view to scott; create materialized view 物化视图名 -- 1. 创建物化视图 build [immediate | deferred] -- 2. 创建方式,默认 immediate refre…...
2.1 黑群晖驱动:10代u核显硬解驱动(解决掉IP、重启无法连接问题)
本文提供了两种10代核显驱动方式:1)第一种(本文:二、仅修改i915.ko驱动10代u核显方法)为网上流传最多但是对主板兼容性要求很高,网上评论常会出现操作后无法识别IP(掉IP)的问题。因此,采用第一种…...
二、CSS
一、CSSHTML的结合方式 1、第一种:在标签的style属性上设置"key:value value;",修改标签样式 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>Title</title>…...
变分推断 (Variational Inference) 解析
前言 如果你对这篇文章可感兴趣,可以点击「【访客必读 - 指引页】一文囊括主页内所有高质量博客」,查看完整博客分类与对应链接。 变分推断 在贝叶斯方法中,针对含有隐变量的学习和推理,通常有两类方式,其一是马尔可…...
27. 移除元素
题目链接:https://leetcode.cn/problems/remove-element/给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输…...
hive临时目录清理
hive运行失败会导致临时目录无法自动清理,因此需要自己写脚本去进行清理 实际发现hive临时目录有两个: /tmp/hive/{user}/* /warehouse/tablespace//hive/**/.hive-staging_hive 分别由配置hive.exec.scratchdir和hive.exec.stagingdir决定: 要注意的…...
如何创建发布新品上市新闻稿
推出新产品对任何企业来说都是一个激动人心的时刻,但向潜在客户宣传并围绕您的新产品引起轰动也可能是一个挑战。最有效的方法之一就是通过发布新品上市新闻稿。精心制作的新闻稿可以帮助我们通过媒体报道、吸引并在目标受众中引起关注。下面,我们将讲述…...
关于.bashrc和setup.bash的理解
在创建了ROS的workspace后,需要将workspace中的setup.bash文件写入~/.bashrc 文件中,让其启动: source /opt/ros/melodic/setup.bash这句话的目的就是在开新的terminal的时候,运行这个setup.bash,而这个setup.bash的作…...
03 Android基础--fragment
03 Android基础--fragment什么是fragment?fragment生命周期?动态的fragment与静态的fragmentfragment常用的两个类与APIFragment与Activity通信什么是fragment? 碎片,一个activity中可以使用多个fragment,可以把activi…...
Redis使用,AOF、RDB
前言 如果有人问你:"你会把 Redis 用在什么业务场景下?" 我想你大概率会说:"我会把它当作缓存使用,因为它把后端数据库中的数据存储在内存中,然后直接从内存中读取数据,响应速度会非常快。…...
SOLIDWORKS Premium 2023 SP1.0 三维设计绘图软件
SOLIDWORKS 中文完美正式版提供广泛工具来处理最复杂的问题,并提供深层技术完成关键细节工作。新功能可助您改善产品开发流程,以更快地将创新产品投入生产。Solidworks 是达索公司最新推出的三维CAD系统,它可让设计师大大缩短产品的设计时间,让产品得以快速、高效地投向市场…...
PyQGIS开发--自动化地图布局案例
前言创建地图布局是 GIS 作业结束时的一项常见任务。 它用于呈现最终结果的输出,作为与用户交流的一种方式,以便从地图中获取信息、知识或见解。 在包括 QGIS 在内的任何 GIS 软件中制作地图布局都非常容易。 但另一方面,当我们必须生成如此大…...
严格模式和非严格模式下的this指向问题
一、全局环境 1.函数调用 非严格模式:this指向是Window // 普通函数 function fn () { console.log(this, this); } fn() // 自执行函数 (function fn () { console.log(this, this); })() 严格模式:this指向是undefined //…...
绍兴网站建设哪家好/平台广告推广
返璞归真这几天项目有一个linux下部署数据库的操作,数据库使用python进行初始化安装。然后问题来了,由于linux服务器涉及安全要求,除了代码以来的Python3.6版本外不允许安装其他插件与工具,不巧的是python的代码报错了…如果放在平…...
公司委托建设网站合同范本/成品短视频网站源码搭建
从上一周周一学习KMP算法之后,每天晚上都失眠到1点过起夜,身体再疲倦脑子都异常清醒,要死了要死了。 kmp算法真的有毒。 这个题,我用优先队列记录最小的下标,用Map记录每个数字是第几天,然后让那个值和队列…...
网站套利怎么做/新闻内容摘抄
点击上方“Java基基”,选择“设为星标”做积极的人,而不是积极废人!每天 14:00 更新文章,每天掉亿点点头发...源码精品专栏 原创 | Java 2021 超神之路,很肝~中文详细注释的开源项目RPC 框架 Dubbo 源码解析网络应用框…...
兰州网站建设网站建设/营销软文范文
设置两个复选框控件,一个按钮控件。将第二个复选框控件命名为“box”:控件设置–属性–控件名称 编辑第一个复选框控件–事件–初始化后 setTimeout(function() {var isAllChecked true;//设置标记状态为选中var boxes _g().getWidgetsByName("…...
可信网站认证收费吗/怎么快速推广app
hdu2098很水的题,但是自己用的是筛子法,但是自己想到惠超市了饿,还执意放到上面ac思路很简单,简单来说就是从1到n-1然后到n-1/2到n-1/2,因此正好一半,因此/2,然后验证是否为素数就行了,因此,验证…...
非法集资罪提供网站建设/线上推广方式都有哪些
数据结构和算法到底有什么用? 数据结构是对在计算机内存中(有时在磁盘中)的数据的一种安排。数据结构包括数组、链表、栈、二叉树、哈希表等等。算法对这些结构中的数据进行各种处理。例如,查找一条特殊的数据项或对数据进行排序…...