Μάθετε τις βέλτιστες πρακτικές του iOS δημιουργώντας μια απλή εφαρμογή συνταγών

Πηγή: ChefStep

Πίνακας περιεχομένων

  • Ξεκινώντας
  • Έκδοση Xcode και Swift
  • Ελάχιστη έκδοση iOS για υποστήριξη
  • Οργάνωση του έργου Xcode
  • Δομή της εφαρμογής των συνταγών
  • Συμβάσεις κώδικα
  • Τεκμηρίωση
  • Μαρκάρισμα τμημάτων κώδικα
  • Έλεγχος πηγής
  • Εξαρτήσεις
  • Είσοδος στο έργο
  • API
  • Οθόνη εκκίνησης
  • Εικονίδιο εφαρμογής
  • Κωδικοποίηση με το SwiftLint
  • Πηγή με ασφάλεια τύπου
  • Δείξε μου τον κωδικό
  • Σχεδίαση του μοντέλου
  • Καλύτερη πλοήγηση με το FlowController
  • Αυτόματη διάταξη
  • Αρχιτεκτονική
  • Έλεγχος μαζικής προβολής
  • Έλεγχος πρόσβασης
  • Lazy ιδιότητες
  • Τα αποσπάσματα κώδικα
  • Δικτύωση
  • Πώς να δοκιμάσετε τον κώδικα δικτύου
  • Εφαρμογή προσωρινής μνήμης για υποστήριξη εκτός σύνδεσης
  • Πώς να ελέγξετε την κρυφή μνήμη
  • Τοποθέτηση απομακρυσμένων εικόνων
  • Κάνοντας τη φόρτωση εικόνας πιο βολική για UIImageView
  • Γενική προέλευση δεδομένων για UITableView και UICollectionView
  • Ελεγκτής και προβολή
  • Χειρισμός ευθυνών με τον παιδικό ελεγκτή προβολής
  • Σύνθεση και ένεση εξάρτησης
  • Ασφάλεια μεταφοράς εφαρμογών
  • Μια προσαρμοσμένη προβολή με δυνατότητα κύλισης
  • Προσθήκη λειτουργιών αναζήτησης
  • Κατανόηση του πλαισίου παρουσίασης
  • Αποκλεισμός των ενεργειών αναζήτησης
  • Δοκιμάζοντας την αποκρυπτογράφηση με Αναστρέψιμη προσδοκία
  • Έλεγχος διεπαφής χρήστη με UITests
  • Κύρια προφυλακτήρας νήματος
  • Μέτρηση των επιδόσεων και των θεμάτων
  • Πρωτότυπο με παιδική χαρά
  • Πού να πάτε από εδώ

Ξεκίνησα την ανάπτυξη iOS όταν ανακοινώθηκε το iOS 7. Και έχω μάθει λίγο, μέσω της εργασίας, των συμβουλών από τους συναδέλφους και την κοινότητα iOS.

Σε αυτό το άρθρο, θα ήθελα να μοιραστώ πολλές καλές πρακτικές, λαμβάνοντας το παράδειγμα μιας απλής εφαρμογής συνταγών. Ο πηγαίος κώδικας βρίσκεται στις Συνταγές GitHub.

Η εφαρμογή είναι μια παραδοσιακή εφαρμογή βασικών λεπτομερειών που παρουσιάζει μια λίστα συνταγών μαζί με τις λεπτομερείς πληροφορίες τους.

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

Έχω προσθέσει συνδέσμους σε ορισμένες λέξεις-κλειδιά όπου ένιωθα ότι η περαιτέρω ανάγνωση θα ήταν επωφελής. Έτσι, σίγουρα τα ελέγξουμε. Κάθε ανατροφοδότηση είναι ευπρόσδεκτη.

Ας αρχίσουμε λοιπόν ...

Εδώ είναι μια επισκόπηση υψηλού επιπέδου για το τι θα χτίσετε.

Ξεκινώντας

Ας αποφασίσουμε για το εργαλείο και τις ρυθμίσεις έργου που χρησιμοποιούμε.

Έκδοση Xcode και Swift

Στο WWDC 2018, η Apple παρουσίασε το Xcode 10 με το Swift 4.2. Ωστόσο, τη στιγμή της γραφής, το Xcode 10 είναι ακόμα σε beta 5. Γι 'αυτό ας κολλήσουμε με το σταθερό Xcode 9 και Swift 4.1. Το Xcode 4.2 έχει μερικά δροσερά χαρακτηριστικά - μπορείτε να παίξετε μαζί του μέσα από αυτή την εκπληκτική Παιδική χαρά. Δεν εισάγει τεράστιες αλλαγές από το Swift 4.1, οπότε μπορούμε να ενημερώσουμε εύκολα την εφαρμογή μας στο εγγύς μέλλον, αν χρειαστεί.

Πρέπει να ορίσετε την έκδοση Swift στη ρύθμιση Project αντί για τις ρυθμίσεις προορισμού. Αυτό σημαίνει ότι όλοι οι στόχοι του έργου μοιράζονται την ίδια έκδοση Swift (4.1).

Ελάχιστη έκδοση iOS για υποστήριξη

Από το καλοκαίρι του 2018, το iOS 12 είναι δημόσια beta 5 και δεν μπορούμε να στοχεύουμε το iOS 12 χωρίς το Xcode 10. Σε αυτήν την ανάρτηση, χρησιμοποιούμε το Xcode 9 και το βασικό SDK είναι το iOS 11. Ανάλογα με την απαίτηση και τις βάσεις χρηστών, πρέπει να υποστηρίξετε τις παλιές εκδόσεις iOS. Παρόλο που οι χρήστες του iOS τείνουν να υιοθετούν νέες εκδόσεις iOS ταχύτερα από όσους χρησιμοποιούν το Android, υπάρχουν μερικοί που παραμένουν σε παλιές εκδόσεις. Σύμφωνα με τις συμβουλές για τα μήλα, πρέπει να υποστηρίξουμε τις δύο πιο πρόσφατες εκδόσεις, οι οποίες είναι iOS 10 και iOS 11. Όπως μετρήθηκε από το App Store στις 31 Μαΐου 2018, μόνο το 5% των χρηστών χρησιμοποιούν το iOS 9 και πριν.

Η στόχευση νέων εκδόσεων iOS σημαίνει ότι μπορούμε να εκμεταλλευτούμε τα νέα SDK, τα οποία οι μηχανικοί της Apple βελτιώνονται κάθε χρόνο. Ο ιστότοπος προγραμματιστών της Apple έχει βελτιωμένη προβολή καταγραφής αλλαγών. Τώρα είναι ευκολότερο να δείτε τι έχει προστεθεί ή τροποποιηθεί.

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

Οργάνωση του έργου Xcode

Όταν δημιουργούμε το νέο έργο, επιλέξτε "Συμπεριλάβετε δοκιμές μονάδας" και "Συμπεριλάβετε τις δοκιμές UI", καθώς αποτελεί συνιστώμενη πρακτική η έγκαιρη καταγραφή των δοκιμών. Οι πρόσφατες αλλαγές στο πλαίσιο του XCTest, ειδικά στις δοκιμές UI, κάνουν τη δοκιμή ένα αεράκι και είναι αρκετά σταθερές.

Πριν προσθέσετε νέα αρχεία στο έργο, κάντε μια μικρή παύση και σκεφτείτε τη δομή της εφαρμογής σας. Πώς θέλουμε να οργανώσουμε τα αρχεία; Έχουμε μερικές επιλογές. Μπορούμε να οργανώσουμε τα αρχεία κατά χαρακτηριστικό / ενότητα ή ρόλο / τύπους. Ο καθένας έχει τα υπέρ και τα κατά και θα τα συζητήσω παρακάτω.

Ανά ρόλο / τύπου:

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

Με χαρακτηριστικό / ενότητα

  • Πλεονεκτήματα: Κάνει τα πάντα αρθρωτά και ενθαρρύνει τη σύνθεση.
  • Μειονεκτήματα: Μπορεί να πάει βρώμικο όταν πολλά αρχεία διαφόρων τύπων είναι συνδυασμένα μαζί.

Μείνετε αρθρωτά

Προσωπικά, προσπαθώ να οργανώσω όσο το δυνατόν περισσότερο τον κώδικα μου με χαρακτηριστικά / στοιχεία. Αυτό διευκολύνει τον εντοπισμό σχετικού κώδικα για επίλυση και την ευκολότερη προσθήκη νέων δυνατοτήτων στο μέλλον. Απαντά την ερώτηση "Τι κάνει αυτή η εφαρμογή;" αντί για "Τι είναι αυτό το αρχείο;" Εδώ είναι ένα καλό άρθρο σχετικά με αυτό.

Ένας καλός κανόνας είναι να παραμείνετε συνεπείς, ανεξάρτητα από τη δομή που επιλέγετε.

Δομή της εφαρμογής των συνταγών

Ακολουθεί η δομή εφαρμογής που χρησιμοποιεί η εφαρμογή μας συνταγών:

Πηγή

Περιέχει αρχεία πηγαίου κώδικα, χωρισμένα σε στοιχεία:

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

Βιβλιοθήκη

Περιέχει τα βασικά συστατικά της εφαρμογής μας:

  • Ροή: περιέχει FlowController για τη διαχείριση των ροών
  • Προσαρμογέας: γενική πηγή δεδομένων για UICollectionView
  • Επέκταση: βολικές επεκτάσεις για κοινές λειτουργίες
  • Πρότυπο: Το μοντέλο της εφαρμογής, αναλύθηκε από το JSON

Πόρος

Περιέχει αρχεία πλυσίματος, πόρων και Storyboard.

Συμβάσεις κώδικα

Συμφωνώ με τους περισσότερους οδηγούς στυλ στο raywenderlich / swift-style-guide και github / swift-style-οδηγός. Αυτά είναι απλά και λογικά χρήσιμα σε ένα έργο Swift. Επίσης, ελέγξτε τις επίσημες Οδηγίες Σχεδιασμού API που έγιναν από την ομάδα Swift της Apple σχετικά με τον τρόπο σύνταξης κώδικα Swift.

Όποιος οδηγός στυλ επιλέγετε να ακολουθήσετε, η σαφήνεια του κώδικα πρέπει να είναι ο σημαντικότερος στόχος σας.

Η εσοχή και ο πόλεμος των καρτελών είναι ένα ευαίσθητο θέμα, αλλά και πάλι, εξαρτάται από τη γεύση. Χρησιμοποιώ τέσσερα κενά διαστήματα σε έργα Android και δύο κενά σε iOS και React. Σε αυτήν την εφαρμογή "Συνταγές", ακολουθώ συνεπή και εύκολο στη λογική απόκλιση, την οποία έγραψα εδώ και εδώ.

Τεκμηρίωση

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

Γράψτε ένα σχόλιο τεκμηρίωσης για κάθε δήλωση. Οι παρατηρήσεις που αποκτήθηκαν από την τεκμηρίωση εγγράφων μπορούν να έχουν βαθιά επίδραση στο σχεδιασμό σας, οπότε μην το απενεργοποιήσετε.

Είναι πολύ εύκολο να δημιουργήσετε πρότυπο σχολίων /// σε Xcode με Cmd + Alt + /. Αν σχεδιάζετε να επαναπροσδιορίσετε τον κώδικα σας σε ένα πλαίσιο για να το μοιραστείτε με άλλους στο μέλλον, τα εργαλεία όπως το jazzy μπορούν να δημιουργήσουν τεκμηρίωση, έτσι ώστε να μπορούν να ακολουθήσουν και άλλοι άνθρωποι.

Μαρκάρισμα τμημάτων κώδικα

Η χρήση του MARK μπορεί να σας βοηθήσει να διαχωρίσετε τμήματα του κώδικα. Συγκεντρώνει επίσης λειτουργίες ωραία στη γραμμή πλοήγησης. Μπορείτε επίσης να χρησιμοποιήσετε ομάδες επέκτασης, σχετικές ιδιότητες και μεθόδους.

Για ένα απλό UIViewController μπορούμε να ορίσουμε τις ακόλουθες MARK:

// MARK: - Init
// MARK: - Δείτε τον κύκλο ζωής
// MARK: - Ρύθμιση
// ΣΗΜΑ: - Δράση
// MARK: - Δεδομένα

Έλεγχος πηγής

Το Git είναι ένα δημοφιλές σύστημα ελέγχου πηγής αυτή τη στιγμή. Μπορούμε να χρησιμοποιήσουμε το αρχείο .gitignore του προτύπου από το gitignore.io/api/swift. Υπάρχουν πλεονεκτήματα και μειονεκτήματα κατά τον έλεγχο αρχείων εξαρτήσεων (CocoaPods και Carthage). Εξαρτάται από το σχέδιό σας, αλλά τείνω να μην δεσμευτώ εξαρτήσεις (node_modules, Carthage, Pods) στον έλεγχο πηγής για να μην γεμίσει η βάση κώδικα. Κάνει επίσης πιο εύκολη την αναθεώρηση των αιτημάτων έλξης.

Εάν ελέγχετε ή όχι στον κατάλογο Pods, το Podfile και το Podfile.lock θα πρέπει πάντα να διατηρούνται υπό τον έλεγχο έκδοσης.

Χρησιμοποιώ και το iTerm2 για να εκτελέσω εντολές και Tree Tree για να προβάλλω τα υποκαταστήματα και τη σταδιοποίηση.

Εξαρτήσεις

Έχω χρησιμοποιήσει πλαίσια τρίτων, και επίσης έκανα και συνέβαλα πολύ στην ανοιχτή πηγή. Χρησιμοποιώντας ένα πλαίσιο σας δίνει ώθηση στην αρχή, αλλά μπορεί επίσης να σας περιορίσει πολλά στο μέλλον. Μπορεί να υπάρχουν κάποιες ασήμαντες αλλαγές που είναι πολύ δύσκολο να εργαστούμε. Το ίδιο συμβαίνει και όταν χρησιμοποιείτε SDK. Η προτίμησή μου είναι να επιλέξω ενεργά πλαίσια ανοιχτού κώδικα. Διαβάστε τον πηγαίο κώδικα και ελέγξτε προσεκτικά τα πλαίσια και συμβουλευτείτε την ομάδα σας εάν σκοπεύετε να τα χρησιμοποιήσετε. Λίγο επιπλέον προσοχή δεν προκαλεί κακό.

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

Υπάρχει ένα αρχείο που ονομάζεται .swift έκδοση της τιμής 4.1 στη ρίζα του έργου για να πει στο CocoaPods ότι το έργο αυτό χρησιμοποιεί το Swift 4.1. Αυτό φαίνεται απλό αλλά με πήρε αρκετό χρόνο για να καταλάβω.

Είσοδος στο έργο

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

API

Ο εύκολος τρόπος για να μάθετε τη δικτύωση iOS είναι μέσω δημόσιων δωρεάν υπηρεσιών API. Εδώ χρησιμοποιώ food2fork. Μπορείτε να εγγραφείτε για λογαριασμό στο http://food2fork.com/about/api. Υπάρχουν πολλά άλλα εκπληκτικά API σε αυτό το δημόσιο αποθετήριο API.

Είναι καλό να διατηρείτε τα διαπιστευτήριά σας σε ασφαλές μέρος. Χρησιμοποιώ 1Password για τη δημιουργία και αποθήκευση των κωδικών μου.

Πριν ξεκινήσουμε την κωδικοποίηση, ας παίξουμε με τα API για να δούμε ποια είδη αιτημάτων απαιτούν και ποιες απαντήσεις επιστρέφουν. Χρησιμοποιώ το εργαλείο αϋπνίας για να δοκιμάσω και να αναλύσω τις απαντήσεις API. Είναι ανοικτή πηγή, δωρεάν, και λειτουργεί εξαιρετικά.

Οθόνη εκκίνησης

Η πρώτη εντύπωση είναι σημαντική, έτσι είναι η οθόνη Launch. Ο προτιμώμενος τρόπος είναι να χρησιμοποιήσετε το LaunchScreen.storyboard αντί για μια στατική εικόνα εκκίνησης.

Για να προσθέσετε μια εικόνα εκκίνησης στον Κατάλογο περιουσιακών στοιχείων, ανοίξτε το LaunchScreen.storyboard, προσθέστε το UIImageView και προσαρτήστε το στο άκρο του UIView. Δεν πρέπει να προσαρμόζουμε την εικόνα στην ασφαλή περιοχή, καθώς θέλουμε η εικόνα να είναι πλήρης. Επίσης, καταργήστε την επιλογή οποιωνδήποτε περιθωρίων στους περιορισμούς αυτόματης διάταξης. Ρυθμίστε το ContentMode του UIImageView ως Aspect Fill έτσι ώστε να εκτείνεται με τη σωστή αναλογία διαστάσεων.

Ρυθμίστε τη διάταξη στο LaunchScreen.

Εικονίδιο εφαρμογής

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

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

Πρέπει να σχεδιάσουμε τετραγωνικές εικόνες μεγέθους μεγαλύτερου από 1024 x 1024, έτσι ώστε να είναι δυνατή η μείωση των εικόνων σε μικρότερες εικόνες. Μπορείτε να το κάνετε αυτό με το χέρι, το σενάριο ή να χρησιμοποιήσετε αυτήν τη μικρή εφαρμογή IconGenerator που έκανα.

Η εφαρμογή IconGenerator μπορεί να δημιουργήσει εικονίδια για iOS σε εφαρμογές iPhone, iPad, macOS και watchOS. Το αποτέλεσμα είναι το appIcon.appiconset που μπορούμε να μεταφέρουμε κατευθείαν στον κατάλογο περιουσιακών στοιχείων. Ο κατάλογος περιουσιακών στοιχείων είναι ο τρόπος που μπορείτε να χρησιμοποιήσετε τα σύγχρονα έργα Xcode.

Κωδικοποίηση με το SwiftLint

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

Για να το εγκαταστήσετε, προσθέστε pod 'SwiftLint', '~> 0.25' στο Podfile. Είναι επίσης μια καλή πρακτική να καθορίσετε την έκδοση των εξαρτήσεων, έτσι ώστε η pod εγκατάσταση να μην ενημερωθεί τυχαία σε μια σημαντική έκδοση που θα μπορούσε να σπάσει την εφαρμογή σας. Στη συνέχεια, προσθέστε ένα αρχείο .swiftlint.yml με τη διαμόρφωση που προτιμάτε. Μια παραδειγματική διαμόρφωση μπορεί να βρεθεί εδώ.

Τέλος, προσθέστε μια νέα φράση δέσμης ενεργειών για να εκτελέσετε το swiftlint μετά τη σύνταξη.

Πηγή με ασφάλεια τύπου

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

imageView.image = R.image.notFound ()

Δείξε μου τον κωδικό

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

Σχεδίαση του μοντέλου

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

Θα πρέπει να δοκιμάζουμε για κάθε μοντέλο της εφαρμογής. Στην ιδανική περίπτωση, χρειαζόμαστε αυτοματοποιημένη δοκιμή μοντέλων από απαντήσεις API σε περίπτωση που το μοντέλο έχει αλλάξει από το backend.

Ξεκινώντας από το Swift 4.0, μπορούμε να συμμορφωνόμαστε με το μοντέλο μας στο Codable για εύκολη σειριοποίηση από και προς το JSON. Το μοντέλο μας πρέπει να είναι αμετάβλητο:

struct Συνταγή: Codable {
  αφήστε τον εκδότη: String
  ας url: URL
  αφήστε το sourceUrl: String
  ας id: String
  αφήστε τον τίτλο: String
  ας το imageUrl: String
  αφήστε το socialRank: Διπλό
  αφήστε τον publisherUrl: URL
enum CodingKeys: String, CodingKey {
    εκδότη υπόθεση
    περίπτωση url = "f2f_url"
    περίπτωση sourceUrl = "source_url"
    περίπτωση id = "recipe_id"
    τίτλο της υπόθεσης
    περίπτωση imageUrl = "image_url"
    περίπτωση socialRank = "social_rank"
    case publisherUrl = "publisher_url"
  }}
}}

Μπορούμε να χρησιμοποιήσουμε κάποια δοκιμαστικά πλαίσια εάν σας αρέσει η φανταστική σύνταξη ή το στυλ RSpec. Ορισμένα πλαίσια δοκιμών τρίτων ενδέχεται να έχουν προβλήματα. Βρίσκω το XCTest αρκετά καλό.

εισαγωγή XCTest
@testable imports Συνταγές
τάξη Δοκιμές Συνταγές: XCTestCase {
  funk testParsing () πετάει {
    ας json: [String: Any] = [
      "εκδότης": "Δύο μπιζέλια και το Pod τους",
      "f2f_url": "http://food2fork.com/view/975e33",
      "τίτλος": "Δεν ψήνουν τα σοκολατένια μπισκότα"
      "source_url": "http://www.twopeasandtheirpod.com/no-bake-chocolate-peanut-butter-pretzel-cookies/",
      "recipe_id": "975e33",
      "image_url": "http://static.food2fork.com/NoBakeChocolatePeanutButterPretzelCookies44147.jpg",
      "social_rank": 99.99999999999974,
      "publisher_url": "http://www.twopeasandtheirpod.com"
    ]
αφήστε δεδομένα = δοκιμάστε JSONSerialization.data (μεJSONObject: json, επιλογές: [])
    ας αποκωδικοποιητής = JSONDecoder ()
    ας συνταγή = δοκιμάστε decoder.decode (Recipe.self, από: δεδομένα)
XCTAssertEqual (recipe.title, "Καβουρδισμένα μπισκότα καραμελών χωρίς σοκολάτα")
    XCTAssertEqual (recipe.id, "975e33")
    XCTAssertEqual (recipe.url, URL (συμβολοσειρά: "http://food2fork.com/view/975e33")!)
  }}
}}

Καλύτερη πλοήγηση με το FlowController

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

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

Υπάρχει το AppFlowController που διαχειρίζεται την αλλαγή του rootViewController. Προς το παρόν αρχίζει το RecipeFlowController.

παράθυρο = UIWindow (πλαίσιο: UIScreen.main.bounds)
παράθυρο; .rootViewController = appFlowController
παράθυρο; .makeKeyAndVisible ()
appFlowController.start ()

Το RecipeFlowController διαχειρίζεται (στην πραγματικότητα είναι) το UINavigationController, το οποίο χειρίζεται το σπρώξιμο του HomeViewController, του RecipesDetailViewController, του SafariViewController.

τελική κλάση RecipeFlowController: UINavigationController {
  /// Ξεκινήστε τη ροή
  func start () {
    let service = ΣυνταγέςΥπηρεσία (δικτύωση: NetworkService ())
    αφήστε τον ελεγκτή = HomeViewController (recipesService: service)
    viewControllers = [ελεγκτής]
    controller.select = {[αυτοδύναμη] συνταγή στο
      αυτο; .startDetail (συνταγή: συνταγή)
    }}
  }}
ιδιωτική λειτουργία startDetail (συνταγή: συνταγή) {}
  ιδιωτική λειτουργία startWeb (url: URL) {}
}}

Το UIViewController μπορεί να χρησιμοποιήσει πληρεξούσιο ή κλείσιμο για να ειδοποιήσει το FlowController για αλλαγές ή επόμενες οθόνες στη ροή. Για τον εκπρόσωπο μπορεί να υπάρχει ανάγκη να ελέγξετε πότε υπάρχουν δύο περιπτώσεις της ίδιας τάξης. Εδώ χρησιμοποιούμε κλείσιμο για απλότητα.

Αυτόματη διάταξη

Η αυτόματη διάταξη ήταν γύρω από το iOS 5, γίνεται όλο και καλύτερα κάθε χρόνο. Παρόλο που μερικοί άνθρωποι εξακολουθούν να έχουν κάποιο πρόβλημα με αυτό, κυρίως λόγω της σύγχυσης στρεβλών περιορισμών και επιδόσεων, αλλά προσωπικά, θεωρώ ότι το Auto Layout είναι αρκετά καλό.

Προσπαθώ να χρησιμοποιήσω το Auto Layout όσο το δυνατόν περισσότερο για να φτιάξω ένα προσαρμοστικό περιβάλλον χρήστη. Μπορούμε να χρησιμοποιήσουμε βιβλιοθήκες όπως το Anchors για να κάνουμε δηλωτική και γρήγορη αυτόματη διάταξη. Ωστόσο, σε αυτήν την εφαρμογή, θα χρησιμοποιήσουμε μόνο το NSLayoutAnchor, καθώς είναι από το iOS 9. Ο παρακάτω κώδικας εμπνέεται από τον περιορισμό. Θυμηθείτε ότι η Αυτόματη Διάταξη στην απλούστερη μορφή της περιλαμβάνει την εναλλαγή των μεταφράσεωνAutoresizingMaskIntoConstraints και την ενεργοποίηση των ενεργών περιορισμών.

επέκταση NSLayoutConstraint {
  static func activate (_ περιορισμούς: [NSLayoutConstraint]) {
    περιορισμούς.Για κάθε {
      ($ 0.firstItem ως "UIView)" μεταφράζειAutoresizingMaskIntoConstraints = false
      $ 0.isActive = true
    }}
  }}
}}

Υπάρχουν στην πραγματικότητα πολλοί άλλοι μηχανισμοί διάταξης στο GitHub. Για να αποκτήσετε μια ιδέα για το ποια θα ήταν κατάλληλη για χρήση, ανατρέξτε στο LayoutFrameworkBenchmark.

Αρχιτεκτονική

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

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

  • εγκλωβίζουν τι διαφέρει
  • ευνοούν τη σύνθεση πάνω από την κληρονομιά
  • πρόγραμμα για τη διεπαφή, όχι για την υλοποίηση

Μετά το παιχνίδι με πολλές διαφορετικές αρχιτεκτονικές, με και χωρίς Rx, ανακάλυψα ότι το απλό MVC είναι αρκετά καλό. Σε αυτό το απλό έργο, υπάρχει μόνο UIViewController με λογική εγκλωβισμένη σε βοηθητικές κλάσεις υπηρεσιών,

Έλεγχος μαζικής προβολής

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

Στην εφαρμογή συνταγών που χρησιμοποιώ,

  • Υπηρεσία για την ένεση στον ελεγκτή προβολής για να εκτελέσετε μια μόνο εργασία
  • Γενική προβολή για να μετακινήσετε την προβολή και να ελέγξετε τη δήλωση στο επίπεδο προβολής
  • Ο ελεγκτής προβολής παιδιού για να συνθέσει ελεγκτές προβολής παιδιών για την κατασκευή περισσότερων λειτουργιών

Εδώ είναι ένα πολύ καλό άρθρο με 8 συμβουλές για την αφαίρεση μεγάλων ελεγκτών.

Έλεγχος πρόσβασης

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

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

Δηλώστε τις ιδιότητες ως τελικές όπου είναι δυνατόν.

τελική κλάση HomeViewController: UIViewController {}

Δηλώστε τις ιδιότητες ως ιδιωτικές ή τουλάχιστον ιδιωτικές (οριστεί).

τελική κλάσηRecipeDetailView: UIView {
  ιδιωτικό let scrollableView = ScrollableView ()
  ιδιωτικό (σύνολο) τεμπέλης var imageView: UIImageView = self.makeImageView ()
}}

Lazy ιδιότητες

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

τελική τάξη RecipeCell: UICollectionViewCell {
  ιδιωτικό (σύνολο) τεμπέλης var containerView: UIView = {
    ας δούμε = UIView ()
    view.clipsToBounds = true
    view.layer.cornerRadius = 5
    view.backgroundColor = Χρώμα.παράθυρο με ΑlphaComponent (0.4)
προβολή επιστροφής
  } ()
}}

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

τελική κλάσηRecipeDetailView: UIView {
  ιδιωτικό (σύνολο) τεμπέλης var imageView: UIImageView = self.makeImageView ()
ιδιωτική λειτουργία makeImageView () -> UIImageView {
    αφήστε το imageView = UIImageView ()
    imageView.contentMode = .scaleAspectFill
    imageView.clipsToBounds = true
    επιστροφή imageView
  }}
}}

Αυτό συμμορφώνεται επίσης με τις συμβουλές του Strive for Fluent Usage.

Αρχίστε ονόματα των μεθόδων του εργοστασίου με "make", για παράδειγμα, x.makeIterator ().

Τα αποσπάσματα κώδικα

Ορισμένη σύνταξη κώδικα είναι δύσκολο να θυμηθεί. Εξετάστε το ενδεχόμενο να χρησιμοποιήσετε αποσπάσματα κώδικα για αυτόματη δημιουργία κώδικα. Αυτό υποστηρίζεται από το Xcode και είναι ο προτιμώμενος τρόπος από τους μηχανικούς της Apple όταν κάνουν demo.

αν #available (iOS 11, *) {
  viewController.navigationItem.searchController = searchController
  viewController.navigationItem.hidesSearchBarWhenScrolling = false
} else {
  viewController.navigationItem.titleView = searchController.searchBar
}}

Έκανα repo με μερικά χρήσιμα αποσπάσματα Swift που πολλοί απολαμβάνουν τη χρήση.

Δικτύωση

Η δικτύωση στο Swift είναι ένα είδος λύσης. Υπάρχουν κουραστικές και επιρρεπείς σε σφάλματα εργασίες όπως η ανάλυση των απαντήσεων HTTP, ο χειρισμός ουρών αιτήσεων, ο χειρισμός ερωτημάτων παραμέτρων. Έχω δει σφάλματα σχετικά με τις αιτήσεις PATCH, με χαμηλότερες μεθόδους HTTP, ... Μπορούμε μόνο να χρησιμοποιήσουμε το Alamofire. Δεν χρειάζεται να χάσετε χρόνο εδώ.

Για αυτή την εφαρμογή, δεδομένου ότι είναι απλό και για να αποφύγετε τις περιττές εξαρτήσεις. Μπορούμε να χρησιμοποιήσουμε απευθείας το URLSession. Ένας πόρος συνήθως περιέχει τη διεύθυνση URL, τη διαδρομή, τις παραμέτρους και τη μέθοδο HTTP.

struct Resource {
  ας url: URL
  ας μονοπάτι: String;
  αφήστε το httpMethod: String
  αφήστε παραμέτρους: [String: String]
}}

Μια απλή υπηρεσία δικτύου μπορεί απλά να αναλύσει τον πόρο στο URLRequest και λέει στο URLSession να εκτελέσει

την τελευταία τάξη NetworkService: Δικτύωση {
  @discardableResult func fetch (πόρος: Πόροι, ολοκλήρωση: @escaping (Data?) -> άκυρο) -> URLSessionTask; {
    φρουρός ας ζητήσει = makeRequest (πηγή: resource) else {
      ολοκλήρωση (μηδέν)
      επιστρέψτε στο μηδέν
    }}
ας task = session.dataTask (με: request, completionHandler: {data, _, error in
      φύλακας ας δεδομένα = δεδομένα, σφάλμα == κανένα άλλο {
        ολοκλήρωση (μηδέν)
        ΕΠΙΣΤΡΟΦΗ
      }}
ολοκλήρωση (δεδομένα)
    })
task.resume ()
    επιστροφή
  }}
}}

Χρησιμοποιήστε ένεση εξάρτησης. Να επιτρέπεται στον καλούντα να καθορίζει τη διεύθυνση URLSessionConfiguration. Εδώ χρησιμοποιούμε την προεπιλεγμένη παράμετρο Swift για να παρέχουμε την πιο κοινή επιλογή.

init (διαμόρφωση: URLSessionConfiguration = URLSessionConfiguration.default) {
  self.session = URLSession (διαμόρφωση: διαμόρφωση)
}}

Χρησιμοποιώ επίσης το URLQueryItem που ήταν από το iOS 8. Κάνει τις παραμέτρους σύνταξης σε ερωτηματικά στοιχεία ωραία και λιγότερο κουραστική.

Πώς να δοκιμάσετε τον κώδικα δικτύου

Μπορούμε να χρησιμοποιήσουμε το URLProtocol και το URLCache για να προσθέσουμε ένα στέλεχος για απαντήσεις δικτύου ή μπορούμε να χρησιμοποιήσουμε πλαίσια όπως το Mockingjay που swizzles URLSessionConfiguration.

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

πρωτόκολλο δικτύωσης {
  @discardableResult func fetch (πόρος: Πόροι, ολοκλήρωση: @escaping (Data?) -> άκυρο) -> URLSessionTask;
}}
τελική τάξη MockNetworkService: Δικτύωση {
  αφήστε δεδομένα: Δεδομένα
  init (fileName: String) {
    let bundle = Bundle (για: MockNetworkService.self)
    ας url = bundle.url (γιαResource: όνομα_αρχείου, μεΕξέταση: "json")!
    self.data = δοκιμάστε! Δεδομένα (contentsOf: url)
  }}
func fetch (πόρος: Πόρος, ολοκλήρωση: @escaping (Δεδομένα;) -> Άκυρο) -> URLSessionTask; {
    ολοκλήρωση (δεδομένα)
    επιστρέψτε στο μηδέν
  }}
}}

Εφαρμογή προσωρινής μνήμης για υποστήριξη εκτός σύνδεσης

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

Σε αυτή την απλή εφαρμογή, μια επαρκής κλάση υπηρεσίας προσωρινής μνήμης είναι αρκετή και ένας καλός τρόπος για να μάθετε πώς λειτουργεί το caching. Τα πάντα στο Swift μπορούν να μετατραπούν σε δεδομένα, έτσι μπορούμε απλά να αποθηκεύσουμε δεδομένα στην κρυφή μνήμη. Το Swift 4 Codable μπορεί να σειριοποιήσει αντικείμενο σε δεδομένα.

Ο παρακάτω κώδικας δείχνει πώς να χρησιμοποιήσετε το FileManager για τη μνήμη cache του δίσκου.

/// Αποθήκευση και φόρτωση δεδομένων στη μνήμη και στη μνήμη cache του δίσκου
τελική κλάση CacheService {
/// Για λήψη ή φόρτωση δεδομένων στη μνήμη
  ιδιωτική αφή μνήμη = NSCache  ()
/// Ο url διαδρομής που περιέχει αποθηκευμένα αρχεία (αρχεία mp3 και αρχεία εικόνας)
  ιδιωτική αφήσετε το DiskPath: URL
/// Για τον έλεγχο του αρχείου ή του καταλόγου υπάρχει σε μια καθορισμένη διαδρομή
  ιδιωτική αφήστε FileManager: FileManager
/// Βεβαιωθείτε ότι όλες οι λειτουργίες εκτελούνται σειριακά
  private let serialQueue = DispatchQueue (ετικέτα: "Συνταγές")
init (αρχείοManager: FileManager = FileManager.default) {
    self.fileManager = fileManager
    κάνω {
      αφήστε documentDirectory = δοκιμάστε το fileManager.url (
        για: .documentDirectory,
        στο: .userDomainMask,
        κατάλληληΓια: μηδέν,
        δημιουργία: αληθής
      )
      diskPath = documentDirectory.appendingPathComponent ("Συνταγές")
      δοκιμάστε createDirectoryIfNeeded ()
    } σύλληψη {
      μοιραίο λάθος()
    }}
  }}
func save (δεδομένα: δεδομένα, κλειδί: συμβολοσειρά, ολοκλήρωση: (() -> άκυρο); = μηδέν) {
    κλειδί = MD5 (πλήκτρο)
serialQueue.async {
      self.memory.setObject (δεδομένα ως NSData, γιαkey: κλειδί ως NSString)
      κάνω {
        δοκιμάστε το data.write (σε: self.filePath (κλειδί: πλήκτρο))
        ολοκλήρωση?()
      } σύλληψη {
        εκτύπωση (σφάλμα)
      }}
    }}
  }}
}}

Για να αποφύγουμε τα παραμορφωμένα και πολύ μακρά ονόματα αρχείων, μπορούμε να τα κατακερματιστούμε. Χρησιμοποιώ το MD5 από το SwiftHash, το οποίο δίνει νεκρό απλό κλειδί χρήσης = MD5 (κλειδί).

Πώς να ελέγξετε την κρυφή μνήμη

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

class CacheServiceTests: XCTestCase {
  ας υπηρεσία = CacheService ()
override func setUp () {
    super.setUp ()
προσπαθήστε? service.clear ()
  }}
func testClear () {
    ας περιμένουμε = self.expectation (description: #function)
    αφήστε string = "Γεια σας κόσμος"
    αφήστε τα δεδομένα = string.data (χρησιμοποιώντας: .utf8)!
service.save (δεδομένα: δεδομένα, πλήκτρο: "κλειδί", ολοκλήρωση: {
      προσπαθήστε? self.service.clear ()
      self.service.load (πλήκτρο: "κλειδί", ολοκλήρωση: {
        XCTAssertNil ($ 0)
        expectation.fulfill ()
      })
    })
περιμένετε (για: [προσδοκία], χρονικό όριο: 1)
  }}
}}

Τοποθέτηση απομακρυσμένων εικόνων

Επίσης, συμβάλλω στο Imaginary, οπότε γνωρίζω λίγο το πώς λειτουργεί. Για απομακρυσμένες εικόνες, πρέπει να το κατεβάσουμε και να το αποθηκεύσουμε στο cache και το κλειδί της κρυφής μνήμης είναι συνήθως η διεύθυνση URL της απομακρυσμένης εικόνας.

Στην εφαρμογή μας recipese, ας φτιάξουμε ένα απλό ImageService με βάση το NetworkService και το CacheService. Βασικά μια εικόνα είναι απλώς ένας πόρος δικτύου που κατεβάζουμε και αποθηκεύουμε. Προτιμούμε τη σύνθεση, ώστε να συμπεριλάβουμε το NetworkService και το CacheService στη ImageService.

/// Ελέγξτε την τοπική προσωρινή μνήμη και μεταφέρετε την απομακρυσμένη εικόνα
τελική κλάση ImageService {
private networkService: Δικτύωση
  ιδιωτική εκχώρηση cacheService: CacheService
  ιδιωτική εργασία var: URLSessionTask;
init (networkService: Δικτύωση, cacheService: CacheService) {
    self.networkService = networkService
    self.cacheService = cacheService
  }}
}}

Συνήθως έχουμε κύτταρα UICollectionViewand UITableView με UIImageView. Και επειδή τα κύτταρα επαναχρησιμοποιούνται, πρέπει να ακυρώσουμε οποιαδήποτε υπάρχουσα εργασία αίτησης προτού υποβάλετε νέα αίτηση.

func fetch (url: URL, ολοκλήρωση: @escaping (UIImage?) -> άκυρο) {
  // Ακύρωση υπάρχουσας εργασίας εάν υπάρχει
  task? .cancel ()
// Δοκιμάστε το φορτίο από την προσωρινή μνήμη
  cacheService.load (κλειδί: url.absoluteString, ολοκλήρωση: {[weak self] cachedData στο
    εάν αφήσουμε data = cachedData, αφήστε image = UIImage (δεδομένα: δεδομένα) {
      DispatchQueue.main.async {
        ολοκλήρωση (εικόνα)
      }}
    } else {
      // Δοκιμάστε να ζητήσετε από το δίκτυο
      ας πηγή = Πόρος (url: url)
      εαυτού; .task = self; .networkService.fetch (πόρος: πόρος, ολοκλήρωση: {networkData in
        εάν αφήσουμε data = networkData, αφήστε image = UIImage (δεδομένα: δεδομένα) {
          // Αποθήκευση στη μνήμη cache
          αυτο ;. cacheService.save (δεδομένα: δεδομένα, κλειδί: url.absoluteString)
          DispatchQueue.main.async {
            ολοκλήρωση (εικόνα)
          }}
        } else {
          print ("Σφάλμα φόρτωσης εικόνας στο \ (url)")
        }}
      })
εαυτό; .task; .resume ()
    }}
  })
}}

Κάνοντας τη φόρτωση εικόνας πιο βολική για UIImageView

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

επέκταση UIImageView {
  func setImage (url: URL, placeholder: UIImage? = μηδέν) {
    αν imageService == μηδέν {
      imageService = ImageService (networkService: NetworkService (), cacheService: CacheService ())
    }}
self.image = σύμβολο κράτησης θέσης
    (url: url, ολοκλήρωση: {[ασθενής εαυτόν] εικόνα στο
      εαυτό; .image = εικόνα
    })
  }}
ιδιωτικό var imageService: ImageService; {
    πάρτε {
      να επιστρέψετε το objc_getAssociatedObject (self, & AssociateKey.imageService) ως; ImageService
    }}
    set {
      objc_setAssociatedObject (
        εαυτός,
        & AssociateKey.imageService,
        newValue,
        objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC
      )
    }}
  }}
}}

Γενική προέλευση δεδομένων για UITableView και UICollectionView

Χρησιμοποιούμε UITableView και UICollectionView σχεδόν σε κάθε εφαρμογή και σχεδόν εκτελούμε το ίδιο πράγμα επανειλημμένα.

  • εμφάνιση ελέγχου ανανέωσης κατά τη φόρτωση
  • λίστα επαναφοράς σε περίπτωση δεδομένων
  • δείχνουν σφάλμα σε περίπτωση βλάβης.

Υπάρχουν πολλά περιτυλίγματα γύρω από το UITableView και UIClelection. Κάθε ένας προσθέτει ένα άλλο στρώμα αφαίρεσης, το οποίο μας δίνει περισσότερη ισχύ αλλά εφαρμόζει περιορισμούς ταυτόχρονα.

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

Χρησιμοποιώ επίσης Upstream με βάση αυτή την ιδέα. Είναι δύσκολο να αναδιπλωθεί γύρω από UITableView και UICollectionView, όσες φορές είναι συγκεκριμένη για την εφαρμογή, οπότε αρκεί ένα λεπτό περιτύλιγμα όπως ο προσαρμογέας.

τελικός προσαρμογέας κατηγορίας : NSObject,
UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
  τα στοιχεία var: [T] = []
  var configure: ((Τ, κυψέλη) -> άκυρο);
  var select: ((T) -> άκυρο);
  var cellHeight: CGFloat = 60
}}

Ελεγκτής και προβολή

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

/// Χρησιμοποιείται για τον διαχωρισμό μεταξύ ελεγκτή και προβολής
κατηγορίας BaseController : UIViewController {
  αφήστε root = T ()
αντικατάσταση func loadView () {
    view = root
  }}
}}
τελική κλάσηRecipeDetailViewController: BaseController  {}

Χειρισμός ευθυνών με τον παιδικό ελεγκτή προβολής

Ο περιέκτης του ελεγκτή προβολής είναι μια ισχυρή ιδέα. Κάθε ελεγκτής προβολής έχει ένα διαχωρισμό ανησυχίας και μπορεί να συντίθεται μαζί για να δημιουργήσει προηγμένα χαρακτηριστικά. Έχω χρησιμοποιήσει το RecipeListViewController για να διαχειριστώ το UIClelectionView και να παρουσιάσω μια λίστα συνταγών.

τελική κλάση RecipeListViewController: UIViewController {
  ιδιωτική συλλογή varView: UICollectionView!
  ας προσαρμογή = προσαρμογέας  ()
  ιδιωτική αφήστε emptyView = EmptyView (κείμενο: "Δεν βρέθηκαν συνταγές!")
}}

Υπάρχει το HomeViewController που ενσωματώνει αυτόν τον RecipeListViewController

/// Εμφάνιση μιας λίστας συνταγών
τελική κλάση HomeViewController: UIViewController {
/// Όταν μια συνταγή επιλέγει
  var select: ((Συνταγή) -> Άκυρο);
ιδιωτικό var refreshControl = UIRefreshControl ()
  ιδιωτικές ας συνταγέςΥπηρεσία: ΣυνταγέςΥπηρεσία
  ιδιωτικό ας αναζήτησηςComponent: SearchComponent
  private let recipeListViewController = RecipeListViewController ()
}}

Σύνθεση και ένεση εξάρτησης

Προσπαθώ να δημιουργήσω στοιχεία και να συνθέσω κώδικα όποτε μπορώ. Βλέπουμε ότι το ImageService χρησιμοποιεί το NetworkService και το CacheService, και το RecipeDetailViewController κάνει χρήση της υπηρεσίας Recipe και RecipesService

Στην ιδανική περίπτωση, τα αντικείμενα δεν πρέπει να δημιουργούν εξαρτήσεις μόνοι τους. Οι εξαρτήσεις πρέπει να δημιουργούνται εκτός και να περνούν από τη ρίζα. Στην εφαρμογή μας η ρίζα είναι AppDelegate και AppFlowController, έτσι ώστε οι εξάρσεις να ξεκινούν από εδώ.

Ασφάλεια μεταφοράς εφαρμογών

Από το iOS 9, όλες οι εφαρμογές πρέπει να υιοθετήσουν Ασφάλεια Μεταφοράς App

Η Ασφάλεια Μεταφοράς App (ATS) επιβάλλει τις βέλτιστες πρακτικές στις ασφαλείς συνδέσεις μεταξύ μιας εφαρμογής και της πίσω πλευράς της. Το ATS αποτρέπει τυχαία αποκάλυψη, παρέχει ασφαλή συμπεριφορά κατά λάθος και είναι εύκολο να υιοθετηθεί. είναι επίσης προεπιλεγμένη στο iOS 9 και το OS X v10.11. Θα πρέπει να υιοθετήσετε το ATS το συντομότερο δυνατόν, ανεξάρτητα από το αν δημιουργείτε μια νέα εφαρμογή ή ενημερώνετε μια υπάρχουσα.

Στην εφαρμογή μας, ορισμένες εικόνες αποκτώνται μέσω σύνδεσης HTTP. Πρέπει να το αποκλείσουμε από τον κανόνα ασφαλείας, αλλά μόνο για αυτόν τον τομέα.

 NSAppTransportSecurity 

   NSExceptionDomains 
  
     food2fork.com 
    
       NSIncludesSubdomains 
      
       NSExceptionAllowsInsecureHTTPLoads 
      
    
  

Μια προσαρμοσμένη προβολή με δυνατότητα κύλισης

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

/// Κάθετη προβολή διάταξης με χρήση της αυτόματης διάταξης στο UIScrollView
τελική κλάση ScrollableView: UIView {
  ιδιωτικό let scrollView = UIScrollView ()
  ιδιωτικό αφήστε contentView = UIView ()
override init (πλαίσιο: CGRect) {
    super.init (πλαίσιο: πλαίσιο)
scrollView.showsHorizontalScrollIndicator = false
    scrollView.alwaysBounceHorizontal = false
    addSubview (scrollView)
scrollView.addSubview (contentView)
NSLayoutConstraint.activate ([
      scrollView.topAnchor.constraint (equalTo: topAnchor),
      scrollView.bottomAnchor.constraint (equalTo: bottomAnchor),
      scrollView.leftAnchor.constraint (equalTo: leftAnchor),
      scrollView.rightAnchor.constraint (equalTo: rightAnchor),
contentView.topAnchor.constraint (equalTo: scrollView.topAnchor),
      contentView.bottomAnchor.constraint (equalTo: scrollView.bottomAnchor),
      contentView.leftAnchor.constraint (equalTo: leftAnchor),
      contentView.rightAnchor.constraint (equalTo: rightAnchor)
    ])
  }}
}}

Τοποθετούμε το UIScrollView στις άκρες. Προσθέτουμε το αριστερό και δεξιό άγκυρα contentView στον εαυτό μας, ενώ ταυτόχρονα συνδέουμε την άνω και κάτω άγκυρα περιεχομένου με το UIScrollView.

Οι προβολές στο εσωτερικό του περιεχομένου έχουν επάνω και κάτω περιορισμούς, οπότε όταν επεκτείνονται, επεκτείνονται και στο contentView. Το UIScrollView χρησιμοποιεί πληροφορίες Auto Layout από αυτό το contentView για να καθορίσει το contentSize του. Εδώ είναι ο τρόπος με τον οποίο το ScrollableView χρησιμοποιείται στο RecipeDetailView.

scrollableView.setup (ζεύγη: [
  ScrollableView.Pair (προβολή: imageView, ένθετο: UIEdgeInsets (κορυφή: 8, αριστερά: 0, κάτω: 0, δεξιά: 0)
  ScrollableView.Pair (προβολή: ingredientHeaderView, ένθετο: UIEdgeInsets (κορυφή: 8, αριστερά: 0, κάτω: 0, δεξιά: 0)
  ScrollableView.Pair (προβολή: ingredientLabel, ένθετο: UIEdgeInsets (κορυφή: 4, αριστερά: 8, κάτω: 0, δεξιά: 0)
  ScrollableView.Pair (προβολή: infoHeaderView, ένθετο: UIEdgeInsets (αρχή: 4, αριστερά: 0, κάτω: 0, δεξιά: 0)
  ScrollableView.Pair (προβολή: instructionButton, ένθετο: UIEdgeInsets (κορυφή: 8, αριστερά: 20, κάτω: 0, δεξιά: 20)
  ScrollableView.Pair (προβολή: originalButton, ένθετο: UIEdgeInsets (κορυφή: 8, αριστερά: 20, κάτω: 0, δεξιά: 20)
  ScrollableView.Pair (προβολή: infoView, ένθετο: UIEdgeInsets (κορυφή: 16, αριστερά: 0, κάτω: 20, δεξιά: 0))
])

Προσθήκη λειτουργιών αναζήτησης

Από το iOS 8 και εξής, μπορούμε να χρησιμοποιήσουμε το UISearchController για να έχουμε μια προεπιλεγμένη εμπειρία αναζήτησης με τη γραμμή αναζήτησης και τον ελεγκτή αποτελεσμάτων. Θα ενσωματώσουμε τη λειτουργία αναζήτησης στο SearchComponent έτσι ώστε να είναι pluggable.

τελική κλάση SearchComponent: NSObject, UISearchResultsUpdating, UISearchBarDelegate {
  ας recipesService: ΣυνταγέςService
  αφήστε το searchController: UISearchController
  ας recipeListViewController = RecipeListViewController ()
}}

Ξεκινώντας από το iOS 11, υπάρχει μια ιδιότητα που ονομάζεται searchController στο UINavigationItem, που διευκολύνει την εμφάνιση της γραμμής αναζήτησης στη γραμμή πλοήγησης.

func add (στο viewController: UIViewController) {
  αν #available (iOS 11, *) {
    viewController.navigationItem.searchController = searchController
    viewController.navigationItem.hidesSearchBarWhenScrolling = false
  } else {
    viewController.navigationItem.titleView = searchController.searchBar
  }}
viewController.definesPresentationContext = true
}}

Σε αυτήν την εφαρμογή, πρέπει να απενεργοποιήσουμε την hidesNavigationBarDuringPresentation για τώρα, καθώς είναι αρκετά buggy. Ας ελπίσουμε ότι θα επιλυθεί σε μελλοντικές ενημερώσεις iOS.

Κατανόηση του πλαισίου παρουσίασης

Η κατανόηση του περιβάλλοντος παρουσίασης είναι ζωτικής σημασίας για την παρουσίαση του ελεγκτή προβολής Στην αναζήτηση, χρησιμοποιούμε το searchResultsController.

self.searchController = UISearchController (αναζήτησηResultsController: recipeListViewController)

Πρέπει να χρησιμοποιήσουμε definesPresentationContext στον ελεγκτή προβολής πηγής (ο ελεγκτής προβολής στον οποίο προσθέτουμε τη γραμμή αναζήτησης). Χωρίς αυτό θα έχουμε την αναζήτησηResultsController να παρουσιαστεί σε πλήρη οθόνη !!!

Όταν χρησιμοποιείτε το τρέχον στυλ Context ή overCurrentContext για να παρουσιάσετε έναν ελεγκτή προβολής, αυτή η ιδιότητα ελέγχει τον υπάρχοντα ελεγκτή προβολής στην ιεραρχία του ελεγκτή προβολής σας που καλύπτεται από το νέο περιεχόμενο. Όταν εμφανιστεί μια παρουσίαση που βασίζεται σε περιβάλλον, το UIKit ξεκινά από τον ελεγκτή προβολής και παρουσιάζει την ιεραρχία του ελεγκτή προβολής. Αν εντοπίσει έναν ελεγκτή προβολής του οποίου η τιμή για αυτήν την ιδιότητα είναι αληθινή, ζητά από τον ελεγκτή προβολής να παρουσιάσει τον νέο ελεγκτή προβολής. Εάν ο ελεγκτής προβολής δεν ορίζει το περιβάλλον παρουσίασης, η UIKit ζητά από τον ελεγκτή ριζικής προβολής του παραθύρου να χειριστεί την παρουσίαση.
Η προεπιλεγμένη τιμή για αυτήν την ιδιότητα είναι ψευδής. Ορισμένοι ελεγκτές προβολής που παρέχονται από το σύστημα, όπως το UINavigationController, αλλάζουν την προεπιλεγμένη τιμή σε true.

Αποκλεισμός των ενεργειών αναζήτησης

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

τελική κατηγορία Debouncer {
  ιδιωτική καθυστέρηση: TimeInterval
  ιδιωτικό var workItem: DispatchWorkItem;
init (καθυστέρηση: TimeInterval) {
    self.delay = καθυστέρηση
  }}
/// Εκκίνηση της ενέργειας μετά από κάποια καθυστέρηση
  λειτουργικό πρόγραμμα (ενέργεια: @escaping () -> άκυρο) {
    workItem; .cancel ()
    workItem = DispatchWorkItem (μπλοκ: δράση)
    DispatchQueue.main.asyncAfter (προθεσμία: .now () + καθυστέρηση, εκτέλεση: workItem!)
  }}
}}

Δοκιμάζοντας την αποκρυπτογράφηση με Αναστρέψιμη προσδοκία

Για να δοκιμάσουμε το Debouncer μπορούμε να χρησιμοποιήσουμε την προσδοκία XCTest σε ανεστραμμένη λειτουργία. Διαβάστε περισσότερα σχετικά με αυτό σε Μονάδα δοκιμής ασύγχρονου κώδικα Swift.

Για να ελέγξετε ότι δεν συμβαίνει μια κατάσταση κατά τη διάρκεια της δοκιμής, δημιουργήστε μια προσδοκία που εκπληρώνεται όταν εμφανίζεται η απροσδόκητη κατάσταση και ορίστε την ιδιότητα isInverted στην αληθινή. Η δοκιμή σας θα αποτύχει αμέσως εάν η ανεστραμμένη προσδοκία ικανοποιηθεί.
class DebouncerTests: XCTestCase {
  func testDebouncing () {
    αφήστε cancelExpectation = self.expectation (περιγραφή: "ακύρωση")
    cancelExpectation.isInverted = true
ας ολοκληρώσετεΕξέταση = εαυτός (βλέπε: "πλήρης")
    ας debouncer = Debouncer (καθυστέρηση: 0,3)
debouncer.schedule {
      cancelExpectation.fulfill ()
    }}
debouncer.schedule {
      fullExpectation.fulfill ()
    }}
περιμένετε (για: [ακύρωσηΕπιθεώρηση, ολοκλήρωσηΕξέτασης], χρονικό όριο: 1)
  }}
}}

Έλεγχος διεπαφής χρήστη με UITests

Μερικές φορές οι μικρές refactoring μπορούν να έχουν μεγάλο αποτέλεσμα. Ένα απενεργοποιημένο κουμπί μπορεί να οδηγήσει σε μη χρησιμοποιήσιμες οθόνες μετά. Το UITest βοηθά στην εξασφάλιση της ακεραιότητας και των λειτουργικών πτυχών της εφαρμογής. Η δοκιμή πρέπει να είναι δηλωτική. Μπορούμε να χρησιμοποιήσουμε το μοτίβο ρομπότ.

class συνταγέςUITests: XCTestCase {
  εφαρμογή app: XCUIApplication!
  override func setUp () {
    super.setUp ()
    continueAfterFailure = ψευδής
    app = XCUIApplication ()
  }}
  funk testScrolling () {
    app.launch ()
    ας συλλογήView = app.collectionViews.element (boundBy: 0)
    collectionView.swipeUp ()
    collectionView.swipeUp ()
  }}
  funk testGoToDetail () {
    app.launch ()
    ας συλλογήView = app.collectionViews.element (boundBy: 0)
    ας firstCell = collectionView.cells.element (boundBy: 0)
    firstCell.tap ()
  }}
}}

Εδώ είναι μερικά από τα άρθρα μου σχετικά με τις δοκιμές.

  • Εκτελέστε UITests με σύνδεση στο Facebook στο iOS
  • Δοκιμή σε Swift με δεδομένο όταν στη συνέχεια μοτίβο

Κύρια προφυλακτήρας νήματος

Η πρόσβαση στο περιβάλλον χρήστη από την ουρά αναμονής μπορεί να οδηγήσει σε πιθανά προβλήματα. Νωρίτερα, έπρεπε να χρησιμοποιήσω το MainThreadGuard, τώρα που το Xcode 9 έχει το Main Checker, απλά ενεργοποίησα αυτό στο Xcode.

Ο Κύριος έλεγχος νήματος είναι ένα αυτόνομο εργαλείο για τις γλώσσες Swift και C που ανιχνεύει την μη έγκυρη χρήση του AppKit, του UIKit και άλλων API σε ένα νήμα φόντου. Η ενημέρωση του περιβάλλοντος χρήστη σε ένα νήμα διαφορετικό από το κύριο νήμα είναι ένα συνηθισμένο λάθος που μπορεί να οδηγήσει σε αναπάντητες ενημερώσεις UI, οπτικά ελαττώματα, αλλοιώσεις δεδομένων και συντριβές.

Μέτρηση των επιδόσεων και των θεμάτων

Μπορούμε να χρησιμοποιήσουμε τα εργαλεία για λεπτομερή προβολή της εφαρμογής. Για γρήγορη μέτρηση, μπορούμε να κατευθυνθούμε στην καρτέλα Navigator Debug και να δούμε τη CPU, τη μνήμη και τη χρήση του δικτύου. Ελέγξτε αυτό το δροσερό άρθρο για να μάθετε περισσότερα σχετικά με τα όργανα.

Πρωτότυπο με παιδική χαρά

Η παιδική χαρά είναι ο συνιστώμενος τρόπος για πρωτότυπο και δημιουργία εφαρμογών. Στο WWDC 2018, η Apple εισήγαγε το Create ML που υποστηρίζει το Playground για να εκπαιδεύσει το μοντέλο. Ελέγξτε αυτό το δροσερό άρθρο για να μάθετε περισσότερα σχετικά με την εξέλιξη στην Swift.

Πού να πάτε από εδώ

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

Σας συνιστώ να ελέγξετε το άρθρο Καλύτεροι χώροι για να μάθετε την ανάπτυξη του iOS για να μάθετε περισσότερα για την ανάπτυξη του iOS.

Εάν έχετε οποιεσδήποτε ερωτήσεις, σχόλια ή σχόλια, μην ξεχάσετε να τα προσθέσετε στα σχόλια. Και αν βρήκατε αυτό το άρθρο χρήσιμο, μην ξεχάσετε να χτυπήσετε.

Αν σας αρέσει αυτή η ανάρτηση, σκεφτείτε να επισκεφτείτε τα άλλα μου άρθρα και εφαρμογές