What are some ways I can optimize this special cased closure? - swift

I want to use the sorted method on an array.
public func sorted(by areInIncreasingOrder: (Self.Iterator.Element, Self.Iterator.Element) -> Bool) -> [Self.Iterator.Element]
And I can see that it takes a function (which takes 2 Elements and returns a Bool), and then returns an an array of the Element (sorted)
Where I am getting stuck, is by trying to pass an anonymous function (closure), using $0 and $1 as arguments
I want to add a special case, for a specific key ("module")
And then I want to access a property on $0 and $1
But this means, ostensibly, that I have to cast those arguments, so that I can then access the properties on those arguments
And to make things worse, because the arguments could either be String or Node, I currently have repeated code (the switch statement)
So anyways, do any of you Swift veterans see things that I could do to make this better?
return self.sorted(by: {
for descriptor in sortDescriptors {
if descriptor.key == "module" {
if let firstNode = $0 as? Node {
if let secondNode = $1 as? Node {
let firstModule = firstNode.module?.name ?? ""
let secondModule = secondNode.module?.name ?? ""
let newDescriptor = NSSortDescriptor(key: nil, ascending: descriptor.ascending, selector: descriptor.selector)
switch newDescriptor.compare(firstModule, to: secondModule) {
case .orderedAscending:
return true
case .orderedDescending:
return false
case .orderedSame:
continue
}
}
}
}
switch descriptor.compare($0, to: $1) {
case .orderedAscending:
return true
case .orderedDescending:
return false
case .orderedSame:
continue
}
}
return false
})
}

Related

Forwarding function generic parameter to generic class type

I have created enum with associated value and I want to be able to dynamically update associated value. As far as I know Swift doesn't support that at the moment.
Because of that I used following approach:
enum PersonInfo {
class EnumValue<T> {
var value: T
init(_ value: T) {
self.value = value
}
}
// Instead of using String or Bool or any other type directly, use EnumValue wrapper
case firstName(EnumValue<String>)
case lastName(EnumValue<String>)
case isAdult(EnumValue<Bool>)
}
I want to add function that would update EnumValue.value property in following way:
func updateAssociatedValue<V>(_ updateValue: V) {
let mirror = Mirror(reflecting: self)
for associatedValue in mirror.children {
guard let value = associatedValue.value as? EnumValue<V> else {
continue
}
value.value = updateValue
}
}
Problem is that this guard statement always fails (guard let value = associatedValue.value as? EnumValue<V>) and I can't figure it out why.
On the other hand, when I write updateAssociatedValue with typed type then things work properly:
// This works
func updateAssociatedValue(_ updateValue: String) {
let mirror = Mirror(reflecting: self)
for associatedValue in mirror.children {
guard let value = associatedValue.value as? EnumValue<String> else {
continue
}
value.value = updateValue
}
}
Things compile normally but during the runtime guard statement always fails. Am I using generic value in some incorrect way? Should I use somehow updateValue.Type or updateValue.self (I tried but it didn't work).
Example of usage:
var array: [PersonInfo] = [
.firstName(PersonInfo.EnumValue("John")),
.lastName(PersonInfo.EnumValue("Doe")),
.isAdult(PersonInfo.EnumValue(false))
]
print(array)
// John, Doe, false
array.first?.updateAssociatedValue("Mike")
print(array)
// Mike, Doe, false
I can always reassign enum value in array but if possible I want to avoid that. That's the reason for asking this question.

Swift - How to use a class type as a parameter / variable

I'm trying to create a somewhat generic function to sort file URLs based the value of an attribute
My goal is to:
1) Pass the URL and some parameters (Including the type) to a function.
which will then loop through the file's attributes
2) Add the matching file attribute and the URL to an array of tuples
3) Sort the tuples by the value of the found attribute
4) Return the sorted array and display the items in sorted order
I believe that I need to pass the type of the attribute into the sorting function so I'm able to set it in the tuple since I'm unable to sort with "Any" but I'm unsure of how to do that
I'm okay with passing anything into the sorting function and constructing or deconstructing the value I need in the sorting function since that will be predefined depending upon what action is selected by the user
//Initial implementation to be later tied to IBActions and simplified
func sortFiles(sortByKeyString : String, fileURLArray : [URL]) -> [URL]
{
switch sortByKeyString {
case "date-created-DESC":
let fileAttributeKeyString : String = "creationDate"
let isSortOrderDesc = true
let objectTypeString : String = NSDate.className()
let sortedFileURLArray = sortFileArrayByType(fileAttributeKeyString: fileAttributeKeyString, fileURLArray: fileURLArray, type: objectTypeString, isSortOrderDesc : isSortOrderDesc)
return sortedFileURLArray
default:
return fileURLArray
}
}
//Generic function to get a files attributes from a URL by requested
type
func sortFileArrayByType(fileAttributeKeyString : String, fileURLArray : [URL], type: String, isSortOrderDesc : Bool) -> [URL] {
let fileManager = FileManager.default
let attributeToLookFor : FileAttributeKey = FileAttributeKey.init(rawValue: fileAttributeKeyString)
var tupleArrayWithURLandAttribute : [(url: URL, attribute: *Any*)]? = nil
for url in fileURLArray {
do {
let attributes = try fileManager.attributesOfItem(atPath: url.path)
for (key, value) in attributes {
if key.rawValue == fileAttributeKeyString {
tupleArrayWithURLandAttribute?.append((url: url, attribute: value))
}
}
let sortedTupleArrayWithURLandAttribute = tupleArrayWithURLandAttribute?.sorted(by: { $0.attribute < $1.attribute)})
// Need to Sort dictionary into array
return sortedTupleArrayWithURLandAttribute
} catch {
return fileURLArray
}
}
}
First read Metatype Type in the The Swift Programming Language. Once read continue with the answer.
From that you have learnt that you can declare a function parameter's type to be the type of types (you are allowed to go crosseyed), AKA metatype, and can therefore pass a type to a function. Combine that with generics and Swift's type inference and you could declare your function as:
func sortFileArrayByType<T>(fileAttributeKeyString : String,
attributeType : T.Type,
fileURLArray : [URL]
) -> [(url: URL, attribute: T)]
where T : Comparable
This adds the parameter attributeType whose type is the metatype of T where T will be inferred. For example the metatype String.self could be passed and T will be inferred to be String.
The where clause constrains T so that only types which are Comparable are allowed, this is required to enable the function to do sorting. File attributes can be Date, String and NSNumber valued; unfortunately the latter does not conform to Comparable so you need to add an extension to make it, the following will suffice:
extension NSNumber : Comparable
{
public static func <(a : NSNumber, b : NSNumber) -> Bool { return a.compare(b) == .orderedAscending }
public static func ==(a : NSNumber, b : NSNumber) -> Bool { return a.compare(b) == .orderedSame }
}
Within the body of the function you need to declare your array of tuples to have attributes of type T:
var tupleArrayWithURLandAttribute : [(url: URL, attribute: T)] = []
and when you add entries you need to cast the value returned by attributesOfItem to be T:
tupleArrayWithURLandAttribute.append((url: url, attribute: value as! T))
Note the use of as! here, you must match the attribute name and the type of its value correctly in the function call or you will get a runtime abort. Handling this as a soft error, if needed, is left as an exercise.
There are a number of typos etc. in the code you posted, they are left for you to fix, having done that your function should work. A call might look like:
let ans = sortFileArrayByType2(fileAttributeKeyString: "NSFileCreationDate",
attributeType: Date.self,
fileURLArray: urlArray)
and the type of ans in this case will be [(url: URL, attribute: Date)]
HTH
So I think I know what you're getting at and this is what I've come up with:
func checkType<T>(_ type: T.Type) {
if type.self == String.self {
print("It's a string!")
} else if type.self == Int.self {
print("It's an int!")
} else {
print("It's something else...")
}
}
And then you can call this either by passing in a type directly to it, or by getting the type of a variable and passing that in as follows:
checkType(String.self) // prints "It's a string!"
let number: Int = 1
checkType(type(of: number)) // prints "It's an int!"
Hope this helps!
What you're looking for here is a way to sort a sequence of URLs by a URLResourceKey (and specifically by the URLResourceValues property related to that key). Unfortunately, URLResourceValues aren't mapped to URLResourceKey in a useful way. But we can fix that with an extension:
extension URLResourceValues {
static func key<T>(for keyPath: KeyPath<Self, T>) -> URLResourceKey {
switch keyPath {
case \Self.creationDate: return .creationDateKey
// ... Other keys ...
default: fatalError()
}
}
}
And it would be very useful to get a value for a URLResourceValues keyPath:
extension URL {
func resourceValue<T>(for keyPath: KeyPath<URLResourceValues, T?>) throws -> T? {
return try resourceValues(forKeys: Set([URLResourceValues.key(for: keyPath)]))[keyPath: keyPath]
}
}
With that, we can build a sorting method based on URLResourceValues (assuming nil is less than other values; you could replace that with throwing for non-existent values):
extension Sequence where Element == URL {
func sorted<T>(by keyPath: KeyPath<URLResourceValues, T?>) throws -> [URL]
where ResourceType: Comparable {
return try self
.sorted { (lhs, rhs) in
guard let lhsValue = try lhs.resourceValue(for: keyPath)
else { return true }
guard let rhsValue = try rhs.resourceValue(for: keyPath)
else { return false }
return lhsValue < rhsValue
}
}
}
And finally, that can be used by passing a keypath, based on URLResourceValues:
let sortedFiles = try files.sorted(by: \.creationDate)

How to merge String & Int to get in ONE flatMap

enum Input { case text(String); case page(Int) }
I am managing pagination with keyword search to API method.
Now I either can pass keywords or page number, but not both at same time in Rx.
I have written following code with help of some existing available gist
let start = Observable.merge(reload, loadNext)
let stringObservable = keyword.asObservable().map { Input.text($0) }
let intObservable = start.asObservable().map { Input.page($0) }
let request_call = Observable.of(stringObservable, intObservable).merge()
let page = request_call
.flatMap { input in
Observable.combineLatest(Observable.just($0), api.loadData(page: $0, keyword: "breaking")) { (pageNumber: $0, items: $1) }
.materialize()
.filter { $0.isCompleted == false }
}
.share()
start keep Page Number, & keyword keeps search keywords.
I need to merge both, I did using ENUM & Merge,
Now I have to call API, but showing as Input,
So How can I get both values in one flatMap
Get rid of the Input enum and use combineLatest instead of merge.
Then request_call will be an Observable<(String, Int)> and you can use the two values in the loadData function.
let start = Observable.merge(reload, loadNext)
let stringObservable = keyword.asObservable()
let intObservable = start.asObservable()
let request_call = Observable.combineLatest(stringObservable, intObservable)
let page = request_call
.flatMap { text, page in
Observable.combineLatest(Observable.just(page), api.loadData(page: page, keyword: text)) { (pageNumber: $0, items: $1) }
.materialize()
.filter { $0.isCompleted == false }
}
.share()

unable to infer complex closure return type

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.

How to test the Optionality of a String?

I have two different scenarios where I need to test the "optionality" of an optional type. I have not been able to figure how to explicitly test if the variable is a .None or a .Some other than with an unwieldy switch statement. How can I test for Someness with an if statement?
Scenario 1
I am writing an address formatter and my inputs are a number of String? types. In this example a simple test for (str != nil) will work. However, since my other need is when dealing with a 'double optional' and a nil test can't distinguish between .Some(.None) and .None a solution to this problem will solve that problem too.
Here's a version that works using a switch
let address1:String? = "123 Main St"
let address2:String? = nil
let apt:String? = "101"
let components = [address1, address2, apt].filter( { (c) -> Bool in
switch c {
case .Some: return true
case .None: return false
}
}).map { return $0! } //Had to map because casting directly to [String] crashes
print(", ".join(components)) //"123 Main St, 101"
What's I'd like to see is something like with an if:
let nice = ["123 Main St", nil, "303"].filter { (c) -> Bool in
return (c == .Some)
}
print(", ".join(nice))
Scenario 2
This is where a nil test won't work. If something is a String?? it can be any of .None, .Some(.None), or .Some(.Some(String)). In my case, the variable is carrying the recordID from an api call which might either be missing entirely (.None), a value (.Some(.Some("ABDEFG")), or explicitly NULL (.Some(.None)).
let teamNoneNone: String?? = .None
let teamSomeNone: String?? = .Some(.None)
let teamSomeSome: String?? = "My favorite local sportsball team"
if teamNoneNone == nil {
print("teamNoneNone is nil but is it .None? We don't know!") //prints
} else {
print("teamNoneNone is not nil")
}
if teamSomeNone == nil {
print("teamSomeNone is nil")
} else {
print("teamSomeNone is not nil but is it .Some(.None)? We don't know!") //prints
}
if teamSomeSome == nil {
print("teamSomeSome is nil but is it .None? We don't know!")
} else {
print("teamSomeSome is not nil but is it .Some(.None) or .Some(.Some())? We don't know!") //prints
}
Via another SO post I found a workaround like this, but it's not very clear what's happening to a casual reader:
if let team: String? = teamSomeNone {
print("teamSomeNone is Some(.None)") //prints
} else {
print("teamSomeNone is .Some(.Some())")
}
if let tests if a value is .None, and if it isn’t, it unwraps it and binds it to a local variable within an if statement.
Using switch with .Some and .None is really a secondary way of handling optionals, if if let doesn’t cut it. But it almost always does, especially now you can do multiple if lets in a single statement, following the latest release of Swift 1.2 to production.
Wanting to filter out the nils in a collection is a common-enough task that Haskell has a standard function for it, called catMaybe. Here’s a version, which I’ll call catSome, that would do the trick in Swift:
func catSome<T>(source: [T?]) -> [T] {
var result: [T] = []
// iterate over the values
for maybe in source {
// if this value isn’t nil, unwrap it
if let value = maybe {
// and append it to the array
result.append(value)
}
}
return result
}
let someStrings: [String?] = ["123 Main St", nil, "101"]
catSome(someStrings) // returns ["123 Main St", "101"]
Doubly-wrapped optionals are a bit of a pain, so the best solution is to avoid them in the first place – often, via use of optional chaining or flatMap.
But if you do find yourself with some, and all you care about is the inner value, you can unwrap them using a double if let:
// later parts of the let can rely on the earlier
if let outer = teamSomeSome, teamName = outer {
println("Fully unwrapped team is \(teamName)")
}
If you want to explicitly know if a double-optional has an inner nil inside an outer value, but isn’t nil itself, you can use if let with a where clause:
if let teamSomeMaybe = teamSomeNone where teamSomeMaybe == nil {
// this will be executed only if it was .Some(.None)
println("SomeNone")
}
The where clause is an extra conditional that can be applied to the unwrapped value.