.key, value: String(describing:

Τρόπος απομόνωσης λογικής αλληλεπίδρασης πελάτη-διακομιστή σε εφαρμογές iOS



Σήμερα, οι περισσότερες εφαρμογές για κινητά βασίζονται σε μεγάλο βαθμό στις αλληλεπιδράσεις μεταξύ πελατών και διακομιστών. Αυτό όχι μόνο σημαίνει ότι μπορούν να εκφορτώσουν τις περισσότερες από τις βαριές εργασίες τους σε διακομιστές υποστήριξης, αλλά επιτρέπει επίσης σε αυτές τις εφαρμογές για κινητές συσκευές να προσφέρουν κάθε είδους δυνατότητες και λειτουργίες που μπορούν να διατεθούν μόνο μέσω του Διαδικτύου.

Οι διακομιστές Back-end είναι συνήθως σχεδιασμένοι για να προσφέρουν τις υπηρεσίες τους μέσω RESTful API . Για απλούστερες εφαρμογές, συχνά αισθανόμαστε στον πειρασμό να αποκτήσουμε δημιουργώντας κώδικα σπαγγέτι. ανάμειξη κώδικα που επικαλείται το API με την υπόλοιπη λογική της εφαρμογής. Ωστόσο, καθώς οι εφαρμογές γίνονται πολύπλοκες και αντιμετωπίζουν όλο και περισσότερα API, μπορεί να ενοχληθεί η αλληλεπίδραση με αυτά τα API με μη δομημένο, μη προγραμματισμένο τρόπο.



Διατηρήστε τον κωδικό εφαρμογής iOS χωρίς ακαταστασία με μια καλά σχεδιασμένη μονάδα δικτύωσης πελατών REST.



Διατηρήστε τον κωδικό εφαρμογής iOS χωρίς ακαταστασία με μια καλά σχεδιασμένη μονάδα δικτύωσης πελατών REST. Τιτίβισμα

Αυτό το άρθρο ασχολείται με μια αρχιτεκτονική προσέγγιση για τη δημιουργία μιας καθαρής ενότητας δικτύωσης πελατών REST για Εφαρμογές iOS που σας επιτρέπει να διατηρείτε όλη τη λογική αλληλεπίδρασης πελάτη-διακομιστή απομονωμένη από τον υπόλοιπο κώδικα της εφαρμογής σας.



Εφαρμογές διακομιστή-πελάτη

Μια τυπική αλληλεπίδραση πελάτη-διακομιστή μοιάζει με αυτό:

  1. Ένας χρήστης εκτελεί κάποια ενέργεια (π.χ., πατώντας σε κάποιο κουμπί ή εκτελεί κάποια άλλη κίνηση στην οθόνη).
  2. Η εφαρμογή προετοιμάζει και στέλνει ένα αίτημα HTTP / REST ως απάντηση στην ενέργεια του χρήστη.
  3. Ο διακομιστής επεξεργάζεται το αίτημα και ανταποκρίνεται ανάλογα στην εφαρμογή.
  4. Η εφαρμογή λαμβάνει την απάντηση και ενημερώνει τη διεπαφή χρήστη βάσει αυτής.

Με μια γρήγορη ματιά, η συνολική διαδικασία μπορεί να φαίνεται απλή, αλλά πρέπει να σκεφτούμε τις λεπτομέρειες.



Ακόμη και αν υποτεθεί ότι ένα API διακομιστή backend λειτουργεί όπως διαφημίζεται (δηλαδή δεν πάντα συμβαίνει!), μπορεί συχνά να είναι κακώς σχεδιασμένο καθιστώντας το αναποτελεσματικό ή ακόμη και δύσκολο στη χρήση. Μια συνηθισμένη ενόχληση είναι ότι όλες οι κλήσεις προς το API απαιτούν από τον καλούντα να παρέχει τις ίδιες πληροφορίες (π.χ. πώς μορφοποιούνται τα δεδομένα αιτήματος, ένα διακριτικό πρόσβασης που μπορεί να χρησιμοποιήσει ο διακομιστής για να αναγνωρίσει τον χρήστη που είναι συνδεδεμένος αυτήν τη στιγμή και ούτω καθεξής).

Οι εφαρμογές για κινητά μπορεί επίσης να χρειαστεί να χρησιμοποιούν ταυτόχρονα πολλαπλούς διακομιστές back-end για διαφορετικούς σκοπούς. Ένας διακομιστής μπορεί, για παράδειγμα, να είναι αφιερωμένος στον έλεγχο ταυτότητας χρήστη, ενώ ένας άλλος ασχολείται μόνο με τη συλλογή αναλυτικών στοιχείων.



Επιπλέον, ένας τυπικός πελάτης REST θα πρέπει να κάνει κάτι περισσότερο από την απλή επίκληση απομακρυσμένων API. Η δυνατότητα ακύρωσης εκκρεμών αιτημάτων ή μια καθαρή και διαχειρίσιμη προσέγγιση στο χειρισμό σφαλμάτων, είναι παραδείγματα λειτουργικότητας που πρέπει να ενσωματωθούν σε οποιαδήποτε ισχυρή εφαρμογή για κινητά.

Επισκόπηση της Αρχιτεκτονικής

Ο πυρήνας του πελάτη REST θα βασιστεί σε αυτά τα ακόλουθα στοιχεία:



Αυτός είναι ο τρόπος με τον οποίο κάθε ένα από αυτά τα συστατικά θα αλληλεπιδρά μεταξύ τους:



Τα βέλη 1 έως 10 στην παραπάνω εικόνα δείχνουν μια ιδανική ακολουθία λειτουργιών μεταξύ της εφαρμογής που καλεί μια υπηρεσία και της υπηρεσίας που τελικά επιστρέφει τα ζητούμενα δεδομένα ως αντικείμενο μοντέλου. Κάθε στοιχείο σε αυτήν τη ροή έχει έναν συγκεκριμένο ρόλο που διασφαλίζει διαχωρισμός των ανησυχιών εντός της ενότητας.

Εκτέλεση

Θα εφαρμόσουμε τον πελάτη REST ως μέρος της φανταστικής εφαρμογής κοινωνικού δικτύου στην οποία θα φορτώσουμε μια λίστα με τους φίλους του χρήστη που είναι συνδεδεμένοι αυτήν τη στιγμή. Θα υποθέσουμε ότι ο απομακρυσμένος διακομιστής μας χρησιμοποιεί JSON για απαντήσεις.



Ας ξεκινήσουμε εφαρμόζοντας τα μοντέλα και τους αναλυτές μας.

Από Raw JSON έως Model Objects

Το πρώτο μας μοντέλο, User, καθορίζει τη δομή των πληροφοριών για οποιονδήποτε χρήστη του κοινωνικού δικτύου. Για να διατηρήσουμε τα πράγματα απλά, θα συμπεριλάβουμε μόνο πεδία που είναι απολύτως απαραίτητα για αυτό το σεμινάριο (σε μια πραγματική εφαρμογή, η δομή θα έχει συνήθως πολύ περισσότερες ιδιότητες).

struct User { var id: String var email: String? var name: String? }

Δεδομένου ότι θα λάβουμε όλα τα δεδομένα χρήστη από το διακομιστή backend μέσω του API του, χρειαζόμαστε έναν τρόπο αναλύστε την απόκριση API σε έγκυρο User αντικείμενο. Για να γίνει αυτό, θα προσθέσουμε έναν κατασκευαστή στο User που δέχεται ένα αντικείμενο ανάλυσης JSON (Dictionary) ως παράμετρο. Θα ορίσουμε το αντικείμενο JSON ως ψευδώνυμο τύπο:

typealias JSON = [String: Any]

Στη συνέχεια, θα προσθέσουμε τη συνάρτηση κατασκευαστή στο User μας δομή ως εξής:

extension User { init?(json: JSON) { guard let id = json['id'] as? String else { return nil } self.id = id self.email = json['email'] as? String self.name = json['name'] as? String } }

Για να διατηρήσουμε τον αρχικό προεπιλεγμένο κατασκευαστή του User, προσθέτουμε τον κατασκευαστή μέσω μιας επέκτασης στο User τύπος.

Στη συνέχεια, για να δημιουργήσετε ένα User αντικείμενο από μια απλή απόκριση API, πρέπει να εκτελέσουμε τα ακόλουθα δύο βήματα:

// Transform raw JSON data to parsed JSON object using JSONSerializer (part of standard library) let userObject = (try? JSONSerialization.jsonObject(with: data, options: [])) as? JSON // Create an instance of `User` structure from parsed JSON object let user = userObject.flatMap(User.init)

Βελτιωμένος χειρισμός σφαλμάτων

Θα καθορίσουμε έναν τύπο που θα αντιπροσωπεύει διαφορετικά σφάλματα που ενδέχεται να προκύψουν κατά την απόπειρα αλληλεπίδρασης με τους διακομιστές backend. Μπορούμε να χωρίσουμε όλα αυτά τα σφάλματα σε τρεις βασικές κατηγορίες:

Μπορούμε να ορίσουμε τα αντικείμενα σφάλματος ως τύπο απαρίθμησης. Και ενώ είμαστε σε αυτό, είναι καλή ιδέα να φτιάξουμε το ServiceError πληκτρολογήστε συμμόρφωση με το Error πρωτόκολλο . Αυτό θα μας επιτρέψει να χρησιμοποιήσουμε και να χειριστούμε αυτές τις τιμές σφάλματος χρησιμοποιώντας τυπικούς μηχανισμούς που παρέχονται από το Swift (όπως η χρήση throw για να ρίξουμε ένα σφάλμα).

enum ServiceError: Error { case noInternetConnection case custom(String) case other }

Σε αντίθεση με noInternetConnection και other λάθη, το προσαρμοσμένο σφάλμα σχετίζεται με μια τιμή. Αυτό θα μας επιτρέψει να χρησιμοποιήσουμε την απόκριση σφάλματος από το διακομιστή ως συσχετισμένη τιμή για το ίδιο το σφάλμα, δίνοντας έτσι στο σφάλμα περισσότερο πλαίσιο.

Τώρα, ας προσθέσουμε ένα errorDescription ιδιοκτησία στο ServiceError enumartion για να κάνουν τα λάθη πιο περιγραφικά. Θα προσθέσουμε μηνύματα με κωδικό κώδικα για το noInternetConnection και other σφάλματα και χρησιμοποιήστε τη σχετική τιμή ως μήνυμα για custom Σφάλματα.

extension ServiceError: LocalizedError { var errorDescription: String? { switch self { case .noInternetConnection: return 'No Internet connection' case .other: return 'Something went wrong' case .custom(let message): return message } } }

Υπάρχει ένα ακόμη πράγμα που πρέπει να εφαρμόσουμε στο ServiceError απαρίθμηση. Στην περίπτωση a custom σφάλμα, πρέπει να μετατρέψουμε τα δεδομένα JSON διακομιστή σε αντικείμενο σφάλματος. Για να το κάνουμε αυτό, χρησιμοποιούμε την ίδια προσέγγιση που χρησιμοποιήσαμε για τα μοντέλα:

extension ServiceError { init(json: JSON) { if let message = json['message'] as? String { self = .custom(message) } else { self = .other } } }

Γέφυρα του χάσματος μεταξύ της εφαρμογής και του διακομιστή Backend

Το στοιχείο πελάτη θα είναι ένας ενδιάμεσος μεταξύ της εφαρμογής και του διακομιστή backend. Είναι ένα κρίσιμο στοιχείο που θα καθορίσει τον τρόπο επικοινωνίας της εφαρμογής και του διακομιστή, αλλά δεν θα γνωρίζει τίποτα για τα μοντέλα δεδομένων και τις δομές τους. Ο πελάτης θα είναι υπεύθυνος για την επίκληση συγκεκριμένων διευθύνσεων URL με τις παρεχόμενες παραμέτρους και την επιστροφή εισερχόμενων δεδομένων JSON που αναλύονται ως αντικείμενα JSON.

enum RequestMethod: String { case get = 'GET' case post = 'POST' case put = 'PUT' case delete = 'DELETE' } final class WebClient { private var baseUrl: String init(baseUrl: String) { self.baseUrl = baseUrl } func load(path: String, method: RequestMethod, params: JSON, completion: @escaping (Any?, ServiceError?) -> ()) -> URLSessionDataTask? { // TODO: Add implementation } }

Ας εξετάσουμε τι συμβαίνει στον παραπάνω κώδικα…

Αρχικά, δηλώσαμε έναν τύπο απαρίθμησης, RequestMethod, που περιγράφει τέσσερις κοινές μεθόδους HTTP. Αυτές είναι μεταξύ των μεθόδων που χρησιμοποιούνται στα API REST.

Το WebClient η τάξη περιέχει το baseURL ιδιότητα που θα χρησιμοποιηθεί για την επίλυση όλων των σχετικών διευθύνσεων URL που λαμβάνει. Σε περίπτωση που η εφαρμογή μας πρέπει να αλληλεπιδράσει με πολλούς διακομιστές, μπορούμε να δημιουργήσουμε πολλές παρουσίες WebClient το καθένα με διαφορετική τιμή για baseURL.

Ο Πελάτης έχει μια μεμονωμένη μέθοδο load, η οποία ακολουθεί μια διαδρομή σε σχέση με baseURL ως παράμετρος, μέθοδος αιτήματος, παράμετροι αιτήματος και κλείσιμο ολοκλήρωσης. Το κλείσιμο ολοκλήρωσης καλείται με το αναλυμένο JSON και ServiceError ως παράμετροι. Προς το παρόν, η παραπάνω μέθοδος δεν έχει εφαρμογή, στην οποία θα φτάσουμε σύντομα.

Πριν από την εφαρμογή του load μέθοδος, χρειαζόμαστε έναν τρόπο για να δημιουργήσουμε ένα URL από όλες τις διαθέσιμες πληροφορίες στη μέθοδο. Θα επεκτείνουμε το URL τάξη για το σκοπό αυτό:

extension URL { init(baseUrl: String, path: String, params: JSON, method: RequestMethod) { var components = URLComponents(string: baseUrl)! components.path += path switch method { case .get, .delete: components.queryItems = params.map { URLQueryItem(name: $0.key, value: String(describing: $0.value)) } default: break } self = components.url! } }

Εδώ απλώς προσθέτουμε τη διαδρομή στο βασικό URL. Για τις μεθόδους GET και DELETE HTTP, προσθέτουμε επίσης τις παραμέτρους ερωτήματος στη συμβολοσειρά URL.

Στη συνέχεια, πρέπει να είμαστε σε θέση να δημιουργήσουμε παρουσίες URLRequest από δεδομένες παραμέτρους. Για να το κάνουμε αυτό θα κάνουμε κάτι παρόμοιο με αυτό που κάναμε για το URL:

extension URLRequest { init(baseUrl: String, path: String, method: RequestMethod, params: JSON) { let url = URL(baseUrl: baseUrl, path: path, params: params, method: method) self.init(url: url) httpMethod = method.rawValue setValue('application/json', forHTTPHeaderField: 'Accept') setValue('application/json', forHTTPHeaderField: 'Content-Type') switch method { case .post, .put: httpBody = try! JSONSerialization.data(withJSONObject: params, options: []) default: break } } }

Εδώ, δημιουργήσαμε πρώτα ένα URL χρησιμοποιώντας τον κατασκευαστή από την επέκταση. Στη συνέχεια αρχικοποιούμε μια παρουσία του URLRequest με αυτό URL, ορίστε μερικές κεφαλίδες HTTP όπως απαιτείται και, στη συνέχεια, στην περίπτωση μεθόδων POST ή PUT HTTP, προσθέστε παραμέτρους στο σώμα του αιτήματος.

Τώρα που έχουμε καλύψει όλες τις προϋποθέσεις, μπορούμε να εφαρμόσουμε το load μέθοδος:

final class WebClient { private var baseUrl: String init(baseUrl: String) { self.baseUrl = baseUrl } func load(path: String, method: RequestMethod, params: JSON, completion: @escaping (Any?, ServiceError?) -> ()) -> URLSessionDataTask? { // Checking internet connection availability if !Reachability.isConnectedToNetwork() { completion(nil, ServiceError.noInternetConnection) return nil } // Adding common parameters var parameters = params if let token = KeychainWrapper.itemForKey('application_token') { parameters['token'] = token } // Creating the URLRequest object let request = URLRequest(baseUrl: baseUrl, path: path, method: method, params: params) // Sending request to the server. let task = URLSession.shared.dataTask(with: request) { data, response, error in // Parsing incoming data var object: Any? = nil if let data = data { object = try? JSONSerialization.jsonObject(with: data, options: []) } if let httpResponse = response as? HTTPURLResponse, (200..<300) ~= httpResponse.statusCode { completion(object, nil) } else { let error = (object as? JSON).flatMap(ServiceError.init) ?? ServiceError.other completion(nil, error) } } task.resume() return task } }

Το load Η παραπάνω μέθοδος εκτελεί τα ακόλουθα βήματα:

  1. Ελέγξτε τη διαθεσιμότητα της σύνδεσης στο Διαδίκτυο. Εάν η σύνδεση στο Διαδίκτυο δεν είναι διαθέσιμη, καλούμε αμέσως το κλείσιμο ολοκλήρωσης με noInternetConnection σφάλμα ως παράμετρος. (Σημείωση: Reachability στον κώδικα είναι μια προσαρμοσμένη κλάση, η οποία χρησιμοποιεί μία από τις κοινές προσεγγίσεις για να ελέγξετε τη σύνδεση στο Διαδίκτυο.)
  2. Προσθέστε κοινές παραμέτρους. . Αυτό μπορεί να περιλαμβάνει κοινές παραμέτρους, όπως διακριτικό εφαρμογής ή αναγνωριστικό χρήστη.
  3. Δημιουργήστε το URLRequest αντικείμενο, χρησιμοποιώντας τον κατασκευαστή από την επέκταση.
  4. Στείλτε το αίτημα στον διακομιστή. Χρησιμοποιούμε το URLSession αντικείμενο για αποστολή δεδομένων στο διακομιστή.
  5. Αναλύστε τα εισερχόμενα δεδομένα. Όταν ο διακομιστής αποκρίνεται, αναλύουμε πρώτα το ωφέλιμο φορτίο απόκρισης σε ένα αντικείμενο JSON χρησιμοποιώντας το JSONSerialization. Στη συνέχεια, ελέγχουμε τον κωδικό κατάστασης της απάντησης. Εάν πρόκειται για έναν κωδικό επιτυχίας (δηλαδή, στην περιοχή μεταξύ 200 και 299), καλούμε το κλείσιμο ολοκλήρωσης με το αντικείμενο JSON. Διαφορετικά, μετατρέπουμε το αντικείμενο JSON σε a ServiceError αντικείμενο και καλέστε το κλείσιμο ολοκλήρωσης με αυτό το αντικείμενο σφάλματος.

Καθορισμός υπηρεσιών για λογικά συνδεδεμένες λειτουργίες

Στην περίπτωση της αίτησής μας, χρειαζόμαστε μια υπηρεσία που θα ασχολείται με εργασίες που σχετίζονται με φίλους ενός χρήστη. Γι 'αυτό, δημιουργούμε ένα FriendsService τάξη. Στην ιδανική περίπτωση, μια τάξη όπως αυτή θα είναι υπεύθυνη για λειτουργίες όπως η λήψη μιας λίστας φίλων, η προσθήκη ενός νέου φίλου, η αφαίρεση ενός φίλου, η ομαδοποίηση ορισμένων φίλων σε μια κατηγορία κ.λπ. Για απλότητα σε αυτό το σεμινάριο, θα εφαρμόσουμε μόνο μία μέθοδο :

final class FriendsService { private let client = WebClient(baseUrl: 'https://your_server_host/api/v1') @discardableResult func loadFriends(forUser user: User, completion: @escaping ([User]?, ServiceError?) -> ()) -> URLSessionDataTask? { let params: JSON = ['user_id': user.id] return client.load(path: '/friends', method: .get, params: params) { result, error in let dictionaries = result as? [JSON] completion(dictionaries?.flatMap(User.init), error) } } }

Το FriendsService η τάξη περιέχει ένα client ιδιότητα τύπου WebClient. Αρχικοποιείται με τη βασική διεύθυνση URL του απομακρυσμένου διακομιστή που είναι υπεύθυνος για τη διαχείριση φίλων. Όπως αναφέρθηκε προηγουμένως, σε άλλες κατηγορίες υπηρεσιών, μπορούμε να έχουμε μια διαφορετική εμφάνιση WebClient αρχικοποιήθηκε με διαφορετική διεύθυνση URL, εάν είναι απαραίτητο.

Στην περίπτωση μιας εφαρμογής που λειτουργεί με έναν μόνο διακομιστή, το WebClient μπορεί να δοθεί στην τάξη ένας κατασκευαστής που ξεκινά με τη διεύθυνση URL του διακομιστή:

final class WebClient { // ... init() { self.baseUrl = 'https://your_server_base_url' } // ... }

Το loadFriends μέθοδος, όταν καλείται, προετοιμάζει όλες τις απαραίτητες παραμέτρους και χρησιμοποιεί την παρουσία του FriendService του WebClient για να κάνετε ένα αίτημα API. Αφού λάβει την απάντηση από το διακομιστή μέσω του WebClient, μετατρέπει το αντικείμενο JSON σε User μοντέλα και καλεί το κλείσιμο ολοκλήρωσης μαζί τους ως παράμετρο.

Μια τυπική χρήση του FriendService μπορεί να μοιάζει με το εξής:

let friendsTask: URLSessionDataTask! let activityIndicator: UIActivityIndicatorView! var friends: [User] = [] func friendsButtonTapped() { friendsTask?.cancel() //Cancel previous loading task. activityIndicator.startAnimating() //Show loading indicator friendsTask = FriendsService().loadFriends(forUser: currentUser) {[weak self] friends, error in DispatchQueue.main.async { self?.activityIndicator.stopAnimating() //Stop loading indicators if let error = error { print(error.localizedDescription) //Handle service error } else if let friends = friends { self?.friends = friends //Update friends property self?.updateUI() //Update user interface } } } }

Στο παραπάνω παράδειγμα, υποθέτουμε ότι η συνάρτηση friendsButtonTapped καλείται κάθε φορά που ο χρήστης πατάει ένα κουμπί που προορίζεται να τους δείξει μια λίστα με τους φίλους του στο δίκτυο. Διατηρούμε επίσης μια αναφορά στην εργασία στο friendsTask ιδιότητα, ώστε να μπορούμε να ακυρώσουμε το αίτημα ανά πάσα στιγμή καλώντας στο friendsTask?.cancel().

Αυτό μας επιτρέπει να έχουμε μεγαλύτερο έλεγχο του κύκλου ζωής των εκκρεμών αιτημάτων, επιτρέποντάς μας να τα τερματίσουμε όταν διαπιστώσουμε ότι έχουν γίνει άσχετα.

συμπέρασμα

Σε αυτό το άρθρο, έχω μοιραστεί μια απλή αρχιτεκτονική μιας λειτουργικής μονάδας δικτύωσης για την εφαρμογή σας iOS που είναι τόσο ασήμαντη για εφαρμογή και μπορεί να προσαρμοστεί στις περίπλοκες ανάγκες δικτύωσης των περισσότερων εφαρμογών iOS. Ωστόσο, το βασικό συμπέρασμα από αυτό είναι ότι ένας κατάλληλα σχεδιασμένος πελάτης REST και τα στοιχεία που το συνοδεύουν - τα οποία είναι απομονωμένα από την υπόλοιπη λογική της εφαρμογής σας - μπορούν να βοηθήσουν στη διατήρηση του κώδικα αλληλεπίδρασης πελάτη-διακομιστή της εφαρμογής σας απλό, ακόμη και όταν η ίδια η εφαρμογή γίνεται όλο και πιο περίπλοκη .

Ελπίζω να βρείτε αυτό το άρθρο χρήσιμο στη δημιουργία της επόμενης εφαρμογής σας iOS. Μπορείτε να βρείτε τον πηγαίο κώδικα αυτής της ενότητας δικτύωσης στο GitHub . Ελέγξτε τον κωδικό, πιείτε τον, αλλάξτε τον, παίξτε μαζί του.

Εάν βρείτε κάποια άλλη αρχιτεκτονική προτιμότερη για εσάς και το έργο σας, μοιραστείτε τις λεπτομέρειες στην παρακάτω ενότητα σχολίων.

Σχετίζεται με: Απλοποίηση της χρήσης RESTful API και ανθεκτικότητας δεδομένων σε iOS με το Mantle και το Realm .value)) } default: break } self = components.url! } }

Εδώ απλώς προσθέτουμε τη διαδρομή στο βασικό URL. Για τις μεθόδους GET και DELETE HTTP, προσθέτουμε επίσης τις παραμέτρους ερωτήματος στη συμβολοσειρά URL.

Στη συνέχεια, πρέπει να είμαστε σε θέση να δημιουργήσουμε παρουσίες URLRequest από δεδομένες παραμέτρους. Για να το κάνουμε αυτό θα κάνουμε κάτι παρόμοιο με αυτό που κάναμε για το URL:

extension URLRequest { init(baseUrl: String, path: String, method: RequestMethod, params: JSON) { let url = URL(baseUrl: baseUrl, path: path, params: params, method: method) self.init(url: url) httpMethod = method.rawValue setValue('application/json', forHTTPHeaderField: 'Accept') setValue('application/json', forHTTPHeaderField: 'Content-Type') switch method { case .post, .put: httpBody = try! JSONSerialization.data(withJSONObject: params, options: []) default: break } } }

Εδώ, δημιουργήσαμε πρώτα ένα URL χρησιμοποιώντας τον κατασκευαστή από την επέκταση. Στη συνέχεια αρχικοποιούμε μια παρουσία του URLRequest με αυτό URL, ορίστε μερικές κεφαλίδες HTTP όπως απαιτείται και, στη συνέχεια, στην περίπτωση μεθόδων POST ή PUT HTTP, προσθέστε παραμέτρους στο σώμα του αιτήματος.

Τώρα που έχουμε καλύψει όλες τις προϋποθέσεις, μπορούμε να εφαρμόσουμε το load μέθοδος:

final class WebClient { private var baseUrl: String init(baseUrl: String) { self.baseUrl = baseUrl } func load(path: String, method: RequestMethod, params: JSON, completion: @escaping (Any?, ServiceError?) -> ()) -> URLSessionDataTask? { // Checking internet connection availability if !Reachability.isConnectedToNetwork() { completion(nil, ServiceError.noInternetConnection) return nil } // Adding common parameters var parameters = params if let token = KeychainWrapper.itemForKey('application_token') { parameters['token'] = token } // Creating the URLRequest object let request = URLRequest(baseUrl: baseUrl, path: path, method: method, params: params) // Sending request to the server. let task = URLSession.shared.dataTask(with: request) { data, response, error in // Parsing incoming data var object: Any? = nil if let data = data { object = try? JSONSerialization.jsonObject(with: data, options: []) } if let httpResponse = response as? HTTPURLResponse, (200..<300) ~= httpResponse.statusCode { completion(object, nil) } else { let error = (object as? JSON).flatMap(ServiceError.init) ?? ServiceError.other completion(nil, error) } } task.resume() return task } }

Το load Η παραπάνω μέθοδος εκτελεί τα ακόλουθα βήματα:

  1. Ελέγξτε τη διαθεσιμότητα της σύνδεσης στο Διαδίκτυο. Εάν η σύνδεση στο Διαδίκτυο δεν είναι διαθέσιμη, καλούμε αμέσως το κλείσιμο ολοκλήρωσης με noInternetConnection σφάλμα ως παράμετρος. (Σημείωση: Reachability στον κώδικα είναι μια προσαρμοσμένη κλάση, η οποία χρησιμοποιεί μία από τις κοινές προσεγγίσεις για να ελέγξετε τη σύνδεση στο Διαδίκτυο.)
  2. Προσθέστε κοινές παραμέτρους. . Αυτό μπορεί να περιλαμβάνει κοινές παραμέτρους, όπως διακριτικό εφαρμογής ή αναγνωριστικό χρήστη.
  3. Δημιουργήστε το URLRequest αντικείμενο, χρησιμοποιώντας τον κατασκευαστή από την επέκταση.
  4. Στείλτε το αίτημα στον διακομιστή. Χρησιμοποιούμε το URLSession αντικείμενο για αποστολή δεδομένων στο διακομιστή.
  5. Αναλύστε τα εισερχόμενα δεδομένα. Όταν ο διακομιστής αποκρίνεται, αναλύουμε πρώτα το ωφέλιμο φορτίο απόκρισης σε ένα αντικείμενο JSON χρησιμοποιώντας το JSONSerialization. Στη συνέχεια, ελέγχουμε τον κωδικό κατάστασης της απάντησης. Εάν πρόκειται για έναν κωδικό επιτυχίας (δηλαδή, στην περιοχή μεταξύ 200 και 299), καλούμε το κλείσιμο ολοκλήρωσης με το αντικείμενο JSON. Διαφορετικά, μετατρέπουμε το αντικείμενο JSON σε a ServiceError αντικείμενο και καλέστε το κλείσιμο ολοκλήρωσης με αυτό το αντικείμενο σφάλματος.

Καθορισμός υπηρεσιών για λογικά συνδεδεμένες λειτουργίες

Στην περίπτωση της αίτησής μας, χρειαζόμαστε μια υπηρεσία που θα ασχολείται με εργασίες που σχετίζονται με φίλους ενός χρήστη. Γι 'αυτό, δημιουργούμε ένα FriendsService τάξη. Στην ιδανική περίπτωση, μια τάξη όπως αυτή θα είναι υπεύθυνη για λειτουργίες όπως η λήψη μιας λίστας φίλων, η προσθήκη ενός νέου φίλου, η αφαίρεση ενός φίλου, η ομαδοποίηση ορισμένων φίλων σε μια κατηγορία κ.λπ. Για απλότητα σε αυτό το σεμινάριο, θα εφαρμόσουμε μόνο μία μέθοδο :

final class FriendsService { private let client = WebClient(baseUrl: 'https://your_server_host/api/v1') @discardableResult func loadFriends(forUser user: User, completion: @escaping ([User]?, ServiceError?) -> ()) -> URLSessionDataTask? { let params: JSON = ['user_id': user.id] return client.load(path: '/friends', method: .get, params: params) { result, error in let dictionaries = result as? [JSON] completion(dictionaries?.flatMap(User.init), error) } } }

Το FriendsService η τάξη περιέχει ένα client ιδιότητα τύπου WebClient. Αρχικοποιείται με τη βασική διεύθυνση URL του απομακρυσμένου διακομιστή που είναι υπεύθυνος για τη διαχείριση φίλων. Όπως αναφέρθηκε προηγουμένως, σε άλλες κατηγορίες υπηρεσιών, μπορούμε να έχουμε μια διαφορετική εμφάνιση WebClient αρχικοποιήθηκε με διαφορετική διεύθυνση URL, εάν είναι απαραίτητο.

υπολογίστε το ποσοστό συμβολαίου από το μισθό

Στην περίπτωση μιας εφαρμογής που λειτουργεί με έναν μόνο διακομιστή, το WebClient μπορεί να δοθεί στην τάξη ένας κατασκευαστής που ξεκινά με τη διεύθυνση URL του διακομιστή:

final class WebClient { // ... init() { self.baseUrl = 'https://your_server_base_url' } // ... }

Το loadFriends μέθοδος, όταν καλείται, προετοιμάζει όλες τις απαραίτητες παραμέτρους και χρησιμοποιεί την παρουσία του FriendService του WebClient για να κάνετε ένα αίτημα API. Αφού λάβει την απάντηση από το διακομιστή μέσω του WebClient, μετατρέπει το αντικείμενο JSON σε User μοντέλα και καλεί το κλείσιμο ολοκλήρωσης μαζί τους ως παράμετρο.

Μια τυπική χρήση του FriendService μπορεί να μοιάζει με το εξής:

let friendsTask: URLSessionDataTask! let activityIndicator: UIActivityIndicatorView! var friends: [User] = [] func friendsButtonTapped() { friendsTask?.cancel() //Cancel previous loading task. activityIndicator.startAnimating() //Show loading indicator friendsTask = FriendsService().loadFriends(forUser: currentUser) {[weak self] friends, error in DispatchQueue.main.async { self?.activityIndicator.stopAnimating() //Stop loading indicators if let error = error { print(error.localizedDescription) //Handle service error } else if let friends = friends { self?.friends = friends //Update friends property self?.updateUI() //Update user interface } } } }

Στο παραπάνω παράδειγμα, υποθέτουμε ότι η συνάρτηση friendsButtonTapped καλείται κάθε φορά που ο χρήστης πατάει ένα κουμπί που προορίζεται να τους δείξει μια λίστα με τους φίλους του στο δίκτυο. Διατηρούμε επίσης μια αναφορά στην εργασία στο friendsTask ιδιότητα, ώστε να μπορούμε να ακυρώσουμε το αίτημα ανά πάσα στιγμή καλώντας στο friendsTask?.cancel().

Αυτό μας επιτρέπει να έχουμε μεγαλύτερο έλεγχο του κύκλου ζωής των εκκρεμών αιτημάτων, επιτρέποντάς μας να τα τερματίσουμε όταν διαπιστώσουμε ότι έχουν γίνει άσχετα.

συμπέρασμα

Σε αυτό το άρθρο, έχω μοιραστεί μια απλή αρχιτεκτονική μιας λειτουργικής μονάδας δικτύωσης για την εφαρμογή σας iOS που είναι τόσο ασήμαντη για εφαρμογή και μπορεί να προσαρμοστεί στις περίπλοκες ανάγκες δικτύωσης των περισσότερων εφαρμογών iOS. Ωστόσο, το βασικό συμπέρασμα από αυτό είναι ότι ένας κατάλληλα σχεδιασμένος πελάτης REST και τα στοιχεία που το συνοδεύουν - τα οποία είναι απομονωμένα από την υπόλοιπη λογική της εφαρμογής σας - μπορούν να βοηθήσουν στη διατήρηση του κώδικα αλληλεπίδρασης πελάτη-διακομιστή της εφαρμογής σας απλό, ακόμη και όταν η ίδια η εφαρμογή γίνεται όλο και πιο περίπλοκη .

Ελπίζω να βρείτε αυτό το άρθρο χρήσιμο στη δημιουργία της επόμενης εφαρμογής σας iOS. Μπορείτε να βρείτε τον πηγαίο κώδικα αυτής της ενότητας δικτύωσης στο GitHub . Ελέγξτε τον κωδικό, πιείτε τον, αλλάξτε τον, παίξτε μαζί του.

Εάν βρείτε κάποια άλλη αρχιτεκτονική προτιμότερη για εσάς και το έργο σας, μοιραστείτε τις λεπτομέρειες στην παρακάτω ενότητα σχολίων.

Σχετίζεται με: Απλοποίηση της χρήσης RESTful API και ανθεκτικότητας δεδομένων σε iOS με το Mantle και το Realm