Hardik Vyas
Hardik Vyas

Reputation: 2253

How to check if device is having poor internet connection in swift

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

Answers (4)

Naresh
Naresh

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

JuliusBahr
JuliusBahr

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

Hardik Vyas
Hardik Vyas

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

Shrestha Goyal
Shrestha Goyal

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

Related Questions