Multipeer Connectivity - 在 Swift 5 中获取文件传输(Internet)速度和文件大小
Multipeer Connectivity - Get file transfer(Internet) speed and File Size in Swift 5
我正在点对点传输照片。一切正常,但我无法获得照片(文件)传输速度 i.g 互联网速度。与 MB 一样,文件已传输。其次,我想获取该文件的大小。
我们正在使用 MCSession
以数据格式传递照片
由于隐私原因,我无法在此处添加项目代码,但我将分享我关注的参考 github 项目。在项目中,我传递的是字符串,在我的例子中是它的照片。一切都一样。
我查看了 Whosebug,但没有找到任何准确的答案!
参考项目Link:
https://github.com/YogeshPateliOS/MultipeerConnectivity-.git
谢谢!
TLDR: 如果你不想阅读冗长的解释并直接看代码,下面所有的想法都汇集在一起,可以通过下载我的 public repository 其中有解释所有这些的注释。
下面是我关于如何实现这一目标的建议
查看您的代码后,我发现您正在使用以下函数发送数据
func send(_ data: Data, toPeers peerIDs: [MCPeerID], with mode: MCSessionSendDataMode)
这没有任何问题,您确实可以将 UIImage 转换为 Data 对象并以这种方式发送,它会起作用。
但是我认为您无法跟踪进度,并且 MultiPeer 不会为您提供任何委托来使用此方法跟踪进度。
相反,您还有另外两个选择。你可以使用
func session(_ session: MCSession,
didFinishReceivingResourceWithName resourceName: String,
fromPeer peerID: MCPeerID,
at localURL: URL?,
withError error: Error?)
或者您可以使用
func startStream(withName streamName: String,
toPeer peerID: MCPeerID) throws -> OutputStream
我将使用第一个选项,因为它更直接,但我认为流选项会给你更好的结果。您可以在此处阅读这两个选项:
Send Resource(我们会实现这个)
步骤 1
我对您的原始代码进行了一些 UI 更新,添加了一个 UIImageView 以向广告商(来宾)显示传输的图像和一个 UIButton 以启动文件从浏览器(主机)传输
UIImageView 有一个名为 @IBOutlet weak var imageView: UIImageView!
的出口和 UI 按钮的一个动作 @IBAction func sendImageAsResource(_ sender: Any)
我还在项目中添加了一个名为 image2.jpg 的图像,我们将从主机发送给来宾。
步骤 2
我还声明了一些额外的变量
// Progress variable that needs to store the progress of the file transfer
var fileTransferProgress: Progress?
// Timer that will be used to check the file transfer progress
var checkProgressTimer: Timer?
// Used by the host to track bytes to receive
var bytesExpectedToExchange = 0
// Used to track the time taken in transfer, this is for testing purposes.
// You might get more reliable results using Date to track time
var transferTimeElapsed = 0.0
步骤 3
通过分别点击访客和主机按钮正常设置主机和访客。之后,点击主机上的Send image as resource
按钮,主机执行的操作如下:
// A new action added to send the image stored in the bundle
@IBAction func sendImageAsResource(_ sender: Any)
{
// Call local function created
sendImageAsResource()
}
func sendImageAsResource()
{
// 1. Get the url of the image in the project bundle.
// Change this if your image is hosted in your documents directory
// or elsewhere.
//
// 2. Get all the connected peers. For testing purposes I am only
// getting the first peer, you might need to loop through all your
// connected peers and send the files individually.
guard let imageURL = Bundle.main.url(forResource: "image2",
withExtension: "jpg"),
let guestPeerID = mcSession.connectedPeers.first else {
return
}
// Retrieve the file size of the image
if let fileSizeToTransfer = getFileSize(atURL: imageURL)
{
bytesExpectedToExchange = fileSizeToTransfer
// Put the file size in a dictionary
let fileTransferMeta = ["fileSize": bytesExpectedToExchange]
// Convert the dictionary to a data object in order to send it via
// MultiPeer
let encoder = JSONEncoder()
if let JSONData = try? encoder.encode(fileTransferMeta)
{
// Send the file size to the guest users
try? mcSession.send(JSONData, toPeers: mcSession.connectedPeers,
with: .reliable)
}
}
// Ideally for best reliability, you will want to develop some logic
// for the guest to respond that it has received the file size and then
// you should initiate the transfer to that peer only after you receive
// this confirmation. For now, I just add a delay so that I am highly
// certain the guest has received this data for testing purposes
DispatchQueue.main.asyncAfter(deadline: .now() + 1)
{ [weak self] in
self?.initiateFileTransfer(ofImage: imageURL, to: guestPeerID)
}
}
func initiateFileTransfer(ofImage imageURL: URL, to guestPeerID: MCPeerID)
{
// Initialize and fire a timer to check the status of the file
// transfer every 0.1 second
checkProgressTimer = Timer.scheduledTimer(timeInterval: 0.1,
target: self,
selector: #selector(updateProgressStatus),
userInfo: nil,
repeats: true)
// Call the sendResource function and send the image from the bundle
// keeping hold of the returned progress object which we need to keep checking
// using the timer
fileTransferProgress = mcSession.sendResource(at: imageURL,
withName: "image2.jpg",
toPeer: guestPeerID,
withCompletionHandler: { (error) in
// Handle errors
if let error = error as NSError?
{
print("Error: \(error.userInfo)")
print("Error: \(error.localizedDescription)")
}
})
}
func getFileSize(atURL url: URL) -> Int?
{
let urlResourceValue = try? url.resourceValues(forKeys: [.fileSizeKey])
return urlResourceValue?.fileSize
}
步骤 4
下一个函数是主客用的。来宾将让事情在以后有意义,但是对于主机,在第 3 步中,您在启动文件传输后存储了一个进度对象,并且您启动了一个每 0.1 秒触发一次的计时器,所以现在实现计时器查询此进度对象以在 UILabel
中显示主机端的进度和数据传输状态
/// Function fired by the local checkProgressTimer object used to track the progress of the file transfer
/// Function fired by the local checkProgressTimer object used to track the progress of the file transfer
@objc
func updateProgressStatus()
{
// Update the time elapsed. As mentioned earlier, a more reliable approach
// might be to compare the time of a Date object from when the
// transfer started to the time of a current Date object
transferTimeElapsed += 0.1
// Verify the progress variable is valid
if let progress = fileTransferProgress
{
// Convert the progress into a percentage
let percentCompleted = 100 * progress.fractionCompleted
// Calculate the data exchanged sent in MegaBytes
let dataExchangedInMB = (Double(bytesExpectedToExchange)
* progress.fractionCompleted) / 1000000
// We have exchanged 'dataExchangedInMB' MB of data in 'transferTimeElapsed'
// seconds. So we have to calculate how much data will be exchanged in 1 second
// using cross multiplication
// For example:
// 2 MB in 0.5s
// ? in 1s
// MB/s = (1 x 2) / 0.5 = 4 MB/s
let megabytesPerSecond = (1 * dataExchangedInMB) / transferTimeElapsed
// Convert dataExchangedInMB into a string rounded to 2 decimal places
let dataExchangedInMBString = String(format: "%.2f", dataExchangedInMB)
// Convert megabytesPerSecond into a string rounded to 2 decimal places
let megabytesPerSecondString = String(format: "%.2f", megabytesPerSecond)
// Update the progress an data exchanged on the UI
numberLabel.text = "\(percentCompleted.rounded())% - \(dataExchangedInMBString) MB @ \(megabytesPerSecondString) MB/s"
// This is mostly useful on the browser side to check if the file transfer
// is complete so that we can safely deinit the timer, reset vars and update the UI
if percentCompleted >= 100
{
numberLabel.text = "Transfer complete!"
checkProgressTimer?.invalidate()
checkProgressTimer = nil
transferTimeElapsed = 0.0
}
}
}
步骤 5
通过实现以下委托方法在接收方(来宾)端处理文件的接收
func session(_ session: MCSession, didReceive data: Data, fromPeer peerID: MCPeerID)
{
// Check if the guest has received file transfer data
if let fileTransferMeta = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Int],
let fileSizeToReceive = fileTransferMeta["fileSize"]
{
// Store the bytes to be received in a variable
bytesExpectedToExchange = fileSizeToReceive
print("Bytes expected to receive: \(fileSizeToReceive)")
return
}
}
func session(_ session: MCSession,
didStartReceivingResourceWithName resourceName: String,
fromPeer peerID: MCPeerID,
with progress: Progress)
{
// Store the progress object so that we can query it using the timer
fileTransferProgress = progress
// Launch the main thread
DispatchQueue.main.async { [unowned self] in
// Fire the timer to check the file transfer progress every 0.1 second
self.checkProgressTimer = Timer.scheduledTimer(timeInterval: 0.1,
target: self,
selector: #selector(updateProgressStatus),
userInfo: nil,
repeats: true)
}
}
func session(_ session: MCSession,
didFinishReceivingResourceWithName resourceName: String,
fromPeer peerID: MCPeerID,
at localURL: URL?,
withError error: Error?)
{
// Verify that we have a valid url. You should get a url to the file in
// the tmp directory
if let url = localURL
{
// Launch the main thread
DispatchQueue.main.async { [weak self] in
// Call a function to handle download completion
self?.handleDownloadCompletion(withImageURL: url)
}
}
}
/// Handles the file transfer completion process on the advertiser/client side
/// - Parameter url: URL of a file in the documents directory
func handleDownloadCompletion(withImageURL url: URL)
{
// Debugging data
print("Full URL: \(url.absoluteString)")
// Invalidate the timer
checkProgressTimer?.invalidate()
checkProgressTimer = nil
// Set the UIImageView with the downloaded image
imageView.image = UIImage(contentsOfFile: url.path)
}
步骤 6
运行 代码和 this is the end result (uploaded to youtube) 在来宾端显示传输完成后的进度和文件,主机端也显示相同的进度。
步骤 7
我没有实现这个,但我相信这一点很简单:
文件大小可以从主机计算出来,并且可以作为消息发送给来宾关于预期大小
您可以计算出一个文件的大概百分比
通过将进度 % 乘以文件大小来下载
可以根据下载的数据量/自传输开始以来经过的时间来计算速度
如果你觉得这些计算不简单,我可以尝试添加这段代码。
更新
我已经更新了上面的代码示例、github 存储库和视频以包括最后 3 个步骤,最终结果如下:
我正在点对点传输照片。一切正常,但我无法获得照片(文件)传输速度 i.g 互联网速度。与 MB 一样,文件已传输。其次,我想获取该文件的大小。
我们正在使用 MCSession
由于隐私原因,我无法在此处添加项目代码,但我将分享我关注的参考 github 项目。在项目中,我传递的是字符串,在我的例子中是它的照片。一切都一样。
我查看了 Whosebug,但没有找到任何准确的答案!
参考项目Link: https://github.com/YogeshPateliOS/MultipeerConnectivity-.git
谢谢!
TLDR: 如果你不想阅读冗长的解释并直接看代码,下面所有的想法都汇集在一起,可以通过下载我的 public repository 其中有解释所有这些的注释。
下面是我关于如何实现这一目标的建议
查看您的代码后,我发现您正在使用以下函数发送数据
func send(_ data: Data, toPeers peerIDs: [MCPeerID], with mode: MCSessionSendDataMode)
这没有任何问题,您确实可以将 UIImage 转换为 Data 对象并以这种方式发送,它会起作用。
但是我认为您无法跟踪进度,并且 MultiPeer 不会为您提供任何委托来使用此方法跟踪进度。
相反,您还有另外两个选择。你可以使用
func session(_ session: MCSession,
didFinishReceivingResourceWithName resourceName: String,
fromPeer peerID: MCPeerID,
at localURL: URL?,
withError error: Error?)
或者您可以使用
func startStream(withName streamName: String,
toPeer peerID: MCPeerID) throws -> OutputStream
我将使用第一个选项,因为它更直接,但我认为流选项会给你更好的结果。您可以在此处阅读这两个选项:
Send Resource(我们会实现这个)
步骤 1
我对您的原始代码进行了一些 UI 更新,添加了一个 UIImageView 以向广告商(来宾)显示传输的图像和一个 UIButton 以启动文件从浏览器(主机)传输
UIImageView 有一个名为 @IBOutlet weak var imageView: UIImageView!
的出口和 UI 按钮的一个动作 @IBAction func sendImageAsResource(_ sender: Any)
我还在项目中添加了一个名为 image2.jpg 的图像,我们将从主机发送给来宾。
步骤 2
我还声明了一些额外的变量
// Progress variable that needs to store the progress of the file transfer
var fileTransferProgress: Progress?
// Timer that will be used to check the file transfer progress
var checkProgressTimer: Timer?
// Used by the host to track bytes to receive
var bytesExpectedToExchange = 0
// Used to track the time taken in transfer, this is for testing purposes.
// You might get more reliable results using Date to track time
var transferTimeElapsed = 0.0
步骤 3
通过分别点击访客和主机按钮正常设置主机和访客。之后,点击主机上的Send image as resource
按钮,主机执行的操作如下:
// A new action added to send the image stored in the bundle
@IBAction func sendImageAsResource(_ sender: Any)
{
// Call local function created
sendImageAsResource()
}
func sendImageAsResource()
{
// 1. Get the url of the image in the project bundle.
// Change this if your image is hosted in your documents directory
// or elsewhere.
//
// 2. Get all the connected peers. For testing purposes I am only
// getting the first peer, you might need to loop through all your
// connected peers and send the files individually.
guard let imageURL = Bundle.main.url(forResource: "image2",
withExtension: "jpg"),
let guestPeerID = mcSession.connectedPeers.first else {
return
}
// Retrieve the file size of the image
if let fileSizeToTransfer = getFileSize(atURL: imageURL)
{
bytesExpectedToExchange = fileSizeToTransfer
// Put the file size in a dictionary
let fileTransferMeta = ["fileSize": bytesExpectedToExchange]
// Convert the dictionary to a data object in order to send it via
// MultiPeer
let encoder = JSONEncoder()
if let JSONData = try? encoder.encode(fileTransferMeta)
{
// Send the file size to the guest users
try? mcSession.send(JSONData, toPeers: mcSession.connectedPeers,
with: .reliable)
}
}
// Ideally for best reliability, you will want to develop some logic
// for the guest to respond that it has received the file size and then
// you should initiate the transfer to that peer only after you receive
// this confirmation. For now, I just add a delay so that I am highly
// certain the guest has received this data for testing purposes
DispatchQueue.main.asyncAfter(deadline: .now() + 1)
{ [weak self] in
self?.initiateFileTransfer(ofImage: imageURL, to: guestPeerID)
}
}
func initiateFileTransfer(ofImage imageURL: URL, to guestPeerID: MCPeerID)
{
// Initialize and fire a timer to check the status of the file
// transfer every 0.1 second
checkProgressTimer = Timer.scheduledTimer(timeInterval: 0.1,
target: self,
selector: #selector(updateProgressStatus),
userInfo: nil,
repeats: true)
// Call the sendResource function and send the image from the bundle
// keeping hold of the returned progress object which we need to keep checking
// using the timer
fileTransferProgress = mcSession.sendResource(at: imageURL,
withName: "image2.jpg",
toPeer: guestPeerID,
withCompletionHandler: { (error) in
// Handle errors
if let error = error as NSError?
{
print("Error: \(error.userInfo)")
print("Error: \(error.localizedDescription)")
}
})
}
func getFileSize(atURL url: URL) -> Int?
{
let urlResourceValue = try? url.resourceValues(forKeys: [.fileSizeKey])
return urlResourceValue?.fileSize
}
步骤 4
下一个函数是主客用的。来宾将让事情在以后有意义,但是对于主机,在第 3 步中,您在启动文件传输后存储了一个进度对象,并且您启动了一个每 0.1 秒触发一次的计时器,所以现在实现计时器查询此进度对象以在 UILabel
中显示主机端的进度和数据传输状态/// Function fired by the local checkProgressTimer object used to track the progress of the file transfer
/// Function fired by the local checkProgressTimer object used to track the progress of the file transfer
@objc
func updateProgressStatus()
{
// Update the time elapsed. As mentioned earlier, a more reliable approach
// might be to compare the time of a Date object from when the
// transfer started to the time of a current Date object
transferTimeElapsed += 0.1
// Verify the progress variable is valid
if let progress = fileTransferProgress
{
// Convert the progress into a percentage
let percentCompleted = 100 * progress.fractionCompleted
// Calculate the data exchanged sent in MegaBytes
let dataExchangedInMB = (Double(bytesExpectedToExchange)
* progress.fractionCompleted) / 1000000
// We have exchanged 'dataExchangedInMB' MB of data in 'transferTimeElapsed'
// seconds. So we have to calculate how much data will be exchanged in 1 second
// using cross multiplication
// For example:
// 2 MB in 0.5s
// ? in 1s
// MB/s = (1 x 2) / 0.5 = 4 MB/s
let megabytesPerSecond = (1 * dataExchangedInMB) / transferTimeElapsed
// Convert dataExchangedInMB into a string rounded to 2 decimal places
let dataExchangedInMBString = String(format: "%.2f", dataExchangedInMB)
// Convert megabytesPerSecond into a string rounded to 2 decimal places
let megabytesPerSecondString = String(format: "%.2f", megabytesPerSecond)
// Update the progress an data exchanged on the UI
numberLabel.text = "\(percentCompleted.rounded())% - \(dataExchangedInMBString) MB @ \(megabytesPerSecondString) MB/s"
// This is mostly useful on the browser side to check if the file transfer
// is complete so that we can safely deinit the timer, reset vars and update the UI
if percentCompleted >= 100
{
numberLabel.text = "Transfer complete!"
checkProgressTimer?.invalidate()
checkProgressTimer = nil
transferTimeElapsed = 0.0
}
}
}
步骤 5
通过实现以下委托方法在接收方(来宾)端处理文件的接收
func session(_ session: MCSession, didReceive data: Data, fromPeer peerID: MCPeerID)
{
// Check if the guest has received file transfer data
if let fileTransferMeta = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Int],
let fileSizeToReceive = fileTransferMeta["fileSize"]
{
// Store the bytes to be received in a variable
bytesExpectedToExchange = fileSizeToReceive
print("Bytes expected to receive: \(fileSizeToReceive)")
return
}
}
func session(_ session: MCSession,
didStartReceivingResourceWithName resourceName: String,
fromPeer peerID: MCPeerID,
with progress: Progress)
{
// Store the progress object so that we can query it using the timer
fileTransferProgress = progress
// Launch the main thread
DispatchQueue.main.async { [unowned self] in
// Fire the timer to check the file transfer progress every 0.1 second
self.checkProgressTimer = Timer.scheduledTimer(timeInterval: 0.1,
target: self,
selector: #selector(updateProgressStatus),
userInfo: nil,
repeats: true)
}
}
func session(_ session: MCSession,
didFinishReceivingResourceWithName resourceName: String,
fromPeer peerID: MCPeerID,
at localURL: URL?,
withError error: Error?)
{
// Verify that we have a valid url. You should get a url to the file in
// the tmp directory
if let url = localURL
{
// Launch the main thread
DispatchQueue.main.async { [weak self] in
// Call a function to handle download completion
self?.handleDownloadCompletion(withImageURL: url)
}
}
}
/// Handles the file transfer completion process on the advertiser/client side
/// - Parameter url: URL of a file in the documents directory
func handleDownloadCompletion(withImageURL url: URL)
{
// Debugging data
print("Full URL: \(url.absoluteString)")
// Invalidate the timer
checkProgressTimer?.invalidate()
checkProgressTimer = nil
// Set the UIImageView with the downloaded image
imageView.image = UIImage(contentsOfFile: url.path)
}
步骤 6
运行 代码和 this is the end result (uploaded to youtube) 在来宾端显示传输完成后的进度和文件,主机端也显示相同的进度。
步骤 7
我没有实现这个,但我相信这一点很简单:
文件大小可以从主机计算出来,并且可以作为消息发送给来宾关于预期大小
您可以计算出一个文件的大概百分比 通过将进度 % 乘以文件大小来下载
可以根据下载的数据量/自传输开始以来经过的时间来计算速度
如果你觉得这些计算不简单,我可以尝试添加这段代码。
更新
我已经更新了上面的代码示例、github 存储库和视频以包括最后 3 个步骤,最终结果如下: