Hi had a confusion on what is the scope of the value that is assigned to outside ref variable inside a completion block. For example in the below code will the values of operationError and savedRecords persist outside of completion block.
func applyLocalChangesToServer(insertedOrUpdatedCKRecords:Array<CKRecord>,deletedCKRecordIDs:Array<CKRecordID>) throws
{
var savedRecords:[CKRecord]?
var conflictedRecords:[CKRecord] = [CKRecord]()
var removeRecords:[CKRecord] = [CKRecord]()
var operationError : NSError?
let ckModifyRecordsOperation = CKModifyRecordsOperation(recordsToSave:insertedOrUpdatedCKRecords, recordIDsToDelete: deletedCKRecordIDs);
ckModifyRecordsOperation.atomic = true
ckModifyRecordsOperation.modifyRecordsCompletionBlock = ({(savedRecords1,deletedRecordIDs1,error)->Void in
operationError = error
if error == nil
{
wasSuccessful = true
savedRecords = savedRecords1
}
else
{
wasSuccessful = false
savedRecords = nil
errorCKS = self.handleError(error!)
}
})
ckModifyRecordsOperation.perRecordCompletionBlock = ({(ckRecord,error)->Void in
if error != nil
{
if error!.code == CKErrorCode.ServerRecordChanged.rawValue
{
conflictedRecords.append(ckRecord!)
}
}
})
self.operationQueue?.addOperation(ckModifyRecordsOperation)
self.operationQueue?.waitUntilAllOperationsAreFinished()
if conflictedRecords.count > 0
{
//Do work here
}
else if operationError != nil //Other then the partial error
{
throw operationError
}
}
Note: Had assign operationError since the func applyLocalChangesToServer throws an error and is inside a while loop.
Your assumption is correct, these variables defied in the enclosure scope will be modified after the completion handler is performed. So you code should work as expected.
Also you can use following:
ckModifyRecordsOperation.main()
instead of:
self.operationQueue?.addOperation(ckModifyRecordsOperation)
self.operationQueue?.waitUntilAllOperationsAreFinished()
Hope it helps.
Related
I'll keep this short
PROBLEM:
I dismiss the view controller and call a function on the incoming controller that fires fine. The value even prints to console fine. I assigned the value to local variable and it prints and works fine until I begin updating the other properties on the struct object which is holding the value. In fact, the value is erased entirely from all the object it was stored. The code only touches the value once so I am not sure what is going on.
This is how I am dismissing:
var controller = AddVendersTableViewController()
override func viewDidLoad() {
...
//MARK: Temp
controller.delegate = self
}
#objc func returnToOriginatingController(){
dismiss(animated: true) { [weak self] in
self?.controller.enabledStatusChecker()
print("This is the initiated view controller — \(String(describing: self?.controller))")
}
submitButton.isHidden = true
}
This is the receiving end on the new controller:
var delegate : CompanyAddressDelegate? = nil
func enabledStatusChecker(){
if delegate != nil {
guard let string = delegate?.getCompanyAddress() else {return}
let localString = string
localVenderObject?.address = localString
print(localVenderObject ?? "this is not working")
print("\(self) — This is the current VC ")
}
print(localVenderObject ?? "No Value in enabledStatus")
if localVenderObject?.name != nil && localVenderObject?.phone != nil && localVenderObject?.email != nil && localVenderObject?.website != nil && localVenderObject?.address != nil {
submitButton.isEnabled = true
submitButton.layer.backgroundColor = UIColor.systemBlue.cgColor // temp color
}
}
I know that probably is a very simple question, however I spent a lot of time trying to figure out why is not working, and still doesn't make sense for me, so I need help.
I am learning iOS, I came from Android where I am used to work with objects.
I want to do something similar that I have been doing with Android(maybe this is the problem).
I have created an object with the methods get and set.
private var _token: String!
var error: String {
get {
guard self._error != nil else { return "" }
return _error
}
set {
self._token = newValue
}
}
When I want to manipulate this object, is when I am having the problem.
let objectCreated = ObjectCreated()
guard let errorReceived = (xml["Whatever"]["Whatever"].element?.text) else { return }
print(errorReceived)
objectCreated.error = errorReceived
print(objectCreated.error)
The first print is printing the correct String, but the second is printing "". So the set method is not doing his job.
Isn't it supposed to be
...
guard self._token != nil else { return "" }
return _token
...
This can be simplified with the nil-coalescing operator
var error: String {
get {
return self._token ?? ""
}
set {
self._token = newValue
}
}
Note: Variable names with leading underscores are unusual in Swift.
private var _error: String!
var error: String {
get {
guard self._error != nil else { return "" }
return _error
}
set {
self._error = newValue
}
}
self._token = newValue >>>> self._error = newValue
Following is the code used by me. With this code deinit is not called, but if I comment out this line weakSelf?.tableView.reloadData() from code deinit gets called. Am I doing something wrong?
ZLNetworkHelper.sharedManager.getUserSavedAddress { (response) in
print("getUserSavedAddressFinished")
ZLProgressIndicator.stopAnimation()
if response.isSuccess && response.value != nil {
weak var weakSelf = self
guard weakSelf != nil else {
return
}
weakSelf!.address = response.value!.sorted(by: {$0.isDefault && !$1.isDefault})
weakSelf!.isExistingAddressSectionExpanded = false
if weakSelf!.address.count == 0 {
weakSelf!.title = LocalizationUtility.RCLocalizedString("ADD_ADDRESS")
}
DispatchQueue.main.async {
weakSelf!.tableView.reloadData()
}
if completion != nil {
completion!(true)
}
}
else {
let message = response.error?.localizedDescription
ZLCustomAlertVC.presentAlertInVC(self, withErrorMessage:message)
}
}
You want to capture self weakly in the closure like:
getUserSavedAddress { [weak self] (response) in
When you capture it later, you're still grabbing a reference to self in the closure.
Try the implementation like this:
ZLNetworkHelper.sharedManager.getUserSavedAddress { [weak self] (response) in
DispatchQueue.main.async {
print("getUserSavedAddressFinished")
ZLProgressIndicator.stopAnimation()
if response.isSuccess && response.value != nil {
self?.address = response.value!.sorted(by: {$0.isDefault && !$1.isDefault})
self?.isExistingAddressSectionExpanded = false
if self?.address.count == 0 {
self?.title = LocalizationUtility.RCLocalizedString("ADD_ADDRESS")
}
self?.tableView.reloadData()
if completion != nil {
completion!(true)
}
}
else {
let message = response.error?.localizedDescription
ZLCustomAlertVC.presentAlertInVC(self, withErrorMessage:message)
}
}
}
(I've only updated this on SO, so you may need to unwrap, etc. as needed)
You can use the code given by Fred Faust but use weakself in the else part also where you present the alert.
As I understand it, the CKModifyRecordsOperation(recordsToSave:, recordsToDelete:) method should make it possible to modify multiple records and delete multiple records all at the same time.
In my code, recordsToSave is an array with 2 CKRecords. I have no records to delete, so I set recordsToDelete to nil. Perplexingly enough, it appears that recordsToSave[0] gets saved to the cloud properly while recordsToSave[1] does not.
To give some more context before I paste my code:
In my app, there's a "Join" button associated with every post on a feed. When the user taps the "Join" button, 2 cloud transactions occur: 1) the post's reference gets added to joinedList of type [CKReference], and 2) the post's record should increment its NUM_PEOPLE property. Based on the CloudKit dashboard, cloud transaction #1 is occurring, but not #2.
Here is my code, with irrelevant parts omitted:
#IBAction func joinOrLeaveIsClicked(_ sender: Any) {
self.container.fetchUserRecordID() { userRecordID, outerError in
if outerError == nil {
self.db.fetch(withRecordID: userRecordID!) { userRecord, innerError in
if innerError == nil {
var joinedList: [CKReference]
if userRecord!.object(forKey: JOINED_LIST) == nil {
joinedList = [CKReference]() // init an empty list
}
else {
joinedList = userRecord!.object(forKey: JOINED_LIST) as! [CKReference]
}
let ref = CKReference(recordID: self.post.recordID, action: .none)
// ... omitted some of the if-else if-else ladder
// add to list if you haven't joined already
else if !joinedList.contains(ref) {
// modifying user record
joinedList.append(ref) // add to list
userRecord?[JOINED_LIST] = joinedList as CKRecordValue // associate list with user record
// modifying post
let oldCount = self.post.object(forKey: NUM_PEOPLE) as! Int
self.post[NUM_PEOPLE] = (oldCount + 1) as CKRecordValue
let operation = CKModifyRecordsOperation(recordsToSave: [userRecord!, self.post], recordIDsToDelete: nil)
self.db.add(operation)
}
// omitted more of the if-else if-else ladder
else {
if let error = innerError as? CKError {
print(error)
}
}
}
}
else {
if let error = outerError as? CKError {
print(error)
}
}
}
}
EDIT
Here's the code I added per the request of the first commenter
operation.modifyRecordsCompletionBlock = { savedRecords, deletedRecordsIDs, error in
if error == nil {
DispatchQueue.main.async(execute: {
self.num.text = String(oldCount + 1) // UI update
})
}
else {
print(error!)
}
}
ANOTHER EDIT
let operation = CKModifyRecordsOperation(recordsToSave: [userRecord!, self.post], recordIDsToDelete: nil)
operation.perRecordCompletionBlock = { record, error in
if error != nil {
let castedError = error as! NSError
print(castedError)
}
}
operation.modifyRecordsCompletionBlock = { savedRecords, deletedRecordsIDs, error in
if error == nil {
DispatchQueue.main.async(execute: {
self.num.text = String(oldCount + 1) // UI update
})
}
else {
print(error!)
}
}
self.db.add(operation)
I have a Realm Object
class CoursesModel: Object {
dynamic var courseName = ""
dynamic var par3Field = 0
dynamic var par4Field = 0
dynamic var par5Field = 0
}
When somebody enters the course name I want to check whether it already exists before writing it to Realm.
Can you please tell me what I'm doing wrong because it doesn't seem to loop through.
class func compareCourse(name : String) -> Bool {
let c = name
do
{
let realm = try Realm()
let course = realm.objects(CoursesModel)
for course in course {
if course == c {
print("course = \(course)")
print("c = \(c)")
return true
}
else {
return false
}
}
}
catch
{
// return nil
}
return false
}
Any help will be greatly appreciated.
EDIT - WORKING CODE HERE
class func compareCourse(name : String) -> Bool {
let c = name
do
{
let realm = try Realm()
let course = realm.objects(CoursesModel)
for course in course {
let a = course.courseName
print("Model Course = \(a)")
print("Passed Course = \(c)")
if a == c {
return true
}
}
}
catch
{
// return nil
}
return false
}
You are returning in both branches of the loop, which immediately exits out of the function. You do not want to return false on the first failure, but only after all have failed (I think).