客户要求APP客户端每次切换Tab,都需要从服务器去获取最新的数据,所以每次切换Tab,客户端都会去主动刷新接口,以获取最新的数据。但是实际发现,每次切换Tab都去刷新,从用户体验上感觉刷新太频繁了;从性能上体验,没有数据更新也去频繁的请求接口,很消耗网络资源。于是,客户针对这一情况,提出了新的需求:当服务器有数据更新的时候,此时客户端去主动刷新列表接口以获取最新的数据,否则不需要客户端主动去请求接口,直接显示上一次从服务器请求的数据。这样就既做到了用户每次可以看到最新的数据,而且不影响用户体验和性能。
要实现列表做到服务器有数据更新时去主动请求接口,没有数据更新时则不请求接口,关键是要知道服务器什么时候数据更新了,需要服务器主动告知客户端某个列表的接口有数据更新了,当用户切换到此列表时,需要客户端主动去请求接口,获取最新的数据。要实现这一功能,采用websocket是最合适的。websocket能保持服务器和客户端的通信,当服务器有数据更新了,通过websocket告知用户需要刷新列表了。
WebSocket 是 HTML5 一种新的协议。它实现了浏览器与服务器全双工通信,能更好的节省服务器资源和带宽并达到实时通讯。
WebSocket工作原理
第一步:客户端连接websocket(连接地址 ws://…)
第二步:服务器向客户端发送心跳包。
如果客户端能收到服务器发送的心跳包,则表明websocket连接成功。
只要客户端不断开websocket,就能一直收到服务器发送的心跳包,如果客户端收不到心跳包了,则说明已经断开了连接,此时需要客户端重新去连接websocket。心跳包的存在,其实就是为了监测websocket是否断开连接了。
第三步:服务器主动推送消息给客户端,客户端根据具体的需求去处理业务逻辑。
第四步:断开websocket连接。
当用户退出登录,或者是App退到了后台,此时则需要主动断开websocket,以减少服务器性能的损耗。
以上,就是websocket的工作原理。
WebSocket的工具类网上有很多,从网上下载一个websocet连接和收发消息的工具类即可。
iOS WebSocket 工具类下载地址:https://www.jianshu.com/p/821b777555d3
- (void)webSocketLogin
{
//请更换websocket连接地址
NSString *url = @"ws://121.40.165.18:8800";
[[SocketRocketUtility instance] SRWebSocketOpenWithURLString:url];
}
连接成功之后,可以通过如下方法,收到服务器推送过来的websocket消息:
- (void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message {
if (webSocket == self.socket) {
NSLog(@"************************** socket收到数据了************************** ");
NSLog(@"message:%@", message);
}
}
打印日志如下:
************************** socket 连接成功**************************
message:{"type":"welcome"} //websocket连接成功的提示
message:{"type":"ping","message":1650606091} //心跳包
message:{"type":"ping","message":1650606094} //心跳包
message:{"type":"ping","message":1650606097} //心跳包
...
Websocket连接辅助测试网站:http://www.websocket-test.com/
比如,模拟 列表页/关注 列表接口的数据更新,服务器会通过websocket向客户端推送如下消息:
{"message":{"type":"refresh_list", "list_key":"列表/关注","timestamp":1669097371}}
这里主要是通过当前数据更新的时间,与上一次接口刷新的时间对比,然后判断是否需要刷新某个列表的接口。根据接口文档提供的需要自动刷新的列表接口有:
["列表页/关注", "列表页/推荐", "列表页/热点", "列表页/问答"]
大概讲一下我的思路:
#import "ApiNameData.h"
@implementation ApiNameData
+ (instancetype)sharedInstance
{
static ApiNameData *_sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_sharedInstance = [[ApiNameData alloc] init];
});
return _sharedInstance;
}
- (NSMutableDictionary *)apiNameDict
{
if(!_apiNameDict){
_apiNameDict = [[NSMutableDictionary alloc] init];
}
return _apiNameDict;
}
@interface RefreshUntil : NSObject
+ (RefreshUntil *)shareInstance;
- (void)refreshSuccess:(NSString *)apiName;
- (BOOL)isNeedRefresh:(NSString *)apiName;
- (void)setRefreshTime:(NSNumber *)timestamp withApiName:(NSString *)apiName;
- (void)clear;
@end
#import "RefreshUntil.h"
@implementation RefreshUntil
+ (RefreshUntil *)shareInstance {
static RefreshUntil *shareInstance = nil;
static dispatch_once_t predicate;
dispatch_once(&predicate, ^{
shareInstance = [[RefreshUntil alloc] init];
});
return shareInstance;
}
- (void)refreshSuccess:(NSString *)apiName
{
[[ApiNameData sharedInstance].apiNameDict setObject:[self nowTimeInterval] forKey:apiName];
}
- (BOOL)isNeedRefresh:(NSString *)apiName
{
NSNumber *timestamp1 = [self nowTimeInterval];
NSNumber *timestamp2 = [ApiNameData sharedInstance].apiNameDict[apiName];
return [timestamp1 doubleValue]<[timestamp2 doubleValue];
}
- (void)setRefreshTime:(NSNumber *)timestamp withApiName:(NSString *)apiName
{
[[ApiNameData sharedInstance].apiNameDict setObject:timestamp forKey:apiName];
}
- (void)clear
{
[[ApiNameData sharedInstance].apiNameDict removeAllObjects];
}
//获取当前时间戳
- (NSNumber *)nowTimeInterval
{
NSDate *date = [NSDate dateWithTimeIntervalSinceNow:0];
NSString *time = [NSString stringWithFormat:@"%.0f", [date timeIntervalSince1970]];
NSNumber *timestamp = [NSNumber numberWithDouble:[time doubleValue]];
return timestamp;
}
@end
- (void)receiveRefreshMessage:(NSDictionary *)messageDict
{
NSString *list_key = messageDict[@"list_key"];
NSNumber *timestamp = messageDict[@"timestamp"];
[[RefreshUntil shareInstance] setRefreshTime:timestamp withApiName:list_key];
}
BOOL isNeedRefresh = [[RefreshUntil shareInstance] isNeedRefresh:@"列表页/关注"];
if(isNeedRefresh==YES){
[self.tableView.mj_header beginRefreshing];
}
[[RefreshUntil shareInstance] refreshSuccess:@"列表页/关注"];
需要主动连接websocket的情况:
需要主动断开websocket的情况:
需要重新连接websocket的情况:
效果图
