ReactiveCocoa之实现篇

2024-03-26 22:58
文章标签 实现 reactivecocoa

本文主要是介绍ReactiveCocoa之实现篇,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

概述

为什么要使用RAC?

一个怪怪的东西,从Demo看也没有让代码变得更好、更短,相反还造成理解上的困难,真的有必要去学它么?相信这是大多数人在接触RAC时的想法。RAC不是单一功能的模块,它是一个Framework,提供了一整套解决方案。其核心思想是「响应数据的变化」,在这个基础上有了Signal的概念,进而可以帮助减少状态变量(可以参考jspahrsummers的PPT),使用MVVM架构,统一的异步编程模型等等。

为什么RAC更加适合编写Cocoa App?说这个之前,我们先来看下Web前端编程,因为有些相似之处。目前很火的AngularJS有一个很重要的特性:数据与视图绑定。就是当数据变化时,视图不需要额外的处理,便可正确地呈现最新的数据。而这也是RAC的亮点之一。RAC与Cocoa的编程模式,有点像AngularJS和jQuery。所以要了解RAC,需要先在观念上做调整。

以下面这个Cell为例


正常的写法可能是这样,很直观。

- (void)configureWithItem:(HBItem *)item

{

self.username.text = item.text;

[self.avatarImageView setImageWithURL: item.avatarURL];

// 其他的一些设置

}

但如果用RAC,可能就是这样

- (id)init

{

if(self = [superinit]) {

@weakify(self);

[RACObserve(self, viewModel) subscribeNext:^(HBItemViewModel *viewModel) {

@strongify(self);

self.username.text = viewModel.item.text;

[self.avatarImageView setImageWithURL: viewModel.item.avatarURL];

// 其他的一些设置

}];

}

}

也就是先把数据绑定,接下来只要数据有变化,就会自动响应变化。在这里,每次viewModel改变时,内容就会自动变成该viewModel的内容。

Signal

Signal是RAC的核心,为了帮助理解,画了这张简化图


这里的数据源和sendXXX,可以理解为函数的参数和返回值。当Signal处理完数据后,可以向下一个Signal或Subscriber传送数据。可以看到上半部分的两个Signal是冷的(cold),相当于实现了某个函数,但该函数没有被调用。同时也说明了Signal可以被组合使用,比如RACSignal *signalB = [signalA map:^id(id x){return x}],或RACSignal *signalB = [signalA take:1]等等。

当signal被subscribe时,就会处于热(hot)的状态,也就是该函数会被执行。比如上面的第二张图,首先signalA可能发了一个网络请求,拿到结果后,把数据通过sendNext方法传递到下一个signal,signalB可以根据需要做进一步处理,比如转换成相应的Model,转换完后再sendNext到subscriber,subscriber拿到数据后,再改变ViewModel,同时因为View已经绑定了ViewModel,所以拿到的数据会自动在View里呈现。

还有,一个signal可以被多个subscriber订阅,这里怕显得太乱就没有画出来,但每次被新的subscriber订阅时,都会导致数据源的处理逻辑被触发一次,这很有可能导致意想不到的结果,需要注意一下。

当数据从signal传送到subscriber时,还可以通过doXXX来做点事情,比如打印数据。

通过这张图可以看到,这非常像中学时学的函数,比如 f(x) = y,某一个函数的输出又可以作为另一个函数的输入,比如 f(f(x)) = z,这也正是「函数响应式编程」(FRP)的核心。

有些地方需要注意下,比如把signal作为local变量时,如果没有被subscribe,那么方法执行完后,该变量会被dealloc。但如果signal有被subscribe,那么subscriber会持有该signal,直到signal sendCompleted或sendError时,才会解除持有关系,signal才会被dealloc。

RACCommand

RACCommand是RAC很重要的组成部分,可以节省很多时间并且让你的App变得更Robust,这篇文章可以帮助你更深入的理解,这里简单做一下介绍。

RACCommand 通常用来表示某个Action的执行,比如点击Button。它有几个比较重要的属性:executionSignals / errors / executing。

1、executionSignals是signal of signals,如果直接subscribe的话会得到一个signal,而不是我们想要的value,所以一般会配合switchToLatest。

2、errors。跟正常的signal不一样,RACCommand的错误不是通过sendError来实现的,而是通过errors属性传递出来的。

3、executing表示该command当前是否正在执行。

假设有这么个需求:当图片载入完后,分享按钮才可用。那么可以这样:

RACSignal *imageAvailableSignal = [RACObserve(self, imageView.image) map:id^(id x){returnx ? @YES : @NO}];

self.shareButton.rac_command = [[RACCommand alloc] initWithEnabled:imageAvailableSignal signalBlock:^RACSignal *(id input) {

// do share logic

}];

除了与UIControl绑定之外,也可以手动执行某个command,比如双击图片点赞,就可以这么实现。

// ViewModel.m

- (instancetype)init

{

self = [superinit];

if(self) {

void(^updatePinLikeStatus)() = ^{

self.pin.likedCount = self.pin.hasLiked ? self.pin.likedCount - 1 : self.pin.likedCount + 1;

self.pin.hasLiked = !self.pin.hasLiked;

};

_likeCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {

// 先展示效果,再发送请求

updatePinLikeStatus();

return[[HBAPIManager sharedManager] likePinWithPinID:self.pin.pinID];

}];

[_likeCommand.errors subscribeNext:^(id x) {

// 发生错误时,回滚

updatePinLikeStatus();

}];

}

returnself;

}

// ViewController.m

- (void)viewDidLoad

{

[superviewDidLoad];

// ...

@weakify(self);

[RACObserve(self, viewModel.hasLiked) subscribeNex:^(id x){

@strongify(self);

self.pinLikedCountLabel.text = self.viewModel.likedCount;

self.likePinImageView.image = [UIImage imageNamed:self.viewModel.hasLiked ? @"pin_liked": @"pin_like"];

}];

UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] init];

tapGesture.numberOfTapsRequired = 2;

[[tapGesture rac_gestureSignal] subscribeNext:^(id x) {

[self.viewModel.likeCommand execute:nil];

}];

}

再比如某个App要通过Twitter登录,同时允许取消登录,就可以这么做 (source)

_twitterLoginCommand = [[RACCommand alloc] initWithSignalBlock:^(id _) {

@strongify(self);

return[[self

twitterSignInSignal]

takeUntil:self.cancelCommand.executionSignals];

}];

RAC(self.authenticatedUser) = [self.twitterLoginCommand.executionSignals switchToLatest];

常用的模式

map + switchToLatest

switchToLatest: 的作用是自动切换signal of signals到最后一个,比如之前的command.executionSignals就可以使用switchToLatest:。

map:的作用很简单,对sendNext的value做一下处理,返回一个新的值。

如果把这两个结合起来就有意思了,想象这么个场景,当用户在搜索框输入文字时,需要通过网络请求返回相应的hints,每当文字有变动时,需要取消上一次的请求,就可以使用这个配搭。这里用另一个Demo,简单演示一下

NSArray *pins = @[@172230988, @172230947, @172230899, @172230777, @172230707];

__block NSInteger index = 0;

RACSignal *signal = [[[[RACSignal interval:0.1 onScheduler:[RACScheduler scheduler]]

take:pins.count]

map:^id(id value) {

return[[[HBAPIManager sharedManager] fetchPinWithPinID:[pins[index++] intValue]] doNext:^(id x) {

NSLog(@"这里只会执行一次");

}];

}]

switchToLatest];

[signal subscribeNext:^(HBPin *pin) {

NSLog(@"pinID:%d", pin.pinID);

} completed:^{

NSLog(@"completed");

}];

// output

// 2014-06-05 17:40:49.851 这里只会执行一次

// 2014-06-05 17:40:49.851 pinID:172230707

// 2014-06-05 17:40:49.851 completed

takeUntil

takeUntil:someSignal 的作用是当someSignal sendNext时,当前的signal就sendCompleted,someSignal就像一个拳击裁判,哨声响起就意味着比赛终止。

它的常用场景之一是处理cell的button的点击事件,比如点击Cell的详情按钮,需要push一个VC,就可以这样:

[[[cell.detailButton

rac_signalForControlEvents:UIControlEventTouchUpInside]

takeUntil:cell.rac_prepareForReuseSignal]

subscribeNext:^(id x) {

// generate and push ViewController

}];

如果不加takeUntil:cell.rac_prepareForReuseSignal,那么每次Cell被重用时,该button都会被addTarget:selector。

替换Delegate

出现这种需求,通常是因为需要对Delegate的多个方法做统一的处理,这时就可以造一个signal出来,每次该Delegate的某些方法被触发时,该signal就会sendNext。

@implementation UISearchDisplayController (RAC)

- (RACSignal *)rac_isActiveSignal {

self.delegate = self;

RACSignal *signal = objc_getAssociatedObject(self, _cmd);

if(signal != nil)returnsignal;

/* Create two signals and merge them */

RACSignal *didBeginEditing = [[self rac_signalForSelector:@selector(searchDisplayControllerDidBeginSearch:)

fromProtocol:@protocol(UISearchDisplayDelegate)] mapReplace:@YES];

RACSignal *didEndEditing = [[self rac_signalForSelector:@selector(searchDisplayControllerDidEndSearch:)

fromProtocol:@protocol(UISearchDisplayDelegate)] mapReplace:@NO];

signal = [RACSignal merge:@[didBeginEditing, didEndEditing]];

objc_setAssociatedObject(self, _cmd, signal, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

returnsignal;

}

@end

代码源于此文

使用ReactiveViewModel的didBecomActiveSignal

ReactiveViewModel是另一个project, 后面的MVVM中会讲到,通常的做法是在VC里设置VM的active属性(RVMViewModel自带该属性),然后在VM里subscribeNext didBecomActiveSignal,比如当Active时,获取TableView的最新数据。

RACSubject的使用场景

一般不推荐使用RACSubject,因为它过于灵活,滥用的话容易导致复杂度的增加。但有一些场景用一下还是比较方便的,比如ViewModel的errors。

ViewModel一般会有多个RACCommand,那这些commands如果出现error了该如何处理呢?比较方便的方法如下:

// HBCViewModel.h

#import "RVMViewModel.h"

@classRACSubject;

@interfaceHBCViewModel : RVMViewModel

@property (nonatomic) RACSubject *errors;

@end

// HBCViewModel.m

#import "HBCViewModel.h"

#import 

@implementation HBCViewModel

- (instancetype)init

{

self = [superinit];

if(self) {

_errors = [RACSubject subject];

}

returnself;

}

- (void)dealloc

{

[_errors sendCompleted];

}

@end

// Some Other ViewModel inherit HBCViewModel

- (instancetype)init

{

_fetchLatestCommand = [RACCommand alloc] initWithSignalBlock:^RACSignal *(id input){

// fetch latest data

}];

_fetchMoreCommand = [RACCommand alloc] initWithSignalBlock:^RACSignal *(id input){

// fetch more data

}];

[self.didBecomeActiveSignal subscribeNext:^(id x) {

[_fetchLatestCommand execute:nil];

}];

[[RACSignal

merge:@[

_fetchMoreCommand.errors,

_fetchLatestCommand.errors

]] subscribe:self.errors];

}

rac_signalForSelector

rac_signalForSelector: 这个方法会返回一个signal,当selector执行完时,会sendNext,也就是当某个方法调用完后再额外做一些事情。用在category会比较方便,因为Category重写父类的方法时,不能再通过[super XXX]来调用父类的方法,当然也可以手写Swizzle来实现,不过有了rac_signalForSelector:就方便多了。

rac_signalForSelector: fromProtocol: 可以直接实现对protocol的某个方法的实现(听着有点别扭呢),比如,我们想实现UIScrollViewDelegate的某些方法,可以这么写

[[self rac_signalForSelector:@selector(scrollViewDidEndDecelerating:) fromProtocol:@protocol(UIScrollViewDelegate)] subscribeNext:^(RACTuple *tuple) {

// do something

}];

[[self rac_signalForSelector:@selector(scrollViewDidScroll:) fromProtocol:@protocol(UIScrollViewDelegate)] subscribeNext:^(RACTuple *tuple) {

// do something

}];

self.scrollView.delegate = nil;

self.scrollView.delegate = self;

注意,这里的delegate需要先设置为nil,再设置为self,而不能直接设置为self,如果self已经是该scrollView的Delegate的话。

有时,我们想对selector的返回值做一些处理,但很遗憾RAC不支持,如果真的有需要的话,可以使用Aspects

MVVM

这是一个大话题,如果有耐心,且英文还不错的话,可以看一下Cocoa Samurai的这两篇文章。PS: Facebook Paper就是基于MVVM构建的。

MVVM是Model-View-ViewModel的简称,它们之间的关系如下


可以看到View(其实是ViewController)持有ViewModel,这样做的好处是ViewModel更加独立且可测试,ViewModel里不应包含任何View相关的元素,哪怕换了一个View也能正常工作。而且这样也能让View/ViewController「瘦」下来。

ViewModel主要做的事情是作为View的数据源,所以通常会包含网络请求。

或许你会疑惑,ViewController哪去了?在MVVM的世界里,ViewController已经成为了View的一部分。它的主要职责是将VM与View绑定、响应VM数据的变化、调用VM的某个方法、与其他的VC打交道。

而RAC为MVVM带来很大的便利,比如RACCommand, UIKit的RAC Extension等等。使用MVVM不一定能减少代码量,但能降低代码的复杂度。

以下面这个需求为例,要求大图滑动结束时,底部的缩略图滚动到对应的位置,并高亮该缩略图;同时底部的缩略图被选中时,大图也要变成该缩略图的大图。


我的思路是横向滚动的大图是一个collectionView,该collectionView是当前页面VC的一个property。底部可以滑动的缩略图是一个childVC的collectionView,这两个collectionView共用一套VM,并且各自RACObserve感兴趣的property。

比如大图滑到下一页时,会改变VM的indexPath属性,而底部的collectionView所在的VC正好对该indexPath感兴趣,只要indexPath变化就滚动到相应的Item

// childVC

- (void)viewDidLoad

{

[superviewDidLoad];

@weakify(self);

[RACObserve(self, viewModel.indexPath) subscribeNext:^(NSNumber *index) {

@strongify(self);

[self scrollToIndexPath];

}];

}

- (void)scrollToIndexPath

{

if(self.collectionView.subviews.count) {

NSIndexPath *indexPath = self.viewModel.indexPath;

[self.collectionView scrollToItemAtIndexPath:indexPath atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally animated:YES];

[self.collectionView.subviews enumerateObjectsUsingBlock:^(UIView *view, NSUInteger idx, BOOL *stop) {

view.layer.borderWidth = 0;

}];

UIView *view = [self.collectionView cellForItemAtIndexPath:indexPath];

view.layer.borderWidth = kHBPinsNaviThumbnailPadding;

view.layer.borderColor = [UIColor whiteColor].CGColor;

}

}

当点击底部的缩略图时,上面的大图也要做出变化,也同样可以通过RACObserve indexPath来实现

// PinsViewController.m

- (void)viewDidLoad

{

[superviewDidLoad];

@weakify(self);

[[RACObserve(self, viewModel.indexPath)

skip:1]

subscribeNext:^(NSIndexPath *indexPath) {

@strongify(self);

[self.collectionView scrollToItemAtIndexPath:indexPath atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally animated:YES];

}];

}

这里有一个小技巧,当Cell里的元素比较复杂时,我们可以给Cell也准备一个ViewModel,这个CellViewModel可以由上一层的ViewModel提供,这样Cell如果需要相应的数据,直接跟CellViewModel要即可,CellViewModel也可以包含一些command,比如likeCommand。假如点击Cell时,要做一些处理,也很方便。

// CellViewModel已经在ViewModel里准备好了

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath

{

HBPinsCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:cellIdentifier forIndexPath:indexPath];

cell.viewModel = self.viewModel.cellViewModels[indexPath.row];

returncell;

}

- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath

{

HBCellViewModel *cellViewModel = self.viewModel.cellViewModels[indexPath.row];

// 对cellViewModel执行某些操作,因为Cell已经与cellViewModel绑定,所以cellViewModel的改变也会反映到Cell上

// 或拿到cellViewModel的数据来执行某些操作

}

ViewModel中signal, property, command的使用

初次使用RAC+MVVM时,往往会疑惑,什么时候用signal,什么时候用property,什么时候用command?

一般来说可以使用property的就直接使用,没必要再转换成signal,外部RACObserve即可。使用signal的场景一般是涉及到多个property或多个signal合并为一个signal。command往往与UIControl/网络请求挂钩。

常见场景的处理

检查本地缓存,如果失效则去请求网络数据并缓存到本地

来源

- (RACSignal *)loadData {

return[[RACSignal

createSignal:^(id subscriber) {

// If the cache is valid then we can just immediately send the

// cached data and be done.

if(self.cacheValid) {

[subscriber sendNext:self.cachedData];

[subscriber sendCompleted];

}else{

[subscriber sendError:self.staleCacheError];

}

}]

// Do the subscription work on some random scheduler, off the main

// thread.

subscribeOn:[RACScheduler scheduler]];

}

- (void)update {

[[[[self

loadData]

// Catch the error from -loadData. It means our cache is stale. Update

// our cache and save it.

catch:^(NSError *error) {

return[[self updateCachedData] doNext:^(id data) {

[self cacheData:data];

}];

}]

// Our work up until now has been on a background scheduler. Get our

// results delivered on the main thread so we can do UI work.

deliverOn:RACScheduler.mainThreadScheduler]

subscribeNext:^(id data) {

// Update your UI based on `data`.

// Update again after `updateInterval` seconds have passed.

[[RACSignal interval:updateInterval] take:1] subscribeNext:^(id _) {

[self update];

}];

}];

}

检测用户名是否可用

来源

- (void)setupUsernameAvailabilityChecking {

RAC(self, availabilityStatus) = [[[RACObserve(self.userTemplate, username)

throttle:kUsernameCheckThrottleInterval]//throttle表示interval时间内如果有sendNext,则放弃该nextValue

map:^(NSString *username) {

if(username.length == 0)return[RACSignalreturn:@(UsernameAvailabilityCheckStatusEmpty)];

return[[[[[FIBAPIClient sharedInstance]

getUsernameAvailabilityFor:username ignoreCache:NO]

map:^(NSDictionary *result) {

NSNumber *existsNumber = result[@"exists"];

if(!existsNumber)return@(UsernameAvailabilityCheckStatusFailed);

UsernameAvailabilityCheckStatus status = [existsNumber boolValue] ? UsernameAvailabilityCheckStatusUnavailable : UsernameAvailabilityCheckStatusAvailable;

return@(status);

}]

catch:^(NSError *error) {

return[RACSignalreturn:@(UsernameAvailabilityCheckStatusFailed)];

}] startWith:@(UsernameAvailabilityCheckStatusChecking)];

}]

switchToLatest];

}

可以看到这里也使用了map + switchToLatest模式,这样就可以自动取消上一次的网络请求。

startWith的内部实现是concat,这里表示先将状态置为checking,然后再根据网络请求的结果设置状态。

使用takeUntil:来处理Cell的button点击

这个上面已经提到过了。

token过期后自动获取新的

开发APIClient时,会用到AccessToken,这个Token过一段时间会过期,需要去请求新的Token。比较好的用户体验是当token过期后,自动去获取新的Token,拿到后继续上一次的请求,这样对用户是透明的。

RACSignal *requestSignal = [RACSignal createSignal:^RACDisposable *(id subscriber) {

// suppose first time send request, access token is expired or invalid

// and next time it is correct.

// the block will be triggered twice.

staticBOOL isFirstTime = 0;

NSString *url = @"http://httpbin.org/ip";

if(!isFirstTime) {

url = @"http://nonexists.com/error";

isFirstTime = 1;

}

NSLog(@"url:%@", url);

[[AFHTTPRequestOperationManager manager] GET:url parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {

[subscriber sendNext:responseObject];

[subscriber sendCompleted];

} failure:^(AFHTTPRequestOperation *operation, NSError *error) {

[subscriber sendError:error];

}];

returnnil;

}];

self.statusLabel.text = @"sending request...";

[[requestSignalcatch:^RACSignal *(NSError *error) {

self.statusLabel.text = @"oops, invalid access token";

// simulate network request, and we fetch the right access token

return[[RACSignal createSignal:^RACDisposable *(id subscriber) {

doubledelayInSeconds = 1.0;

dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));

dispatch_after(popTime, dispatch_get_main_queue(), ^(void){

[subscriber sendNext:@YES];

[subscriber sendCompleted];

});

returnnil;

}] concat:requestSignal];

}] subscribeNext:^(id x) {

if([x isKindOfClass:[NSDictionaryclass]]) {

self.statusLabel.text = [NSString stringWithFormat:@"result:%@", x[@"origin"]];

}

} completed:^{

NSLog(@"completed");

}];

注意事项

RAC我自己感觉遇到的几个难点是: 1) 理解RAC的理念。 2) 熟悉常用的API。3) 针对某些特定的场景,想出比较合理的RAC处理方式。不过看多了,写多了,想多了就会慢慢适应。下面是我在实践过程中遇到的一些小坑。

ReactiveCocoaLayout

有时Cell的内容涉及到动态的高度,就会想到用Autolayout来布局,但RAC已经为我们准备好了ReactiveCocoaLayout,所以我想不妨就拿来用一下。

ReactiveCocoaLayout的使用好比「批地」和「盖房」,先通过insetWidth:height:nullRect从某个View中划出一小块,拿到之后还可以通过divideWithAmount:padding:fromEdge 再分成两块,或sliceWithAmount:fromEdge再分出一块。这些方法返回的都是signal,所以可以通过RAC(self.view, frame) = someRectSignal 这样来实现绑定。但在实践中发现性能不是很好,多批了几块地就容易造成主线程卡顿。

所以ReactiveCocoaLayout最好不用或少用。

调试


刚开始写RAC时,往往会遇到这种情况,满屏的调用栈信息都是RAC的,要找出真正出现问题的地方不容易。曾经有一次在使用[RACSignal combineLatest: reduce:^id{}]时,忘了在Block里返回value,而Xcode也没有提示warning,然后就是莫名其妙地挂起了,跳到了汇编上,也没有调用栈信息,这时就只能通过最古老的注释代码的方式来找到问题的根源。

不过写多了之后,一般不太会犯这种低级错误。

strongify / weakify dance

因为RAC很多操作都是在Block中完成的,这块最常见的问题就是在block直接把self拿来用,造成block和self的retain cycle。所以需要通过@strongify和@weakify来消除循环引用。

有些地方很容易被忽略,比如RACObserve(thing, keypath),看上去并没有引用self,所以在subscribeNext时就忘记了weakify/strongify。但事实上RACObserve总是会引用self,即使target不是self,所以只要有RACObserve的地方都要使用weakify/strongify。

小结

以上是我在做花瓣客户端和side project时总结的一些经验,但愿能带来一些帮助,有误的地方也欢迎指正和探讨。

推荐一下jspahrsummers的这个project,虽然是用RAC3.0写的,但很多理念也可以用到RAC2上面。

最后感谢Github的iOS工程师们,感谢你们带来了RAC,以及在Issues里的耐心解答。

这篇关于ReactiveCocoa之实现篇的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!


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

相关文章

Nginx实现动态封禁IP的步骤指南

《Nginx实现动态封禁IP的步骤指南》在日常的生产环境中,网站可能会遭遇恶意请求、DDoS攻击或其他有害的访问行为,为了应对这些情况,动态封禁IP是一项十分重要的安全策略,本篇博客将介绍如何通过NG... 目录1、简述2、实现方式3、使用 fail2ban 动态封禁3.1 安装 fail2ban3.2 配

Java中实现订单超时自动取消功能(最新推荐)

《Java中实现订单超时自动取消功能(最新推荐)》本文介绍了Java中实现订单超时自动取消功能的几种方法,包括定时任务、JDK延迟队列、Redis过期监听、Redisson分布式延迟队列、Rocket... 目录1、定时任务2、JDK延迟队列 DelayQueue(1)定义实现Delayed接口的实体类 (

将java程序打包成可执行文件的实现方式

《将java程序打包成可执行文件的实现方式》本文介绍了将Java程序打包成可执行文件的三种方法:手动打包(将编译后的代码及JRE运行环境一起打包),使用第三方打包工具(如Launch4j)和JDK自带... 目录1.问题提出2.如何将Java程序打包成可执行文件2.1将编译后的代码及jre运行环境一起打包2

通俗易懂的Java常见限流算法具体实现

《通俗易懂的Java常见限流算法具体实现》:本文主要介绍Java常见限流算法具体实现的相关资料,包括漏桶算法、令牌桶算法、Nginx限流和Redis+Lua限流的实现原理和具体步骤,并比较了它们的... 目录一、漏桶算法1.漏桶算法的思想和原理2.具体实现二、令牌桶算法1.令牌桶算法流程:2.具体实现2.1

MySQL8.0设置redo缓存大小的实现

《MySQL8.0设置redo缓存大小的实现》本文主要在MySQL8.0.30及之后版本中使用innodb_redo_log_capacity参数在线更改redo缓存文件大小,下面就来介绍一下,具有一... mysql 8.0.30及之后版本可以使用innodb_redo_log_capacity参数来更改

C++使用栈实现括号匹配的代码详解

《C++使用栈实现括号匹配的代码详解》在编程中,括号匹配是一个常见问题,尤其是在处理数学表达式、编译器解析等任务时,栈是一种非常适合处理此类问题的数据结构,能够精确地管理括号的匹配问题,本文将通过C+... 目录引言问题描述代码讲解代码解析栈的状态表示测试总结引言在编程中,括号匹配是一个常见问题,尤其是在

Java实现检查多个时间段是否有重合

《Java实现检查多个时间段是否有重合》这篇文章主要为大家详细介绍了如何使用Java实现检查多个时间段是否有重合,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录流程概述步骤详解China编程步骤1:定义时间段类步骤2:添加时间段步骤3:检查时间段是否有重合步骤4:输出结果示例代码结语作

使用C++实现链表元素的反转

《使用C++实现链表元素的反转》反转链表是链表操作中一个经典的问题,也是面试中常见的考题,本文将从思路到实现一步步地讲解如何实现链表的反转,帮助初学者理解这一操作,我们将使用C++代码演示具体实现,同... 目录问题定义思路分析代码实现带头节点的链表代码讲解其他实现方式时间和空间复杂度分析总结问题定义给定

Java覆盖第三方jar包中的某一个类的实现方法

《Java覆盖第三方jar包中的某一个类的实现方法》在我们日常的开发中,经常需要使用第三方的jar包,有时候我们会发现第三方的jar包中的某一个类有问题,或者我们需要定制化修改其中的逻辑,那么应该如何... 目录一、需求描述二、示例描述三、操作步骤四、验证结果五、实现原理一、需求描述需求描述如下:需要在

如何使用Java实现请求deepseek

《如何使用Java实现请求deepseek》这篇文章主要为大家详细介绍了如何使用Java实现请求deepseek功能,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1.deepseek的api创建2.Java实现请求deepseek2.1 pom文件2.2 json转化文件2.2