草庐IT

数据持久化方案解析(二十二) —— SwiftUI App中Core Data和CloudKit之间的数据共享(二)

刀客传奇 2023-03-28 原文

版本记录

版本号 时间
V1.0 2022.05.30 星期一

前言

数据的持久化存储是移动端不可避免的一个问题,很多时候的业务逻辑都需要我们进行本地化存储解决和完成,我们可以采用很多持久化存储方案,比如说plist文件(属性列表)、preference(偏好设置)、NSKeyedArchiver(归档)、SQLite 3CoreData,这里基本上我们都用过。这几种方案各有优缺点,其中,CoreData是苹果极力推荐我们使用的一种方式,我已经将它分离出去一个专题进行说明讲解。这个专题主要就是针对另外几种数据持久化存储方案而设立。
1. 数据持久化方案解析(一) —— 一个简单的基于SQLite持久化方案示例(一)
2. 数据持久化方案解析(二) —— 一个简单的基于SQLite持久化方案示例(二)
3. 数据持久化方案解析(三) —— 基于NSCoding的持久化存储(一)
4. 数据持久化方案解析(四) —— 基于NSCoding的持久化存储(二)
5. 数据持久化方案解析(五) —— 基于Realm的持久化存储(一)
6. 数据持久化方案解析(六) —— 基于Realm的持久化存储(二)
7. 数据持久化方案解析(七) —— 基于Realm的持久化存储(三)
8. 数据持久化方案解析(八) —— UIDocument的数据存储(一)
9. 数据持久化方案解析(九) —— UIDocument的数据存储(二)
10. 数据持久化方案解析(十) —— UIDocument的数据存储(三)
11. 数据持久化方案解析(十一) —— 基于Core Data 和 SwiftUI的数据存储示例(一)
12. 数据持久化方案解析(十二) —— 基于Core Data 和 SwiftUI的数据存储示例(二)
13. 数据持久化方案解析(十三) —— 基于Unit Testing的Core Data测试(一)
14. 数据持久化方案解析(十四) —— 基于Unit Testing的Core Data测试(二)
15. 数据持久化方案解析(十五) —— 基于Realm和SwiftUI的数据持久化简单示例(一)
16. 数据持久化方案解析(十六) —— 基于Realm和SwiftUI的数据持久化简单示例(二)
17. 数据持久化方案解析(十七) —— 基于NSPersistentCloudKitContainer的Core Data和CloudKit的集成示例(一)
18. 数据持久化方案解析(十八) —— 基于NSPersistentCloudKitContainer的Core Data和CloudKit的集成示例(二)
19. 数据持久化方案解析(十九) —— 基于批插入和存储历史等高效CoreData使用示例(一)
20. 数据持久化方案解析(二十) —— 基于批插入和存储历史等高效CoreData使用示例(二)
21. 数据持久化方案解析(二十一) —— SwiftUI App中Core Data和CloudKit之间的数据共享(一)

源码

1. Swift

首先看下工程组织结构

下面一起看下源码

1. CoreDataStack.swift
import CoreData
import CloudKit

final class CoreDataStack: ObservableObject {
  static let shared = CoreDataStack()

  var ckContainer: CKContainer {
    let storeDescription = persistentContainer.persistentStoreDescriptions.first
    guard let identifier = storeDescription?.cloudKitContainerOptions?.containerIdentifier else {
      fatalError("Unable to get container identifier")
    }
    return CKContainer(identifier: identifier)
  }

  var context: NSManagedObjectContext {
    persistentContainer.viewContext
  }

  var privatePersistentStore: NSPersistentStore {
    guard let privateStore = _privatePersistentStore else {
      fatalError("Private store is not set")
    }
    return privateStore
  }

  var sharedPersistentStore: NSPersistentStore {
    guard let sharedStore = _sharedPersistentStore else {
      fatalError("Shared store is not set")
    }
    return sharedStore
  }

  lazy var persistentContainer: NSPersistentCloudKitContainer = {
    let container = NSPersistentCloudKitContainer(name: "MyTravelJournal")

    guard let privateStoreDescription = container.persistentStoreDescriptions.first else {
      fatalError("Unable to get persistentStoreDescription")
    }
    let storesURL = privateStoreDescription.url?.deletingLastPathComponent()
    privateStoreDescription.url = storesURL?.appendingPathComponent("private.sqlite")
    let sharedStoreURL = storesURL?.appendingPathComponent("shared.sqlite")
    guard let sharedStoreDescription = privateStoreDescription.copy() as? NSPersistentStoreDescription else {
      fatalError("Copying the private store description returned an unexpected value.")
    }
    sharedStoreDescription.url = sharedStoreURL

    guard let containerIdentifier = privateStoreDescription.cloudKitContainerOptions?.containerIdentifier else {
      fatalError("Unable to get containerIdentifier")
    }
    let sharedStoreOptions = NSPersistentCloudKitContainerOptions(containerIdentifier: containerIdentifier)
    sharedStoreOptions.databaseScope = .shared
    sharedStoreDescription.cloudKitContainerOptions = sharedStoreOptions
    container.persistentStoreDescriptions.append(sharedStoreDescription)

    container.loadPersistentStores { loadedStoreDescription, error in
      if let error = error as NSError? {
        fatalError("Failed to load persistent stores: \(error)")
      } else if let cloudKitContainerOptions = loadedStoreDescription.cloudKitContainerOptions {
        guard let loadedStoreDescritionURL = loadedStoreDescription.url else {
          return
        }

        if cloudKitContainerOptions.databaseScope == .private {
          let privateStore = container.persistentStoreCoordinator.persistentStore(for: loadedStoreDescritionURL)
          self._privatePersistentStore = privateStore
        } else if cloudKitContainerOptions.databaseScope == .shared {
          let sharedStore = container.persistentStoreCoordinator.persistentStore(for: loadedStoreDescritionURL)
          self._sharedPersistentStore = sharedStore
        }
      }
    }

    container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
    container.viewContext.automaticallyMergesChangesFromParent = true
    do {
      try container.viewContext.setQueryGenerationFrom(.current)
    } catch {
      fatalError("Failed to pin viewContext to the current generation: \(error)")
    }

    return container
  }()

  private var _privatePersistentStore: NSPersistentStore?
  private var _sharedPersistentStore: NSPersistentStore?
  private init() {}
}

// MARK: Save or delete from Core Data
extension CoreDataStack {
  func save() {
    if context.hasChanges {
      do {
        try context.save()
      } catch {
        print("ViewContext save error: \(error)")
      }
    }
  }

  func delete(_ destination: Destination) {
    context.perform {
      self.context.delete(destination)
      self.save()
    }
  }
}

// MARK: Share a record from Core Data
extension CoreDataStack {
  func isShared(object: NSManagedObject) -> Bool {
    isShared(objectID: object.objectID)
  }

  func canEdit(object: NSManagedObject) -> Bool {
    return persistentContainer.canUpdateRecord(forManagedObjectWith: object.objectID)
  }

  func canDelete(object: NSManagedObject) -> Bool {
    return persistentContainer.canDeleteRecord(forManagedObjectWith: object.objectID)
  }

  func isOwner(object: NSManagedObject) -> Bool {
    guard isShared(object: object) else { return false }
    guard let share = try? persistentContainer.fetchShares(matching: [object.objectID])[object.objectID] else {
      print("Get ckshare error")
      return false
    }
    if let currentUser = share.currentUserParticipant, currentUser == share.owner {
      return true
    }
    return false
  }

  func getShare(_ destination: Destination) -> CKShare? {
    guard isShared(object: destination) else { return nil }
    guard let shareDictionary = try? persistentContainer.fetchShares(matching: [destination.objectID]),
      let share = shareDictionary[destination.objectID] else {
      print("Unable to get CKShare")
      return nil
    }
    share[CKShare.SystemFieldKey.title] = destination.caption
    return share
  }

  private func isShared(objectID: NSManagedObjectID) -> Bool {
    var isShared = false
    if let persistentStore = objectID.persistentStore {
      if persistentStore == sharedPersistentStore {
        isShared = true
      } else {
        let container = persistentContainer
        do {
          let shares = try container.fetchShares(matching: [objectID])
          if shares.first != nil {
            isShared = true
          }
        } catch {
          print("Failed to fetch share for \(objectID): \(error)")
        }
      }
    }
    return isShared
  }
}
2. CloudSharingController.swift
import CloudKit
import SwiftUI

struct CloudSharingView: UIViewControllerRepresentable {
  let share: CKShare
  let container: CKContainer
  let destination: Destination

  func makeCoordinator() -> CloudSharingCoordinator {
    CloudSharingCoordinator(destination: destination)
  }

  func makeUIViewController(context: Context) -> UICloudSharingController {
    share[CKShare.SystemFieldKey.title] = destination.caption
    let controller = UICloudSharingController(share: share, container: container)
    controller.modalPresentationStyle = .formSheet
    controller.delegate = context.coordinator
    return controller
  }

  func updateUIViewController(_ uiViewController: UICloudSharingController, context: Context) {
  }
}

final class CloudSharingCoordinator: NSObject, UICloudSharingControllerDelegate {
  let stack = CoreDataStack.shared
  let destination: Destination
  init(destination: Destination) {
    self.destination = destination
  }

  func itemTitle(for csc: UICloudSharingController) -> String? {
    destination.caption
  }

  func cloudSharingController(_ csc: UICloudSharingController, failedToSaveShareWithError error: Error) {
    print("Failed to save share: \(error)")
  }

  func cloudSharingControllerDidSaveShare(_ csc: UICloudSharingController) {
    print("Saved the share")
  }

  func cloudSharingControllerDidStopSharing(_ csc: UICloudSharingController) {
    if !stack.isOwner(object: destination) {
      stack.delete(destination)
    }
  }
}
3. AddDestinationView.swift
import SwiftUI

struct AddDestinationView: View {
  @Environment(\.presentationMode) var presentationMode
  @Environment(\.managedObjectContext) var managedObjectContext
  @State private var caption: String = ""
  @State private var details: String = ""
  @State private var inputImage: UIImage?
  @State private var image: Image?
  @State private var showingImagePicker = false
  private var stack = CoreDataStack.shared

  var body: some View {
    NavigationView {
      Form {
        Section {
          TextField("Caption", text: $caption)
        } footer: {
          Text("Caption is required")
            .font(.caption)
            .foregroundColor(caption.isBlank ? .red : .clear)
        }

        Section {
          TextEditor(text: $details)
        } header: {
          Text("Description")
        } footer: {
          Text("Description is required")
            .font(.caption)
            .foregroundColor(details.isBlank ? .red : .clear)
        }

        Section {
          if image == nil {
            Button {
              self.showingImagePicker = true
            } label: {
              Text("Add a photo")
            }
          }

          image?
            .resizable()
            .scaledToFit()
        } footer: {
          Text("Photo is required")
            .font(.caption)
            .foregroundColor(image == nil ? .red : .clear)
        }

        Section {
          Button {
            createNewDestination()
            presentationMode.wrappedValue.dismiss()
          } label: {
            Text("Save")
          }
          .disabled(caption.isBlank || details.isBlank || image == nil)
        }
      }
      .sheet(isPresented: $showingImagePicker, onDismiss: loadImage) {
        ImagePicker(image: $inputImage)
      }
      .navigationTitle("Add Destination")
    }
  }
}

// MARK: Loading image and creating a new destination
extension AddDestinationView {
  private func loadImage() {
    guard let inputImage = inputImage else { return }
    image = Image(uiImage: inputImage)
  }

  private func createNewDestination() {
    let destination = Destination(context: managedObjectContext)
    destination.id = UUID()
    destination.createdAt = Date.now
    destination.caption = caption
    destination.details = details
    let imageData = inputImage?.jpegData(compressionQuality: 0.8)
    destination.image = imageData
    stack.save()
  }
}

struct AddDestinationView_Previews: PreviewProvider {
  static var previews: some View {
    AddDestinationView()
  }
}
4. HomeView.swift
import SwiftUI

struct HomeView: View {
  @State private var showAddDestinationSheet = false
  @Environment(\.managedObjectContext) var managedObjectContext
  @FetchRequest(sortDescriptors: [SortDescriptor(\.createdAt, order: .reverse)])
  var destinations: FetchedResults<Destination>
  private let stack = CoreDataStack.shared

  var body: some View {
    NavigationView {
      // swiftlint:disable trailing_closure
      VStack {
        List {
          ForEach(destinations, id: \.objectID) { destination in
            NavigationLink(destination: DestinationDetailView(destination: destination)) {
              VStack(alignment: .leading) {
                Image(uiImage: UIImage(data: destination.image ?? Data()) ?? UIImage())
                  .resizable()
                  .scaledToFill()

                Text(destination.caption)
                  .font(.title3)
                  .foregroundColor(.primary)

                Text(destination.details)
                  .font(.callout)
                  .foregroundColor(.secondary)
                  .multilineTextAlignment(.leading)

                if stack.isShared(object: destination) {
                  Image(systemName: "person.3.fill")
                    .resizable()
                    .scaledToFit()
                    .frame(width: 30)
                }
              }
            }
            .swipeActions(edge: .trailing, allowsFullSwipe: false) {
              Button(role: .destructive) {
                stack.delete(destination)
              } label: {
                Label("Delete", systemImage: "trash")
              }
              .disabled(!stack.canDelete(object: destination))
            }
          }
        }
        Spacer()
        Button {
          showAddDestinationSheet.toggle()
        } label: {
          Text("Add Destination")
        }
        .buttonStyle(.borderedProminent)
        .padding(.bottom, 8)
      }
      .emptyState(destinations.isEmpty, emptyContent: {
        VStack {
          Text("No destinations quite yet")
            .font(.headline)
          Button {
            showAddDestinationSheet.toggle()
          } label: {
            Text("Add Destination")
          }
          .buttonStyle(.borderedProminent)
        }
      })
      .sheet(isPresented: $showAddDestinationSheet, content: {
        AddDestinationView()
      })
      .navigationTitle("My Travel Journal")
      .navigationViewStyle(.stack)
    }
  }
}

struct ContentView_Previews: PreviewProvider {
  static var previews: some View {
    HomeView()
  }
}

// MARK: Custom view modifier
extension View {
  func emptyState<EmptyContent>(_ isEmpty: Bool, emptyContent: @escaping () -> EmptyContent) -> some View where EmptyContent: View {
    modifier(EmptyStateViewModifier(isEmpty: isEmpty, emptyContent: emptyContent))
  }
}

struct EmptyStateViewModifier<EmptyContent>: ViewModifier where EmptyContent: View {
  var isEmpty: Bool
  let emptyContent: () -> EmptyContent

  func body(content: Content) -> some View {
    if isEmpty {
      emptyContent()
    } else {
      content
    }
  }
}
5. ImagePicker.swift
import SwiftUI

struct ImagePicker: UIViewControllerRepresentable {
  @Environment(\.presentationMode) var presentationMode
  @Binding var image: UIImage?

  func makeUIViewController(context: UIViewControllerRepresentableContext<ImagePicker>) -> UIImagePickerController {
    let picker = UIImagePickerController()
    picker.delegate = context.coordinator
    return picker
  }

  func updateUIViewController(_ uiViewController: UIImagePickerController, context: UIViewControllerRepresentableContext<ImagePicker>) {
  }

  func makeCoordinator() -> Coordinator {
    Coordinator(self)
  }

  final class Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate {
    let parent: ImagePicker
    init(_ parent: ImagePicker) {
      self.parent = parent
    }

    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) {
      if let uiImage = info[.originalImage] as? UIImage {
        parent.image = uiImage
      }
      parent.presentationMode.wrappedValue.dismiss()
    }
  }
}
6. DestinationDetailView.swift
import CloudKit
import SwiftUI

struct DestinationDetailView: View {
  @ObservedObject var destination: Destination
  @State private var share: CKShare?
  @State private var showShareSheet = false
  @State private var showEditSheet = false
  private let stack = CoreDataStack.shared

  var body: some View {
    // swiftlint:disable trailing_closure
    List {
      Section {
        VStack(alignment: .leading, spacing: 4) {
          if let imageData = destination.image, let image = UIImage(data: imageData) {
            Image(uiImage: image)
              .resizable()
              .scaledToFit()
          }
          Text(destination.caption)
            .font(.headline)
          Text(destination.details)
            .font(.subheadline)
          Text(destination.createdAt.formatted(date: .abbreviated, time: .shortened))
            .font(.footnote)
            .foregroundColor(.secondary)
            .padding(.bottom, 8)
        }
      }

      Section {
        if let share = share {
          ForEach(share.participants, id: \.self) { participant in
            VStack(alignment: .leading) {
              Text(participant.userIdentity.nameComponents?.formatted(.name(style: .long)) ?? "")
                .font(.headline)
              Text("Acceptance Status: \(string(for: participant.acceptanceStatus))")
                .font(.subheadline)
              Text("Role: \(string(for: participant.role))")
                .font(.subheadline)
              Text("Permissions: \(string(for: participant.permission))")
                .font(.subheadline)
            }
            .padding(.bottom, 8)
          }
        }
      } header: {
        Text("Participants")
      }
    }
    .sheet(isPresented: $showShareSheet, content: {
      if let share = share {
        CloudSharingView(share: share, container: stack.ckContainer, destination: destination)
      }
    })
    .sheet(isPresented: $showEditSheet, content: {
      EditDestinationView(destination: destination)
    })
    .onAppear(perform: {
      self.share = stack.getShare(destination)
    })
    .toolbar {
      ToolbarItem(placement: .navigationBarTrailing) {
        Button {
          showEditSheet.toggle()
        } label: {
          Text("Edit")
        }
        .disabled(!stack.canEdit(object: destination))
      }
      ToolbarItem(placement: .navigationBarTrailing) {
        Button {
          if !stack.isShared(object: destination) {
            Task {
              await createShare(destination)
            }
          }
          showShareSheet = true
        } label: {
          Image(systemName: "square.and.arrow.up")
        }
      }
    }
  }
}

// MARK: Returns CKShare participant permission, methods and properties to share
extension DestinationDetailView {
  private func createShare(_ destination: Destination) async {
    do {
      let (_, share, _) = try await stack.persistentContainer.share([destination], to: nil)
      share[CKShare.SystemFieldKey.title] = destination.caption
      self.share = share
    } catch {
      print("Failed to create share")
    }
  }

  private func string(for permission: CKShare.ParticipantPermission) -> String {
    switch permission {
    case .unknown:
      return "Unknown"
    case .none:
      return "None"
    case .readOnly:
      return "Read-Only"
    case .readWrite:
      return "Read-Write"
    @unknown default:
      fatalError("A new value added to CKShare.Participant.Permission")
    }
  }

  private func string(for role: CKShare.ParticipantRole) -> String {
    switch role {
    case .owner:
      return "Owner"
    case .privateUser:
      return "Private User"
    case .publicUser:
      return "Public User"
    case .unknown:
      return "Unknown"
    @unknown default:
      fatalError("A new value added to CKShare.Participant.Role")
    }
  }

  private func string(for acceptanceStatus: CKShare.ParticipantAcceptanceStatus) -> String {
    switch acceptanceStatus {
    case .accepted:
      return "Accepted"
    case .removed:
      return "Removed"
    case .pending:
      return "Invited"
    case .unknown:
      return "Unknown"
    @unknown default:
      fatalError("A new value added to CKShare.Participant.AcceptanceStatus")
    }
  }

  private var canEdit: Bool {
    stack.canEdit(object: destination)
  }
}
7. EditDestinationView.swift
import SwiftUI

struct EditDestinationView: View {
  let destination: Destination
  private var stack = CoreDataStack.shared
  private var hasInvalidData: Bool {
    return destination.caption.isBlank ||
    destination.details.isBlank ||
    (destination.caption == captionText && destination.details == detailsText)
  }

  @State private var captionText: String = ""
  @State private var detailsText: String = ""
  @Environment(\.presentationMode) var presentationMode
  @Environment(\.managedObjectContext) var managedObjectContext

  init(destination: Destination) {
    self.destination = destination
  }

  var body: some View {
    NavigationView {
      VStack {
        VStack(alignment: .leading) {
          Text("Caption")
            .font(.caption)
            .foregroundColor(.secondary)
          TextField(text: $captionText) {}
            .textFieldStyle(.roundedBorder)
        }
        .padding(.bottom, 8)

        VStack(alignment: .leading) {
          Text("Details")
            .font(.caption)
            .foregroundColor(.secondary)
          TextEditor(text: $detailsText)
        }
      }
      .padding()
      .navigationTitle("Edit Destination")
      .toolbar {
        ToolbarItem(placement: .navigationBarTrailing) {
          Button {
            managedObjectContext.performAndWait {
              destination.caption = captionText
              destination.details = detailsText
              stack.save()
              presentationMode.wrappedValue.dismiss()
            }
          } label: {
            Text("Save")
          }
          .disabled(hasInvalidData)
        }
        ToolbarItem(placement: .navigationBarLeading) {
          Button {
            presentationMode.wrappedValue.dismiss()
          } label: {
            Text("Cancel")
          }
        }
      }
    }
    .onAppear {
      captionText = destination.caption
      detailsText = destination.details
    }
  }
}

// MARK: String
extension String {
  var isBlank: Bool {
    self.trimmingCharacters(in: .whitespaces).isEmpty
  }
}
8. Destination+CoreDataClass.swift
import CoreData

@objc(Destination)
public class Destination: NSManagedObject {
}
9. Destination+CoreDataProperties.swift
import CoreData

// MARK: Fetch request and managed object properties
extension Destination {
  @nonobjc
  public class func fetchRequest() -> NSFetchRequest<Destination> {
    return NSFetchRequest<Destination>(entityName: "Destination")
  }

  @NSManaged public var caption: String
  @NSManaged public var createdAt: Date
  @NSManaged public var details: String
  @NSManaged public var id: UUID
  @NSManaged public var image: Data?
}

// MARK: Identifiable
extension Destination: Identifiable {
}
10. AppMain.swift
import SwiftUI

@main
struct AppMain: App {
  @UIApplicationDelegateAdaptor(AppDelegate.self) var delegate
  var body: some Scene {
    WindowGroup {
      HomeView()
        .environment(\.managedObjectContext, CoreDataStack.shared.context)
    }
  }
}
11. AppDelegate.swift
import CloudKit
import SwiftUI

final class AppDelegate: NSObject, UIApplicationDelegate {
  func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
    let sceneConfig = UISceneConfiguration(name: nil, sessionRole: connectingSceneSession.role)
    sceneConfig.delegateClass = SceneDelegate.self
    return sceneConfig
  }
}

final class SceneDelegate: NSObject, UIWindowSceneDelegate {
  func windowScene(_ windowScene: UIWindowScene, userDidAcceptCloudKitShareWith cloudKitShareMetadata: CKShare.Metadata) {
    let shareStore = CoreDataStack.shared.sharedPersistentStore
    let persistentContainer = CoreDataStack.shared.persistentContainer
    persistentContainer.acceptShareInvitations(from: [cloudKitShareMetadata], into: shareStore) { _, error in
      if let error = error {
        print("acceptShareInvitation error :\(error)")
      }
    }
  }
}

后记

本篇主要讲述了使用 SwiftUI AppCore DataCloudKit之间的数据共享,感兴趣的给个赞或者关注~~~

有关数据持久化方案解析(二十二) —— SwiftUI App中Core Data和CloudKit之间的数据共享(二)的更多相关文章

  1. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  2. ruby-on-rails - Rails 应用程序之间的通信 - 2

    我构建了两个需要相互通信和发送文件的Rails应用程序。例如,一个Rails应用程序会发送请求以查看其他应用程序数据库中的表。然后另一个应用程序将呈现该表的json并将其发回。我还希望一个应用程序将存储在其公共(public)目录中的文本文件发送到另一个应用程序的公共(public)目录。我从来没有做过这样的事情,所以我什至不知道从哪里开始。任何帮助,将不胜感激。谢谢! 最佳答案 无论Rails是什么,几乎所有Web应用程序都有您的要求,大多数现代Web应用程序都需要相互通信。但是有一个小小的理解需要你坚持下去,网站不应直接访问彼此

  3. ruby - 通过 ruby​​ 进程共享变量 - 2

    我正在编写一个gem,我必须在其中fork两个启动两个webrick服务器的进程。我想通过基类的类方法启动这个服务器,因为应该只有这两个服务器在运行,而不是多个。在运行时,我想调用这两个服务器上的一些方法来更改变量。我的问题是,我无法通过基类的类方法访问fork的实例变量。此外,我不能在我的基类中使用线程,因为在幕后我正在使用另一个不是线程安全的库。所以我必须将每个服务器派生到它自己的进程。我用类变量试过了,比如@@server。但是当我试图通过基类访问这个变量时,它是nil。我读到在Ruby中不可能在分支之间共享类变量,对吗?那么,还有其他解决办法吗?我考虑过使用单例,但我不确定这是

  4. ruby-on-rails - Rails 模型——非持久类成员或属性? - 2

    对于Rails模型,是否可以/建议让一个类的成员不持久保存到数据库中?我想将用户最后选择的类型存储在session变量中。由于我无法从我的模型中设置session变量,我想将值存储在一个“虚拟”类成员中,该成员只是将值传递回Controller。你能有这样的类(class)成员吗? 最佳答案 将非持久属性添加到Rails模型就像任何其他Ruby类一样:classUser扩展解释:在Ruby中,所有实例变量都是私有(private)的,不需要在赋值前定义。attr_accessor创建一个setter和getter方法:classUs

  5. ruby - #之间? Cooper 的 *Beginning Ruby* 中的错误或异常 - 2

    在Cooper的书BeginningRuby中,第166页有一个我无法重现的示例。classSongincludeComparableattr_accessor:lengthdef(other)@lengthother.lengthenddefinitialize(song_name,length)@song_name=song_name@length=lengthendenda=Song.new('Rockaroundtheclock',143)b=Song.new('BohemianRhapsody',544)c=Song.new('MinuteWaltz',60)a.betwee

  6. ruby - Ruby 有 `Pair` 数据类型吗? - 2

    有时我需要处理键/值数据。我不喜欢使用数组,因为它们在大小上没有限制(很容易不小心添加超过2个项目,而且您最终需要稍后验证大小)。此外,0和1的索引变成了魔数(MagicNumber),并且在传达含义方面做得很差(“当我说0时,我的意思是head...”)。散列也不合适,因为可能会不小心添加额外的条目。我写了下面的类来解决这个问题:classPairattr_accessor:head,:taildefinitialize(h,t)@head,@tail=h,tendend它工作得很好并且解决了问题,但我很想知道:Ruby标准库是否已经带有这样一个类? 最佳

  7. ruby-on-rails - `a ||= b` 和 `a = b if a.nil 之间的区别? - 2

    我正在检查一个Rails项目。在ERubyHTML模板页面上,我看到了这样几行:我不明白为什么不这样写:在这种情况下,||=和ifnil?有什么区别? 最佳答案 在这种特殊情况下没有区别,但可能是出于习惯。每当我看到nil?被使用时,它几乎总是使用不当。在Ruby中,很少有东西在逻辑上是假的,只有文字false和nil是。这意味着像if(!x.nil?)这样的代码几乎总是更好地表示为if(x)除非期望x可能是文字false。我会将其切换为||=false,因为它具有相同的结果,但这在很大程度上取决于偏好。唯一的缺点是赋值会在每次运行

  8. ruby - 我如何添加二进制数据来遏制 POST - 2

    我正在尝试使用Curbgem执行以下POST以解析云curl-XPOST\-H"X-Parse-Application-Id:PARSE_APP_ID"\-H"X-Parse-REST-API-Key:PARSE_API_KEY"\-H"Content-Type:image/jpeg"\--data-binary'@myPicture.jpg'\https://api.parse.com/1/files/pic.jpg用这个:curl=Curl::Easy.new("https://api.parse.com/1/files/lion.jpg")curl.multipart_form_

  9. 世界前沿3D开发引擎HOOPS全面讲解——集3D数据读取、3D图形渲染、3D数据发布于一体的全新3D应用开发工具 - 2

    无论您是想搭建桌面端、WEB端或者移动端APP应用,HOOPSPlatform组件都可以为您提供弹性的3D集成架构,同时,由工业领域3D技术专家组成的HOOPS技术团队也能为您提供技术支持服务。如果您的客户期望有一种在多个平台(桌面/WEB/APP,而且某些客户端是“瘦”客户端)快速、方便地将数据接入到3D应用系统的解决方案,并且当访问数据时,在各个平台上的性能和用户体验保持一致,HOOPSPlatform将帮助您完成。利用HOOPSPlatform,您可以开发在任何环境下的3D基础应用架构。HOOPSPlatform可以帮您打造3D创新型产品,HOOPSSDK包含的技术有:快速且准确的CAD

  10. FOHEART H1数据手套驱动Optitrack光学动捕双手运动(Unity3D) - 2

    本教程将在Unity3D中混合Optitrack与数据手套的数据流,在人体运动的基础上,添加双手手指部分的运动。双手手背的角度仍由Optitrack提供,数据手套提供双手手指的角度。 01  客户端软件分别安装MotiveBody与MotionVenus并校准人体与数据手套。MotiveBodyMotionVenus数据手套使用、校准流程参照:https://gitee.com/foheart_1/foheart-h1-data-summary.git02  数据转发打开MotiveBody软件的Streaming,开始向Unity3D广播数据;MotionVenus中设置->选项选择Unit

随机推荐