Swift but a bit Sweeter - More Syntactic Sugar for Swift
This is a collection of extensions and operators that make swift a bit sweeter. I have added these from multiple projects where I've been using these.
Note: These operators are supposed to help me in the way I write Swift. Which is a functional style. So most of these regard possible problems and annoyances with functional programming in Swift.
Please: Contribute to make Swift a bit cooler looking... Post your ideas in the issues as enhancements
Sweeft is available both as a Pod in Cocoapods and as a Dependency in the Swift Package Manager. So you can choose how you include Sweeft into your project.
Add 'Sweeft' to your Podfile:
pod 'Sweeft'
Add 'Sweeft' to your Package.swift:
import PackageDescription
let package = Package(
// ... your project details
dependencies: [
// As a required dependency
.Package(url: "ssh://git@github.com/mathiasquintero/Sweeft.git", majorVersion: 0)
]
)
Add this to your Cartfile:
github "mathiasquintero/Sweeft"
I know what you're wondering. Why the hell do I need this? Well. Sweeft allows you to make your code so much shorter.
For instance: let's say you have an array with some integers and some nil values.
let array: [Int?]? = [1, 2, 3, nil, 5, nil]
And now you want to store all of the even numbers in a single array. Easy right:
var even = [Int]()
if let array = array {
for i in array {
if let i = i, i % 2 == 0 {
even.append(i)
}
}
}
Seems a bit too much. Now those who know swift a bit better will tell me to write something more along the lines of:
let even = (array ?? [])
.flatMap { $0 }
.filter { $0 & 1 == 0 }
But even that seems a bit too long. Here's that same code written using Sweeft:
let even = !array |> { $0 & 1 == 0 }
Now to be clear, the last two solutions are following the same principles.
First we get rid of all the nil values from the array and cast it as a [Int] using the prefix '!'. Then we just call filter. But since our fingers are too lazy we spelled it '|>' ;)
Ok. Another example:
Say you're really curious and want to know all the numbers from 0 to 1000 that are both palindromes and primes. Exciting! I know.
Well easy:
let palindromePrimes = (0...1000) |> { $0.isPalindrome } |> { $0.isPrime }
First we filter out the non-palindromes. And then we filter out the non-primes.
Ok. If you still are not sure if you should use Sweeft, see this example.
Say you're looping over an array:
for item in array {
// Do Stuff
}
And all of the sudden you notice that you're going to need the index of the item as well. So now you have to use a range:
for index in 0..<array.count {
let item = array[index]
// Do Stuff
}
But you still haven't accounted for the fact that this will crash if the array is empty: So you need:
if !array.isEmpty {
for index in 0..<array.count {
let item = array[index]
// Do Stuff
}
}
Ok... That's too much work for a loop. Instead you could just use '.withIndex' property of the array.
for (item, index) in array.withIndex {
// Do Stuff
}
Which I know array.enumerated()
already does. But array.withIndex
just sounds so much clearer. ;)
Or even better. With the built in for-each operator:
array => { item, index in
// Do Stuff
}
I think we can all agree that's much cleaner looking.
Will pipe the left value to the function to the right. Just like in Bash:
value | function
is the same as:
function(value)
If you want to access any value from an Array or a Dictionary:
let first = array | 0
let second = array | 1
Any it will return nil if there is nothing in that index. So it won't crash ;)
You can also use negative numbers to go the other way around:
let last = array | -1
let secondToLast = array | -2
Awesome, right?
This will call map with the function to the right:
array => function
is the same as:
array.map(function)
If the closure returns void instead of a value, it will run it as a loop and not map:
array => { item in
// Do Stuff
}
The same as above but with flatMap.
If you're doing reduce to the same type as your array has. You can reduce without specifying an initial result. Instead it will take the first item as an initial result:
So if you want to sum all the items in an Array:
let sum = array ==> (+)
let mult = array ==> (*)
Or you could just use the standard:
let sum = array.sum { $0 }
let mult = array.multiply { $0 }
You could also use the standard reduce by specifying the initial result
let joined = myStrings ==> "" ** { "\($0), \($1)" }
or if you feel like it, you can also flip the opperands:
let joined = myStrings ==> { "\($0), \($1)" } ** ""
or since String conforms to 'Defaultable' and we know the default string is "", we can use the '>' operator to tell reduce to use the default:
let joined = myStrings ==> >{ "\($0), \($1)" }
The same as above but with filter
Will turn any Collection into a dictionary by dividing each element into a key and a value:
array >>= { item in
// generate key and value from item
return (key, item)
}
When handling an array you can call the above functions with a closure that also accepts the index of the item:
For example:
array => { item, index in
// Map
return ...
}
array ==> initial ** { result, item, index in
// Reduce
return ...
}
array >>= { item, index in
// Turn into dictionary
return (..., ...)
}
If you want some of functions inputs to be already filled you can use ** to bind them to the function.
let inc = (+) ** 1
inc(3) // 4
inc(4) // 5
You can also go from right to left with <**
Say you want to combine to closures into one, that takes both inputs and delivers both outputs. For example you have a dictionary dict [Int: Int] and you want to increase every key by one and get the square of every value.
Simple:
let dict = [ 2 : 4, 3 : 5]
let newDict = dict >>= inc <*> { $0 ** 2 } // [3 : 16, 4 : 25]
Or if you want to use binding:
let newDict = dict >>= inc <*> ((**) <** 2) // Now no one can read. Perfect!
But what if both functions should take the same input? Then use <+> and both closures will be fed the same input:
let dict = [1, 2, 3, 4]
let newDict = dict >>= inc <+> { $0 ** 2 } // [2 : 1, 3 : 4, 4 : 9, 5 : 16]
And now your code will look so awesome:
Will cast a function to allow any input and drop it.
**{
// Do stuff
}
is the same as:
{ _,_ in
// Do stuff
}
or as a postfix it will drop the output
{
return something
}**
is equivalent to:
{
_ = something
}
Will assign b to a if b is not nil
a <- b
is equivalent to:
a = b ?? a
Will assign the result of a map to an array.
array <- handler
is equivalent to:
array = array.map(handler)
If the handler returns an optional, but the array can't handle optionals then it will drop all of the optionals.
Will assign the result of a filter to the array
array <| handler
is the same as:
array = array.filter(handler)
This way you can concatenate arrays quickly:
let concat = firstArray + secondArray
Or even do:
firstArray += secondArray
let array = [1, nil, 3]
!array // [1, 3]
// a = 1 and b = 2
a <=> b // a = 2 and b = 1
Note: the type has to conform to the Defaultable protocol.
let i: Int? = nil
let j: Int? = 2
i.? // 0
j.? // 2
It even works inside a Collection:
let array = [1, 2, nil, 4]
array.? // [1, 2, 0, 4]
Not to be confused with the default value of an array:
let a: [Int?]? = nil
let b: [Int?]? = [1, 2, nil, 4]
a.? // []
b.? // [1, 2, nil, 4]
(b.?).? // [1, 2, 0, 4]
Will check if a value is not nil
??myVariable
is equivalent to:
myVariable != nil
It can even be given to closures.
This means:
??{ (item: String) in
return item.date("HH:mm, dd:MM:yyyy")
}
Is a closure of type (String) -> (Bool)
Meaning if the date in the string is nil or not.
You you want to run a closure in a specific queue:
queue >>> {
// Do Stuff.
}
Or if you want to run it after a certain time interval in seconds. For example the following will run the closure after 5 seconds:
5.0 >>> {
// Do Stuff
}
And of course you can combine them:
(queue, 5.0) >>> {
// Do Stuff
}
// Or
(5.0, queue) >>> {
// Do Stuff
}
If you want to be more modular with your closures you can always chain them toghether and turn them into a bigger closure.
For example: Let's say you have an array of dates. And you want an array of the hours of each.
let hours = dates => { $0.string("hh") } >>> Int.init
Which is equivalent to saying:
let hours = dates.map { date in
let string = date.string("hh")
return Int(string)
}
The precedence works as follows:
Chaining before mapping. So >>> will be evaluated before =>.
Of course you don't need chaining in the previous example. You could use the pipe and will be even shorter:
let hours = dates => { $0.string("hh") | Int.init }
But that's the cool thing about Sweeft. It gives you options ;)
Storing data to UserDefaults can be lead to issues. Mainly because of the fact that UserDefaults uses strings as keys, which make it easy to make mistakes. Furthermore the code to read something from user defaults requires you to cast the value to the type that you want. Which is unnecesarily complicated.
Which is why Sweeft has a Status API. Meaning that anything that has to be stored into UserDefaults has to be it's own Status Struct.
For example if you want to store the amount of times in which your app was opened:
We start by creating the keys for your app.
Simply create an enum for them that inherits from StatusKey:
enum AppDefaults: String, StatusKey {
case timesOpened
/// More Cases here...
}
And now we create the Status:
struct TimesOpened: Status {
static let key: AppDefaults = .timesOpened
static let defaultValue: Int = 0
}
To access it you can simply call it from your code:
let times = TimesOpened.value
// Do Something with it...
TimesOpened.value = times + 1
Sometimes you may want to store more complex information than just stock types that are usually supported in user defaults.
For that there's the ObjectStatus. First off, your Data has to conform to the protocol 'StatusSerializable'. Meaning it can be serialized and deserialized into a [String:Any]
.
For example:
extension MyData: StatusSerializable {
var serialized: [String:Any] {
return [
// Store your data
]
}
init?(from status: [String:Any]) {
// Read data from the dictionary and set everything up
// This init is allowed to fail ;)
}
}
And then create your ObjectStatus:
struct MyDataStatus: ObjectStatus {
static let key: AppDefaults = .myDataKey // Create a Key for this too
static let defaultValue: MyData = MyData() // Hand some default value here
}
Sweeft also abstracts away a lot of repetetive work on sending requests to your REST API.
To access an API you simply have to describe the API by creating a list of endpoints you can access.
enum MyEndpoint: String, APIEndpoint {
case login = "login"
case user = "user/{id}"
}
Then you can create your own API Object:
struct MyAPI: API {
typealias Endpoint = MyEndpoint
let baseURL = "https://..."
}
And do any requests from it, be it Data, JSON or anything that conforms to our DataRepresentable Protocol. Like so:
let api = MyAPI()
api.doJSONRequest(with: .get, to: .user, arguments: ["id": 1234])
.onSuccess { json in
let name = json["name"].string ?? "No name for the user is available"
print(name)
}
.onError { error in
print("Some Error Happened :(")
print(error)
}
The code above does a GET Request to /user/1234 and if the request is successful it will read the name attribute of the JSON Object and print it out.
A deserializable object is any object that conforms to the protocol Deserializable and can therefore be instantiated from JSON. For instance let's say that we get an object representing a user from our API.
struct User {
let name: String
}
extension User: Deserializable {
init?(from: JSON) {
guard let name = json["name"].string else {
return nil
}
self.init(name: name)
}
}
Having done this you can automatically get User's from your api by calling get(:) or getAll(:) respectively.
For example you can now call:
let api = MyAPI()
User.get(using: api, at: .user, arguments: ["id": 1234])
.onSuccess { user in
print(user.name)
}
.onError { error in
print(error)
}
Please contribute and help me make swift even better with more cool ways to simplify the swift syntax. Fork and star this repo and #MakeSwiftGreatAgain