前言
- 上一章我們主要講述了如何利用服務(wù)器實(shí)現(xiàn)群聊.本章主要學(xué)習(xí)是從客戶(hù)端的角度簡(jiǎn)單實(shí)現(xiàn)群聊功能.
步驟:
- 1, 導(dǎo)入框架并且描述storyboard.設(shè)置"dataSource"監(jiān)聽(tīng)?zhēng)讉€(gè)控件等.見(jiàn)下圖 1.
Snip20160309_2.png
- 2, 創(chuàng)建客戶(hù)端的Socket對(duì)象,連接QQ服務(wù)器
#import "ViewController.h"
#import "GCDAsyncSocket.h"
@interface ViewController ()<GCDAsyncSocketDelegate, UITableViewDataSource>
/** 消息編輯框 */
@property (weak, nonatomic) IBOutlet UITextField *textField;
/** 顯示聊天內(nèi)容的tableView */
@property (weak, nonatomic) IBOutlet UITableView *chatTableView;
/** 發(fā)送消息 */
- (IBAction)sendMessage:(UIButton *)sender;
/** 客戶(hù)端的Socket對(duì)象 */
@property(nonatomic, strong) GCDAsyncSocket *clientSocket;
/** 數(shù)據(jù)源 */
@property(nonatomic, strong) NSMutableArray *dataSources;
@end
@implementation ViewController
#pragma mark - 懶加載
- (NSMutableArray *)dataSources
{
if (_dataSources == nil) {
_dataSources = [NSMutableArray array];
}
return _dataSources;
}
- (void)viewDidLoad {
[super viewDidLoad];
// 創(chuàng)建客戶(hù)端的Socket對(duì)象
GCDAsyncSocket *clientSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_global_queue(0, 0)];
// 發(fā)送連接請(qǐng)求
NSError *error = nil;
[clientSocket connectToHost:@"192.168.1.100" onPort:1886 error:&error];
// 判斷是否連接成功,但是真正的連接不是在這兒,而是在代理方法中連接或者斷開(kāi).
if (!error) {
NSLog(@"連接成功");
} else
{
NSLog(@"連接失敗啦%@",error);
}
// 保存創(chuàng)建好的客戶(hù)端的Socket對(duì)象
self.clientSocket = clientSocket;
}
-
注意點(diǎn) :
- 1, clientSocket是一個(gè)局部變量,所以需要定義一個(gè)屬性強(qiáng)引用,這樣在后面的的方法中才能拿到該對(duì)象.
2, 定義一個(gè)可變的數(shù)組,用于保存客戶(hù)端發(fā)送的消息(客戶(hù)端每發(fā)送一次消息就需要將消息保存到數(shù)組中),而且這個(gè)可變數(shù)組需要懶加載,用到時(shí)再創(chuàng)建.
3, 請(qǐng)求連接之后,真正的連接或者斷開(kāi)連接都是在代理方法中可以驗(yàn)證的.
#pragma mark - GCDAsyncSocketDelegate
/**
* 只要客戶(hù)端和服務(wù)器連接成功就會(huì)調(diào)該方法,第一個(gè)參數(shù)是客戶(hù)端,clientSocket是一個(gè)局部變量,所以需要定義成
* 一個(gè)屬性強(qiáng)引用著.
*/
- (void)socket:(GCDAsyncSocket *)clientSocket didConnectToHost:(NSString *)host port:(uint16_t)port
{
NSLog(@"連接成功");
// 連接成功,接收客戶(hù)端發(fā)送的數(shù)據(jù)
[clientSocket readDataWithTimeout:-1 tag:0];
}
/**
* 只要斷開(kāi)連接就一定會(huì)來(lái)到這個(gè)方法
*/
- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err
{
NSLog(@"斷開(kāi)連接");
}
- 4, 客戶(hù)端接收消息
/**
* 監(jiān)聽(tīng)客戶(hù)端是否發(fā)送了消息,只要發(fā)送了消息就會(huì)來(lái)到這個(gè)方法, 讀取信息
*/
- (void)socket:(GCDAsyncSocket *)clientSocket didReadData:(NSData *)data withTag:(long)tag
{
// 將發(fā)送的數(shù)據(jù)轉(zhuǎn)化為字符串
NSString *messageStr = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
// 判斷客戶(hù)端是否發(fā)送了消息,如果發(fā)送了消息,將消息保存至數(shù)據(jù)源中
if (messageStr) {
// 保存消息
[self.dataSources addObject:messageStr];
}
// 刷新UI,必須要回到主線程,否則數(shù)據(jù)顯示不出來(lái)
[[NSOperationQueue mainQueue]addOperationWithBlock:^{
// 刷新表格
[self.chatTableView reloadData];
}];
// 讀取(接收)數(shù)據(jù)
[clientSocket readDataWithTimeout:-1 tag:0];
}
-
注意點(diǎn) :
- 1, 將NSData轉(zhuǎn)為字符串后,需要判斷是否為空,如果不為空需要將字符串保存到dataSources數(shù)組中.
2, 接收到數(shù)據(jù)之后需要展示數(shù)據(jù),所以需要刷新表格,但是,當(dāng)前線程是在全局線程上執(zhí)行的,也就是說(shuō)當(dāng)前是一個(gè)異步函數(shù),屬于子線程,所以需要回到主線程上執(zhí)行刷新操作.
3, 當(dāng)刷新表格后都要監(jiān)聽(tīng)讀取數(shù)據(jù),否則顯示一次數(shù)據(jù)之后,就不會(huì)再接收任何數(shù)據(jù)了.
4, 數(shù)據(jù)源方法,用于展示數(shù)據(jù)到tableView上
#pragma mark - UITableViewDataSource
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.dataSources.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// 定義一個(gè)標(biāo)識(shí)和Storyboard中定義的ID一致
NSString * const ID = @"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
cell.textLabel.text = self.dataSources[indexPath.row];
return cell;
}
- 5, 監(jiān)聽(tīng)發(fā)送按鈕
- (IBAction)sendMessage:(UIButton *)sender {
NSString *str = self.textField.text;
if (str == 0) { // 說(shuō)明沒(méi)有消息發(fā)送
return;
}
// 來(lái)到這里表示有發(fā)送消息
// 將消息保存到數(shù)據(jù)源中
[self.dataSources addObject:str];
// 刷新表格
[self.chatTableView reloadData];
// 發(fā)送消息
[self.clientSocket writeData:[str dataUsingEncoding:NSUTF8StringEncoding] withTimeout:-1 tag:0];
}
-
注意 :
- 1, 需要判斷消息編輯框中是否有數(shù)據(jù),如果沒(méi)有,直接返回,如果有,那么將數(shù)據(jù)保存到數(shù)組中, 刷新表格,最后發(fā)送消息.
-
知識(shí)拓展
- 1, 客戶(hù)端有沒(méi)有連接或者是有沒(méi)有斷開(kāi)都是是通過(guò)代理方法來(lái)檢驗(yàn)的.
- 2, 全局隊(duì)列是異步函數(shù)的,執(zhí)行耗時(shí)操作是在子線程, 刷新表格需要回到主線程上去進(jìn)行刷新操作.