| 版本号 | 时间 |
|---|---|
| V1.0 | 2022.05.30 星期一 |
数据的持久化存储是移动端不可避免的一个问题,很多时候的业务逻辑都需要我们进行本地化存储解决和完成,我们可以采用很多持久化存储方案,比如说
plist文件(属性列表)、preference(偏好设置)、NSKeyedArchiver(归档)、SQLite 3、CoreData,这里基本上我们都用过。这几种方案各有优缺点,其中,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. 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 App中Core Data和CloudKit之间的数据共享,感兴趣的给个赞或者关注~~~

我主要使用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
我构建了两个需要相互通信和发送文件的Rails应用程序。例如,一个Rails应用程序会发送请求以查看其他应用程序数据库中的表。然后另一个应用程序将呈现该表的json并将其发回。我还希望一个应用程序将存储在其公共(public)目录中的文本文件发送到另一个应用程序的公共(public)目录。我从来没有做过这样的事情,所以我什至不知道从哪里开始。任何帮助,将不胜感激。谢谢! 最佳答案 无论Rails是什么,几乎所有Web应用程序都有您的要求,大多数现代Web应用程序都需要相互通信。但是有一个小小的理解需要你坚持下去,网站不应直接访问彼此
我正在编写一个gem,我必须在其中fork两个启动两个webrick服务器的进程。我想通过基类的类方法启动这个服务器,因为应该只有这两个服务器在运行,而不是多个。在运行时,我想调用这两个服务器上的一些方法来更改变量。我的问题是,我无法通过基类的类方法访问fork的实例变量。此外,我不能在我的基类中使用线程,因为在幕后我正在使用另一个不是线程安全的库。所以我必须将每个服务器派生到它自己的进程。我用类变量试过了,比如@@server。但是当我试图通过基类访问这个变量时,它是nil。我读到在Ruby中不可能在分支之间共享类变量,对吗?那么,还有其他解决办法吗?我考虑过使用单例,但我不确定这是
对于Rails模型,是否可以/建议让一个类的成员不持久保存到数据库中?我想将用户最后选择的类型存储在session变量中。由于我无法从我的模型中设置session变量,我想将值存储在一个“虚拟”类成员中,该成员只是将值传递回Controller。你能有这样的类(class)成员吗? 最佳答案 将非持久属性添加到Rails模型就像任何其他Ruby类一样:classUser扩展解释:在Ruby中,所有实例变量都是私有(private)的,不需要在赋值前定义。attr_accessor创建一个setter和getter方法:classUs
在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
有时我需要处理键/值数据。我不喜欢使用数组,因为它们在大小上没有限制(很容易不小心添加超过2个项目,而且您最终需要稍后验证大小)。此外,0和1的索引变成了魔数(MagicNumber),并且在传达含义方面做得很差(“当我说0时,我的意思是head...”)。散列也不合适,因为可能会不小心添加额外的条目。我写了下面的类来解决这个问题:classPairattr_accessor:head,:taildefinitialize(h,t)@head,@tail=h,tendend它工作得很好并且解决了问题,但我很想知道:Ruby标准库是否已经带有这样一个类? 最佳
我正在检查一个Rails项目。在ERubyHTML模板页面上,我看到了这样几行:我不明白为什么不这样写:在这种情况下,||=和ifnil?有什么区别? 最佳答案 在这种特殊情况下没有区别,但可能是出于习惯。每当我看到nil?被使用时,它几乎总是使用不当。在Ruby中,很少有东西在逻辑上是假的,只有文字false和nil是。这意味着像if(!x.nil?)这样的代码几乎总是更好地表示为if(x)除非期望x可能是文字false。我会将其切换为||=false,因为它具有相同的结果,但这在很大程度上取决于偏好。唯一的缺点是赋值会在每次运行
我正在尝试使用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_
无论您是想搭建桌面端、WEB端或者移动端APP应用,HOOPSPlatform组件都可以为您提供弹性的3D集成架构,同时,由工业领域3D技术专家组成的HOOPS技术团队也能为您提供技术支持服务。如果您的客户期望有一种在多个平台(桌面/WEB/APP,而且某些客户端是“瘦”客户端)快速、方便地将数据接入到3D应用系统的解决方案,并且当访问数据时,在各个平台上的性能和用户体验保持一致,HOOPSPlatform将帮助您完成。利用HOOPSPlatform,您可以开发在任何环境下的3D基础应用架构。HOOPSPlatform可以帮您打造3D创新型产品,HOOPSSDK包含的技术有:快速且准确的CAD
本教程将在Unity3D中混合Optitrack与数据手套的数据流,在人体运动的基础上,添加双手手指部分的运动。双手手背的角度仍由Optitrack提供,数据手套提供双手手指的角度。 01 客户端软件分别安装MotiveBody与MotionVenus并校准人体与数据手套。MotiveBodyMotionVenus数据手套使用、校准流程参照:https://gitee.com/foheart_1/foheart-h1-data-summary.git02 数据转发打开MotiveBody软件的Streaming,开始向Unity3D广播数据;MotionVenus中设置->选项选择Unit