مدیریت دسترسی اپلیکیشن ها به اینترنت در سوئیفت — به زبان ساده
امروزه اغلب اپلیکیشنهای موبایل برای کارکرد صحیح به یک اتصال اینترنتی فعال نیاز دارند. با این حال در اغلب موارد با قطع اتصال اینترنت مواجه میشویم. در چنین مواردی، توسعهدهنده باید روشهایی طراحی کند تا تجربه کاربری را قابلتحملتر سازد و یا دست کم این وضعیت را به کاربر اطلاع دهد. در این مقاله شیوه مدیریت دسترسی اپلیکیشن ها به اینترنت در سوئیفت را مورد بررسی قرار میدهیم.
در ادامه اپلیکیشن سادهای را میبینید که قصد داریم بسازیم و سناریوهای مختلف مدیریت اتصال اینترنت را بررسی میکنیم:
پیشنیازها
برای این که بتوانید این مقاله را پیگیری کنید به موارد زیر نیاز دارید:
- Xcode را روی سیستم خود نصب کنید.
- از زبان برنامهنویسی سوئیفت معلوماتی داشته باشید.
- Cocoapods (+) روی سیستمتان نصب باشد.
زمانی که الزامات فوق را فراهم ساختید، میتوانید باقی این مقاله را مطالعه کنید.
راهاندازی فضای کاری
پیش از آغاز باید یک playground بسازیم. این همان جایی است که حالتهای مختلف را نوشته و آنها را مدیریت میکنیم. سوئیفت یک پیادهسازی Reachability خاص خود برای تشخیص مشکلات اتصال اینترنت دارد، اما ما از یک کتابخانه شخص ثالث استفاده میکنیم. دلیل این امر آن است که این کتابخانه کاربرد آسانتری دارد و API آن بسیار گویاتر از قابلیت داخلی سوئیفت است. Xcode را باز کنید و یک پروژه جدید راهاندازی کنید.
این پروژه یک playground ساده خواهد بود که میتوانید روی آن آزمایش کنید. برای تشخیص مواردی که اتصال قطع میشود باید از پکیج Reachability.swift استفاده کنید. این پکیج جایگزین برای کتابخانه Reachability اپل است که در سوئیفت با کلوژرها بازنویسی شده است. ترمینال را باز کرده و دستور زیر را در آن اجرا کنید:
$ pod init
بدین ترتیب یک Podfile جدید ایجاد میشود که در آن میتوانیم وابستگیهای Cocoapods را اعلان کنیم. Podfile را باز کرده و محتوای آن را با کد زیر عوض کنید:
1platform :ios, '9.0'
2target 'project_name' do
3 use_frameworks!
4 pod 'ReachabilitySwift'
5 pod 'Alamofire'
6end
شما باید **project_name** را با نام پروژه عوض کنید.
فایل را ذخیره کنید و دستور زیر را برای نصب Pod-ها در پروژهتان اجرا نمایید:
$ pod install
هنگامی که نصب پایان یافت، فایل *.xcworkspace را در ریشه پروژه باز کنید. بدین ترتیب Xcode اجرا میشود.
ایجاد ابزار مدیریت دسترسی (Reachability) به شبکه
یک کلاس جدید به نام NetworkManager ایجاد کنید. این کلاس وضعیت شبکه را ذخیره کرده و یک پراکسی ساده برای پکیج Reachability خواهد بود. در این فایل، کد زیر را بچسبانید:
1import Foundation
2import Reachability
3class NetworkManager: NSObject {
4 var reachability: Reachability!
5 static let sharedInstance: NetworkManager = {
6 return NetworkManager()
7 }()
8 override init() {
9 super.init()
10 // Initialise reachability
11 reachability = Reachability()!
12 // Register an observer for the network status
13 NotificationCenter.default.addObserver(
14 self,
15 selector: #selector(networkStatusChanged(_:)),
16 name: .reachabilityChanged,
17 object: reachability
18 )
19 do {
20 // Start the network status notifier
21 try reachability.startNotifier()
22 } catch {
23 print("Unable to start notifier")
24 }
25 }
26 @objc func networkStatusChanged(_ notification: Notification) {
27 // Do something globally here!
28 }
29 static func stopNotifier() -> Void {
30 do {
31 // Stop the network status notifier
32 try (NetworkManager.sharedInstance.reachability).startNotifier()
33 } catch {
34 print("Error stopping notifier")
35 }
36 }
37
38 // Network is reachable
39 static func isReachable(completed: @escaping (NetworkManager) -> Void) {
40 if (NetworkManager.sharedInstance.reachability).connection != .none {
41 completed(NetworkManager.sharedInstance)
42 }
43 }
44 // Network is unreachable
45 static func isUnreachable(completed: @escaping (NetworkManager) -> Void) {
46 if (NetworkManager.sharedInstance.reachability).connection == .none {
47 completed(NetworkManager.sharedInstance)
48 }
49 }
50 // Network is reachable via WWAN/Cellular
51 static func isReachableViaWWAN(completed: @escaping (NetworkManager) -> Void) {
52 if (NetworkManager.sharedInstance.reachability).connection == .cellular {
53 completed(NetworkManager.sharedInstance)
54 }
55 }
56 // Network is reachable via WiFi
57 static func isReachableViaWiFi(completed: @escaping (NetworkManager) -> Void) {
58 if (NetworkManager.sharedInstance.reachability).connection == .wifi {
59 completed(NetworkManager.sharedInstance)
60 }
61 }
62]
در کلاس فوق، چند تابع کمکی تعریف کردهایم که شروع به نظارت بر وضعیت شبکه میکنند. یک sharedInstance داریم که یک سینگلتون است و در صورتی که نخواهیم چندین وهله از کلاس NetworkManager ایجاد شود میتوانیم آن را فراخوانی کنیم.
در متد init یک وهله از Reachability ایجاد میکنیم و سپس یک نوتیفیکیشن با استفاده از کلاس NotificationCenter ثبت میکنیم. اینک هر بار که وضعیت شبکه تغییر یابد، Callback توصیف شده از سوی NotificationCenter که همان networkStatusChanged است فراخوانی خواهد شد. این کار به منظور اجرای وظیفهای سراسری که در زمان قطع شدن شبکه باید فعال شود، انجام میشود.
تابعهای کمکی دیگری نیز تعریف کردهایم که به طور کلی به اجرای کد کمک میکنند و به وضعیت اتصال اینترنت وابسته هستند. از جمله میتوان به *isReachable*, *isUnreachable*, *isReachableViaWWAN* و *isReachableViaWiFi* اشاره کرد. کاربرد هر یک از این تابعهای کمکی به طور کلی مانند زیر هستند:
1NetworkManager.isReachable { networkManagerInstance in
2 print("Network is available")
3}
4NetworkManager.isUnreachable { networkManagerInstance in
5 print("Network is Unavailable")
6}
این یک شنونده رویداد نیست و تنها یک بار اجرا خواهد شد. برای استفاده از شنونده و درک تغییرات شبکه به صورت آنی باید از NetworkManager.sharedInstance.reachability.whenReachable** استفاده کنید که در مثال بعدی مقاله بررسی شده است. اکنون یک کلاس مدیریتی داریم و در ادامه چگونگی استفاده از آن در اپلیکیشن را بررسی میکنیم.
مدیریت دسترسی به شبکه در زمان اجرای اپلیکیشن
برخی اوقات اپلیکیشن تکیه زیادی روی اتصال اینترنت دارد و شما باید وضعیت اینترنت را در زمان باز شدن اپلیکیشن تعیین کنید. در ادامه شیوه مدیریت این وضعیت را با استفاده از کلاس NetworkManager بررسی میکنیم.
یک کنترلر جدید به نام LaunchViewController ایجاد کنید. با نمای کنترلر اول روی استوریبورد به عنوان یک کنترلر اجرای اولیه رفتار میکنیم. بدین ترتیب تلاش خواهیم کرد تشخیص دهیم دستگاه کاربر آنلاین است یا نه و اگر نباشد، یک صفحه آفلاین برای مدیریت این وضعیت ایجاد میکنیم تا کاربر کلاً وارد اپلیکیشن نشود. در فایل LaunchController محتوای موجود را با کد زیر عوض کنید:
1import UIKit
2class LaunchViewController: UIViewController {
3 let network: NetworkManager = NetworkManager.sharedInstance
4 override func viewDidLoad() {
5 super.viewDidLoad()
6 NetworkManager.isUnreachable { _ in
7 self.showOfflinePage()
8 }
9 }
10 private func showOfflinePage() -> Void {
11 DispatchQueue.main.async {
12 self.performSegue(
13 withIdentifier: "NetworkUnavailable",
14 sender: self
15 )
16 }
17 }
18}
در این کلاس از متد *isUnreachable* در NetworkManager‘s برای اجرای متد showOffline در موارد عدم دسترسی به شبکه استفاده میکنیم. یک کنترلر نمای جدید به نام OfflineViewController ایجاد کنید. فایل Main.storyboard را باز کرده و کلاس سفارشی نمای اول را به صورت LaunchViewController تنظیم کنید.
سپس یک کنترل نمای جدید در استوریبورد ایجاد کنید. OfflineViewController را به عنوان یک کلاس سفارشی برای این کنترلر نمای جدید تعیین کنید. اکنون یک segue دستی به نام NetworkUnavailable بین کنترلر نمای جدید و LaunchViewController ایجاد کنید و هنگامی که کار تمام شد، باید مانند زیر باشد:
اینک اپلیکیشن را اجرا میکنیم. توجه داشته باشید پیش از آن که اپلیکیشن را اجرا کنید، سیستمی که روی آن کار میکنید باید آفلاین باشد، زیرا شبیهساز iOS از اتصال اینترنتی این رایانه استفاده میکند. هنگامی که اپلیکیشن را اجرا میکنید، باید به صفحه آفلاین که طراحی کردیم برسید. اکنون کنترلر نمایی ایجاد میکنیم که در زمان برقراری اتصال نمایش مییابد.
مدیریت رویدادها در زمان آنلاین شدن دستگاه
یک کنترلر نمای ناوبری جدید روی استوریبورد و زیر کنترلر نمای آفلاین ایجاد کنید. میخواهیم یک کنترلر ایجاد میکنیم که آخرین مطالب Reddit را نمایش دهد. یک کلاس کنترلر جدید به نام PostsTableViewController ایجاد میکنیم. اکنون این کلاس سفارشی را برای کنترلر نمای متصل به کنترلر نمای ناوبری میسازیم. در ادامه یک segue دستی به نام MainController از کنترلر نمای ناوبری به کنترلر نمای اجرا و کنترلر نمای آفلاین میسازیم. بدین ترتیب باید چیزی مانند زیر داشته باشیم:
اینک کلاس LaunchViewController را باز میکنیم و در انتهای متد viewDidLoad کد زیر را اضافه میکنیم:
1NetworkManager.isReachable { _ in
2 self.showMainPage()
3}
سپس متد زیر را در ادامه کنترلر اضافه میکنیم:
1private func showMainPage() -> Void {
2 DispatchQueue.main.async {
3 self.performSegue(
4 withIdentifier: "MainController",
5 sender: self
6 )
7 }
8}
بدین ترتیب مطمئن خواهیم بود که وقتی اپلیکیشن اجرا میشود، اتصال را بررسی میکند و اگر اتصال موجود باشد، PostsTableViewController را عرضه میکند. در غیر این صورت اپلیکیشن OfflineViewController را عرضه میکند.
تا به اینجا همه چیز عالی است، اما زمانی که کاربر روی OfflineViewController ضربه بزند و سپس شبکه دوباره آنلاین شود چطور؟ این حالت را نیز در ادامه مدیریت میکنیم. OfflineViewController را باز کرده و کد آن را با کد زیر عوض کنید:
1import UIKit
2class OfflineViewController: UIViewController {
3 let network = NetworkManager.sharedInstance
4 override func viewDidLoad() {
5 super.viewDidLoad()
6 // If the network is reachable show the main controller
7 network.reachability.whenReachable = { _ in
8 self.showMainController()
9 }
10 }
11 override func viewWillAppear(_ animated: Bool) {
12 super.viewWillAppear(animated)
13 navigationController?.setNavigationBarHidden(true, animated: animated)
14 }
15 override func viewWillDisappear(_ animated: Bool) {
16 super.viewWillDisappear(animated)
17 navigationController?.setNavigationBarHidden(false, animated: animated)
18 }
19 private func showMainController() -> Void {
20 DispatchQueue.main.async {
21 self.performSegue(withIdentifier: "MainController", sender: self)
22 }
23 }
24}
در کنترلر فوق، میتوان دید که در متد viewDidLoad مقدار تکمیل whenReachable را روی کنترلر اصلی تنظیم کردهایم. این بدان معنی است که تا زمانی که آفلاین باشد، منتظر آنلاین شدن مجدد باقی میماند. در این زمان PostsTableViewController عرضه میشود.
همچنین متدهای viewWillAppear و viewWillDisappear به صورت override درمیآیند تا مطمئن شویم که نوار ناوبری روی کنترلر نمای آفلاین نمایش نمییابد.
واکشی پستها از Reddit API در سوئیفت
اکنون منطقی که ادامه به واکشی دادهها از Reddit میکند را مینویسیم تا روی PostsTableViewController نمایش یابد. فایل را باز کنید و محتوای آن را با کد زیر عوض کنید:
1import UIKit
2import Alamofire
3struct RedditPost {
4 let title: String!
5 let subreddit: String!
6}
7class PostsTableViewController: UITableViewController {
8 var posts = [RedditPost]()
9 let network = NetworkManager.sharedInstance
10 override func viewDidLoad() {
11 super.viewDidLoad()
12 navigationItem.title = "Latest Posts"
13 // Fetch the posts and then reload the table
14 fetchPosts { posts in
15 self.posts = posts
16 self.tableView.reloadData()
17 }
18 }
19 private func fetchPosts(completion: @escaping (_ posts: [RedditPost]) -> Void) -> Void {
20 // Send a request to the Reddit API
21 Alamofire.request("https://api.reddit.com").validate().responseJSON { response in
22 switch response.result {
23 case .success(let JSON):
24 let data = JSON as! [String:AnyObject]
25 guard let children = data["data"]!["children"] as? [AnyObject] else { return }
26 var posts = [RedditPost]()
27 // Loop through the Reddit posts and then assign a post to the posts array
28 for child in 0...children.count-1 {
29 let post = children[child]["data"] as! [String: AnyObject]
30 posts.append(RedditPost(
31 title: post["title"] as! String,
32 subreddit: "/r/" + (post["subreddit"] as! String)
33 ))
34 }
35 DispatchQueue.main.async {
36 completion(posts)
37 }
38 case .failure(let error):
39 print(error)
40 }
41 }
42 }
43 override func didReceiveMemoryWarning() {
44 super.didReceiveMemoryWarning()
45 }
46 // MARK: - Table view data source
47 override func numberOfSections(in tableView: UITableView) -> Int {
48 return 1
49 }
50 // Return the number of posts available
51 override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
52 return self.posts.count
53 }
54 override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
55 let cell = tableView.dequeueReusableCell(withIdentifier: "PostCell", for: indexPath)
56 let post = posts[indexPath.row] as RedditPost
57 cell.textLabel?.text = post.title
58 cell.detailTextLabel?.text = post.subreddit
59 return cell
60 }
61}
در متد fetchPosts از Alamofire برای ارسال یک درخواست Get به Reddit API استفاده میکنیم. سپس پاسخ را تحلیل کرده و آن را به struct-ی به نام RedditPost که در ابتدای فایل ایجاد کردیم اضافه میکنیم. بدین ترتیب دادههایی که به آن ارسال میکنیم منسجم میمانند.
مدیریت رویدادها در زمان آفلاین شدن دستگاه
اکنون یک سناریوی دیگر را بررسی میکنیم. فرض کنید وقتی که مشغول تماشای آخرین مطالب ردیت هستید، اتصال اینترنت قطع میشود. چه رخ خواهد داد؟ در این بخش تلاش میکنیم در صورت وقوع این اتفاق، یک صفحه آفلاین را مجدداً نمایش دهیم.
همان طور که قبلاً عمل کردیم، یک segue به نام NetworkUnavailable از PostsTableViewController به OfflineViewController ایجاد میکنیم. سپس کد زیر را به انتهای متد viewDidLoad اضافه میکنیم:
1network.reachability.whenUnreachable = { reachability in
2 self.showOfflinePage()
3}
اکنون متد را در ادامه کنترلر اضافه میکنیم:
1private func showOfflinePage() -> Void {
2 DispatchQueue.main.async {
3 self.performSegue(withIdentifier: "NetworkUnavailable", sender: self)
4 }
5}
بدین ترتیب زمانی که دستگاه آفلاین میشود را مورد رصد قرار میدهیم و در صورت وقوع این اتفاق صفحه آفلاین showOfflinePage نمایش مییابد. بدین ترتیب ما موفق شدیم رویدادهای آنلاین و آفلاین شدن را با استفاده از NetworkManager در سوئیفت مدیریت کنیم.
سخن پایانی
در این مقاله شیوه مطمئن شدن از توانایی مدیریت رویدادهای آنلاین و آفلاین شدن در اپلیکیشنهای سوئیفت را مورد بررسی قرار دادیم. شما میتوانید این مسئله را به هر صورتی که دوست دارد پیادهسازی کنید. سورس کد این مقاله را میتوانید در این ریپو (+) ملاحظه کنید.
اگر این نوشته برای شما مفید بوده است، آموزشهای زیر نیز به شما پیشنهاد میشوند:
- مجموعه آموزشهای برنامهنویسی
- آموزش برنامه نویسی Swift (سوئیفت) برای برنامه نویسی iOS
- مجموعه آموزشهای دروس علوم و مهندسی کامپیوتر
- ساخت الگوریتم بازگشتی در سوئیفت — از صفر تا صد
- آموزش سوئیفت (Swift) — مجموعه مقالات مجله فرادرس
==