IM編程
整合工具欄
-
UITextField
設(shè)置leftView
和rightView

let sendBtn = UIButton(frame: CGRect(x: 0, y: 0, width: 50, height: 32))
sendBtn.setTitleColor(UIColor.black, for: .normal)
sendBtn.setTitle("刪除", for: .normal)
inputTextField.rightView = sendBtn
// 如果不加這個(gè)屬性按鈕不會(huì)顯示
inputTextField.rightViewMode = .always
- 使用閉包傳值
// 定義一個(gè)閉包
var textCallback: ((String) -> Void)?
// 給閉包復(fù)制
textCallback("hello world")
// 調(diào)用閉包
// weak self 防止循環(huán)引用
textCallback = { [weak self] text in
print(text) // "hello world"
}
- 監(jiān)聽(tīng)鍵盤(pán)的高度變化
// MARK: 注冊(cè)通知
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillChangeFrame(_:)), name: NSNotification.Name.UIKeyboardWillChangeFrame, object: nil)
}
// 注銷(xiāo)通知
deinit {
NotificationCenter.default.removeObserver(self)
}
// 處理方法
@objc fileprivate func keyboardWillChangeFrame(_ note : Notification) {
let duration = note.userInfo![UIKeyboardAnimationDurationUserInfoKey] as! Double
let endFrame = (note.userInfo![UIKeyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
let inputViewY = endFrame.origin.y - kChatToolsViewHeight
UIView.animate(withDuration: duration, animations: {
UIView.setAnimationCurve(UIViewAnimationCurve(rawValue: 7)!)
let endY = inputViewY == (kScreenH - kChatToolsViewHeight) ? kScreenH : inputViewY
self.chatToolsView.frame.origin.y = endY
})
}
// 發(fā)送通知
inputTextField.becomeFirstResponder()
IM編程
Socket 在實(shí)際項(xiàng)目中的應(yīng)用
大多應(yīng)用在實(shí)時(shí)通訊
- 直播 進(jìn)出直播間 聊天 送禮物
- 微信聊天
- 游戲
TCP/UDP
- TCP:傳輸控制協(xié)議 八毯。是專(zhuān)門(mén)設(shè)計(jì)用于在不可靠的因特網(wǎng)上提供可靠的秦驯,端到端的字節(jié)流通信的協(xié)議。它是一種面向連接的協(xié)議吆鹤。TCP連接是字節(jié)流而非報(bào)文流宝泵。(TCP類(lèi)似于打電話, 雙方直接通信)

- 用戶數(shù)據(jù)報(bào)協(xié)議 好啰。不需要建立連接,不可靠(UDP類(lèi)似于發(fā)短信, 雙方發(fā)出消息后等待別人的回復(fù))

iOS中Socket編程
- BSD Socket 是UNIX系統(tǒng)中通用的網(wǎng)絡(luò)接口儿奶,它不僅支持各種不同的網(wǎng)絡(luò)類(lèi)型框往,而且也是一種內(nèi)部進(jìn)程之間的通信機(jī)制。在我們iOS中也可以使用闯捎,但是它所有的函數(shù)都是基于C語(yǔ)言的椰弊,所有在實(shí)際的項(xiàng)目開(kāi)發(fā)中,我們都是使用封裝好的
- CFSocket是蘋(píng)果官方提供給我們進(jìn)行Socket編程瓤鼻,里面還是有很多C語(yǔ)言的東西秉版,使用起來(lái)稍微麻煩一點(diǎn)。
- AsyncSocket是一個(gè)開(kāi)源的庫(kù)茬祷,用來(lái)進(jìn)行iOS的Socket編程就非常方便, 但是目前只有OC版本, 并且長(zhǎng)時(shí)間沒(méi)有更新
- ysocket目前使用Swift進(jìn)行Socket編程時(shí),常用的一個(gè)庫(kù)
消息傳輸
TCP在傳輸數(shù)據(jù)時(shí),傳輸?shù)氖亲止?jié)流
在讀取消息時(shí),需要知道數(shù)據(jù)的長(zhǎng)度, 否則就會(huì)出現(xiàn)讀取不完整或者讀取長(zhǎng)度過(guò)多的情況, 因此讀取方法要求我們傳入本次讀取的消息長(zhǎng)度
如何解決該問(wèn)題呢?
- 方案一: 客戶端發(fā)送兩次消息清焕,消息一是記錄后續(xù)消息長(zhǎng)度, 消息二是真正的消息
- 方案二: 客戶端發(fā)送一次消息,消息有一個(gè)Header,用于記錄消息的長(zhǎng)度, 后續(xù)為真實(shí)消息內(nèi)容
消息類(lèi)型 ProtocolBuffer
- ProtocolBuffer(也稱(chēng)PB/GPB): google 的一種數(shù)據(jù)交換的格式, 可以實(shí)現(xiàn)跨平臺(tái), 方便的序列化&反序列化, 并且數(shù)據(jù)量相對(duì)json小
- ProtoBuf支持多平臺(tái)和語(yǔ)言, 包括C++/Java/Python等等
- ProtoBuf支持直接將對(duì)象序列化成Data, 也支持直接將Data序列化為對(duì)象類(lèi)型
- 一條消息數(shù)據(jù)祭犯,用protobuf序列化后的大小是json的10分之一秸妥,xml格式的20分之一,是二進(jìn)制序列化的10分之一
ysocket 的使用(以TCP為例)
- 服務(wù)器端
fileprivate lazy var serverSocket: TCPServer = TCPServer(address: "0.0.0.0", port: 7878)
fileprivate var isServerRunning : Bool = false
func startRunning() {
// 1.開(kāi)始監(jiān)聽(tīng)
serverSocket.listen()
isServerRunning = true
// 2.開(kāi)啟接受客戶端 異步 否則阻塞主線程
DispatchQueue.global().async {
while self.isServerRunning {
if let client = self.serverSocket.accept() {
if let lMsg = client.read(4) {
// 1.讀取長(zhǎng)度的data
let headData = Data(bytes: lMsg, count: 4)
var length: Int = 0
(headData as NSData).getBytes(&length, length: 4)
// 2.根據(jù)長(zhǎng)度, 讀取真實(shí)消息
guard let msg = client.read(length) else {
return
}
let data = Data(bytes: msg, count: length)
let str = String.init(data: data, encoding: String.Encoding.utf8)!
print(str)
} else {
// isClientConnected = false
print("客戶端斷開(kāi)了連接")
client.close()
}
}
}
}
}
func stopRunning() {
isServerRunning = false
}
- 客戶端
fileprivate lazy var clientSocket: TCPClient = TCPClient(address: "0.0.0.0", port: 7878)
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
switch clientSocket.connect(timeout: 5) {
case .success:
let str = "Hello World"
let data = str.data(using: String.Encoding.utf8)!
// 1.將消息長(zhǎng)度, 寫(xiě)入到data
var length = data.count
let headerData = Data(bytes: &length, count: 4)
// 2.發(fā)送消息
let totalData = headerData + data
switch clientSocket.send(data: totalData) {
case .success:
print("發(fā)送成功")
case .failure(let error):
print(error)
}
case .failure(let error):
print(error)
}
}
ProtocolBuffer使用
- 環(huán)境安裝
ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
brew install automake
brew install libtool
brew install protobuf
brew install protobuf-swift
- 客戶端集成(通過(guò)cocoapods)
use_frameworks!
pod 'ProtocolBuffers-Swift'
- 服務(wù)器集成
因?yàn)榉?wù)器使用Mac編寫(xiě),不能直接使用cocoapods集成
因?yàn)樾枰獙⒐こ叹幾g為靜態(tài)庫(kù)來(lái)集成
到Git中下載整個(gè)庫(kù)
執(zhí)行腳本: ./scripts/build.sh
添加: ./src/ProtocolBuffers/ ProtocolBuffers.xcodeproj到項(xiàng)目中
ProtocolBuffer的使用
- 創(chuàng)建.proto文件
在項(xiàng)目中, 創(chuàng)建一個(gè)(或多個(gè)).proto文件
之后會(huì)通過(guò)該文件, 自動(dòng)幫我們生成需要的源文件(比如C++生成.cpp源文件, 比如java生成.java源文件, Swift就生成.swift源文件)
- 源碼規(guī)范
syntax = "proto2";
message Person {
required int64 id = 1;
required string name = 2;
optional string email = 3;
}
- 具體說(shuō)明
syntax = "proto2"; 為定義使用的版本號(hào), 目前常用版本proto2/proto3
message是消息定義的關(guān)鍵字沃粗,等同于C++/Swift中的struct/class粥惧,或是Java中的class
Person為消息的名字,等同于結(jié)構(gòu)體名或類(lèi)名
required前綴表示該字段為必要字段陪每,既在序列化和反序列化之前該字段必須已經(jīng)被賦值
optional前綴表示該字段為可選字段, 既在序列化和反序列化時(shí)可以沒(méi)有被賦值
repeated通常被用在數(shù)組字段中
int64和string分別表示整型和字符串型的消息字段
id和name和email分別表示消息字段名影晓,等同于Swift或是C++中的成員變量名
標(biāo)簽數(shù)字1和2則表示不同的字段在序列化后的二進(jìn)制數(shù)據(jù)中的布局位置, 需要注意的是該值在同一message中不能重復(fù)
- 定義有枚舉類(lèi)型Protocol Buffer消息
enum UserStatus {
OFFLINE = 0; //表示處于離線狀態(tài)的用戶
ONLINE = 1; //表示處于在線狀態(tài)的用戶
}
message UserInfo {
required int64 acctID = 1;
required string name = 2;
required UserStatus status = 3;
}
- 定義有類(lèi)型嵌套
enum UserStatus {
OFFLINE = 0;
ONLINE = 1;
}
message UserInfo {
required int64 acctID = 1;
required string name = 2;
required UserStatus status = 3;
}
message LogonRespMessage {
required LoginResult logonResult = 1;
required UserInfo userInfo = 2;
}
- 代碼編寫(xiě)完成后, 生成對(duì)應(yīng)語(yǔ)言代碼
protoc person.proto --swift_out="./"
- 序列化
// 1.創(chuàng)建UserInfo類(lèi)型
let user = UserInfo.Builder()
user.acctID = Int64(1)
user.name = "Bob"
user.status = .ONLINE
// 2.獲取對(duì)應(yīng)的data
let userData = (try! user.build()).data()
- 反序列化
let user = try! UserInfo.parseFrom(data: data)