I currently do this in my code to cope with optionals...
I do a
fetchedResultController.performFetch(nil)
let results = fetchedResultController.fetchedObjects as [doesnotmatter]
// add all items to server that have no uid
for result in results {
let uid = result.valueForKey("uid") as String?
if uid == nil
{
let name = result.valueForKey("name") as String?
let trainingday = result.valueForKey("trainingdayRel") as Trainingdays?
if let trainingday = trainingday
{
let trainingUID = trainingday.valueForKey("uid") as String?
if let trainingUID = trainingUID
{
let urlstring = "http://XXXX/myGym/addTrainingday.php?apikey=XXXXXX&date=\(date)&appid=47334&exerciseUID=\(exerciseUID)"
let sUrl = urlstring.stringByAddingPercentEscapesUsingEncoding(NSASCIIStringEncoding)
let url = NSURL(string: sUrl!)
// save the received uid in our database
if let dictionary = Dictionary<String, AnyObject>.loadJSONFromWeb(url!)
{
trainingday.setValue(uid, forKey: "uid")
}
self.managedObjectContext!.save(nil)
}
}
}
}
Actually I would also need an "else"-clause for each and every "if let" statement. That seems totally terrible code to me! Is there no better way to do this?
Yes, with switch-case and pattern matching you can achieve this:
var x : SomeOptional?
var y : SomeOptional?
switch (x, y)
{
case (.Some(let x), .Some(let y)): doSomething() // x and y present
case (.None(let x), .Some(let y)): doSomethingElse() // x not present, but y
// And so on for the other combinations
default: break
}
Have a look at this blog post: Swift: Unwrapping Multiple Optionals
Edit (slightly off-topic and opinion-based): this is one of my favorite features in Swift. It also lets you implement FSMs with only few code, which is great.
Related
I am consuming an API that gives me the next page in the Header inside a field called Link. (For example Github does the same, so it isn't weird.Github Doc)
The service that I am consuming retrieve me the pagination data in the following way:
As we can see in the "Link" gives me the next page,
With $0.response?.allHeaderFields["Link"]: I get </api/games?page=1&size=20>; rel="next",</api/games?page=25&size=20>; rel="last",</api/games?page=0&size=20>; rel="first".
I have found the following code to read the page, but it is very dirty... And I would like if anyone has dealt with the same problem or if there is a standard way of face with it. (I have also searched if alamofire supports any kind of feature for this but I haven't found it)
// MARK: - Pagination
private func getNextPageFromHeaders(response: NSHTTPURLResponse?) -> String? {
if let linkHeader = response?.allHeaderFields["Link"] as? String {
/* looks like:
<https://api.github.com/user/20267/gists?page=2>; rel="next", <https://api.github.com/user/20267/gists?page=6>; rel="last"
*/
// so split on "," the on ";"
let components = linkHeader.characters.split {$0 == ","}.map { String($0) }
// now we have 2 lines like '<https://api.github.com/user/20267/gists?page=2>; rel="next"'
// So let's get the URL out of there:
for item in components {
// see if it's "next"
let rangeOfNext = item.rangeOfString("rel=\"next\"", options: [])
if rangeOfNext != nil {
let rangeOfPaddedURL = item.rangeOfString("<(.*)>;", options: .RegularExpressionSearch)
if let range = rangeOfPaddedURL {
let nextURL = item.substringWithRange(range)
// strip off the < and >;
let startIndex = nextURL.startIndex.advancedBy(1) //advance as much as you like
let endIndex = nextURL.endIndex.advancedBy(-2)
let urlRange = startIndex..<endIndex
return nextURL.substringWithRange(urlRange)
}
}
}
}
return nil
}
I think that the forEach() could have a better solution, but here is what I got:
let linkHeader = "</api/games?page=1&size=20>; rel=\"next\",</api/games?page=25&size=20>; rel=\"last\",</api/games?page=0&size=20>; rel=\"first\""
let links = linkHeader.components(separatedBy: ",")
var dictionary: [String: String] = [:]
links.forEach({
let components = $0.components(separatedBy:"; ")
let cleanPath = components[0].trimmingCharacters(in: CharacterSet(charactersIn: "<>"))
dictionary[components[1]] = cleanPath
})
if let nextPagePath = dictionary["rel=\"next\""] {
print("nextPagePath: \(nextPagePath)")
}
//Bonus
if let lastPagePath = dictionary["rel=\"last\""] {
print("lastPagePath: \(lastPagePath)")
}
if let firstPagePath = dictionary["rel=\"first\""] {
print("firstPagePath: \(firstPagePath)")
}
Console output:
$> nextPagePath: /api/games?page=1&size=20
$> lastPagePath: /api/games?page=25&size=20
$> firstPagePath: /api/games?page=0&size=20
I used components(separatedBy:) instead of split() to avoid the String() conversion at the end.
I created a Dictionary for the values to hold and removed the < and > with a trim.
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.
I'm trying to parse data from a dictionary. I have code that currently works but I think there is a better more concise way to do it.
I have three options for what my dictionary can equal
let dictionary:[String:Any] = ["pic":"picture"] //opt1
let dictionary:[String:Any] = ["pic":2] //opt2
let dictionary:[String:Any] = ["pi":"this"] //opt3
This is the code that I am currently using to parse the data that I would like to be improved.
let _pic = dictionary["pic"]
if _pic != nil && !(_pic is String) {
print("error")
return
}
let pic = _pic as? String
For each option i'd like different things to happen for:
opt1
pic:String? = Optional(picture)
opt2 An error to be shown
opt3
pic:String? = nil
You can try this,
guard let _pic = dictionary["pic"] as? String else { return }
let _pic = dictionary["pic"]
This by default gives you an optional value for _pic that's of type Any?. As such, your code seems OK based on your requirements and I don't think you need the last line let pic = _pic as? String
I think you need to do two tests. Here's one way:
guard let picAsAny = dictionary["pic"]
else { /* No key in the dictionary */ }
guard let pic = picAsAny as? String
else { /* error wrong type */ }
// pic is now a (nonoptional) string
Obviously you can use if statements instead of guards depending on context.
I want to initialize a enum from a variable of type String?, like:
guard let rawId = request.queryParameters["id"] else {
return
}
guard let id = MyIdentifier(rawValue: rawId) else {
return
}
In this case, request.queryParameters["id"] returns String?. Then after I ensure that it is String in rawId, I convert it into an enum instance id.
However, the code is dirty and I want to write it in one-line if at all possible.
However, I don't like to make it unwrapped via forced optional unwrapping, because if it can not be transformed to String, the app would end up with an error, since rawValue: only takes String. I meant something like the following, which I don't like:
guard let id = MyIdentifier(rawValue: request.queryParameters["id"]!) else {
return
}
So is it still possible to define the guard let in one-line, maybe using where and/or case in guard?
You have two conditions there, trying to combine them into one condition is not always possible.
In your exact case I believe an empty id will behave the same as a nil id, therefore nil coalescing can be used:
guard let id = MyIdentifier(rawValue: request.queryParameters["id"] ?? "") else {
return
}
However, there is nothing dirty about splitting two checks into two statements. Code is not written to be short, it's written to be clear:
guard let rawId = request.queryParameters["id"],
let id = MyIdentifier(rawValue: rawId) else
return
}
Also, there is nothing wrong with creating a custom initializer for your enum:
init?(id: String?) {
guard let id = id else {
return nil
}
self.init(rawValue: id)
}
and then
guard let id = MyIdentifier(id: request.queryParameters["id"]) else {
return
}
Try this:
You can simply combine both the statements into a single statement ,i.e,
guard let id = request.queryParameters["id"], let id2 = MyIdentifier(rawValue: id) else {
return
}
I want to unwrap two optionals in one if statement, but the compiler complaints about an expected expression after operator at the password constant.
What could be the reason?
if let email = self.emailField?.text && let password = self.passwordField?.text
{
//do smthg
}
Done in Swift.
Great news. Unwrapping multiple optionals in a single line is now supported in Swift 1.2 (XCode 6.3 beta, released 2/9/15).
No more tuple/switch pattern matching needed. It's actually very close to your original suggested syntax (thanks for listening, Apple!)
if let email = emailField?.text, password = passwordField?.text {
}
Another nice thing is you can also add where for a "guarding condition":
var email: String? = "baz#bar.com"
var name: String? = "foo"
if let n = name, e = email where contains(e, "#") {
println("name and email exist, email has #")
}
Reference: XCode 6.3 Beta Release Notes
Update for Swift 3:
if let email = emailField?.text, let password = passwordField?.text {
}
each variable must now be preceded by a let keyword
How about wrapping the optionals in a tuple and using switch to pattern match?
switch (self.emailField?.text, self.passwordField?.text) {
case let (.Some(email), .Some(password)):
// unwrapped 'email' and 'password' strings available here
default:
break
}
It's definitely a bit noisier, but at least it could also be combined with a where clause as well.
The usage
if let x = y {
}
is not equivalent to
if (let x = y) { // this is actually not allowed
}
"if let" is effectively a two-word keyword, which is equivalent to
if y != nil {
let x = y!
// rest of if let block
}
Before Swift 1.2
Like #James, I've also created an unwrap function, but this one uses the existing if let for control flow, instead of using a closure:
func unwrap<T1, T2>(optional1: T1?, optional2: T2?) -> (T1, T2)? {
switch (optional1, optional2) {
case let (.Some(value1), .Some(value2)):
return (value1, value2)
default:
return nil
}
}
This can be used like so:
if let (email, password) = unwrap(self.emailField?.text, self.passwordField?.text)
{
// do something
}
From: https://gist.github.com/tomlokhorst/f9a826bf24d16cb5f6a3
Note that if you want to handle more cases (like when one of the two fields is nil), you're better off with a switch statement.
Swift 4
if let suggestions = suggestions, let suggestions1 = suggestions1 {
XCTAssert((suggestions.count > suggestions1.count), "TEST CASE FAILED: suggestion is nil. delete sucessful");
}
I can't explain why the above code doesn't work, but this would be good a replacement:
if let email = self.emailField?.text
{
if let password = self.passwordField?.text
{
//do smthg
}
}
Based on #Joel's answer, I've created a helper method.
func unwrap<T, U>(a:T?, b:U?, handler:((T, U) -> ())?) -> Bool {
switch (a, b) {
case let (.Some(a), .Some(b)):
if handler != nil {
handler!(a, b)
}
return true
default:
return false
}
}
// Usage
unwrap(a, b) {
println("\($0), \($1)")
}