Reputation: 2253
I want to check if device is having very slow internet connection.
I have used Reachability class to check for internet connection is available or not.
But here i want to check after every few sec that internet speed is not poor.
Is this possible to find and yes than how can i do it.
Upvotes: 9
Views: 16725
Reputation: 955
We can device the network connection strength based on the download and upload speed. Here is the code to check that -
import Foundation
enum SpeedTestError: Error {
case pageNotFound
case somethingWendWrong
}
protocol SpeedTesterDelegate {
func didMeasuredDownloadSpeed(mbps: Double, kbps: Double)
func didMeasuredUploadSpeed(mbps: Double, kbps: Double)
}
final public class SpeedTester: NSObject {
/// The shared session
private var session: URLSession
/// Configuration
private var configuration: URLSessionConfiguration
/// After one minute the api will return timeout error
private var timeout: TimeInterval = 60
/// Wait until internet connectivity doesn't come back if gone.
private let waitsForConnectivity: Bool = true
var delegate: SpeedTesterDelegate?
/// This will make this class as the singlton
init(delegate: SpeedTesterDelegate? = nil) {
/// ephemeral - a session configuration that uses no persistent storage for caches, cookies, or credentials.
configuration = URLSessionConfiguration.ephemeral
configuration.timeoutIntervalForRequest = timeout
configuration.waitsForConnectivity = waitsForConnectivity
session = URLSession.init(configuration: configuration)
self.delegate = delegate
}
/// This function will make an api call to check the download spead. The actual speed calculation is happening in the delegate callback
/// - Parameter url: URL to hit
public func checkDownloadSpeed(url: String) async throws {
guard let url = URL.init(string: url) else { throw SpeedTestError.pageNotFound }
let urlRequest: URLRequest = URLRequest.init(url: url)
let _ = try await session.data(for: urlRequest, delegate: self)
}
/// This function will make an post api call to check the upload spead. The actual speed calculation is happening in the delegate callback.
/// - Parameter url: URL to hit
public func checkUploadSpeed(url: String) async throws {
guard let url = URL.init(string: url) else { throw SpeedTestError.pageNotFound }
var urlRequest: URLRequest = URLRequest.init(url: url)
urlRequest.httpMethod = "POST"
/// Add your confuguration for the upload api.
let dummyData = Data.init(count: 1024 * 1024)
let _ = try await session.upload(for: urlRequest, from: dummyData, delegate: self)
}
}
//MARK: - URLSessionTaskDelegate -
extension SpeedTester: URLSessionTaskDelegate {
public func urlSession(_ session: URLSession, task: URLSessionTask, didFinishCollecting metrics: URLSessionTaskMetrics) {
/// Calculating the download speed
if task.countOfBytesReceived > 0 {
let speed = calculateSpeed(bytes: task.countOfBytesReceived, time: metrics.taskInterval.duration)
print("Donwload Speed - ", speed.mbps, " Mbps ", speed.kbps, " Kbps")
self.delegate?.didMeasuredUploadSpeed(mbps: speed.mbps, kbps: speed.kbps)
} else {
print("Nothing have been recieved")
}
/// Calculating the upload speed
if task.countOfBytesSent > 0 {
let speed = calculateSpeed(bytes: task.countOfBytesSent, time: metrics.taskInterval.duration)
print("Upload Speed - ", speed.mbps, " Mbps ", speed.kbps, " Kbps")
self.delegate?.didMeasuredUploadSpeed(mbps: speed.mbps, kbps: speed.kbps)
} else {
print("Nothing have been sent")
}
}
private func calculateSpeed(bytes: Int64, time: TimeInterval) -> (mbps: Double, kbps: Double) {
let oneSecondBytes = Double(bytes) / time
/// Kbps - Kilobits per second so conver the bytes to bits by multiplying with 8, then device by 1000.
let kbps = (oneSecondBytes * 8) / 1000
/// Mbps - Megabits per second, device Kbps to 1000.
let mpbs = kbps / 1000
return (mbps: mpbs, kbps: kbps)
}
}
You can call the checkDownloadSpeed
or checkUploadSpeed
functions with your respective api. And based on the speed you may define you strength parameter.
Upvotes: 1
Reputation: 91
I have updated the top answer for async / await and modern Swift:
import Foundation
protocol NetworkSpeedDelegate: AnyObject {
func speedDidChange(speed: NetworkSpeed)
}
public enum NetworkSpeed: String {
case slow
case fast
case hostUnreachable
}
/// Class that tests the network quality for a given url
final class NetworkSpeedTester {
private(set) var currentNetworkSpeed = NetworkSpeed.fast
/// Delegate called when the network speed changes
weak var delegate: NetworkSpeedDelegate?
private let testURL: URL
private var timerForSpeedTest: Timer?
private let updateInterval: TimeInterval
private let urlSession: URLSession
/// Create a new instance of network speed tester.
/// You need to call start / stop on this instance.
///
/// - Parameters:
/// - updateInterval: the time interval in seconds to elapse between checks
/// - testUrl: the test url to check against
init(updateInterval: TimeInterval, testUrl: URL) {
self.updateInterval = updateInterval
self.testURL = testUrl
let urlSessionConfig = URLSessionConfiguration.ephemeral
urlSessionConfig.timeoutIntervalForRequest = updateInterval - 1.0
urlSessionConfig.requestCachePolicy = .reloadIgnoringLocalAndRemoteCacheData
self.urlSession = URLSession(configuration: urlSessionConfig)
}
deinit {
stop()
}
/// Starts the check
func start() {
timerForSpeedTest = Timer.scheduledTimer(timeInterval: updateInterval,
target: self,
selector: #selector(testForSpeed),
userInfo: nil,
repeats: true)
}
/// Stops the check
func stop(){
timerForSpeedTest?.invalidate()
timerForSpeedTest = nil
}
@objc private func testForSpeed() {
Task {
let startTime = Date()
do {
_ = try await urlSession.data(for: URLRequest(url: testURL))
let endTime = Date()
let duration = abs(endTime.timeIntervalSince(startTime))
switch duration {
case 0.0...4.0:
currentNetworkSpeed = .fast
delegate?.speedDidChange(speed: .fast)
default:
currentNetworkSpeed = .slow
delegate?.speedDidChange(speed: .slow)
}
} catch let error {
guard let urlError = error as? URLError else {
return
}
switch urlError.code {
case .cannotConnectToHost,
.cannotFindHost,
.clientCertificateRejected,
.dnsLookupFailed,
.networkConnectionLost,
.notConnectedToInternet,
.resourceUnavailable,
.serverCertificateHasBadDate,
.serverCertificateHasUnknownRoot,
.serverCertificateNotYetValid,
.serverCertificateUntrusted,
.timedOut:
currentNetworkSpeed = .hostUnreachable
delegate?.speedDidChange(speed: .hostUnreachable)
default:
break
}
}
}
}
}
Upvotes: 0
Reputation: 2253
I have founded solution and adding it here.
Simply create class and paste it and use it where you want.
protocol NetworkSpeedProviderDelegate: class {
func callWhileSpeedChange(networkStatus: NetworkStatus)
}
public enum NetworkStatus :String
{case poor; case good; case disConnected}
class NetworkSpeedTest: UIViewController {
weak var delegate: NetworkSpeedProviderDelegate?
var startTime = CFAbsoluteTime()
var stopTime = CFAbsoluteTime()
var bytesReceived: CGFloat = 0
var testURL:String?
var speedTestCompletionHandler: ((_ megabytesPerSecond: CGFloat, _ error: Error?) -> Void)? = nil
var timerForSpeedTest:Timer?
func networkSpeedTestStart(UrlForTestSpeed:String!){
testURL = UrlForTestSpeed
timerForSpeedTest = Timer.scheduledTimer(timeInterval: 60.0, target: self, selector: #selector(testForSpeed), userInfo: nil, repeats: true)
}
func networkSpeedTestStop(){
timerForSpeedTest?.invalidate()
}
@objc func testForSpeed()
{
testDownloadSpeed(withTimout: 2.0, completionHandler: {(_ megabytesPerSecond: CGFloat, _ error: Error?) -> Void in
print("%0.1f; KbPerSec = \(megabytesPerSecond)")
if (error as NSError?)?.code == -1009
{
self.delegate?.callWhileSpeedChange(networkStatus: .disConnected)
}
else if megabytesPerSecond == -1.0
{
self.delegate?.callWhileSpeedChange(networkStatus: .poor)
}
else
{
self.delegate?.callWhileSpeedChange(networkStatus: .good)
}
})
}
}
extension NetworkSpeedTest: URLSessionDataDelegate, URLSessionDelegate {
func testDownloadSpeed(withTimout timeout: TimeInterval, completionHandler: @escaping (_ megabytesPerSecond: CGFloat, _ error: Error?) -> Void) {
// you set any relevant string with any file
let urlForSpeedTest = URL(string: testURL!)
startTime = CFAbsoluteTimeGetCurrent()
stopTime = startTime
bytesReceived = 0
speedTestCompletionHandler = completionHandler
let configuration = URLSessionConfiguration.ephemeral
configuration.timeoutIntervalForResource = timeout
let session = URLSession(configuration: configuration, delegate: self, delegateQueue: nil)
guard let checkedUrl = urlForSpeedTest else { return }
session.dataTask(with: checkedUrl).resume()
}
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
bytesReceived += CGFloat(data.count)
stopTime = CFAbsoluteTimeGetCurrent()
}
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
let elapsed = (stopTime - startTime) //as? CFAbsoluteTime
let speed: CGFloat = elapsed != 0 ? bytesReceived / (CGFloat(CFAbsoluteTimeGetCurrent() - startTime)) / 1024.0 : -1.0
// treat timeout as no error (as we're testing speed, not worried about whether we got entire resource or not
if error == nil || ((((error as NSError?)?.domain) == NSURLErrorDomain) && (error as NSError?)?.code == NSURLErrorTimedOut) {
speedTestCompletionHandler?(speed, nil)
}
else {
speedTestCompletionHandler?(speed, error)
}
}
}
After That how to use it.So implement delegate and use it.
class ViewController: UIViewController, NetworkSpeedProviderDelegate {
func callWhileSpeedChange(networkStatus: NetworkStatus) {
switch networkStatus {
case .poor:
break
case .good:
break
case .disConnected:
break
}
}
let test = NetworkSpeedTest()
override func viewDidLoad() {
super.viewDidLoad()
test.delegate = self
test.networkSpeedTestStop()
test.networkSpeedTestStart(UrlForTestSpeed: "Paste Your Any Working URL ")
// Do any additional setup after loading the view.
}
}
Upvotes: 8
Reputation: 51
You can call the Reachability class every time after a fixed interval by using method given below:
override func viewDidLoad() {
scheduledTimerWithTimeInterval()
}
func scheduledTimerWithTimeInterval(){
// Scheduling timer to Call the function "updateCounting" with the interval of 'x' seconds
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(self.updateCounting), userInfo: nil, repeats: true)
}
@objc func updateCounting(){
\\Do your stuff here(Check Reachabilty Here)
}
EDIT: This is how you can check signal strength for cellular networks.
func getSignalStrength() -> Int {
let application = UIApplication.shared
let statusBarView = application.value(forKey: "statusBar") as! UIView
let foregroundView = statusBarView.value(forKey: "foregroundView") as! UIView
let foregroundViewSubviews = foregroundView.subviews
var dataNetworkItemView:UIView? = nil
for subview in foregroundViewSubviews {
if subview.isKind(of: NSClassFromString("UIStatusBarSignalStrengthItemView")!) {
dataNetworkItemView = subview
break
}
}
if dataNetworkItemView == nil
{
return 0
}
return dataNetworkItemView?.value(forKey: "signalStrengthBars") as! Int
}
For Wifi Network this is how you can get signal strength
private func getWiFiRSSI() -> Int? {
let app = UIApplication.shared
var rssi: Int?
let exception = tryBlock {
guard let statusBar = app.value(forKey: "statusBar") as? UIView else { return }
if let statusBarMorden = NSClassFromString("UIStatusBar_Modern"), statusBar .isKind(of: statusBarMorden) { return }
guard let foregroundView = statusBar.value(forKey: "foregroundView") as? UIView else { return }
for view in foregroundView.subviews {
if let statusBarDataNetworkItemView = NSClassFromString("UIStatusBarDataNetworkItemView"), view .isKind(of: statusBarDataNetworkItemView) {
if let val = view.value(forKey: "wifiStrengthRaw") as? Int {
rssi = val
break
}
}
}
}
if let exception = exception {
print("getWiFiRSSI exception: \(exception)")
}
return rssi
}
EDIT 2: Add this extension to access your status bar view
extension UIApplication {
var statusBarUIView: UIView? {
if #available(iOS 13.0, *) {
let tag = 38482458385
if let statusBar = self.keyWindow?.viewWithTag(tag) {
return statusBar
} else {
let statusBarView = UIView(frame: UIApplication.shared.statusBarFrame)
statusBarView.tag = tag
self.keyWindow?.addSubview(statusBarView)
return statusBarView
}
} else {
if responds(to: Selector(("statusBar"))) {
return value(forKey: "statusBar") as? UIView
}
}
return nil
}
}
Upvotes: 0