This function is not acting as expected. I'm trying to nil a set of fields.The earlier section gets the correct field names, and is used in other functions. I've got about ten tables, and they all share the same context, in case that matters.
The first unexpected thing is that "yes, changes" never runs, so I presume that the settings object is detached from its context. Or perhaps CoreData treats nil as some kind of exception to triggering the .hasChanges flag?
When it runs, the save throws no errors, and the object displays as expected, displayed with the values set to nil. But there are no changes in the db.
I can save data into these fields without problem, and confirm that in the db; this problem only happens with setting the value to nil.
static func clearSettings(_ s : Set<PaletteElementType>? = nil) {
guard var setting = activeSetting() else {return}
print(setting.id)
let cats = s ?? PaletteView.compCatButtons
let tgts = setting.getAssociatedFieldNames(tgts: cats, clearing : true, values: false)
for (key, val) in tgts {
var src = Set((val as? Dictionary<FieldNameSuffixes, String>)!.values)
if key == .catBgndButton {
src = src.union(["opacity", "opacityStart", "opacityStartDelta","opacityEnd", "opacityEndDelta", "opacityTimer"])
}
for s in src {
print(s)
setting.setNilValueForKey(s)
if Blocks.context!.hasChanges {
print("yes, changes")
}
do {
try Blocks.context!.save()
print("deleted \(setting.value(forKey: s))")
} catch { print("deadly dogs")}
}
print("val is \(setting)")
}
}
OK, working when I do it this way:
static func clearSettings(_ s : Set<PaletteElementType>? = nil) {
guard var setting = activeSetting() else {return}
print(setting.id)
let cats = s ?? PaletteView.compCatButtons
let tgts = setting.getAssociatedFieldNames(tgts: cats, clearing : true, values: false)
for (key, val) in tgts {
var src = Set((val as? Dictionary<FieldNameSuffixes, String>)!.values)
if key == .catBgndButton {
src = src.union(["opacity", "opacityStart", "opacityStartDelta","opacityEnd", "opacityEndDelta", "opacityTimer"])
}
for n in setting.entity.attributesByName.enumerated() {
if src.contains( n.element.key as String) {
print("found one")
setting.setNilValueForKey(n.element.key)
}
}
do {
try Blocks.context!.save()
} catch {print("bumpy beasts")}
print("val is \(setting)")
}
}
Happy it's working, but I don't really understand the distinction here. What is the better way to handle this? I'm not chasing some super performant code, so I don't mind a few extra loops... but what's the deal?
Related
This may be tricky question so maybe someone here can help unless what I'm trying to do is forbidden by design.
So, I am fetching all the cloudkit records in packs to avoid 400 records limit. My function is:
func ck_queryAllStations(in_private:Bool) -> [CKRecord] {
var records = [CKRecord]()
let pred = NSPredicate(value: true)
let query = CKQuery(recordType:"RadioStation", predicate: pred)
let pDatabase:CKDatabase
if in_private == true {
pDatabase = CKContainer.default().privateCloudDatabase
} else {
pDatabase = CKContainer.default().publicCloudDatabase
}
let queryOperation = CKQueryOperation(query: query)
queryOperation.qualityOfService = .userInitiated
queryOperation.recordFetchedBlock = { record in
records.append(record)
print("rekord dopisany do tablicy\n")
}
queryOperation.queryCompletionBlock = { cursor,error in
if cursor != nil {
print("Jest Wiecej danych do pobrania - \(String(describing: cursor))")
self.fetchRecordsCloudKit(cursor: cursor!,in_private: in_private)
} else {
// print(self.records)
}
}
pDatabase.add(queryOperation)
return records
}
However,to achieve this I must use helper recursive function that is defined as below:
func fetchRecordsCloudKit(cursor:CKQueryOperation.Cursor?,in_private:Bool) {
print("Funkcja fetchRecordsCloudKit uruchomiona\n")
let pDatabase:CKDatabase
if in_private == true {
pDatabase = CKContainer.default().privateCloudDatabase
} else {
pDatabase = CKContainer.default().publicCloudDatabase
}
let queryoperation = CKQueryOperation(cursor: cursor!)
// queryoperation.qualityOfService = .userInitiated
queryoperation.recordFetchedBlock = { record in
records.append(record) // THIS LINE GIVES ERROR
print("rekord dopisany do tablicy")
}
queryoperation.queryCompletionBlock = { cursor,error in
if cursor != nil {
print("Wiecej danych do pobrania")
print(cursor as Any)
self.fetchRecordsCloudKit(cursor: cursor!,in_private: in_private)
}
}
pDatabase.add(queryoperation)
}
Problem is self.fetchRecordsCloudKit(cursor: cursor!,in_private: in_private) does not know anything about var records = [CKRecord]() variable.
If function is launch inside function, shouldn't it have access to all its variables the same way as class' method have acces to all class global variables?
I tried to pass records variable to recursive function as inout parameter, but apparently inout parameters in closures are forbidden since Swift 3.
So what to do? Is the only solution moving records array outside anything? In my mind that looks dirty hack to make array available globally BEFORE each asynchronous operation finish.
unable to build swift project because of this error.
// showing error with inputs.flatmap
fileprivate func makeShippingAddressDictWith(inputs: [TextFieldData]) -> [String: String] {
var shippingDict: [String: String] = [:]
let _ = inputs.flatMap { input in
if let shippingFieldType = input.type as? ShippingDictKeyable.Type {
shippingDict[shippingFieldType.shippingDictKey] = input.text
}
return nil
}
// FIXME: these empty values are the result of a poorly designed request in GDKECommerce
shippingDict["email"] = ""
shippingDict["second_name"] = ""
shippingDict["suffix"] = ""
shippingDict["title"] = ""
shippingDict["salutation"] = ""
shippingDict["company_name"] = ""
return shippingDict
}
}
You could use .forEach instead of .flatMap. Then you would not have to worry about a return type that you are ignoring anyway (with let _ =).
Combining this with a filter would produce a cleaner functional statement if that's what you're after:
inputs.map{ ( $0.text, $0.type as? ShippingDictKeyable.Type) }
.filter{ $1 != nil }
.forEach{ shippingDict[$1!.shippingDictKey] = $0 }
// FIXME: these empty values are the result of a poorly designed request in GDKECommerce
let blankAttributes = ["email", "second_name", "suffix", "title", "salutation", "company_name"]
blankAttributes.forEach{ shippingDict[$0] = "" }
Or use a for loop as suggested by Hamish.
If performance is a factor, the compiler will produce faster code with the for loop than with map/filter/forEach.
Note that, if you want to go crazy with functional style, Swift 4 will let you return the whole dictionary in a single line:
return [String:String]( uniqueKeysWithValues:
inputs.map{ ($0.type as? ShippingDictKeyable.Type, $0.text) }
.filter{ $0.0 != nil }
.map{($0!.shippingDictKey,$1)}
+ ["email", "second_name", "suffix", "title", "salutation", "company_name"]
.map{($0,"")}
)
This may only work in the playground though cause real projects tend to complain about expressions being too complex more often.
The Class Book is what I'm struggling on. The goal is to turn the exam block to true.
I try to compare the title of every book in the liste-array with the title I´m searching for. If it´s found the function should output the book-element and if not, return nil.
This is what I currently have:
class Book {
var title:String;
var isbn:String;
var price:Float;
public init(_ title:String, _ isbn:String, _ price:Float) {
self.title=title;
self.isbn=isbn;
self.price=price;
}
}
//findBookWithTitle should output the first element with the same title.
func findBookWithTitle(_ title:String, inListe liste:[Book]) -> Book? {
var data=liste;
var stitle=title;
var memory:Int=0;
for i in 0..<data.count{
if data[i].title==stitle{
memory=i;
}else{
return nil;
}
}
return data[memory]
}
//exam block
let daten=[Book("Book A","ISBN A",12),Book("Buch B","ISBN B",14),Book("Book C","ISBN C",17)];
let a1a = findBookWithTitle("Book C", inListe: daten) === daten[2];
let a1b = findBookWithTitle("Book A", inListe: daten) === daten[0];
let a1c = findBookWithTitle("Testbook", inListe: daten) === nil;
let a1 = a1a && a1b && a1c;
I don´t have any errors and don't know where to start fixing the problem.
Your code does not work because you return nil as soon as a non-matching book title is encountered during the enumeration.
The correct approach is to return as soon as a book with a matching title is found, and to return nil if no matching book was found during
the enumeration:
func findBookWithTitle(_ title:String, inListe liste:[Book]) -> Book? {
for i in 0..<liste.count {
if liste[i].title == title {
return liste[i]
}
}
return nil
}
(Note that there is no need to make variable copies of all the parameters.)
As already suggested above, this can be simplified to
func findBookWithTitle(_ title:String, inListe liste:[Book]) -> Book? {
return liste.first(where: { $0.title == title })
}
using the first(where:) method for arrays (or more generally, for sequences).
I want to use Swift (not Objective-C runtime) Reflection to create a method like this:
func valueFor(property:String, of object:Any) -> Any? {
...
}
To some extent, I can do this using:
func valueFor(property:String, of object:Any) -> Any? {
let mirror = Mirror(reflecting: object)
return mirror.descendant(property)
}
With
class TestMe {
var x:Int!
}
let t = TestMe()
t.x = 100
let result = valueFor(property: "x", of: t)
print("\(result); \(result!)")
This prints out what I'd expect:
Optional(100); 100
When I do:
let t2 = TestMe()
let result2 = valueFor(property: "x", of: t2)
print("\(result2)")
The output is:
Optional(nil)
This might seem reasonable, except that if I do:
var x:Int!
print("\(x)")
This prints out:
nil
and not Optional(nil). The bottom line is that I'm having difficulty programmatically determining that the value of t2.x is nil using my valueFor method.
If I continue the above code with:
if result2 == Optional(nil)! {
print("Was nil1")
}
if result2 == nil {
print("Was nil2")
}
Neither of these print statements output anything.
When I put a breakpoint into Xcode and look at the value of result2 with the debugger, it shows:
▿ Optional<Any>
- some : nil
So, my question is: How can I determine if the original member variable was nil using the result from valueFor?
Additional1:
If I do:
switch result2 {
case .some(let x):
// HERE
break
default:
break
}
and put a breakpoint at HERE, the value of x turns out to be nil. But, even if I assign it to an Any?, comparing it to nil is not true.
Additional2:
If I do:
switch result2 {
case .some(let x):
let z:Any? = x
print("\(z)")
if z == nil {
print("Was nil3")
}
break
default:
break
}
This prints out (only):
Optional(nil)
I find this especially odd. result2 prints out exactly the same thing!
This is a bit of a hack, but I think it's going to solve the problem for me. I'm still looking for better solutions:
func isNilDescendant(_ any: Any?) -> Bool {
return String(describing: any) == "Optional(nil)"
}
func valueFor(property:String, of object:Any) -> Any? {
let mirror = Mirror(reflecting: object)
if let child = mirror.descendant(property), !isNilDescendant(child) {
return child
}
else {
return nil
}
}
well, i know it has been 4 years, but I am on Xcode 12 and still facing the same issue. since this question seems to be unanswered, I will add what worked for me.
func valueFor(property: String, of object: Any) -> Any? {
let optionalPropertyName = "some"
let mirror = Mirror(reflecting: object)
if let child = mirror.descendant(property) {
if let optionalMirror = Mirror(reflecting: child), optionalMirror.displayStyle == DisplayStyle.optional {
return optionalMirror.descendant(optionalPropertyName)
} else {
return child
}
} else {
return nil
}
}
by using Mirror to check for optional and then extract the optional using "some" you get back either a true object or nil. when this is returned to the caller via the Any? return, you are now able to nil check the value and have that work appropriately.
I'm trying to use swift reflection to check for changes in objects so I can send only changed properties up to the server. Some of my properties are optional. To compare those values, I need to unwrap them but, of course, you can ONLY unwrap actual values, not nil values. So, I need to check if one of the values is nil before I compare them.
In my playground, I tried the following:
import UIKit
class myClass
{
var fieldOne:String?
var fieldTwo:Int?
var fieldThree:Float?
}
var oneMyClass = myClass()
oneMyClass.fieldOne = "blah"
oneMyClass.fieldThree = 3.5
var oneOtherClass = myClass()
oneOtherClass.fieldOne = "stuff"
oneOtherClass.fieldTwo = 3
let aMirror = Mirror(reflecting: oneMyClass)
let bMirror = Mirror(reflecting: oneOtherClass)
for thing in aMirror.children
{
for thing2 in bMirror.children
{
if thing.label! == thing2.label!
{
print("property: \(thing.label!)")
print("before: \(thing.value)")
print("after: \(thing2.value)")
print("")
//let myTest = thing.value == nil ? "nil" : "not nil"
}
}
}
And it generates the following output:
property: fieldOne
before: Optional("blah")
after: Optional("stuff")
property: fieldTwo
before: nil
after: Optional(3)
property: fieldThree
before: Optional(3.5)
after: nil
As you can see, the expected properties are displayed as "nil". However, if you uncomment the let statement, you get an error stating:
playground52.swift:37:38: error: value of type 'Any' (aka 'protocol<>') can never be nil, comparison isn't allowed
And yet, we know from the output that it IS nil. How can this be and what can I do about it?
Based on this answer, I recommend using if case Optional<Any>.some(_).
For example:
aMirror.children.forEach {
guard let propertyName = $0.label else { return }
if case Optional<Any>.some(_) = $0.value {
print("property: \(propertyName) is not nil")
} else {
print("property: \(propertyName) is nil")
}
}
Thats look like some sort of bug. Look at that
let x = childMirror.value == nil ? "Nil" : "Not Nil" //dont compile.
let y = { (value:Any?) in
return value == nil ? "Nil" : "Not Nil"
}
let z = y(childMirror.value) //compile, but doesn't evaluate.
I guess the problem is because Any can store a Optional, but can't be wrapped around one. Try this:
func getValue(unknownValue:Any) -> Any {
let value = Mirror(reflecting: unknownValue)
if value.displayStyle != .Optional || value.children.count != 0 {
return "Not Nil"
} else {
return "Nil"
}
}