1.配置XMPP(XMPPConfig.m)

2.配置XMPPFramework框架

3.创建单例类(XMPPManager.h/XMPPManager.m)管理器

XMPPManager.m:

#import "XMPPManager.h"

#import "AppDelegate.h"

//连接服务器的目的

typedef NS_ENUM(NSInteger, ConnectToServerPopurpose)

{

ConnectToServerPopurposeLogin, //登录

ConnectToServerPopurposeRegist //注册

};

@interface XMPPManager ()<XMPPStreamDelegate,XMPPRosterDelegate>

@property (nonatomic, assign) ConnectToServerPopurpose serverPurpose; //连接服务器的目的

@property (nonatomic, copy) NSString *loginPassword; //登录密码

@property (nonatomic, copy) NSString *registerPassword; //注册密码

- (void)connectServer; //连接服务器

- (void)disConnectWithServer; //断开服务器

@end

@implementation XMPPManager

static XMPPManager *manager = nil;

+ (XMPPManager *)defaultXMPPManager {

//gcd once 程序执行期间只执行一次

static dispatch_once_t onceToken;

dispatch_once(&onceToken, ^{

manager = [[XMPPManager alloc] init];

});

return manager;

}

//重写init方法

- (instancetype)init

{

self = [super init];

if (self) {

//创建通信通道用于和服务器进行连接和沟通

self.stream = [[XMPPStream alloc] init];

//设置服务器

self.stream.hostName = kHostName;

//设置端口号

self.stream.hostPort = kHostPort;

//添加代理

[self.stream addDelegate:self delegateQueue:dispatch_get_main_queue()];

//创建好友列表仓库

XMPPRosterCoreDataStorage *rosterCorDataStorage = [XMPPRosterCoreDataStorage sharedInstance];

//创建花名册对象

self.roster = [[XMPPRoster alloc] initWithRosterStorage:rosterCorDataStorage dispatchQueue:dispatch_get_main_queue()];

//将花名册对象添加到stream活动

[self.roster activate:self.stream];

//添加代理

[self.roster addDelegate:self delegateQueue:dispatch_get_main_queue()];

//创建信息归档对象

XMPPMessageArchivingCoreDataStorage *messageArchingCoreStorage = [XMPPMessageArchivingCoreDataStorage sharedInstance];

//创建信息归档对象

self.messageArching = [[XMPPMessageArchiving alloc] initWithMessageArchivingStorage:messageArchingCoreStoragedispatchQueue:dispatch_get_main_queue()];

//添加活动到通信管道

[self.messageArching activate:self.stream];

//获取数据管理器

self.managerContext = messageArchingCoreStorage.mainThreadManagedObjectContext;

}

return self;

}

#pragma mark - XMPPRosterDelegate

- (void)xmppRoster:(XMPPRoster *)sender didReceivePresenceSubscriptionRequest:(XMPPPresence *)presence {

//获取请求对象的JID

XMPPJID *requestJID = presence.from;

//创建提示框

UIAlertController *alertVC = [UIAlertController alertControllerWithTitle:@"添加好友提醒" message:requestJID.userpreferredStyle:UIAlertControllerStyleAlert];

//创建事件

UIAlertAction *addAction = [UIAlertAction actionWithTitle:@"添加" style:(UIAlertActionStyleDefault) handler:^(UIAlertAction *action) {

//接受好友请求

[self.roster acceptPresenceSubscriptionRequestFrom:requestJID andAddToRoster:YES];

}];

UIAlertAction *rejectAction = [UIAlertAction actionWithTitle:@"拒绝" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {

//拒绝好友请求

[self.roster rejectPresenceSubscriptionRequestFrom:requestJID];

}];

//添加事件

[alertVC addAction:addAction];

[alertVC addAction:rejectAction];

//弹出提示框

//获取AppDelegate

AppDelegate *appDelegate  = [UIApplication sharedApplication].delegate;

//获取根视图控制器

UIViewController *rootVC = appDelegate.window.rootViewController;

[rootVC presentViewController:alertVC animated:YES completion:nil];

}

//连接服务器

- (void)connectServer {

if ([self.stream isConnected] || [self.stream isConnecting]) {

//断开连接

[self disConnectWithServer];

}

//建立新的连接(30秒超时)

NSError *error = nil;

[self.stream connectWithTimeout:30 error:&error];

if (error) {

NSLog(@"connect fail");

}

}

//登录

- (void)loginWithUserName:(NSString *)username password:(NSString *)pw {

self.loginPassword = pw; //记录登录密码

self.serverPurpose = ConnectToServerPopurposeLogin; //登录标识

//获取jid 唯一标识

XMPPJID *myJID = [XMPPJID jidWithUser:username domain:kDomin resource:kResource];

self.stream.myJID = myJID; //设置JID

//连接服务器

[self connectServer];

}

//注册

- (void)registWithUserName:(NSString *)username passeord:(NSString *)pw {

self.registerPassword = pw; //记录注册密码

self.serverPurpose = ConnectToServerPopurposeRegist; //注册标识

//获取JID唯一标识

XMPPJID *myJID = [XMPPJID jidWithUser:username domain:kDomin resource:kResource];

self.stream.myJID = myJID; //设置JID

//连接服务器

[self connectServer];

}

//添加好友

- (void)addFriend:(NSString *)name {

//创建JID

XMPPJID *myJID = [XMPPJID jidWithString:[NSString stringWithFormat:@"%@@%@",name,kHostName]];

//添加好友

[self.roster subscribePresenceToUser:myJID];

}

//删除好友

- (void)deleteFriend:(NSString *)name {

//获取JID

XMPPJID *myJID = [XMPPJID jidWithString:[NSString stringWithFormat:@"%@@%@",name,kHostName]];

[self.roster removeUser:myJID];

}

//断开服务器连接

- (void)disConnectWithServer {

//断开服务器

[self.stream disconnect];

}

#pragma mark - XMPPStreamDelegate

//成功连接服务器

- (void)xmppStreamDidConnect:(XMPPStream *)sender {

NSLog(@"connect success");

switch (self.serverPurpose) {

case ConnectToServerPopurposeLogin:

//登录

{

[self.stream authenticateWithPassword:self.loginPassword error:nil];

}

break;

case ConnectToServerPopurposeRegist:

//注册

{

[self.stream registerWithPassword:self.registerPassword error:nil];

}

break;

default:

break;

}

}

//连接超时

- (void)xmppStreamConnectDidTimeout:(XMPPStream *)sender {

NSLog(@"connect time out");

}

@end

用户登录:

#import "LoginViewController.h"

#import "XMPPManager.h"

#import "RosterTableViewController.h"

BOOL isClickButton = YES;

@interface LoginViewController ()<XMPPStreamDelegate>

@property (weak, nonatomic) IBOutlet UITextField *userNameTF;

@property (weak, nonatomic) IBOutlet UITextField *passwordTF;

@end

@implementation LoginViewController

- (void)viewDidLoad {

[super viewDidLoad];

//添加代理

[[XMPPManager defaultXMPPManager].stream addDelegate:self delegateQueue:dispatch_get_main_queue()];

}

- (void)didReceiveMemoryWarning {

[super didReceiveMemoryWarning];

// Dispose of any resources that can be recreated.

}

/*

#pragma mark - Navigation

// In a storyboard-based application, you will often want to do a little preparation before navigation

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {

// Get the new view controller using [segue destinationViewController].

// Pass the selected object to the new view controller.

}

*/

#pragma mark - handleAction

- (IBAction)handleLogin:(UIButton *)sender {

isClickButton = YES; //标识点击了登录button

[[XMPPManager defaultXMPPManager] loginWithUserName:self.userNameTF.text password:self.passwordTF.text];

}

- (IBAction)handleRegister:(UIButton *)sender {

}

#pragma mark - XMPPStreamDelegate

//登录成功

- (void)xmppStreamDidAuthenticate:(XMPPStream *)sender {

//上线--更改状态

XMPPPresence *presence = [XMPPPresence presenceWithType:@"available"];

[[XMPPManager defaultXMPPManager].stream sendElement:presence];

if (isClickButton) {

//提示

UIAlertController *alertVC = [UIAlertController alertControllerWithTitle:@"温馨提示" message:@"欢迎回来" preferredStyle:(UIAlertControllerStyleAlert)];

//添加事件

UIAlertAction *action = [UIAlertAction actionWithTitle:@"好" style:(UIAlertActionStyleDefault) handler:^(UIAlertAction *action) {

//获取从storyBoard中获取联系人列表界面

RosterTableViewController *rosterVC = [self.storyboard instantiateViewControllerWithIdentifier:@"contact"];

//传值

rosterVC.userName = self.userNameTF.text;

rosterVC.paassWord = self.passwordTF.text;

//push

[self.navigationController pushViewController:rosterVC animated:YES];

}];

[alertVC addAction:action];

//弹出提示

[self presentViewController:alertVC animated:YES completion:nil];

//更改BOOL

isClickButton = NO;

}

}

//登录失败

- (void)xmppStream:(XMPPStream *)sender didNotAuthenticate:(NSXMLElement *)error {

//提示

UIAlertController *alertVC = [UIAlertController alertControllerWithTitle:@"温馨提示" message:@"账号或密码错误请核对" preferredStyle:(UIAlertControllerStyleAlert)];

//添加事件

UIAlertAction *action = [UIAlertAction actionWithTitle:@"好" style:(UIAlertActionStyleDefault) handler:^(UIAlertAction *action) {

}];

[alertVC addAction:action];

//弹出提示

[self presentViewController:alertVC animated:YES completion:nil];

}

@end

用户注册:

#import "RegisterViewController.h"

#import "XMPPManager.h"

@interface RegisterViewController ()<XMPPStreamDelegate>

@property (weak, nonatomic) IBOutlet UITextField *userNameTF;

@property (weak, nonatomic) IBOutlet UITextField *passwordTF;

@property (weak, nonatomic) IBOutlet UITextField *rePasswordTF;

@end

@implementation RegisterViewController

- (void)viewDidLoad {

[super viewDidLoad];

//添加代理

[[XMPPManager defaultXMPPManager].stream addDelegate:self delegateQueue:dispatch_get_main_queue()];

}

- (void)didReceiveMemoryWarning {

[super didReceiveMemoryWarning];

// Dispose of any resources that can be recreated.

}

/*

#pragma mark - Navigation

// In a storyboard-based application, you will often want to do a little preparation before navigation

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {

// Get the new view controller using [segue destinationViewController].

// Pass the selected object to the new view controller.

}

*/

#pragma mark - handleAction

- (IBAction)handleSubmit:(UIButton *)sender {

//注册

[[XMPPManager defaultXMPPManager] registWithUserName:self.userNameTF.text passeord:self.passwordTF.text];

}

- (IBAction)handleCleare:(UIButton *)sender {

}

#pragma mark - XMPPStreamDelegate

//注册成功

- (void)xmppStreamDidRegister:(XMPPStream *)sender {

NSLog(@"register success");

}

//注册失败

- (void)xmppStream:(XMPPStream *)sender didNotRegister:(NSXMLElement *)error {

NSLog(@"register fail");

}

@end

联系人列表

#import "RosterTableViewController.h"

#import "RosterCell.h"

#import "ChatTableViewController.h"

#import "XMPPManager.h"

@interface RosterTableViewController ()<XMPPRosterDelegate>

@property (nonatomic, strong) NSMutableArray *contacts; //联系人数组

@end

@implementation RosterTableViewController

- (void)viewDidLoad {

[super viewDidLoad];

// Uncomment the following line to preserve selection between presentations.

// self.clearsSelectionOnViewWillAppear = NO;

// Uncomment the following line to display an Edit button in the navigation bar for this view controller.

//     self.navigationItem.rightBarButtonItem = self.editButtonItem;

self.contacts = [NSMutableArray array]; //创建数组

//添加代理

[[XMPPManager defaultXMPPManager].roster addDelegate:self delegateQueue:dispatch_get_main_queue()];

//再次登录

[[XMPPManager defaultXMPPManager] loginWithUserName:self.userName password:self.paassWord];

}

- (void)didReceiveMemoryWarning {

[super didReceiveMemoryWarning];

// Dispose of any resources that can be recreated.

}

#pragma mark - Table view data source

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {

return 1;

}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {

return self.contacts.count;

}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

RosterCell *cell = [tableView dequeueReusableCellWithIdentifier:@"roster" forIndexPath:indexPath];

//获取对应的联系人JID

XMPPJID *jid = self.contacts[indexPath.row];

cell.textLabel.text = jid.user;

return cell;

}

#pragma mark - XMPPRosterDelegate

//开始检索好友

- (void)xmppRosterDidBeginPopulating:(XMPPRoster *)sender {

NSLog(@"begin search friends");

}

//检索好友,每执行一次获取一个好友信息

- (void)xmppRoster:(XMPPRoster *)sender didRecieveRosterItem:(NSXMLElement *)item {

NSLog(@"%@",[[item attributeForName:@"jid"] stringValue]);

//获取JIDStr

NSString *jidStr = [[item attributeForName:@"jid"] stringValue];

//获取JID

XMPPJID *myJID = [XMPPJID jidWithString:jidStr resource:kResource];

//防止重复添加好友

for (XMPPJID *JID in self.contacts) {

if ([JID.user isEqualToString:myJID.user]) {

return;

}

}

//放入数组

[self.contacts addObject:myJID];

//刷新界面

[self.tableView insertRowsAtIndexPaths:@[[NSIndexPath indexPathForRow:self.contacts.count - 1 inSection:0]]withRowAnimation:UITableViewRowAnimationLeft];

}

//结束检索好友

- (void)xmppRosterDidEndPopulating:(XMPPRoster *)sender {

NSLog(@"end search friends");

}

#pragma mark - Navigation

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {

//界面传值

//获取下一个试图控制器

ChatTableViewController *chatVC = segue.destinationViewController;

//获取cell

UITableViewCell *cell = sender;

//获取下标

NSInteger index = [self.tableView indexPathForCell:cell].row;

//获取对应的对象

XMPPJID *JID = self.contacts[index];

chatVC.friendJID = JID;

}

@end

聊天控制器

#import "ChatTableViewController.h"

#import "ChatCell.h"

@interface ChatTableViewController ()<XMPPStreamDelegate>

@property (nonatomic, strong) NSMutableArray *messageArray; //用来存储信息

@end

@implementation ChatTableViewController

- (void)viewDidLoad {

[super viewDidLoad];

// Uncomment the following line to preserve selection between presentations.

// self.clearsSelectionOnViewWillAppear = NO;

// Uncomment the following line to display an Edit button in the navigation bar for this view controller.

self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"发送" style:(UIBarButtonItemStyleDone) target:selfaction:@selector(sendMessage)];

//添加代理

[[XMPPManager defaultXMPPManager].stream addDelegate:self delegateQueue:dispatch_get_main_queue()];

//创建数组

self.messageArray = [NSMutableArray array];

//获取本地聊天信息

[self reloadMessage];

}

//发送新的消息

- (void)sendMessage

{

//创建新的信息

XMPPMessage *message = [XMPPMessage messageWithType:@"chat" to:self.friendJID];

//添加信息体

[message addBody:@"比如说"];

[[XMPPManager defaultXMPPManager].stream sendElement:message];

}

- (void)didReceiveMemoryWarning {

[super didReceiveMemoryWarning];

// Dispose of any resources that can be recreated.

}

#pragma mark - Table view data source

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {

return 1;

}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {

return self.messageArray.count;

}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

ChatCell *cell = [tableView dequeueReusableCellWithIdentifier:@"chat" forIndexPath:indexPath];

//获取对应数组元素

XMPPMessage *message = self.messageArray[indexPath.row];

if ([message isKindOfClass:[XMPPMessage class]]) {

//将收到的信息放到左边,发送的放到右边

if ([message.from.user isEqualToString:self.friendJID.user]) {

cell.textLabel.text = message.body;

cell.detailTextLabel.text = @"";

}else {

cell.textLabel.text = @"";

cell.detailTextLabel.text = message.body;

}

}else {

//数组中的对象时XMPPMessageArchiving_Mesage_CoreDataObject类型

XMPPMessageArchiving_Message_CoreDataObject *mes = (XMPPMessageArchiving_Message_CoreDataObject *)message;

//Outgoing发送,用来判断消息是接受的 还是发送的

if (![mes isOutgoing]) {

cell.textLabel.text = mes.message.body;

cell.detailTextLabel.text = @"";

}else {

cell.textLabel.text = @"";

cell.detailTextLabel.text = mes.message.body;

}

}

return cell;

}

//读取本地信息

- (void)reloadMessage

{

//获取数据管理器

NSManagedObjectContext *managerContext = [XMPPManager defaultXMPPManager].managerContext;

//请求对象

NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];

//实体描述对象

//XMPPMessageArchiving_Message_CoreDataObject是持久化信息对应的实体类

NSEntityDescription *entity = [NSEntityDescription entityForName:@"XMPPMessageArchiving_Message_CoreDataObject"inManagedObjectContext:managerContext];

[fetchRequest setEntity:entity];

// 查询条件

NSPredicate *predicate = [NSPredicate predicateWithFormat:@"bareJidStr == %@ AND streamBareJidStr == %@", self.friendJID.bare,[XMPPManagerdefaultXMPPManager].stream.myJID.bare];

[fetchRequest setPredicate:predicate];

// 排序

//按照时间排序

NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"timestamp"

ascending:YES];

[fetchRequest setSortDescriptors:[NSArray arrayWithObjects:sortDescriptor, nil]];

NSError *error = nil;

//执行查询,获取符合条件的对象

NSArray *fetchedObjects = [managerContext executeFetchRequest:fetchRequest error:&error];

if (fetchedObjects == nil) {

NSLog(@"your content is null for search");

}

//将查询到的本地聊天信息存放到数组中

[self.messageArray addObjectsFromArray:fetchedObjects];

//刷新数据

[self.tableView reloadData];

}

//展示信息

- (void)showMessageWithMessage:(XMPPMessage *)message {

//将信息放入数组

[self.messageArray addObject:message];

//刷新数据

NSIndexPath *indexPath = [NSIndexPath indexPathForRow:self.messageArray.count - 1 inSection:0];

[self.tableView insertRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationRight];

//滑动tableView到对应的cell

[self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:(UITableViewScrollPositionBottom) animated:YES];

}

#pragma mark - XMPPSteamDelegate

//接收信息

- (void)xmppStream:(XMPPStream *)sender didReceiveMessage:(XMPPMessage *)message {

NSLog(@"%@",message.body);

//只获取当前好友的聊天信息

if ([message.from.user isEqualToString:self.friendJID.user]) {

//展示信息

[self showMessageWithMessage:message];

}

}

//发送信息

- (void)xmppStream:(XMPPStream *)sender didSendMessage:(XMPPMessage *)message {

[self showMessageWithMessage:message];

}

/*

#pragma mark - Navigation

// In a storyboard-based application, you will often want to do a little preparation before navigation

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {

// Get the new view controller using [segue destinationViewController].

// Pass the selected object to the new view controller.

}

*/

@end

XMPP即时通讯(代码实现)的更多相关文章

  1. XMPP即时通讯资料记录

    几天开始研究XMPP即时通讯的技术,来实现移动应用的计时聊天功能.记录下参考的博客地址,还挺详细的. http://blog.csdn.net/fhbystudy/article/details/16 ...

  2. XMPP即时通讯

    XMPP:XMPP是基于XML的点对点通讯协议,The Extensible Messaging and Presence Protocol(可扩展通讯和表示协议). XMPP可用于服务类实时通讯,表 ...

  3. iOS开发之XMPP即时通讯简单实现

    首先搭载服务器和数据库 搭载服务器我用的是openfire,数据库用的是mysql 这里推荐两个链接 配置mysql,用的是mysql workbench http://justsee.iteye.c ...

  4. XMPP即时通讯基础知识

    XMPP参考 一.定义 XMPP 是一种很类似于http协议的一种数据传输协议,它的过程就如同“解包装--〉包装”的过程,用户只需要明白它接受的类型,并理解它返回的类型,就可以很好的利用xmpp来进行 ...

  5. xmpp即时通讯的笔记(摘抄)

    xmpp的使用: 即时通讯 instant messaging(IM) :  -->实时收发信息! 即时通讯相关软件: **QQ,MSN,GoogleTalk,AIM,Jabber(XMPP别名 ...

  6. iOS中 XMPP即时通讯实现的主要步骤

    这里只是列出实现的只要步骤,不是全部代码. 首先导入XMPPFramework,及相关配置,完成后开始. 创建一个XMPPHelper  类来管理要进行的操作. XMPPHelper.h文件如下 ty ...

  7. XMPP即时通讯协议使用(前传)——协议详解

    XMPP详解 XMPP(eXtensible Messaging and Presence Protocol,可扩展消息处理和现场协议)是一种在两个地点间传递小型结构化数据的协议.在此基础上,XMPP ...

  8. XMPP即时通讯协议使用(七)——利用Strophe实现WebIM及strophe.plugins插件使用

    Strophe简介与Openfire配置 Strophe.js是为XMPP写的一个js类库.因为http协议本身不能实现持久连接,所以strophe利用BOSH模拟实现持久连接. 官方文档: http ...

  9. XMPP即时通讯协议使用(六)——开发Openfire聊天记录插件

    转载地址:http://www.cnblogs.com/hoojo/archive/2013/03/29/openfire_plugin_chatlogs_plugin_.html 开发环境: Sys ...

随机推荐

  1. Swift对面向对象提供了良好的支持,下面介绍几个其独有的特性。

    Swift对面向对象提供了良好的支持,下面介绍几个其独有的特性. 懒加载属性 Swift在语言层面上提供了类中懒加载属性的支持,使用lazy作为关键字: class Renderer { lazy v ...

  2. iOS 详解NSXMLParser方法解析XML数据方法

    前一篇文章已经介绍了如何通过URL从网络上获取xml数据.下面介绍如何将获取到的数据进行解析. 下面先看看xml的数据格式吧! <?xml version="1.0" enc ...

  3. 转载 C#中敏捷开发规范

    转载原地址 http://www.cnblogs.com/weixing/archive/2012/03/05/2380492.html 1.命名规则和风格 Naming Conventions an ...

  4. Oracle管道函数示例

    Oracle的管道函数需要定义下面的三样: Record/Object Type:定义一个Record或Object类型的变量,这个变量用于表示返回结果集的一行数据,有点像C#中的DataRow类. ...

  5. IDF实验室-简单编程-特殊的日子 writeup

    题目:http://ctf.idf.cn/index.php?g=game&m=article&a=index&id=50 题目提示要爆破,代表加密应该是不可逆的. 密文:4D ...

  6. cocos2d-x Animation

    转自:http://codingnow.cn/cocos2d-x/810.html 这一篇来学习怎么使用cocos2d-x引擎播放帧动画,就是把一帧一帧的图片像电影那样显示出来.1. 首先来了解一下相 ...

  7. 关于java.util.Properties读取中文乱码的正确解决方案(不要再用native2ascii.exe了)

    从Spring框架流行后,几乎根本不用自己写解析配置文件的代码了,但近日一个基础项目(实在是太基础,不能用硕大繁琐的Spring), 碰到了用java.util.Properties读取中文内容(UT ...

  8. SQLServer2005日志传送常见的几个问题

    1.STANDBY 只读方式还原数据库:[备份数据库服务器]将完全备份文件复制到备份数据库服务器上,并以STANDBY的方式进行恢复 . SQL语句: RESTORE DATABASE [CNBlog ...

  9. 并行编程之多线程共享非volatile变量,会不会可能导致线程while死循环

    背景 大家都知道线程之间共享变量要用volatilekeyword.可是,假设不用volatile来标识,会不会导致线程死循环?比方以下的伪代码: static int flag = -1; void ...

  10. [RxJS] Subject basic

    A Subject is a type that implements both Observer and Observable types. As an Observer, it can subsc ...