Sweeft 0.15.2

Sweeft 0.15.2

测试已测试
语言语言 SwiftSwift
许可证 MIT
发布最新发布2018年2月
SwiftSwift 版本3.2
SPM支持 SPM

Mathias Quintero 维护。



Sweeft 0.15.2

Sweeft

更甜的 Swift - 为 Swift 添加更多语法糖

这是扩展和运算符的集合,可以使 Swift 代码更简洁。我从多个项目中使用过这些。

注意:这些运算符是为了帮助我以函数式风格编写 Swift。也就是函数式编程。
所以大多数这些都涉及 Swift 中函数式编程的可能问题和烦恼。

请:为使 Swift 看起来更酷...在问题区域提出您的新想法作为改进

安装 Sweeft

Sweeft 作为 Cocoapods 的库和 Swift 包管理器中的依赖项都可获取。因此,您可以选择如何将 Sweeft 包含到您的项目中。

Cocoapods

将 'Sweeft' 添加到您的 Podfile

pod 'Sweeft'

Swift 包管理器

将 'Sweeft' 添加到您的 Package.swift

import PackageDescription

let package = Package(
    // ... your project details
    dependencies: [
        // As a required dependency
        .Package(url: "ssh://[email protected]/mathiasquintero/Sweeft.git", majorVersion: 0)
    ]
)

Carthage

将此添加到您的 Cartfile

github "mathiasquintero/Sweeft"

为什么使用 Sweeft?

我知道你在想什么。我为什么需要这个?
哎,Sweeft 允许您将代码变得更短。

例如:假设你有一个包含一些整数和 nil 值的数组。

let array: [Int?]? = [1, 2, 3, nil, 5, nil]

现在你想要存储一个包含所有偶数的单个数组。简单吧?

var even = [Int]()
if let array = array {
    for i in array {
        if let i = i, i % 2 == 0 {
            even.append(i)
        }
    }
}

看起来有点复杂。
现在那些稍微了解 Swift 的人会告诉我写一些类似于以下内容的东西:

let even = (array ?? [])
            .flatMap { $0 }
            .filter { $0 & 1 == 0 }

但这仍然看起来有点长。这里是用 Sweeft 编写的相同代码

let even = !array |> { $0 & 1 == 0 }

现在清楚的是,最后两个解决方案遵循相同的原理。

首先,我们从数组中删除所有的 nil 值,并将其作为 [Int] 进行类型转换,使用前缀 '!'。
然后我们只调用 filter。但是由于我们太懒了,所以我们用 '|>' 来编写它 ;)

你仍然不愿意信服?

好,我再举一个例子。

比如说,你真的很好奇,想了解所有 0 到 1000 之间既是回文数又是素数的数字。刺激!我知道。

简单。

let palindromePrimes = (0...1000) |> { $0.isPalindrome } |> { $0.isPrime }

首先,我们过滤掉非回文数。
然后我们过滤掉非素数。

哇!你真难对付。

好吧。如果你仍然不确定是否应该使用 Sweeft,请看这个例子。

比如说,你正在遍历一个数组

for item in array {
    // Do Stuff
}

突然你意识到你还需要那个对象的索引。
所以现在你需要使用范围

for index in 0..<array.count {
    let item = array[index]
    // Do Stuff
}

但你还没有考虑到这是空数组时会崩溃的情况
所以你需要

if !array.isEmpty {
    for index in 0..<array.count {
        let item = array[index]
        // Do Stuff
    }
}

好吧... 对一个循环来说,这太费事了。而是你可以使用数组实例的 '.withIndex' 属性。

for (item, index) in array.withIndex {
    // Do Stuff
}

我知道array.enumerated()已经做到了这一点。但array.withIndex听起来更清楚。;)

甚至更好。使用内置的for-each运算符

array => { item, index in
    // Do Stuff
}

我想我们都可以同意这看起来更简洁。

用法

运算符

(|) 管道

将左边的值传递给右边的函数。就像在Bash中一样

value | function

等同于

function(value)

(|) 安全获取值

如果您想从数组或字典中访问任何值

let first = array | 0
let second = array | 1

如果没有值,它将返回nil。所以它不会崩溃;)

您还可以使用负数去访问另一个方向

let last = array | -1
let secondToLast = array | -2

很酷,不是吗?

(=>) 使用...

这将调用右边的函数

array => function

等同于

array.map(function)

(=>) For each

如果闭包返回空值而不是值,它将作为循环运行而不是映射

array => { item in
    // Do Stuff
}

(==>) 使用...

就像上面一样,但使用flatMap。

(==>) 简单的Reduce with...

如果您正在进行与数组相同类型的reduce操作。您可以在不指定初始结果的情况下进行reduce。相反,它将取第一个项作为初始结果

所以如果您想要计算数组中所有项的和

let sum = array ==> (+)
let mult = array ==> (*)

或者您也可以使用标准的

let sum = array.sum { $0 }
let mult = array.multiply { $0 }

(==>) 使用...

您也可以通过指定初始结果使用标准的reduce

let joined = myStrings ==> "" ** { "\($0), \($1)" }

或者如果您愿意,您也可以翻转操作数

let joined = myStrings ==> { "\($0), \($1)" } ** ""

或者由于String遵循'Defaultable'协议,我们知道默认字符串是"",我们可以使用'>'运算符来告诉reduce使用默认值

let joined = myStrings ==> >{ "\($0), \($1)" }

(|>) 使用...

就像上面一样,但使用filter。

(>>=) 转换为字典

将任何集合转换为字典,通过将每个元素分别划分为键和值

array >>= { item in
    // generate key and value from item

    return (key, item)
}
带索引

处理数组时,您可以调用上述函数并传递一个闭包,该闭包也接受项目索引

例如

array => { item, index in
    // Map
    return ...
}

array ==> initial ** { result, item, index in
    // Reduce
    return ...
}

array >>= { item, index in
    // Turn into dictionary
    return (..., ...)
}

( ** ) 与输入绑定

如果要在函数中填充一些输入,可以使用**将其绑定到函数。

let inc = (+) ** 1

inc(3) // 4
inc(4) // 5

您也可以从右到左使用<**

(<+>) 或 (<*>) 并行化闭包。

比如说,您想要将两个闭包组合成一个新的闭包,该闭包同时接受两个输入并产生两个输出。
例如,您有一个字典 [Int: Int],您想要将每个键加一,并获取每个值的平方。

很简单

let dict = [ 2 : 4, 3 : 5]
let newDict = dict >>= inc <*> { $0 ** 2 } // [3 : 16, 4 : 25]

或者如果您想使用绑定

let newDict = dict >>= inc <*> ((**) <** 2)  // Now no one can read. Perfect!

但是如果两个函数都应该接受相同的输入,则使用<+>,并且两个闭包都将被分配相同的输入

let dict = [1, 2, 3, 4]
let newDict = dict >>= inc <+> { $0 ** 2 } // [2 : 1, 3 : 4, 4 : 9, 5 : 16] 

现在您的代码将看起来很棒

( ** ) 从函数中删除输入/输出

将函数强制转换为允许任何输入并丢弃它。

**{
    // Do stuff
}

等同于

{ _,_ in
    // Do stuff
}

或者作为后缀它会丢弃输出

{
    return something
}**

等同于

{
    _ = something
}

(<-) 非nil赋值

如果b不为nil则将b赋值给a

a <- b

等同于

a = b ?? a

(<-) 将map的结果赋值

将map的结果赋值给数组。

array <- handler

等同于

array = array.map(handler)

如果处理程序返回一个可选类型,但数组不能处理可选类型,则将丢弃所有的可选类型。

(<|) 将filter的结果赋值

将filter的结果赋值给数组

array <| handler

等同于

array = array.filter(handler)

(+) 合并数组

这样您可以快速合并数组

let concat = firstArray + secondArray

或者甚至:

firstArray += secondArray

(!) 将数组中所有的可选值移除

let array = [1, nil, 3]
!array // [1, 3]

(<=>) 交换两个变量的值

// a = 1 and b = 2
a <=> b // a = 2 and b = 1

(.?) 解包可选类型。如果没有它将给出默认值

注意:该类型必须符合Defaultable协议。

let i: Int? = nil
let j: Int? = 2

i.? // 0
j.? // 2

甚至在Collection中使用它

let array = [1, 2, nil, 4]
array.? // [1, 2, 0, 4]

不要与数组的默认值混淆

let a: [Int?]? = nil
let b: [Int?]? = [1, 2, nil, 4]

a.? // []
b.? // [1, 2, nil, 4]
(b.?).? // [1, 2, 0, 4]

(??) 检测nil

将检查一个值是否不为nil

??myVariable

等同于

myVariable != nil

它甚至可以被提供给闭包。

这意味着

??{ (item: String) in
    return item.date("HH:mm, dd:MM:yyyy")
}

这是一个类型为 (String) -> (Bool) 的闭包。意味着字符串中的日期是否为空。

>>> 在队列中运行

如果你想在一个特定的队列中运行闭包

queue >>> {
    // Do Stuff.
}

或者你想在秒数后运行它。例如以下示例将在5秒后运行闭包

5.0 >>> {
    // Do Stuff
}

当然,你可以将它们组合在一起

(queue, 5.0) >>> {
    // Do Stuff
}

// Or

(5.0, queue) >>> {
    // Do Stuff
}

>>> 链接闭包

如果你想让你的闭包更具模块化,你可以始终将它们链接在一起,并将它们转换成一个更大的闭包。

例如:假设你有一个日期数组。你想要一个包含每个小时的数组。

let hours = dates => { $0.string("hh") } >>> Int.init

这相当于说

let hours = dates.map { date in
    let string = date.string("hh")
    return Int(string)
}

优先级按以下顺序工作

链接优先于映射。因此 >>> 将先于 => 计算。

当然,在之前的例子中你不需要链接。你可以使用管道,这样会更短

let hours = dates => { $0.string("hh") | Int.init }

但那正是 Sweeft 的酷之处。它为你提供了选择 ;)

用户默认设置

将数据存储到 UserDefaults 可能会导致问题。主要原因是UserDefaults 使用字符串作为键,这很容易出错。此外,从用户默认设置中读取某些内容需要将值转换为你想要的类型,这不可避免地变得复杂。

这就是为什么 Sweeft 有一个状态API。这意味着任何需要存储到 UserDefaults 的内容都必须是它自己的状态 Struct 对象。

状态

例如,如果你想存储应用打开的次数

我们首先创建你应用程序的键

简单地为它们创建一个继承自 StatusKey 的枚举

enum AppDefaults: String, StatusKey {
    case timesOpened
    /// More Cases here...
}

现在我们创建状态

struct TimesOpened: Status {
    static let key: AppDefaults = .timesOpened
    static let defaultValue: Int = 0
}

要访问它,你只需从你的代码中调用它

let times = TimesOpened.value
// Do Something with it...
TimesOpened.value = times + 1

对象状态

有时你可能想存储比通常在UserDefaults中支持的字类型更复杂的信息。

为此,有一个对象状态。首先,你的数据必须符合 'StatusSerializable' 协议。这意味着它可以被序列化和反序列化为 [String:Any]

例如

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 ;)
    }

}

然后创建你的对象状态

struct MyDataStatus: ObjectStatus {
    static let key: AppDefaults = .myDataKey // Create a Key for this too
    static let defaultValue: MyData = MyData() // Hand some default value here
}

REST和HTTP

Sweeft 还抽象了很多重复的工作,如向你的REST API发送请求。

API和端点

要访问一个API,你只需要创建一个你可以访问的端点列表来描述API。

enum MyEndpoint: String, APIEndpoint {
    case login = "login"
    case user = "user/{id}"
}

然后你可以创建自己的API对象

struct MyAPI: API {
    typealias Endpoint = MyEndpoint
    let baseURL = "https://..."
}

并通过它进行任何请求,无论是数据、JSON还是任何符合我们的 DataRepresentable 协议的对象。例如

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)
    }

上面的代码执行了对 /user/1234 的 GET 请求。如果请求成功,它将读取JSON对象的 name 属性并输出。

可反序列化对象

可反序列化对象是指任何符合 Deserializable 协议的对象,因此可以从JSON中实例化。
例如,假设我们从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)
    }
    
}

完成这些后,你可以通过调用 get(:) 或 getAll(:) 分别获取API中的用户。

例如,你现在可以调用

let api = MyAPI()
User.get(using: api, at: .user, arguments: ["id": 1234])
    .onSuccess { user in
        print(user.name)
    }
    .onError { error in
        print(error)
    }

贡献

请贡献并帮助我以更多简化 Swift 语法的方法让 Swift 更加出色。
分支和星标此仓库并 #MakeSwiftGreatAgain