SwiftUI - Làm cách nào để chuyển môi trườngObject vào Mô hình xem?


16

Tôi đang tìm cách tạo một Môi trường môi trường có thể được truy cập bởi Mô hình xem (không chỉ là chế độ xem).

Đối tượng Môi trường theo dõi dữ liệu phiên ứng dụng, ví dụ: LogIn, mã thông báo truy cập, v.v., dữ liệu này sẽ được chuyển vào các mô hình xem (hoặc các lớp dịch vụ khi cần) để cho phép gọi API để truyền dữ liệu từ Môi trường này.

Tôi đã cố gắng chuyển trong đối tượng phiên tới trình khởi tạo của lớp mô hình khung nhìn từ khung nhìn nhưng gặp lỗi.

Làm cách nào tôi có thể truy cập / chuyển Môi trườngObject vào mô hình xem bằng SwiftUI?

Xem liên kết đến dự án thử nghiệm: https://gofile.io/?c=vgHLVx


Tại sao không vượt qua viewmodel như EO?
E.Coms

Có vẻ như trên đầu, sẽ có nhiều mô hình xem, tải lên tôi đã liên kết chỉ là một ví dụ đơn giản
Michael

2
Tôi không chắc tại sao câu hỏi này bị hạ thấp, tôi cũng tự hỏi như vậy. Tôi sẽ trả lời với những gì tôi đã làm, hy vọng người khác có thể nghĩ ra thứ gì đó tốt hơn.
Michael Ozeryansky

2
@ E.Coms Tôi dự kiến ​​Môi trường thường là một đối tượng. Tôi biết nhiều công việc, có vẻ như một mùi mã để làm cho chúng có thể truy cập trên toàn cầu như thế.
Michael Ozeryansky

@Michael Bạn thậm chí đã tìm thấy một giải pháp cho điều này?
Brett

Câu trả lời:


3

Tôi chọn không có ViewModel. (Có lẽ thời gian cho một mẫu mới?)

Tôi đã thiết lập dự án của mình với một RootViewvà một số khung nhìn con. Tôi thiết lập của tôi RootViewvới một Appđối tượng là Môi trường. Thay vì ViewModel truy cập Mô hình, tất cả các lớp xem truy cập của tôi trên Ứng dụng. Thay vì ViewModel xác định bố cục, phân cấp khung nhìn xác định bố cục. Từ việc thực hiện điều này trong thực tế cho một vài ứng dụng, tôi đã thấy quan điểm của mình vẫn nhỏ và cụ thể. Như một sự đơn giản hóa quá mức:

class App {
   @Published var user = User()

   let networkManager: NetworkManagerProtocol
   lazy var userService = UserService(networkManager: networkManager)

   init(networkManager: NetworkManagerProtocol) {
      self.networkManager = networkManager
   }

   convenience init() {
      self.init(networkManager: NetworkManager())
   }
}
struct RootView {
    @EnvironmentObject var app: App

    var body: some View {
        if !app.user.isLoggedIn {
            LoginView()
        } else {
            HomeView()
        }
    }
}
struct HomeView: View {
    @EnvironmentObject var app: App

    var body: some View {
       VStack {
          Text("User name: \(app.user.name)")
          Button(action: { app.userService.logout() }) {
             Text("Logout")
          }
       }
    }
}

Trong các bản xem trước của tôi, tôi khởi tạo MockAppmột lớp con App. MockApp khởi tạo các trình khởi tạo được chỉ định với đối tượng Mocked. Ở đây, UserService không cần phải bị chế giễu, nhưng nguồn dữ liệu (ví dụ NetworkManagerProtocol) thì không.

struct HomeView_Previews: PreviewProvider {
    static var previews: some View {
        Group {
            HomeView()
                .environmentObject(MockApp() as App) // <- This is needed for EnvironmentObject to treat the MockApp as an App Type
        }
    }

}

Chỉ cần một lưu ý: Tôi nghĩ tốt hơn là tránh xích như thế app.userService.logout(). userServicephải riêng tư và chỉ được truy cập từ bên trong lớp ứng dụng. Mã ở trên sẽ trông như thế này: Button(action: { app.logout() })và chức năng đăng xuất sau đó sẽ gọi trực tiếp userService.logout().
pawello2222

@ pawello2222 Không tốt hơn, nó chỉ là mô hình mặt tiền mà không có bất kỳ lợi ích nào, nhưng bạn có thể làm như bạn muốn.
Michael Ozeryansky

3

Bạn không nên. Đó là một quan niệm sai lầm phổ biến rằng SwiftUI hoạt động tốt nhất với MVVM.

MVVM không có chỗ trong SwfitUI. Bạn đang hỏi rằng nếu bạn có thể đẩy một hình chữ nhật đến

phù hợp với một hình tam giác. Nó sẽ không phù hợp.

Hãy bắt đầu với một số sự kiện và từng bước làm việc:

  1. ViewModel là một mô hình trong MVVM.

  2. MVVM không xem xét loại giá trị (ví dụ: không có điều đó trong java).

  3. Mô hình kiểu giá trị (mô hình không có trạng thái) được coi là an toàn hơn tham chiếu

    loại mô hình (mô hình với trạng thái) theo nghĩa bất biến.

Bây giờ, MVVM yêu cầu bạn thiết lập một mô hình theo cách mà bất cứ khi nào nó thay đổi, nó

cập nhật xem theo một số cách xác định trước. Điều này được gọi là ràng buộc.

Không ràng buộc, bạn sẽ không có sự phân chia mối quan tâm tốt đẹp, ví dụ; tái cấu trúc

mô hình và các trạng thái liên quan và giữ chúng tách biệt khỏi tầm nhìn.

Đây là hai điều mà hầu hết các nhà phát triển MVVM của iOS thất bại:

  1. iOS không có cơ chế "ràng buộc" theo nghĩa java truyền thống.

    Một số người sẽ bỏ qua sự ràng buộc và nghĩ rằng việc gọi một đối tượng ViewModel

    tự động giải quyết mọi thứ; một số sẽ giới thiệu Rx dựa trên KVO và

    làm phức tạp mọi thứ khi MVVM được cho là làm cho mọi thứ đơn giản hơn.

  2. mô hình với nhà nước là quá nguy hiểm

    bởi vì MVVM tập trung quá nhiều vào ViewModel, quá ít về quản lý nhà nước

    và các quy tắc chung trong việc quản lý Kiểm soát; hầu hết các nhà phát triển kết thúc

    nghĩ rằng một mô hình với trạng thái được sử dụng để cập nhật chế độ xem có thể tái sử dụng

    kiểm tra .

    đây là lý do Swift giới thiệu loại giá trị ở vị trí đầu tiên; một mô hình mà không có

    tiểu bang.

Bây giờ với câu hỏi của bạn: bạn hỏi liệu ViewModel của bạn có thể có quyền truy cập vào Môi trường môi trường (EO) không?

Bạn không nên. Bởi vì trong SwiftUI, một mô hình phù hợp với Chế độ xem tự động có

tham chiếu đến EO. Ví dụ;

struct Model: View {
    @EnvironmentObject state: State
    // automatic binding in body
    var body: some View {...}
}

Tôi hy vọng mọi người có thể đánh giá cao cách thiết kế SDK nhỏ gọn.

Trong SwiftUI, MVVM là tự động . Không cần một đối tượng ViewModel riêng biệt

mà liên kết thủ công để xem yêu cầu tham chiếu EO được truyền cho nó.

Đoạn mã trên MVVM. Ví dụ; một mô hình với ràng buộc để xem.

Nhưng vì mô hình là loại giá trị, nên thay vì tái cấu trúc mô hình và trạng thái như

xem mô hình, bạn tái cấu trúc kiểm soát (ví dụ trong phần mở rộng giao thức).

Đây là mẫu thiết kế thích ứng SDK chính thức với tính năng ngôn ngữ, thay vì chỉ

thi hành nó Chất trên hình thức.

Nhìn vào giải pháp của bạn, bạn phải sử dụng singleton về cơ bản là toàn cầu. Bạn

nên biết nguy hiểm như thế nào khi truy cập toàn cầu ở bất cứ đâu mà không cần bảo vệ

tính bất biến mà bạn không có vì bạn phải sử dụng mô hình kiểu tham chiếu!

TL; DR

Bạn không làm MVVM theo cách java trong SwiftUI. Và cách Swift-y để làm điều đó là không cần thiết

để làm điều đó, nó đã được tích hợp sẵn.

Hy vọng nhiều nhà phát triển thấy điều này vì đây dường như là một câu hỏi phổ biến.


1

Dưới đây cung cấp cách tiếp cận phù hợp với tôi. Đã thử nghiệm với nhiều giải pháp bắt đầu với Xcode 11.1.

Vấn đề bắt nguồn từ cách đưa vào môi trường môi trường, lược đồ chung

SomeView().environmentObject(SomeEO())

tức là, ở góc nhìn thứ nhất được tạo, ở đối tượng môi trường được tạo thứ hai, ở đối tượng môi trường thứ ba được đưa vào khung nhìn

Vì vậy, nếu tôi cần tạo / thiết lập mô hình khung nhìn trong view constructor thì đối tượng môi trường vẫn chưa xuất hiện ở đó.

Giải pháp: phá vỡ mọi thứ và sử dụng tiêm phụ thuộc rõ ràng

Đây là cách nó trông trong mã (lược đồ chung)

// somewhere, say, in SceneDelegate

let someEO = SomeEO()                            // create environment object
let someVM = SomeVM(eo: someEO)                  // create view model
let someView = SomeView(vm: someVM)              // create view 
                   .environmentObject(someEO)

Không có sự đánh đổi nào ở đây, bởi vì ViewModel và Môi trường, theo thiết kế, kiểu tham chiếu (thực tế, ObservableObject), vì vậy tôi chuyển qua đây và chỉ có các tham chiếu (còn gọi là con trỏ).

class SomeEO: ObservableObject {
}

class BaseVM: ObservableObject {
    let eo: SomeEO
    init(eo: SomeEO) {
       self.eo = eo
    }
}

class SomeVM: BaseVM {
}

class ChildVM: BaseVM {
}

struct SomeView: View {
    @EnvironmentObject var eo: SomeEO
    @ObservedObject var vm: SomeVM

    init(vm: SomeVM) {
       self.vm = vm
    }

    var body: some View {
        // environment object will be injected automatically if declared inside ChildView
        ChildView(vm: ChildVM(eo: self.eo)) 
    }
}

struct ChildView: View {
    @EnvironmentObject var eo: SomeEO
    @ObservedObject var vm: ChildVM

    init(vm: ChildVM) {
       self.vm = vm
    }

    var body: some View {
        Text("Just demo stub")
    }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.