Phát triển ứng dụng trên SwiftUI. Phần 1: Luồng dữ liệu và Redux

Phát triển ứng dụng trên SwiftUI. Phần 1: Luồng dữ liệu và Redux

Sau khi tham dự phiên họp Thông điệp Liên bang tại WWDC 2019, tôi quyết định tìm hiểu sâu về SwiftUI. Tôi đã dành nhiều thời gian làm việc với nó và hiện đã bắt đầu phát triển một ứng dụng thực sự có thể hữu ích cho nhiều người dùng.

Tôi gọi nó là MovieSwiftUI - đây là một ứng dụng để tìm kiếm phim mới và cũ, cũng như thu thập chúng vào bộ sưu tập bằng cách sử dụng API TMDB. Tôi luôn yêu thích phim ảnh và thậm chí đã thành lập một công ty hoạt động trong lĩnh vực này dù đã lâu rồi. Công ty khó có thể được gọi là tuyệt vời, nhưng ứng dụng thì tuyệt vời!

Chúng tôi nhắc nhở: cho tất cả độc giả của "Habr" - giảm giá 10 rúp khi đăng ký bất kỳ khóa học Skillbox nào bằng mã khuyến mại "Habr".

Hộp kỹ năng khuyến nghị: Khóa học giáo dục trực tuyến "Nghề Java Developer".

Vậy MovieSwiftUI có thể làm gì?

  • Tương tác với API - hầu hết mọi ứng dụng hiện đại đều thực hiện điều này.
  • Tải dữ liệu không đồng bộ theo yêu cầu và phân tích JSON vào mô hình Swift bằng cách sử dụng Có thể mã hóa.
  • Hiển thị hình ảnh được tải theo yêu cầu và lưu trữ chúng.
  • Ứng dụng dành cho iOS, iPadOS và macOS này cung cấp UX tốt nhất cho người dùng các hệ điều hành này.
  • Người dùng có thể tạo dữ liệu và tạo danh sách phim của riêng họ. Ứng dụng lưu và khôi phục dữ liệu người dùng.
  • Chế độ xem, thành phần và mô hình được phân tách rõ ràng bằng mẫu Redux. Luồng dữ liệu ở đây là một chiều. Nó có thể được lưu trữ đầy đủ, khôi phục và ghi đè.
  • Ứng dụng sử dụng các thành phần cơ bản của SwiftUI, TabbedView, SegmentedControl, NavigationView, Form, Modal, v.v. Nó cũng cung cấp các chế độ xem, cử chỉ, UI/UX tùy chỉnh.

Phát triển ứng dụng trên SwiftUI. Phần 1: Luồng dữ liệu và Redux
Thực tế thì ảnh động mượt mà, ảnh GIF hơi giật

Làm việc trên ứng dụng này đã mang lại cho tôi rất nhiều trải nghiệm và nhìn chung đó là một trải nghiệm tích cực. Tôi đã có thể viết một ứng dụng đầy đủ chức năng, vào tháng 13, tôi sẽ cải thiện nó và xuất bản nó trên AppStore, đồng thời với việc phát hành iOS XNUMX.

Redux, BindableObject và EnvironmentObject

Phát triển ứng dụng trên SwiftUI. Phần 1: Luồng dữ liệu và Redux

Tôi đã làm việc với Redux được khoảng hai năm nên tôi tương đối thành thạo về nó. Đặc biệt, tôi sử dụng nó ở lối vào cho Phản ứng trang web cũng như để phát triển các ứng dụng gốc iOS (Swift) và Android (Kotlin).

Tôi chưa bao giờ hối hận khi chọn Redux làm kiến ​​trúc luồng dữ liệu để xây dựng ứng dụng SwiftUI. Phần thách thức nhất khi sử dụng Redux trong ứng dụng UIKit là làm việc với cửa hàng, nhận và truy xuất dữ liệu cũng như ánh xạ dữ liệu đó tới các chế độ xem/thành phần của bạn. Để làm điều này, tôi phải tạo một loại thư viện trình kết nối (sử dụng ReSwift và ReKotlin). Hoạt động tốt, nhưng khá nhiều mã. Thật không may, nó chưa (chưa) là nguồn mở.

Tin tốt! Điều duy nhất cần lo lắng với SwiftUI - nếu bạn định sử dụng Redux - là cửa hàng, trạng thái và bộ giảm tốc. Tương tác với cửa hàng được SwiftUI đảm nhiệm hoàn toàn nhờ @EnvironmentObject. Vì vậy, cửa hàng bắt đầu bằng BindableObject.

Tôi đã tạo một gói Swift đơn giản, SwiftUIFlux, cung cấp cách sử dụng cơ bản của Redux. Trong trường hợp của tôi, nó là một phần của MovieSwiftUI. tôi cũng vậy đã viết hướng dẫn từng bước, nó sẽ giúp bạn sử dụng thành phần này.

Nó hoạt động như thế nào?

final public class Store<State: FluxState>: BindableObject {
    public let willChange = PassthroughSubject<Void, Never>()
        
    private(set) public var state: State
    
    private func _dispatch(action: Action) {
        willChange.send()
        state = reducer(state, action)
    }
}

Mỗi khi bạn kích hoạt một hành động, bạn sẽ kích hoạt hộp số. Nó sẽ đánh giá các hành động theo trạng thái hiện tại của ứng dụng. Sau đó, nó sẽ trả về trạng thái được sửa đổi mới phù hợp với loại hành động và dữ liệu.

Chà, vì store là BindableObject nên nó sẽ thông báo cho SwiftUI khi giá trị của nó thay đổi bằng cách sử dụng thuộc tính willChange do PassthroughSubject cung cấp. Điều này là do BindableObject phải cung cấp Loại nhà xuất bản nhưng việc triển khai giao thức chịu trách nhiệm quản lý nó. Nhìn chung, đây là một công cụ rất mạnh mẽ của Apple. Theo đó, trong chu kỳ kết xuất tiếp theo, SwiftUI sẽ giúp hiển thị nội dung của các khung nhìn theo sự thay đổi trạng thái.

Trên thực tế, đây là tất cả trái tim và sự kỳ diệu của SwiftUI. Bây giờ, trong bất kỳ chế độ xem nào đăng ký một trạng thái, chế độ xem sẽ được hiển thị tùy theo dữ liệu nào nhận được từ trạng thái và những gì đã thay đổi.

class SceneDelegate: UIResponder, UIWindowSceneDelegate {
 
    var window: UIWindow?
 
 
    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        if let windowScene = scene as? UIWindowScene {
            let window = UIWindow(windowScene: windowScene)
            
            let controller = UIHostingController(rootView: HomeView().environmentObject(store))
            window.rootViewController = controller
            self.window = window
            window.makeKeyAndVisible()
        }
    }
}
 
struct CustomListCoverRow : View {
    @EnvironmentObject var store: Store<AppState>
    
    let movieId: Int
    var movie: Movie! {
        return store.state.moviesState.movies[movieId]
    }
    
    var body: some View {
        HStack(alignment: .center, spacing: 0) {
            Image(movie.poster)
        }.listRowInsets(EdgeInsets())
    }
}

Cửa hàng được đưa vào dưới dạng EnvironmentObject khi ứng dụng khởi động và sau đó có thể truy cập được ở bất kỳ chế độ xem nào bằng cách sử dụng @EnvironmentObject. Không có hình phạt về hiệu suất vì các thuộc tính dẫn xuất được truy xuất hoặc tính toán nhanh chóng từ trạng thái ứng dụng.

Đoạn mã trên thay đổi hình ảnh nếu áp phích phim thay đổi.

Và điều này thực sự được thực hiện chỉ bằng một dòng, với sự trợ giúp của các khung nhìn được kết nối với trạng thái. Nếu bạn đã làm việc với ReSwift trên iOS hoặc thậm chí kết nối với React, bạn sẽ hiểu được sự kỳ diệu của SwiftUI.

Bây giờ bạn có thể thử kích hoạt hành động và xuất bản trạng thái mới. Đây là một ví dụ phức tạp hơn.

struct CustomListDetail : View {
    @EnvironmentObject var store: Store<AppState>
 
    let listId: Int
    
    var list: CustomList {
        store.state.moviesState.customLists[listId]!
    }
    
    var movies: [Int] {
        list.movies.sortedMoviesIds(by: .byReleaseDate, state: store.state)
    }
    
    var body: some View {
        List {
            ForEach(movies) { movie in
                NavigationLink(destination: MovieDetail(movieId: movie).environmentObject(self.store)) {
                    MovieRow(movieId: movie, displayListImage: false)
                }
            }.onDelete { (index) in
               self.store.dispatch(action: MoviesActions.RemoveMovieFromCustomList(list: self.listId, movie: self.movies[index.first!]))
            }
        }
    }
}

Trong đoạn mã trên, tôi đang sử dụng hành động .onDelete từ SwiftUI cho mỗi IP. Điều này cho phép hàng trong danh sách hiển thị thao tác vuốt iOS thông thường để xóa. Vì vậy, khi người dùng chạm vào nút xóa, nó sẽ kích hoạt hành động tương ứng và xóa phim khỏi danh sách.

Chà, vì thuộc tính danh sách có nguồn gốc từ trạng thái BindableObject và được đưa vào dưới dạng EnvironmentObject, SwiftUI cập nhật danh sách vì ForEach được liên kết với thuộc tính được tính toán của phim.

Đây là một phần của bộ giảm tốc MoviesState:

func moviesStateReducer(state: MoviesState, action: Action) -> MoviesState {
    var state = state
    switch action {
    
    // other actions.
    
    case let action as MoviesActions.AddMovieToCustomList:
        state.customLists[action.list]?.movies.append(action.movie)
        
    case let action as MoviesActions.RemoveMovieFromCustomList:
        state.customLists[action.list]?.movies.removeAll{ $0 == action.movie }
        
    default:
        break
    }
    return state
}

Bộ giảm tốc được thực thi khi bạn gửi một hành động và trả về một trạng thái mới, như đã nêu ở trên.

Tôi sẽ chưa đi vào chi tiết - làm thế nào SwiftUI thực sự biết những gì cần hiển thị. Để hiểu điều này sâu sắc hơn, đáng để xem phiên WWDC trên luồng dữ liệu trong SwiftUI. Nó cũng giải thích chi tiết tại sao và khi nào nên sử dụng Tiểu bang, @Binding, ObjectBinding và EnvironmentObject.

Hộp kỹ năng khuyến nghị:

Nguồn: www.habr.com

Thêm một lời nhận xét