Network là một phần không thể thiếu của hầu hết các ứng dụng iOS. Một nhiệm vụ phổ biến liên quan đến network là phát hiện kết nối Internet. Thông thường, nó xuất hiện trong ba trường hợp:

  • Kiểm tra kết nối trước khi kích hoạt một yêu cầu HTTP.
  • Tắt hoặc bật các tính năng của ứng dụng dựa trên trạng thái kết nối mạng.
  • Đính kèm các ràng buộc đối với hoạt động mạng, ví dụ: vô hiệu hóa tải xuống tệp lớn qua mạng di động.

Các câu trả lời phổ biến nhất về cách phát hiện trạng thái kết nối mạng trên iOS đề xuất bằng cách sử dụng SCNetworkReachability. Trong bài viết này, hãy thảo luận về lý do tại sao giải pháp này kém tối ưu và đưa ra các phương pháp làm việc tốt nhất với kết nối Internet do Apple đề xuất.

Kiểm tra kết nối trước khi thực hiện yêu cầu

Trong tài liệu của mình, Apple nói rằng chúng ta không nên kiểm tra kết nối Internet trước khi kích hoạt một yêu cầu HTTP. Từ Networking Overviews.

The SCNetworkReachability API is not intended for use as a preflight mechanism for determining network connectivity. […]

Chúng ta có thể nghe những lời khuyên tương tự trong nhiều phiên WWDC, từ Advances in Networking, phần 1, phút thứ 50

Pre-flight check is a very bad indicator of where your flow will end up on.

Từ Advances in Networking, phần 2, phút thứ 56

Pre-flight checks have inherited race conditions.

Từ Networking Best Practices, phút thứ 33

Don’t check the Reachability object to determine whether something is available. It can be deceptive in some cases.

Lý do cho những tuyên bố này là:

  • Biết trước Wi-Fi sẽ hoạt động như thế nào cho đến khi bạn thử là không thể [Advances in Networking, phần 2, phút 57].
  • Kết nối Internet kiểm tra trước các điều kiện cố hữu, cho kết quả dương tính giả và âm tính giả, vấn đề thời gian kiểm tra đến thời gian sử dụng [Apple dev forum thread].
  • Khả năng tiếp cận sẽ báo cáo không thể truy cập được nếu phần cứng mạng bị tắt nguồn. Cố gắng tạo kết nối sẽ bật nguồn phần cứng mạng. Nếu bạn đang dựa vào Khả năng tiếp cận (hoặc NWPathMonitor) nói rằng một cái gì đó có thể có thể truy cập được, thì bạn đang dựa vào một cái gì đó khác (một quy trình nền) để cung cấp năng lượng cho phần cứng mạng [AFNetworking GitHub thread]

Giải pháp là sử dụng các API kết nối thích ứng (Adaptable Connectivity APIs). Bằng cách chọn tham gia vào tính năng này, chúng tôi nói với URLSession rằng nó nên đợi kết nối với máy chủ thay vì URLSessionTask bị lỗi vì thiếu kết nối.

Tất cả những gì cần thiết để kích hoạt Khả năng kết nối thích ứng là đặt cờ waitsForConnectivity trên URLSessionConfiguration:

let config = URLSessionConfiguration.default
config.waitsForConnectivity = true
config.timeoutIntervalForResource = 300

let session = URLSession(configuration: config)

let url = URL(string: "https://www.example.com/")!

session.dataTask(with: url) { data, response, error in
    // ...
}

Bây giờ chúng ta đã sẵn sàng. Nếu một thiết bị đang trực tuyến, các yêu cầu HTTP sẽ kích hoạt ngay lập tức. Nếu không, URLSession sẽ đợi cho đến khi thiết bị được kết nối với Internet, rồi kích hoạt một yêu cầu.

Theo mặc định, URLSession đợi kết nối Internet trong tối đa 7 ngày. Nếu đây không phải là hành vi bạn muốn, bạn có thể đặt timeoutIntervalForResource thành một giá trị mới.

Bật hoặc tắt các tính năng của ứng dụng dựa trên kết nối Internet

Không khuyến khích bật hoặc tắt các tính năng của ứng dụng dựa trên kết nối Internet. Lý do cho điều này là chúng tôi không thể kiểm tra trạng thái kết nối qua Reachability hoặc NWPathMonitor một cách đáng tin cậy:

  • Tín hiệu Wi-Fi có thể biến mất sau khi ứng dụng của bạn kiểm tra khả năng truy cập nhưng trước khi kết nối.
  • Có thể truy cập các máy chủ khác nhau qua các giao diện khác nhau.
  • Bạn không thể tin rằng việc kiểm tra khả năng tiếp cận cho một máy chủ là hợp lệ cho một máy chủ khác.
  • Có thể truy cập các địa chỉ IP khác nhau cho cùng một máy chủ qua các giao diện khác nhau.
  • Bạn không thể cho rằng kết nối Internet, sau khi được thiết lập, sẽ vẫn được thiết lập hoặc băng thông đó sẽ không bao giờ tăng hoặc giảm.
  • Hỗ trợ Wi-Fi có thể tự động chuyển sang mạng di động nếu bạn có kết nối Wi-Fi kém để bạn có thể tiếp tục sử dụng Internet.
  • Thiết bị của bạn có thể nghĩ rằng nó đang sử dụng Wi-Fi, nhưng khi cố gắng sử dụng, thiết bị lại không hoạt động.

Như đã nhấn mạnh trong Advances Networking, thay vì tắt các tính năng của ứng dụng và để người dùng hy vọng điều tốt nhất, chúng tôi chỉ ra trên giao diện người dùng rằng trạng thái kết nối hiện tại không đủ mà không chặn bất kỳ hành động nào.

Hãy đưa ra phương pháp để làm việc khi kết nối Internet không đủ.

Cho rằng bạn đã chọn tham gia Adaptable Connectivity - Khả năng kết nối thích ứng, giờ đây bạn có thể xử lý các bản cập nhật kết nối thông qua URLSessionDelegate

Các phương thức sau được gọi một lần cho mỗi URLSessionTask và là một nơi tốt để chỉ ra trạng thái trên giao diện người dùng, ví dụ: bằng cách hiển thị chế độ ngoại tuyến hoặc chế độ chỉ mạng di động:

class NetworkingHandler: NSObject, URLSessionDelegate {
    func urlSession(_ session: URLSession, taskIsWaitingForConnectivity task: URLSessionTask) {
        // Indicate network status, e.g., offline mode
    }
    
    func urlSession(_ session: URLSession, task: URLSessionTask, willBeginDelayedRequest: URLRequest, completionHandler: (URLSession.DelayedRequestDisposition, URLRequest?) -> Void) {
        // Indicate network status, e.g., back to online
    }
}

let session = URLSession(configuration: config, delegate: NetworkingHandler(), delegateQueue: .main)

Trong trường hợp bạn chưa chọn tham gia Adaptable Connectivity, bạn nên sử dụng NWPathMonitor để nhận cập nhật trạng thái mạng. Đầu tiên, khởi tạo một monitor. Đảm bảo rằng bạn lưu trữ một tham chiếu mạnh mẽ đến nó, ví dụ: bằng cách đặt nó làm thuộc tính trong AppDelegate:

import Network

let monitor = NWPathMonitor()
monitor.start(queue: .global()) // Deliver updates on the background queue

Sau đó, sử dụng pathUpdateHandler để xử lý các thay đổi trạng thái:

monitor.pathUpdateHandler = { path in
    if path.status == .satisfied {
        // Indicate network status, e.g., back to online
    } else {
        // Indicate network status, e.g., offline mode
    }
}

Lợi ích của việc sử dụng NWPathMonitor qua SCNetworkReachability là nó cung cấp các API cấp cao hơn và cung cấp thêm thông tin về kết nối mạng. Chúng tôi không còn cần phải tạo một ổ cắm mạng và lắng nghe các thay đổi của nó - tất cả đều sử dụng các con trỏ và API C cấp thấp.

Khi chúng tôi nhận được bản cập nhật trạng thái mạng - thông qua URLSessionDelegate hoặc NWPathMonitor - chúng tôi có thể chẩn đoán kết nối hiện tại qua NWPathMonitor:

if monitor.currentPath.status == .satisfied {
    // Connected
} else {
    // No connection
}

if monitor.currentPath.isExpensive { 
    // Using an expensive interface, such as Cellular or a Personal Hotspot
}

if monitor.currentPath.isConstrained {
    // Using Low Data Mode
}

Vẫn có khả năng kết nối sẽ bị ngắt sau khi nó được thiết lập. Trong trường hợp như vậy, bạn sẽ nhận được lỗi NSURLErrorNetworkConnectionLost trong trình xử lý hoàn thành của URLSessionTask. Đây là một nơi tốt để chẩn đoán trạng thái kết nối bằng NWPathMonitor để xử lý lỗi một cách thích hợp.

Gắn các Ràng buộc vào Hoạt động Mạng

Một tình huống phổ biến khác là hạn chế hoạt động mạng dựa trên trạng thái kết nối Internet. Ví dụ: tắt tính năng tự động phát video hoặc tải xuống tự động hoặc phát trực tuyến chất lượng cao qua mạng di động.

Networking Overview gợi ý rằng chúng ta không nên sử dụng Khả năng tiếp cận để quyết định xem hoạt động mạng có cần bị hạn chế hay không:

Việc kiểm tra cờ khả năng tiếp cận không đảm bảo rằng lưu lượng truy cập của bạn sẽ không bao giờ được gửi qua kết nối di động.

Cách ưu tiên để giảm thiểu việc sử dụng dữ liệu là áp dụng Low Data Mode (Chế độ dữ liệu thấp). Mục đích của nó là hạn chế việc sử dụng mạng nền và tiết kiệm việc sử dụng mạng di động và Wi-Fi. Vì Chế độ dữ liệu thấp là một tùy chọn trên toàn hệ thống, nên nó trao quyền kiểm soát cho người dùng.

Chúng tôi có thể bật Chế độ dữ liệu thấp ở cấp URLSession:

var config = URLSessionConfiguration.default
config.allowsConstrainedNetworkAccess = false

let session = URLSession(configuration: config)

let url = URL(string: "https://www.example.com/")!

session.dataTask(with: url) { data, response, error in
    // ...
}

Hoặc trên cơ sở yêu cầu:

let url = URL(string: "https://www.example.com/")!
var request = URLRequest(url: url)
request.allowsConstrainedNetworkAccess = false

URLSession.shared.dataTask(with: request) { data, response, error in
    // ...
}

Trong trường hợp này, khi không có mạng khả dụng nào mà không có ràng buộc, các yêu cầu HTTP sẽ không thành công với URLErrornetworkUnavailableReason được đặt thành .constrained

Dưới đây là một số ví dụ về cách các ứng dụng iOS tích hợp sẵn thích ứng với Chế độ dữ liệu thấp:

  • App Store: Tự động phát video, cập nhật tự động và tải xuống tự động bị tắt.
  • Nhạc: Tự động tải xuống và phát trực tuyến chất lượng cao bị tắt.
  • Podcast: Tần suất cập nhật nguồn cấp dữ liệu bị hạn chế và các tập chỉ được tải xuống qua Wi-Fi.
  • Tin tức: Tìm nạp trước bài viết bị tắt.
  • iCloud: Cập nhật bị tạm dừng và sao lưu tự động, cập nhật Ảnh iCloud bị tắt.
  • FaceTime: Tốc độ bit của video được tối ưu hóa cho băng thông thấp hơn.

Nếu Chế độ dữ liệu thấp không phải là thứ bạn cần, bạn có thể sử dụng các thuộc tính sau để đưa ra quyết định về những việc cần làm trên một mạng nhất định:

  • allowsExpensiveNetworkAccess 
  • allowsCellularAccess

Cái được ưu tiên hơn là allowExpensiveNetworkAccess.

Tóm lược

Tóm tắt danh sách các phương pháp hay nhất mà chúng ta đã thảo luận:

  • Tránh kiểm tra kết nối trước khi chạy. Thay vào đó, hãy chọn tham gia các API kết nối thích ứng.
  • Không bật hoặc tắt các tính năng của ứng dụng dựa trên kết nối Internet. Thay vào đó, chỉ ra trạng thái kết nối trên giao diện người dùng và chẩn đoán lỗi mạng.
  • Đừng phỏng đoán người dùng của bạn bằng cách ngăn các kết nối được gửi qua mạng di động. Thay vào đó, hãy áp dụng Chế độ dữ liệu thấp.

Cảm ơn bạn đọc, bài viết đã được dịch bởi Techmaster Team

Nguồn bài viết tại đây!