field `accessory` of type `Transaction` is not invalidated (moved or destroyed). not invalidated - flow cadence - cadence

I'm developing a game and I have an avatar resource with the following definition that stores accessory NFTs and includes an equip function that returns any accessory that was existing in the slot
pub resource Avatar {
pub let id: UInt64
pub let accessories: #{String: Accessory}
init() {
self.id = Avatar.totalMinted + 1
self.accessories <- {}
Avatar.totalMinted = Avatar.totalMinted + 1
}
destroy () {
Avatar.totalAvatars = Avatar.totalAvatars - 1
destroy self.accessories
}
pub fun equip(accessory: #Accessory): #Accessory? {
// Shift accessory into accessories dictionary and return old accessory
let oldAccessory <- self.accessories[accessory.getAccessoryData().slot] <- accessory
return <- oldAccessory
}
}
Now in my transaction I get the accessory from my collection and equip it, making sure i deposit the old accessory in the slot if that exists.
import Avatar from 0x03
import NonFungibleToken from 0x01
transaction(avatarId: UInt64, accessoryId: UInt64) {
let collection: &Avatar.Collection
let avatar: auth &Avatar.Avatar
let accessory: #Avatar.Accessory
prepare(signer: AuthAccount) {
self.collection = signer.borrow<&Avatar.Collection>(from: Avatar.CollectionStoragePath) ?? panic("could not borrow collection")
self.avatar = self.collection.borrowAuth(id: avatarId) as? auth &Avatar.Avatar ?? panic("problem")
self.accessory <- self.collection.withdraw(withdrawID: accessoryId) as! #Avatar.Accessory
}
execute {
if let oldAccessory <- self.avatar.equip(accessory: <- self.accessory) {
self.collection.deposit(token: <- oldAccessory)
}
}
}
I get the error field accessory of type Transaction is not invalidated (moved or destroyed). not invalidated - flow cadence
I have a feeling the type checker may not understand that the resource should be moved/destroyed by the end of this but maybe it's confused since it hands over the custody to Avatar.
Am I missing something?

Have you tried not using if let here? I am not sure that the compiler is smart enough to understand that the return from equip is nil even though you use if let

Related

Run CKQueryOperation with results from previous CKQueryOperation

I have an app that is a shopping list. I can store prices per product and vendor in my app, the model is
Product
Vendor
Price
One product can have multiple prices from different vendors.
I store the price information with references to the product and vendor (CKRecord.Reference).
Now I am using the below to fetch all the prices related to a product:
public func fetchDataByProduct(product: Product, completionHandler: #escaping (Bool) -> Void){
self.pricesBuffer = []
let cloudContainer = CKContainer.init(identifier: "iCloud.XYZ")
let publicDatabase = cloudContainer.publicCloudDatabase
let reference = CKRecord.Reference(recordID: product.recordID, action: .deleteSelf)
let predicate = NSPredicate(format: "priceToProduct == %#", reference)
let query = CKQuery(recordType: "Price", predicate: predicate)
let operation = CKQueryOperation(query: query)
operation.recordFetchedBlock = { record in
let price = Price()
price.recordID = record.recordID
price.grossPrice = record.object(forKey: "grossPrice") as? Double
let dummy = record.object(forKey: "priceToVendor") as! CKRecord.Reference
price.vendorRecordID = dummy.recordID
self.pricesBuffer.append(price)
}
operation.queryCompletionBlock = { [unowned self] (cursor, error) in
self.pricesBuffer.forEach({price in
price.retrieveVendor()
})
DispatchQueue.main.async {
if error == nil {
self.prices = self.pricesBuffer
completionHandler(true)
} else {
}
}
}
publicDatabase.add(operation)
}
My problem is now that I cannot retrieve the vendor name which is part of the Vendor object (Vendor.name).
I have tried to loop over the pricesBuffer and run this one per price but the problem seems to be that CloudKit first completes the initial request to fetchDataByProduct() and then afterwards fetches the vendor data but then its too late because that updated data does not get pushed to my View (SwiftUI).
publicDatabase.fetch(withRecordID: self.vendorRecordID, completionHandler:  {[unowned self] record, error in
if let record = record {
print(record)
self.vendor.recordID = record.recordID
self.vendor.name = record["name"] as! String
print(self.vendor.name)
}
})
Any ideas how to solve this? I believe I have to add a second CKQueryOperation to the mix and use the .addDependency() but I cannot wrap my head around how that should look like in the end.
Let's say you use the operation to fetch prices like above.
let predicate = NSPredicate(format: "priceToProduct == %#", reference)
let query = CKQuery(recordType: "Price", predicate: predicate)
let pricesOperation = CKQueryOperation(query: query)
pricesOperation.database = publicDatabase // not required if you don't use OperationQueue
Then you can construct operation to fetch vendors, I will create simple operation for demo purposes.
let vendorQuery = CKQuery(recordType: "Vendor", predicate: predicate)
let vendorsOperation = CKQueryOperation(query: vendorQuery)
vendorsOperation.database = publicDatabase // not required if you don't use OperationQueue
Then you can set dependency, first fetch prices than vendors.
vendorsOperation.addDependency(pricesOperation)
And lastly submit those operations to OperationQueue
let operationQueue = OperationQueue()
operationQueue.addOperations([pricesOperation, vendorsOperation], waitUntilFinished: false)
Edit: If you don't want to use OperationQueue, simply submit those operations to database, but first set dependency before submitting the operations to be executed.
vendorsOperation.addDependency(pricesOperation)
publicDatabase.add(pricesOperation)
publicDatabase.add(vendorsOperation)

Cannot fetch multiple CKReference records from public Database in a for loop

I have a contact CKRecord with many location CKRecords ( 1 to many relationship)
Both contact CKRecord and Location CKRecord are created in public Database. I add CKReference fro contact to locaiotn via a field named owningContact on location.
ckRecord["owningContact"] = CKReference(record: contactRecord!, action: .deleteSelf)
I go to cloudKit dashboard and verify both the records exist. The location CKRecord has field owningContact that has the recordName of the contact CKRecord. I defined a function to get locations like this:
private func iCloudFetchLocations(withContactCKRecord: CKRecord, completionHandler: #escaping ([CKRecord]?, Error?) -> Void) {
var records = [CKRecord]()
let recordToMatch = CKReference(recordID: withContactCKRecord.recordID, action: .deleteSelf)
let predicate = NSPredicate(format: "owningContact == %#", recordToMatch)
// Create the query object.
let query = CKQuery(recordType: "location", predicate: predicate)
let queryOp = CKQueryOperation(query: query)
queryOp.resultsLimit = 1
queryOp.qualityOfService = .userInteractive
queryOp.recordFetchedBlock = {
records.append($0)
print($0)
}
queryOp.queryCompletionBlock = { (cursor, error) in
guard error == nil else {
if let ckerror = error as? CKError {
self.aErrorHandler.handleCkError(ckerror: ckerror)
}
return
}
if (cursor != nil) {
let newOperation = CKQueryOperation(cursor: cursor!)
newOperation.resultsLimit = queryOp.resultsLimit
newOperation.recordFetchedBlock = queryOp.recordFetchedBlock
newOperation.queryCompletionBlock = queryOp.queryCompletionBlock
self.publicDB?.add(newOperation)
}
completionHandler(records, error)
}
self.publicDB?.add(queryOp)
}
Then I call the code to fetch location CKRecord based on contact CKRecord like this:
let predicate = NSPredicate(format: "TRUEPREDICATE")
let query = CKQuery(recordType: Cloud.Entity.Contact, predicate: predicate)
publicDB?.perform(query, inZoneWith: nil, completionHandler: { (records, error) in
guard error == nil else {
if let ckerror = error as? CKError {
self.aErrorHandler.handleCkError(ckerror: ckerror)
}
return
completion(false)
}
if let contactRecords = records {
for aContactRecord in contactRecords {
// fetch Location Data
self.iCloudFetchLocations(withContactCKRecord: aContactRecord, completionHandler: { records, error in
guard error == nil else {
if let ckerror = error as? CKError {
self.aErrorHandler.handleCkError(ckerror: ckerror)
}
return
completion(false)
}
if let locationRecords = records {
}
})
}
}
})
I have two contacts the first one has been CKReferenc'ed to the location, where as the second contact is still not yet CKReferenc'ed to the location.
I think here is the problem: First time in the loop contact CKRecord information is sent by calling iCloudFetchLocations which returns immediately without waiting for cloud response, and the for loop sends the second contact and calls iCloudFetchLocations again. Since the second contact has no CKReference to the location, the call fails and I can never get to the first contact's location since it hasn't returned yet.
How to fix this?
I found that I had not set the CKReference field: owningContact as Queryable. The way I found out is printing error like this: 
if let ckerror = error as? CKError {
print(ckerror.userInfo)
print(ckerror.errorUserInfo)
self.aErrorHandler.handleCkError(ckerror: ckerror)
}
As soon as I did that it started working, Since I was in a for loop it was timing out on previous fetch I think.

How to Use Core Data Models with In-Memory Store Type Using Prepopulated SQLite File?

I am following an outdated tutorial from the Ray Wenderlich team that walks through the repopulation of a Core Data-backed application by using a Command Line Tool application.
I have successfully prepopulated the intended entities, verified by performing an NSFetchRequest.
Now, I want to use the same prepopulated data in my unit tests to verify that my interactions with CoreData are happening correctly. I tried setting up my mocked CoreDataStack subclass to use an in-memory store, but when I attempt to verify that I have the prepopulated data for use in my unit tests, I am getting a count of 0.
The class responsible for interacting with CoreData in my application's target, named CoreDataStack, follows:
/// The object that is responsible for managing interactions with Core Data.
internal class CoreDataStack {
// MARK: - Properties
/// The name of the `NSManagedObjectModel` object used for storing information with Core Data.
private let modelName: String
/// The `NSManagedObjectContext` object that is associated with the main queue.
internal lazy var mainContext: NSManagedObjectContext = {
return self.storeContainer.viewContext
}()
/// The `NSPersistentContainer` object that encapsulates the application's Core Data stack.
internal lazy var storeContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: self.modelName)
let directory = NSPersistentContainer.defaultDirectoryURL()
let storeURL = directory.appendingPathComponent("\(self.modelName).sqlite")
if !FileManager.default.fileExists(atPath: (storeURL.path)) {
guard let populatedURL = Bundle.main.url(forResource: self.modelName, withExtension: "sqlite") else {
fatalError("Invalid populated .sqlite file URL")
}
do {
try FileManager.default.copyItem(at: populatedURL, to: storeURL)
} catch {
fatalError("Error: \(error)")
}
}
let description = NSPersistentStoreDescription()
description.url = storeURL
container.persistentStoreDescriptions = [description]
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Error: \(error)")
}
})
return container
}()
// MARK: - Initialization
/// Returns an instance of `CoreDataStack`.
/// - parameter modelName: The name of the `NSManagedObjectModel` object used for storing information with Core Data.
internal init(modelName: String) {
self.modelName = modelName
}
/// Attempts to save items to Core Data by committing changes to `NSManagedObject`s in a `NSManagedObjectContext`.
/// - parameter context: The `NSManagedObjectContext` of which changes should be committed.
internal func saveContext(_ context: NSManagedObjectContext) {
context.perform {
do {
try context.save()
} catch let error as NSError {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
}
}
}
The subclass of CoreDataStack, MockCoreDataStack, used for testing follows:
internal class MockCoreDataStack: CoreDataStack {
// MARK: - Initialization
convenience init() {
self.init(modelName: "Currency")
}
override init(modelName: String) {
super.init(modelName: modelName)
let container = NSPersistentContainer(name: modelName)
let directory = NSPersistentContainer.defaultDirectoryURL()
let storeURL = directory.appendingPathComponent("\(modelName).sqlite")
if !FileManager.default.fileExists(atPath: (storeURL.path)) {
guard let populatedURL = Bundle(for: type(of: self)).url(forResource: modelName, withExtension: "sqlite") else {
fatalError("Invalid populated .sqlite file URL")
}
do {
try FileManager.default.copyItem(at: populatedURL, to: storeURL)
} catch {
fatalError("Error: \(error)")
}
}
let description = NSPersistentStoreDescription()
description.url = storeURL
description.type = NSInMemoryStoreType
container.persistentStoreDescriptions = [description]
container.loadPersistentStores { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
}
self.storeContainer = container
}
}
The resulting count of my fetch request is 0 in my unit tests target. I expect to return a count consisting of the number of prepopulated objects, just as I get when I return the count in my application's target.
What am I doing incorrectly that is causing me to not return the expected result?
The in memory store doesn't use the store URL. It just creates an empty store in memory.
As an alternative to the in memory store, you could possibly create a parent NSManagedObjectContext between the persistent store and the contexts you actually use. (I'm not sure how that would play with NSPersistentContainer, however.)
Then you can just rollback() the parent context when you want to reset back to your initial state.

RealmSwift update object in different threads(closures)

Kindly help with Real objects adding/updating
I'd like store and update User class.
User class consist of Client class,
Client class consist of Avatar property and Rooms List.
Issue is that I'm facing error "The Realm is already in a write transaction" because my Client class avatar property and rooms list are fetched and pushed to Realm in different closures at the same time.
func fetchRooms() {
roomsDelegate?.contactRooms(entityID: entityID,
success: {rooms in
self.addRooms(rooms: rooms)
},
fail: { error in
print (error)
})
}
func addRooms(rooms: [VMRoom]?) {
if let r = rooms {
do{
try realm?.write {
realm?.add(r, update: true)
self.rooms.append(objectsIn: r)
} }
catch let e {
print(e.localizedDescription)
}
}
}
func getAvatarURL() {
do{
try realm?.write {
avatarURL = avatarDelegate?.contactAvatarURL(eExtention: eExtention)
} }
catch let e {
print(e.localizedDescription)
}
}
Like Realm is saying, you've got an error in your app's logic if you're trying to open up two write transactions in one thread. I'd recommend you review your logic to see if you can make it more streamlined.
But in any case, to fix your current code, one way to mitigate this would be to check you're not already in a write transaction when you're setting the avatar URL.
func getAvatarURL() {
let inWriteTransaction = realm?.isInWriteTransaction
do {
if !inWriteTransaction {
realm?.beginWrite()
}
avatarURL = avatarDelegate?.contactAvatarURL(eExtention: eExtention)
if !inWriteTransaction {
try realm.commitWrite()
}
catch let e {
print(e.localizedDescription)
}
}

Pusher Api for Swift 3 crashes while connecting

I have integrated the Pusher framework for my application in Swift 3 using cocoa pods [ pod 'PusherSwift' ].
These are the lines of code :
let pusher = Pusher(key: "XXXXXXXXXXXXXXXXXXXX")
// subscribe to channel and bind to event
let channel = pusher.subscribe("test_channel")
let _ = channel.bind(eventName: "my_event", callback: { (data: Any?) -> Void in
if let data = data as? [String : AnyObject] {
if let message = data["message"] as? String {
print(message)
}
}
})
pusher.connect()
The app crashes at pusher.connect() at the line - self.delegate?.debugLog?(message: "[PUSHER DEBUG] Network reachable"). No crash report is shown.
open lazy var reachability: Reachability? = {
let reachability = Reachability.init()
reachability?.whenReachable = { [unowned self] reachability in
self.delegate?.debugLog?(message: "[PUSHER DEBUG] Network reachable")
if self.connectionState == .disconnected || self.connectionState == .reconnectingWhenNetworkBecomesReachable {
self.attemptReconnect()
}
}
reachability?.whenUnreachable = { [unowned self] reachability in
self.delegate?.debugLog?(message: "[PUSHER DEBUG] Network unreachable")
}
return reachability
}()
This looks like you might be getting bitten by the same issue described here.
I think it's that the PusherConnection object is taken as unowned into the reachability closure but because you're not keeping a reference to the Pusher instance outside of the viewDidLoad function then the connection object gets cleaned up whereas the reachability object does not.
So, to fix this you probably need to declare the pusher object outside of the function where you instantiate it, so that it hangs around. e.g.
class ViewController: UIViewController, PusherDelegate {
var pusher: Pusher! = nil
...
and then within viewDidLoad do pusher = Pusher(... as normal.
I don't think you need to use pusher.connect().
See for example detailed docs:
let pusher = Pusher(key: "YOUR_APP_KEY")
let myChannel = pusher.subscribe("my-channel")
myChannel.bind(eventName: "new-price", callback: { (data: Any?) -> Void in
if let data = data as? [String : AnyObject] {
if let price = data["price"] as? String, company = data["company"] as? String {
print("\(company) is now priced at \(price)")
}
}
})
Alternatively try this first and see if it connects:
let pusher = Pusher(key: "XXXXXXXXXXXXXXXXXXXX")
pusher.connect()
Then bind to your channel.