在 Swift 中过滤多次
Filtering multiple times in Swift
我对 Swift 或 OOP 几乎一无所知,但我必须构建一个基本的 BLE 扫描仪应用程序。我找到了一个例子,我读了它,试图从它的立场来理解和发展。
现在,我有这样一个功能
@objc private func startSearch() {
bluetoothProvider.startScanning()
.filter { [weak self] newPeripheral in
guard let self = self else { return false }
return !self.scannedPeripherals.contains(where: { [=11=].peripheral.identifier == newPeripheral.peripheral.identifier
})
}
.subscribe(onNext: { [weak self] in self?.scannedPeripherals.append([=11=]) })
.disposed(by: disposeBag)
}
从一周开始我真的什么都不懂,守卫部分...但我想我得到了其他东西。现在我想至少添加一个其他过滤器,但它看起来不像我可以在教程或示例中找到的任何其他过滤器机制。任何人都可以在这里解释格式或帮助添加另一个过滤器吗?
我试过这些
.filter { [weak self] filteredPeripheral in
guard let self = self else { return false }
return !self.scannedPeripherals.contains(where: { _ in filteredPeripheral.rssi.intValue < 0
})
}
.filter{ [weak self] filteredPeripheral in
guard let self = self else { return false }
return !self.scannedPeripherals.contains(where: { [=12=].peripheral.identifier.uuidString.contains("0863")
})
}
但看起来没有任何影响。
在没有更多上下文的情况下,我不是 100% 肯定你正在尝试做什么,但我的直觉是你正在尝试过滤掉某些与这些模式不匹配的外围设备。您的方法通常是正确的,但您正在检查已扫描的外围设备,而不是新传入的外围设备。尝试添加此过滤器:
.filter { newPeripheral in
return (newPeripheral.rssi.intValue < 0 &&
newPeripheral.peripheral.identifier.uuidString.contains("0863"))
}
您发现的代码中的第一个过滤器似乎只是检查重复项,这就是它查看 scannedPeripherals
的原因(以及它需要 weak self
说明符的原因)。
您当然可以将过滤器链接在一起。根据您的评论,我认为您不需要在第二个或第三个过滤器中对 self 的任何引用:
@objc private func startSearch()
{
// You don't need [weak self]
bluetoothProvider.startScanning()
.filter
{ newPeripheral in
return !self.scannedPeripherals.contains {
[=10=].peripheral.identifier == newPeripheral.peripheral.identifier
}
}.filter { [=10=].rssi.intValue >= 0 }
.filter { [=10=].peripheral.identifier.uuidString.contains("0863") }
}
由于您没有动态组合过滤器,也没有映射到它们之间的不同类型,因此通过将它们压缩到一个过滤器中,您会获得稍微更好的性能。
@objc private func startSearch()
{
bluetoothProvider.startScanning().filter
{ newPeripheral in
!self.scannedPeripherals.contains {
[=11=].peripheral.identifier == newPeripheral.peripheral.identifier
}
&& newPeripheral.rssi >= 0
&& peripheral.identifier.uuidString.contains("0863")
}
}
由于您是 Swift 的新手,我想提出学习闭包的建议。首先编写一个传递给 filter
的命名函数(或其他采用闭包参数的方法)。让它工作。一旦命名函数开始工作,然后将其转换为闭包。
您甚至可以更进一步,编写一个执行您希望它执行的操作的循环。循环的主体本质上就是将进入您的闭包的内容。然后将循环的主体移动到命名函数中,这样您的循环就可以为每个元素调用它。当它起作用时,将循环更改为对 filter
(或 map
或 forEach
)的调用,并将您的命名函数传递给它。如果可行,请将您的函数更改为闭包。
随着时间的推移,您将对关闭感到足够舒服,以至于不需要中间折射。但一开始它很有帮助,因为它从您已经理解的东西开始,然后朝着新事物努力。
以防万一你还没有注意到它,我还会提到你看到 [=20=]
的地方,这是对传递给闭包的第一个参数的引用,而闭包的定义没有不要给它起个名字。在您的第一个闭包中,忽略不必要的 [weak self]
,您从 newPeripheral in
开始。这为该闭包的参数提供了一个名称,因此您可以通过 newPeripheral
引用它,但是当您调用 contains
时,您将传递嵌套在第一个闭包中的另一个闭包。那个嵌套的闭包没有命名它的参数,所以它使用 [=20=]
来引用它。对于多于一个参数的闭包,也可以有</code>、<code>
等...
此外,如果您熟悉 C 中“回调”函数的概念,那基本上就是闭包经常使用的用途,除了与 C 函数指针不同的是,它们不必引用命名函数,但可以动态定义,闭包可以从周围范围捕获变量。
weak
虽然你的问题实际上不是关于 weak
和 guard
你提到你不理解它们所以我想我只是简单地解释一下。
[weak self]
是“捕获语法”——基本上它是一种告诉编译器如何将符号从周围上下文导入到您传递给 filter
的闭包中的方法。在这种情况下,您将捕获 self
作为 weak
参考。
要了解什么是 weak
引用,了解什么是引用很有用。引用是 Swift 引用堆中动态分配的内容的方式。这样,它就像 C 中指针的主要用途。所有基本类型,加上 Array
、Dictionary
、String
、Set
和任何 struct
或者你定义的 enum
都是 value 类型,所以对它们进行 weak
引用是没有意义的。事实上,它们唯一的引用是当它们作为 inout
参数传递时。
除非您冒险进入 UnsafePointer
类型家族的领域,否则您可能在 Swift 中使用的唯一 reference 类型是一些有点像 class
.
普通引用,通常称为 strong 引用,增加被引用对象的引用计数。当它超出范围时,它会减少引用计数。当引用计数变为 0 时,它所引用的对象将被取消初始化和释放。这就是 Swift 内存管理的核心。
但是,在某些情况下您可以创建强引用循环。想象一个树数据结构,其中 children 保留对其 parent 的引用。在这种情况下,如果根节点超出范围,则树(如果它不止一个根节点)将永远不会被释放,因为引用计数永远不会变为 0,因为 children 持有强引用到 parent.
weak
引用是解决该问题的方法,因为它们不会增加引用计数,所以在树数据结构中,您希望每个节点对其 children,而是 weak
对其 parent 的引用。这样,当树的根超出范围时,整个树就会被释放。对于 doubly-linked 列表,同样的想法也是正确的。每个节点对其下一个节点都有一个强引用,对其前一个节点有 weak
引用(或者相反,只要只有一个强引用)。
weak
引用使用 Optional
语法,因为当您要访问它们时,它们引用的内容可能不存在。
强引用循环也可能发生在一种称为“转义”的闭包类型中损失。有时您会在带有闭包参数的函数声明中看到 @escaping
,通常用于完成处理程序。 “转义”闭包是一种存储起来供以后调用的闭包。在那种情况下,只要闭包保存在某个地方,任何对闭包内部 self
的强引用都可以防止其引用的 object 被取消初始化和释放。基本上是内存泄漏。
filter
不采用 @escaping
关闭。它不存储闭包,它在返回之前调用它,然后就完成了。所以它的声明不包含 @escaping
,并且在这种情况下不需要捕获对 self
的弱引用。
我没有提到 unowned
引用,但它们有点像隐式展开的 weak
引用。
guard
guard
基本上是一个倒置的 if
语句,增加了它的 else
块 必须 return
, throw
或调用一个 returns Never
的函数,这是一个函数表明它从不 returns 的方式,要么因为它终止了程序,就像 fatalError()
那样,或者因为某种原因进入无限循环。在循环中,guard
的else
块也可以break
或continue
,在switch
语句中,它可以break
或fallthrough
.
您在评论中提到您已经完成了嵌入式 C。C 程序通常有“保护”ifs 以在出现错误情况时提前退出,或者对于简单的情况将它们移出主逻辑。
int find(char x, const char* s)
{
if (s == NULL) return -1; // This is a guard "if" in C
for(int i = 0; s[i] != 0; ++i) {
if (s[i] == x) return i;
}
return -1;
}
基本上
guard condition else { return }
等同于
if !condition { return }
认为 guard
是说它的条件必须为真才能到达后面的主代码,否则控制权将传递给 else
块,该块必须以某种方式退出当前上下文。
通常您可以同样轻松地使用任何一个,但是如果您将 guard
或 if
与可选绑定结合使用,就会有所不同。
guard let x = foo() else { return }
// x is defined from here on
if let y = foo() {
// y is defined here
}
// y is not defined here
我对 Swift 或 OOP 几乎一无所知,但我必须构建一个基本的 BLE 扫描仪应用程序。我找到了一个例子,我读了它,试图从它的立场来理解和发展。
现在,我有这样一个功能
@objc private func startSearch() {
bluetoothProvider.startScanning()
.filter { [weak self] newPeripheral in
guard let self = self else { return false }
return !self.scannedPeripherals.contains(where: { [=11=].peripheral.identifier == newPeripheral.peripheral.identifier
})
}
.subscribe(onNext: { [weak self] in self?.scannedPeripherals.append([=11=]) })
.disposed(by: disposeBag)
}
从一周开始我真的什么都不懂,守卫部分...但我想我得到了其他东西。现在我想至少添加一个其他过滤器,但它看起来不像我可以在教程或示例中找到的任何其他过滤器机制。任何人都可以在这里解释格式或帮助添加另一个过滤器吗?
我试过这些
.filter { [weak self] filteredPeripheral in
guard let self = self else { return false }
return !self.scannedPeripherals.contains(where: { _ in filteredPeripheral.rssi.intValue < 0
})
}
.filter{ [weak self] filteredPeripheral in
guard let self = self else { return false }
return !self.scannedPeripherals.contains(where: { [=12=].peripheral.identifier.uuidString.contains("0863")
})
}
但看起来没有任何影响。
在没有更多上下文的情况下,我不是 100% 肯定你正在尝试做什么,但我的直觉是你正在尝试过滤掉某些与这些模式不匹配的外围设备。您的方法通常是正确的,但您正在检查已扫描的外围设备,而不是新传入的外围设备。尝试添加此过滤器:
.filter { newPeripheral in
return (newPeripheral.rssi.intValue < 0 &&
newPeripheral.peripheral.identifier.uuidString.contains("0863"))
}
您发现的代码中的第一个过滤器似乎只是检查重复项,这就是它查看 scannedPeripherals
的原因(以及它需要 weak self
说明符的原因)。
您当然可以将过滤器链接在一起。根据您的评论,我认为您不需要在第二个或第三个过滤器中对 self 的任何引用:
@objc private func startSearch()
{
// You don't need [weak self]
bluetoothProvider.startScanning()
.filter
{ newPeripheral in
return !self.scannedPeripherals.contains {
[=10=].peripheral.identifier == newPeripheral.peripheral.identifier
}
}.filter { [=10=].rssi.intValue >= 0 }
.filter { [=10=].peripheral.identifier.uuidString.contains("0863") }
}
由于您没有动态组合过滤器,也没有映射到它们之间的不同类型,因此通过将它们压缩到一个过滤器中,您会获得稍微更好的性能。
@objc private func startSearch()
{
bluetoothProvider.startScanning().filter
{ newPeripheral in
!self.scannedPeripherals.contains {
[=11=].peripheral.identifier == newPeripheral.peripheral.identifier
}
&& newPeripheral.rssi >= 0
&& peripheral.identifier.uuidString.contains("0863")
}
}
由于您是 Swift 的新手,我想提出学习闭包的建议。首先编写一个传递给 filter
的命名函数(或其他采用闭包参数的方法)。让它工作。一旦命名函数开始工作,然后将其转换为闭包。
您甚至可以更进一步,编写一个执行您希望它执行的操作的循环。循环的主体本质上就是将进入您的闭包的内容。然后将循环的主体移动到命名函数中,这样您的循环就可以为每个元素调用它。当它起作用时,将循环更改为对 filter
(或 map
或 forEach
)的调用,并将您的命名函数传递给它。如果可行,请将您的函数更改为闭包。
随着时间的推移,您将对关闭感到足够舒服,以至于不需要中间折射。但一开始它很有帮助,因为它从您已经理解的东西开始,然后朝着新事物努力。
以防万一你还没有注意到它,我还会提到你看到 [=20=]
的地方,这是对传递给闭包的第一个参数的引用,而闭包的定义没有不要给它起个名字。在您的第一个闭包中,忽略不必要的 [weak self]
,您从 newPeripheral in
开始。这为该闭包的参数提供了一个名称,因此您可以通过 newPeripheral
引用它,但是当您调用 contains
时,您将传递嵌套在第一个闭包中的另一个闭包。那个嵌套的闭包没有命名它的参数,所以它使用 [=20=]
来引用它。对于多于一个参数的闭包,也可以有</code>、<code>
等...
此外,如果您熟悉 C 中“回调”函数的概念,那基本上就是闭包经常使用的用途,除了与 C 函数指针不同的是,它们不必引用命名函数,但可以动态定义,闭包可以从周围范围捕获变量。
weak
虽然你的问题实际上不是关于 weak
和 guard
你提到你不理解它们所以我想我只是简单地解释一下。
[weak self]
是“捕获语法”——基本上它是一种告诉编译器如何将符号从周围上下文导入到您传递给 filter
的闭包中的方法。在这种情况下,您将捕获 self
作为 weak
参考。
要了解什么是 weak
引用,了解什么是引用很有用。引用是 Swift 引用堆中动态分配的内容的方式。这样,它就像 C 中指针的主要用途。所有基本类型,加上 Array
、Dictionary
、String
、Set
和任何 struct
或者你定义的 enum
都是 value 类型,所以对它们进行 weak
引用是没有意义的。事实上,它们唯一的引用是当它们作为 inout
参数传递时。
除非您冒险进入 UnsafePointer
类型家族的领域,否则您可能在 Swift 中使用的唯一 reference 类型是一些有点像 class
.
普通引用,通常称为 strong 引用,增加被引用对象的引用计数。当它超出范围时,它会减少引用计数。当引用计数变为 0 时,它所引用的对象将被取消初始化和释放。这就是 Swift 内存管理的核心。
但是,在某些情况下您可以创建强引用循环。想象一个树数据结构,其中 children 保留对其 parent 的引用。在这种情况下,如果根节点超出范围,则树(如果它不止一个根节点)将永远不会被释放,因为引用计数永远不会变为 0,因为 children 持有强引用到 parent.
weak
引用是解决该问题的方法,因为它们不会增加引用计数,所以在树数据结构中,您希望每个节点对其 children,而是 weak
对其 parent 的引用。这样,当树的根超出范围时,整个树就会被释放。对于 doubly-linked 列表,同样的想法也是正确的。每个节点对其下一个节点都有一个强引用,对其前一个节点有 weak
引用(或者相反,只要只有一个强引用)。
weak
引用使用 Optional
语法,因为当您要访问它们时,它们引用的内容可能不存在。
强引用循环也可能发生在一种称为“转义”的闭包类型中损失。有时您会在带有闭包参数的函数声明中看到 @escaping
,通常用于完成处理程序。 “转义”闭包是一种存储起来供以后调用的闭包。在那种情况下,只要闭包保存在某个地方,任何对闭包内部 self
的强引用都可以防止其引用的 object 被取消初始化和释放。基本上是内存泄漏。
filter
不采用 @escaping
关闭。它不存储闭包,它在返回之前调用它,然后就完成了。所以它的声明不包含 @escaping
,并且在这种情况下不需要捕获对 self
的弱引用。
我没有提到 unowned
引用,但它们有点像隐式展开的 weak
引用。
guard
guard
基本上是一个倒置的 if
语句,增加了它的 else
块 必须 return
, throw
或调用一个 returns Never
的函数,这是一个函数表明它从不 returns 的方式,要么因为它终止了程序,就像 fatalError()
那样,或者因为某种原因进入无限循环。在循环中,guard
的else
块也可以break
或continue
,在switch
语句中,它可以break
或fallthrough
.
您在评论中提到您已经完成了嵌入式 C。C 程序通常有“保护”ifs 以在出现错误情况时提前退出,或者对于简单的情况将它们移出主逻辑。
int find(char x, const char* s)
{
if (s == NULL) return -1; // This is a guard "if" in C
for(int i = 0; s[i] != 0; ++i) {
if (s[i] == x) return i;
}
return -1;
}
基本上
guard condition else { return }
等同于
if !condition { return }
认为 guard
是说它的条件必须为真才能到达后面的主代码,否则控制权将传递给 else
块,该块必须以某种方式退出当前上下文。
通常您可以同样轻松地使用任何一个,但是如果您将 guard
或 if
与可选绑定结合使用,就会有所不同。
guard let x = foo() else { return }
// x is defined from here on
if let y = foo() {
// y is defined here
}
// y is not defined here