Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Conference Talk: SwiftUI Architectures

Conference Talk: SwiftUI Architectures

On the developer conference "360 | iDev", we gave a talk about architectures that can be used together with SwiftUI. More than 60 developers joined our 4-hour workshop.

QuickBird

August 09, 2020
Tweet

More Decks by QuickBird

Other Decks in Programming

Transcript

  1. 1 SwiftUI Architectures Building maintainable Apps with Declarative UI What

    you will build What you will learn Prerequisites You will refactor an app written using a Model-View architecture to a Redux architecture as well as a MVVM approach. How to evaluate app architectures How to write an app using a Model-View architecture How to write an app using a Redux architecture How to write an app using a MVVM architecture • Xcode • Swift • Patterns Basics • Intermediate SwiftUI Knowledge Key Vocabulary • Views • MVVM • Redux • SwiftUI • Swift 360 | iDev
  2. SwiftUI Architectures © 2020 QuickBird Studios 360 | iDev 2020

    Introduction: Who are we? 2 • Ghulam Nasir • born in Peshawar , living in Munich . • Senior Mobile Developer at QuickBird Studios • Husband, Father of Two • gnasirky nasirky • Paul Kraft • born in Nuremberg , living in Munich . • Swift Developer at QuickBird Studios (mostly iOS with UIKit) • TUM Master’s student in Computer Science • pauljokraft pauljohanneskraft
  3. SwiftUI Architectures © 2020 QuickBird Studios 360 | iDev 2020

    Getting to know each other • Sadly, there is not really time for an actual introduction round, but we would like to get some insight into what your background is and how we can best conduct the workshop. • Have you already had your first contact with SwiftUI (e.g. looked at code, seen tutorials, built something)? • Have you built SwiftUI views on your own? • Have you built a SwiftUI application that has gone live? • Have you already had issues with state management in SwiftUI? • Have you struggled with your app’s architecture when using SwiftUI? • Have you ever created UIs with UIKit? 5
  4. SwiftUI Architectures © 2020 QuickBird Studios 360 | iDev 2020

    Asking questions during the workshop • The workshop is divided into “theory” and “exercise” parts. • During “theory” parts, feel free to write questions in Slack, but please do not interrupt the speaker unless it is really important. • During “exercise” parts, you can ask questions in Slack or Zoom, depending on your preference. Please “raise your hand” before you start with the question, we will then unmute your microphone once it is your turn. If there are many people asking questions using Zoom, feel free to use Slack instead. 6
  5. SwiftUI Architectures © 2020 QuickBird Studios 360 | iDev 2020

    How can we evaluate different architecture approaches? 8
  6. SwiftUI Architectures © 2020 QuickBird Studios 360 | iDev 2020

    SOLID Principles • A type should only have a single responsibility. • A type should be open for extension, but closed for modification. • Subtypes should always conform to the behavior a parent type promises. (Liskov Substitution Principle) • A type should not be forced to implement an interface it does not use. (Interface Segregation) • Entities should depend on abstractions, not concretions. (Dependency Inversion) 9
  7. SwiftUI Architectures © 2020 QuickBird Studios 360 | iDev 2020

    Let’s see what is important to us…. 10
  8. SwiftUI Architectures © 2020 QuickBird Studios 360 | iDev 2020

    Metric: Code Overhead 11 Description Possible benefits Possible drawbacks • We do not want to write repeated code not serving a well-defined purpose. • only write as much code as needed for the given problem • increased code overhead must serve a justifiable cause • not choosing an abstraction to decrease code overhead might turn out to be the wrong decision • decreased development time • focus on more important tasks
  9. SwiftUI Architectures © 2020 QuickBird Studios 360 | iDev 2020

    Metric: Testability 13 Description Possible benefits Possible drawbacks • We want to be able to test code easily, both individually and as a whole • We want to be able to mock certain functionalities of the application • We want to be sure, everything works correctly • Increased development time • Increased complexity • Increased overhead • Identify bugs early • Allows for easy mocking, e.g. for demonstration
  10. SwiftUI Architectures © 2020 QuickBird Studios 360 | iDev 2020

    Metric: State consistency 15 Description Possible benefits Possible drawbacks • We want to always show the user the current model state without creating inconsistently displayed data • The information shown to the user should not contradict itself • State changes might contradict itself • Memory usage should be minimized • View state might not be updated when model state changes • Increased coupling • Error propagation • Great UX • Less error-prone
  11. SwiftUI Architectures © 2020 QuickBird Studios 360 | iDev 2020

    Metric: Code consistency 17 Description Possible benefits Possible drawbacks • We want to write consistent code according to a specific structure to allow finding a specific code piece faster • have common abstractions for similar abstraction cases • finding the right abstraction to fit most problems, but also not hinder them from being solved in a clean fashion • Higher learning curve for new developers • The consistent solution might not be the best for the given solution • Reduced maintenance costs • Reduced learning curve for existing developers
  12. SwiftUI Architectures © 2020 QuickBird Studios 360 | iDev 2020

    Metric: Flexibility in App 19 Description Possible benefits Possible drawbacks • We want to be able to move a view from one part of the app to the other with only minor changes of the code. • Make sure to keep dependencies to other individual parts of the application small, e.g. relying on the type of your parent view or parts of its state • Increased coupling to the whole app • • Reduced maintenance costs for UI redesigns • Decreased coupling to individual parts of the app
  13. SwiftUI Architectures © 2020 QuickBird Studios 360 | iDev 2020

    Metric: Flexibility across Apps 21 Description Possible benefits Possible drawbacks • We want to be able to easily reuse parts of the business logic or views in other apps - with only minor changes • Example: We want to reuse a complex view, but use a different business logic (e.g. different data source) • Example: We need a custom view for an app, but the business logic is similar to an existing app • Reuse is rarely actually needed for the complex views, since they are so specialized • Reduced development time • Less error-prone (since it worked in its previous environment)
  14. SwiftUI Architectures © 2020 QuickBird Studios 360 | iDev 2020

    Metric: Modularity 23 Description Possible benefits Possible drawbacks • Low coupling, High cohesion • We want code that is doing similar operations to be close to each other • We do not want to overdo it, since it will be unreadable, if you modularize too much • Steeper learning for new developers • Easy to find code related to a specific topic (for existing devs) • Less error-prone
  15. SwiftUI Architectures © 2020 QuickBird Studios 360 | iDev 2020

    Metric: Code proximity 25 Description Possible benefits Possible drawbacks • Code connected to a specific feature should not be spread across the whole architecture • We want to easily find out which view actions cause which Model operations • We do not want to study the whole architecture to understand a small feature • Swapping out dependencies is hard • More error-prone for consistency and compatibility issues • Easy to find code related to a specific feature and its direct consequences
  16. SwiftUI Architectures © 2020 QuickBird Studios 360 | iDev 2020

    What would you also consider? What is most important to you? When to focus on which metric? 27 10 minutes
  17. SwiftUI Architectures © 2020 QuickBird Studios 360 | iDev 2020

    How SwiftUI works 28 f(x) = y user interface state body @State @Binding @Published
  18. SwiftUI Architectures © 2020 QuickBird Studios 360 | iDev 2020

    SwiftUI Concepts • @State • stored view state, owned by the view specifying the state variable • @Binding • combination of getter and setter - e.g. to access State-wrapped properties, or properties of ObservableObjects • ObservableObject • protocol to be implemented by a view model - updates view, whenever its properties change • @ObservedObject • wrapper for ObservableObject to be used for updating views - initial value needs to be set when creating view • @EnvironmentObject • ObservableObject is matched instead of being provided with a default value - if it cannot find a value, your app crashes. it matches types - i.e. subclassing is not a good idea (in comparison to ObservedObject) 29
  19. SwiftUI Architectures © 2020 QuickBird Studios 360 | iDev 2020

    Let’s dive right in … Model-View 30
  20. SwiftUI Architectures © 2020 QuickBird Studios 360 | iDev 2020

    Architecture: Model-View 31 View Model subscribe to model state changes fetch model data
  21. SwiftUI Architectures © 2020 QuickBird Studios 360 | iDev 2020

    Architecture: Model-View 1. View has full access to the underlying Model. 2. View is responsible for all the processing such as fetching the required data • by using a service or • getting it from the previous view and preparing the data for use etc. X
  22. SwiftUI Architectures © 2020 QuickBird Studios 360 | iDev 2020

    QuickGit: Views 32 LoginView AuthenticationView ProfileView RepositoryList RepositoryView
  23. SwiftUI Architectures © 2020 QuickBird Studios 360 | iDev 2020

    QuickGit: Views 33 HomeView ProfileView ContentView LoginView MainView SettingsView AuthenticationView RepositoryView
  24. SwiftUI Architectures © 2020 QuickBird Studios 360 | iDev 2020

    Sample Implementation struct RepositoryView: View { @Binding var user: User let service: UserService var body: some View { List(user.repositories, id: \.id) { repository in Text(repository.name) } .onAppear(perform: fetchRepositories) } private func fetchRepositories() { … } } 34
  25. 35

  26. SwiftUI Architectures © 2020 QuickBird Studios 360 | iDev 2020

    Exercise 1: Explore the example app 36 20 minutes git clone https://www.github.com/nasirky/QuickGit.git git checkout model-view
  27. SwiftUI Architectures © 2020 QuickBird Studios 360 | iDev 2020

    Metrics for Model-View 37 Code Proximity Performance Code Overhead Flexibility in App Testability Flexibility across Apps Modularity State Consistency Code Consistency
  28. SwiftUI Architectures © 2020 QuickBird Studios 360 | iDev 2020

    Extracting business logic from views • General idea • Views will only contain UI code, no business logic • Possibility to change the business logic for another use case of the same UI • Some considerations for the abstraction of business logic in SwiftUI • You cannot specify a protocol with `@ObservedObject` (Swift limitation) • We will define types for input and state • In combination with generics, this will allow us to use one view with different business logic without any code changes • There are other ways around this limitation, of course • Feel free to share your solution with the group! 39
  29. SwiftUI Architectures © 2020 QuickBird Studios 360 | iDev 2020

    Redux-like architecture 40 View Model trigger actions based on user input access view data Store subscribe to model state changes fetch model data *
  30. SwiftUI Architectures © 2020 QuickBird Studios 360 | iDev 2020

    Redux: Components 41 Model Store View Reducer State Action
  31. SwiftUI Architectures © 2020 QuickBird Studios 360 | iDev 2020

    Redux: Behavior (1) 42 Model Store View Reducer State
  32. SwiftUI Architectures © 2020 QuickBird Studios 360 | iDev 2020

    Redux: Behavior (2) 43 Model Store View Reducer Action
  33. SwiftUI Architectures © 2020 QuickBird Studios 360 | iDev 2020

    Redux: Behavior (3) 44 Model Store View Reducer Translation into actual change of state
  34. SwiftUI Architectures © 2020 QuickBird Studios 360 | iDev 2020

    Redux: Behavior (4) 45 Model Store View Reducer State*
  35. SwiftUI Architectures © 2020 QuickBird Studios 360 | iDev 2020

    How would we integrate Redux into our app? 1. Create AppState, AppAction & AppReducer 2. Inject Store into initial view 3. Adapt initial view to use Store 46
  36. 47

  37. SwiftUI Architectures © 2020 QuickBird Studios 360 | iDev 2020

    Demo: AppAction • Let’s start with our abstraction by removing the login logic from the existing views and moving that logic into a Store. X enum AppAction { case storedLogin case login(code: String) case logout }
  38. SwiftUI Architectures © 2020 QuickBird Studios 360 | iDev 2020

    Demo: AppState • Then, we define a state containing the variables we need for these actions. • The LoginService is not subject to change and will be injected into the Reducer instead of being specified here. So, the dependency between the views and LoginService is replaced by a dependency of the views to the AppAction instead. X struct AppState { var githubService: GitHubService? var isLoggedIn: Bool { githubService != nil } } The GitHubService is actually needed by the subviews and cannot easily be removed (yet). Later on, it would be nice, if we could use this property as a stored property, instead of relying on the githubService property.
  39. SwiftUI Architectures © 2020 QuickBird Studios 360 | iDev 2020

    Demo: Store final class Store<State, Action>: ObservableObject { @Published private(set) var state: State private let reducer: Reducer<State, Action> private var cancellables: Set<AnyCancellable> = [] init(initialState: State, reducer: Reducer<State, Action>) { self.state = initialState self.reducer = reducer } func send(_ action: Action) { reducer.reduce(state, action) .receive(on: DispatchQueue.main) .sink(receiveValue: perform) .store(in: &cancellables) } private func perform(change: Reducer<State, Action>.Change) { change.perform(&state) } } X
  40. SwiftUI Architectures © 2020 QuickBird Studios 360 | iDev 2020

    Demo: Reducer • A reducer takes the current state and the sent action as an input. • With that input, it does model operations (possibly asynchronous) and returns a publisher (AnyPublisher in Combine) of how the state should change based on the state and the action. X struct Change<State> { let perform: (inout State) -> Void } struct Reducer<State, Action> { typealias Change = QuickGit.Change<State> let reduce: (State, Action) -> AnyPublisher<Change, Never> }
  41. SwiftUI Architectures © 2020 QuickBird Studios 360 | iDev 2020

    Demo: AppReducer X extension Reducer where State == AppState, Action == AppAction { static func appReducer() -> Reducer { let loginService: LoginService = DefaultLoginService() return Reducer { state, action in switch action { case .storedLogin: return loginService.storedLogin() .map { information in Change { $0.githubService = DefaultGitHubService(information: information) } } .ignoreFailure() case let .login(code): return loginService.login(code: code) .map { information in Change { $0.githubService = DefaultGitHubService(information: information) } } .ignoreFailure() case .logout: loginService.logout() return .just(Change { $0.githubService = nil }) } } } }
  42. SwiftUI Architectures © 2020 QuickBird Studios 360 | iDev 2020

    Demo: Make use of the Store • Add @ObservedObject var store: Store<AppState, AppAction> to all views that require the store. • Inject a store “Store(initialState: AppState(), reducer: .appReducer())” to the ContentView • Remove references of the removed service, in our case LoginService. • Replace all model operations with actions sent to the store. X
  43. SwiftUI Architectures © 2020 QuickBird Studios 360 | iDev 2020

    git clone https://www.github.com/nasirky/QuickGit.git git checkout redux-start Exercise 2: Extend the Redux implementation 48 20 minutes
  44. SwiftUI Architectures © 2020 QuickBird Studios 360 | iDev 2020

    Metrics for Redux 49 State Consistency Flexibility in App Testability Code Overhead Flexibility across Apps Code Consistency Modularity Code Proximity
  45. SwiftUI Architectures © 2020 QuickBird Studios 360 | iDev 2020

    …until abstraction is at its peak. ViewState MVVM 50
  46. SwiftUI Architectures © 2020 QuickBird Studios 360 | iDev 2020

    ViewState MVVM 51 View Model Keep view up-to-date trigger actions based on user input ViewModel Listen to model state changes change model based on user input Input State
  47. SwiftUI Architectures © 2020 QuickBird Studios 360 | iDev 2020

    ViewState MVVM 52 ViewModel Input State performs action on View triggers notifies/updates
  48. SwiftUI Architectures © 2020 QuickBird Studios 360 | iDev 2020

    Step 1 Create the ViewModel Base with Generic types Input and State 54
  49. SwiftUI Architectures © 2020 QuickBird Studios 360 | iDev 2020

    Generic implementation class ViewModel<Input, State>: ObservableObject { @Published var state: State init(state: State) { self.state = state } func trigger(_ input: Input) { assertionFailure("Please implement this!") } } 55
  50. SwiftUI Architectures © 2020 QuickBird Studios 360 | iDev 2020

    Step 2 Create concrete Implementations 56
  51. SwiftUI Architectures © 2020 QuickBird Studios 360 | iDev 2020

    57 What will the Input be? What will the State be? Possible Actions the ViewModel can perform based on the Input
  52. SwiftUI Architectures © 2020 QuickBird Studios 360 | iDev 2020

    Step 3 Inject the ViewModel into the View 58
  53. SwiftUI Architectures © 2020 QuickBird Studios 360 | iDev 2020

    Step 4 Retrieve state from the viewModel e.g. display to the user in some form 59
  54. SwiftUI Architectures © 2020 QuickBird Studios 360 | iDev 2020

    Step 4 Send inputs to the viewModel perform user’s action on the underlying model 60
  55. SwiftUI Architectures © 2020 QuickBird Studios 360 | iDev 2020

    Exercise 3: Extend the MVVM implementation 63 20 minutes git clone https://www.github.com/nasirky/QuickGit.git git checkout mvvm-start
  56. SwiftUI Architectures © 2020 QuickBird Studios 360 | iDev 2020

    Metrics for MVVM 64 Testability Flexibility across Apps Modularity State Consistency Code Consistency Code Proximity Flexibility in App Code Overhead
  57. SwiftUI Architectures © 2020 QuickBird Studios 360 | iDev 2020

    Let’s recap! What did we learn and how can we continue from here? 65
  58. SwiftUI Architectures © 2020 QuickBird Studios 360 | iDev 2020

    Model-View Redux MVVM . 66 Smaller apps Prototyping View Model View View Model Store View View Model View Model View View Model ➡ ➡ Smaller apps Fast-paced development Medium-Larger apps Heterogeneous features
  59. SwiftUI Architectures © 2020 QuickBird Studios 360 | iDev 2020

    • Thank you for listening! You made it through! • We will end this workshop with an open discussion, so feel free (but not obligated) to stay. Find more about us at quickbirdstudios.com Learn more about interesting topics quickbirdstudios.com/ blog Open Discussion 67 end of session
  60. SwiftUI Architectures © 2020 QuickBird Studios 360 | iDev 2020

    References • https://quickbirdstudios.com/blog/swiftui-architecture-redux-mvvm/ • https://developer.apple.com/documentation/swiftui 68