测试已测试 | ✗ |
语言语言 | SwiftSwift |
许可证 | MIT |
发布最新发布 | 2018年2月 |
SwiftSwift 版本 | 3.2 |
SPM支持 SPM | ✓ |
由 Mathias Quintero 维护。
更甜的 Swift - 为 Swift 添加更多语法糖
这是扩展和运算符的集合,可以使 Swift 代码更简洁。我从多个项目中使用过这些。
注意:这些运算符是为了帮助我以函数式风格编写 Swift。也就是函数式编程。
所以大多数这些都涉及 Swift 中函数式编程的可能问题和烦恼。
请:为使 Swift 看起来更酷...在问题区域提出您的新想法作为改进
Sweeft 作为 Cocoapods 的库和 Swift 包管理器中的依赖项都可获取。因此,您可以选择如何将 Sweeft 包含到您的项目中。
将 'Sweeft' 添加到您的 Podfile
pod 'Sweeft'
将 '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)
]
)
将此添加到您的 Cartfile
github "mathiasquintero/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)
如果闭包返回空值而不是值,它将作为循环运行而不是映射
array => { item in
// Do Stuff
}
就像上面一样,但使用flatMap。
如果您正在进行与数组相同类型的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
}
如果b不为nil则将b赋值给a
a <- b
等同于
a = b ?? a
将map的结果赋值给数组。
array <- handler
等同于
array = array.map(handler)
如果处理程序返回一个可选类型,但数组不能处理可选类型,则将丢弃所有的可选类型。
将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
??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
}
Sweeft 还抽象了很多重复的工作,如向你的REST 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