Computer >> บทช่วยสอนคอมพิวเตอร์ >  >> ระบบเครือข่าย >> อินเทอร์เน็ต

ตรวจจับและจัดการ Internet Connectivity ใน Swift: คู่มือปฏิบัติจริง

โดย Neo Ighodaro

Mobile Applications บ่อยครั้งต้องการ Active Internet Connection เพื่อทำงานอย่างถูกต้อง เป็นเรื่องปกติที่ Internet Connection จะ Lost ในกรณีเหล่านี้ Developer ต้องหาวิธีทำให้ Experience บearable หรืออย่างน้อย Notify User

ใน Article นี้ เราจะดูวิธี Detect Internet Connection Issues ใน Swift และวิธี Handle บางอย่าง

นี่คือ Sample Application ที่เราจะสร้าง และวิธีมัน Handle Internet Connectivity Scenarios ต่างๆ:

ตรวจจับและจัดการ Internet Connectivity ใน Swift: คู่มือปฏิบัติจริง

Requirements

เพื่อ Follow Along ใน Article นี้ คุณต้องการ:

  • Xcode Installed บน Machine
  • Knowledge ของ Swift Programming Language
  • Cocoapods Installed บน Machine

พร้อม Requirements ข้างต้นแล้ว เริ่มกันเลย

Setting up Workspace

ก่อนเริ่ม สร้าง Playground นี่คือที่เราจะเขียน Use Cases และ Handle พวกเขา

Swift มี Reachability Implementation ของตัวเองสำหรับ Detect Connection Issues แต่เราจะใช้ Third-party Library เพราะง่ายกว่าและ API Expressive กว่า Built-in

เปิด Xcode สร้าง Project ใหม่

ตรวจจับและจัดการ Internet Connectivity ใน Swift: คู่มือปฏิบัติจริง

Project นี้จะเป็น Simple Playground สำหรับ Experiment

Detect Connection Offline ใช้ Reachability.swift ** Package เป็น "Replacement for Apple's Reachability Re-written in Swift with Closures"

เปิด Terminal รัน Command:

$ pod init

สร้าง Podfile ใหม่สำหรับ Declare Cocoapods Dependencies เปิด Podfile แทนที่ Contents ด้วย Code:

platform :ios, '9.0'
target 'project_name' do
  use_frameworks!
  pod 'ReachabilitySwift'
  pod 'Alamofire'
end

ต้อง Replace **project_name** ด้วยชื่อ Project ของคุณ

Save File รัน Command Install Pods:

$ pod install

Install Complete เปิด *.xcworkspace File ใน Root ของ Project จะ Launch Xcode

Creating Network Reachability Manager

สร้าง NetworkManager Class Class นี้ Store Network Status และเป็น Simple Proxy ไป Reachability Package ใน File Paste Code:

import Foundation
import Reachability

class NetworkManager: NSObject {
    var reachability: Reachability!
    static let sharedInstance: NetworkManager = { return NetworkManager() }()
    
    override init() {
        super.init()
        // Initialise reachability
        reachability = Reachability()!
        
        // Register Observer สำหรับ Network Status
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(networkStatusChanged(_:)),
            name: .reachabilityChanged,
            object: reachability
        )
        
        do {
            // Start Network Status Notifier
            try reachability.startNotifier()
        } catch {
            print("Unable to start notifier")
        }
    }
    
    @objc func networkStatusChanged(_ notification: Notification) {
        // Do something globally here!
    }
    
    static func stopNotifier() -> Void {
        do {
            try (NetworkManager.sharedInstance.reachability).startNotifier()
        } catch {
            print("Error stopping notifier")
        }
    }
    
    // Network Reachable
    static func isReachable(completed: @escaping (NetworkManager) -> Void) {
        if (NetworkManager.sharedInstance.reachability).connection != .none {
            completed(NetworkManager.sharedInstance)
        }
    }
    
    // Network Unreachable
    static func isUnreachable(completed: @escaping (NetworkManager) -> Void) {
        if (NetworkManager.sharedInstance.reachability).connection == .none {
            completed(NetworkManager.sharedInstance)
        }
    }
    
    // Network Reachable via WWAN/Cellular
    static func isReachableViaWWAN(completed: @escaping (NetworkManager) -> Void) {
        if (NetworkManager.sharedInstance.reachability).connection == .cellular {
            completed(NetworkManager.sharedInstance)
        }
    }
    
    // Network Reachable via WiFi
    static func isReachableViaWiFi(completed: @escaping (NetworkManager) -> Void) {
        if (NetworkManager.sharedInstance.reachability).connection == .wifi {
            completed(NetworkManager.sharedInstance)
        }
    }
}

ใน Class ข้างต้น เรา Define Helper Functions สำหรับ Network Status Monitoring มี sharedInstance เป็น Singleton เรียกได้ถ้าไม่สร้าง Multiple Instances ของ NetworkManager

ใน init Method สร้าง Instance ของ Reachability Register Notification ด้วย NotificationCenter Class ตอนนี้ทุกครั้ง Network Status เปลี่ยน Callback ที่ NotificationCenter ระบุ (คือ networkStatusChanged) จะถูก Call ใช้ทำ Global Action เมื่อ Network Unreachable

Define Helper Functions อื่นๆ ทำให้รัน Code ขึ้นอยู่กับ Internet Connection Status ง่ายขึ้น มี isReachable, isUnreachable, isReachableViaWWAN และ isReachableViaWiFi

Usage ของ Helper จะประมาณนี้:

NetworkManager.isReachable { networkManagerInstance in
    print("Network is available")
}

NetworkManager.isUnreachable { networkManagerInstance in
    print("Network is Unavailable")
}

นี่ไม่ใช่ Event Listener จะ Run แค่ครั้งเดียว ถ้าอยาก Listener Pick Up Network Changes Real-time ต้องใช้ NetworkManager.sharedInstance.reachability.whenReachable จะแสดงตัวอย่างใน Article ต่อไป

ตอนนี้มี Manager Class แล้ว ดูวิธีใช้ใน Application

Handling Network Availability on Application Launch

บางครั้ง Application ขึ้นอยู่กับ Internet Connection มาก ต้อง Detect Status on Launch ดูวิธี Handle ด้วย NetworkManager Class

สร้าง LaunchViewController ใหม่ จะ Treat First Controller View บน Storyboard เป็น Launch Controller จะ Detectว่า Device Online ถ้าไม่ Online สร้าง Offline Page Handle เพื่อ User ไม่เข้า App เลย

ใน LaunchController แทนที่ Contents ด้วย Code:

import UIKit

class LaunchViewController: UIViewController {
    let network: NetworkManager = NetworkManager.sharedInstance
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        NetworkManager.isUnreachable { _ in
            self.showOfflinePage()
        }
    }
    
    private func showOfflinePage() -> Void {
        DispatchQueue.main.async {
            self.performSegue(
                withIdentifier: "NetworkUnavailable",
                sender: self
            )
        }
    }
}

ใน Class นี้ ใช้ NetworkManager isUnreachable Method Fire showOffline Method เมื่อ Network Unavailable สร้าง View Controller นั้น สร้าง OfflineViewController ใหม่

เปิด Main.storyboard Set Custom Class ของ First View เป็น LaunchViewController

สร้าง View Controller ใหม่ใน Storyboard Set OfflineViewController เป็น Custom Class สร้าง Manual Segue ชื่อ NetworkUnavailable ระหว่าง New View Controller และ LaunchViewController เสร็จแล้วควรประมาณนี้:

ตรวจจับและจัดการ Internet Connectivity ใน Swift: คู่มือปฏิบัติจริง

Run Application Note: Development Machine ควร Offline เพราะ iOS Simulator ใช้ Internet ของ Machine Run แล้วควรได้ Offline Page ที่สร้าง

สร้าง View Controller ที่แสดงเมื่อมี Connection

Handling Events When Device Comes Online

ตอนนี้มี Offline View Controller ทำงานเมื่อ Offline มา Handle เมื่อ Device Online กลับมา

สร้าง Navigation View Controller ใหม่ใน Storyboard ใต้ Offline View Controller สร้าง Controller แสดง Latest Reddit Posts สร้าง PostsTableViewController Class ใหม่ Set เป็น Custom Class สำหรับ View Controller ที่ Attached กับ Navigation View Controller

สร้าง Manual Segue ชื่อ MainController จาก Navigation View Controller ไป Launch View Controller และ Offline View Controller ควรประมาณนี้:

ตรวจจับและจัดการ Internet Connectivity ใน Swift: คู่มือปฏิบัติจริง

เปิด LaunchViewController Class Bottom ของ viewDidLoad Method เพิ่ม:

NetworkManager.isReachable { _ in self.showMainPage() }

เพิ่ม Method ด้านล่างใน Controller:

private func showMainPage() -> Void {
    DispatchQueue.main.async {
        self.performSegue(
            withIdentifier: "MainController",
            sender: self
        )
    }
}

นี่ทำให้ App Launch ตรวจสอบ Connectivity ถ้า Available Present PostsTableViewController ไม่ใช่ Present OfflineViewController

แต่ถ้า User Hit OfflineViewController แล้ว Network Online กลับมา? Handle Scenario นี้

เปิด OfflineViewController แทนที่ Code ด้วย:

import UIKit

class OfflineViewController: UIViewController {
    let network = NetworkManager.sharedInstance
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // If Network Reachable Show Main Controller
        network.reachability.whenReachable = { _ in
            self.showMainController()
        }
    }
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        navigationController?.setNavigationBarHidden(true, animated: animated)
    }
    
    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        navigationController?.setNavigationBarHidden(false, animated: animated)
    }
    
    private func showMainController() -> Void {
        DispatchQueue.main.async {
            self.performSegue(withIdentifier: "MainController", sender: self)
        }
    }
}

ใน Controller ข้างบน ใน viewDidLoad Set whenReachable Completion Show Main Controller หมายถึงตราบใดที่ Offline Watch เมื่อ Device Online กลับมา เมื่อเกิด Present PostsTableViewController

Override viewWillAppear และ viewWillDisappear ให้แน่ใจว่า Navigation Bar ไม่แสดงบน Offline View Controller

Fetching Posts from Reddit API ใน Swift

เพิ่ม Logic Fetch Data จาก Reddit แสดงบน PostsTableViewController เปิด File แทนที่ Contents:

import UIKit
import Alamofire

struct RedditPost {
    let title: String!
    let subreddit: String!
}

class PostsTableViewController: UITableViewController {
    var posts = [RedditPost]()
    let network = NetworkManager.sharedInstance
    
    override func viewDidLoad() {
        super.viewDidLoad()
        navigationItem.title = "Latest Posts"
        
        // Fetch Posts แล้ว Reload Table
        fetchPosts { posts in
            self.posts = posts
            self.tableView.reloadData()
        }
    }
    
    private func fetchPosts(completion: @escaping (_ posts: [RedditPost]) -> Void) -> Void {
        // Send Request ไป Reddit API
        Alamofire.request("https://api.reddit.com").validate().responseJSON { response in
            switch response.result {
            case .success(let JSON):
                let data = JSON as! [String:AnyObject]
                guard let children = data["data"]!["children"] as? [AnyObject] else { return }
                var posts = [RedditPost]()
                
                // Loop Reddit Posts Assign Post ไป Posts Array
                for child in 0...children.count-1 {
                    let post = children[child]["data"] as! [String: AnyObject]
                    posts.append(RedditPost(
                        title: post["title"] as! String,
                        subreddit: "/r/" + (post["subreddit"] as! String)
                    ))
                }
                
                DispatchQueue.main.async {
                    completion(posts)
                }
            case .failure(let error):
                print(error)
            }
        }
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
    
    // MARK: - Table View Data Source
    override func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }
    
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return self.posts.count
    }
    
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "PostCell", for: indexPath)
        let post = posts[indexPath.row] as RedditPost
        cell.textLabel?.text = post.title
        cell.detailTextLabel?.text = post.subreddit
        return cell
    }
}

ใน fetchPosts Method ใช้ Alamofire ส่ง GET Request ไป Reddit API Parse Response เพิ่มใน RedditPost Struct ที่สร้าง Top of File ทำให้ Data ที่ Pass ไป tableView Consistent

Handling Events When Device Goes Offline

Handle Scenario อีกอย่าง: ระหว่าง View Reddit Posts Lost Connectivity ทำไง? แสดง Offline Page อีกครั้ง

เหมือนเดิม สร้าง Manual Segue NetworkUnavailable จาก PostsTableViewController ไป OfflineViewController เพิ่ม Code Bottom ของ viewDidLoad:

network.reachability.whenUnreachable = { reachability in
    self.showOfflinePage()
}

เพิ่ม Method ด้านล่างใน Controller:

private func showOfflinePage() -> Void {
    DispatchQueue.main.async {
        self.performSegue(withIdentifier: "NetworkUnavailable", sender: self)
    }
}

นี่ Listen เมื่อ Device Offline ถ้าเกิดจะ showOfflinePage

That's all! เรา Handle Offline และ Online Events ด้วย NetworkManager ใน Swift ได้แล้ว

Conclusion

ใน Article นี้ เราพิจารณาวิธีทำให้ Application Handle Online/Offline Events เมื่อเกิดขึ้น สามารถ Implement แบบไหนก็ได้ หากมี Questions หรือ Feedback Comment ได้ด้านล่าง

Source Code ของ Playground นี้อยู่ที่ GitHub

Article นี้เผยแพร่ครั้งแรกบน Pusher

เรียนรู้เขียนโค้ดฟรี หลักสูตรโอเพนซอร์สของ freeCodeCamp ช่วยให้ผู้คนมากกว่า 40,000 คนได้งานเป็นนักพัฒนา เริ่มต้นได้เลย