我在swift中有以下A类和B类:
protocol ClassBProtocol {
func doSomething(
completionHandler: @escaping (
ClassBProtocol?,
Error?
) -> Void
)
}
class ClassB: ClassBProtocol
{
init(key: String) {
self.key = key
}
func doSomething(
completionHandler: @escaping (
ClassBProtocol?,
Error?
) -> Void
) {
// Does some network requests and if it was successful does the following:
completionHandler(ClassB(), nil)
}
}
public class ClassA: ClassAProtocol {
static var instance: ClassA? = nil
static let initQueue = DispatchQueue(label: "queue")
static let semaphore = DispatchSemaphore(value: 1)
@objc public static func getSomething(
withKey key: String,
completionHandler: @escaping (ClassA?, Error?) -> Void
) {
ClassA.initQueue.async {
ClassA.semaphore.wait()
DispatchQueue.main.async {
if let objectA = ClassA.instance {
ClassA.semaphore.signal()
completionHandler(objectA, nil)
return
}
let objectB = ClassB(withKey: key)
objectB.doSomething { response, error in
guard let response = response else {
ClassA.semaphore.signal()
completionHandler(nil, error)
return
}
let objectA = ClassA()
ClassA.instance = objectA
ClassA.semaphore.signal()
completionHandler(objectA, nil)
}
}
}
}
}
以下测试用例用于测试ClassA. getSomething()并确保不会发生争用情况:
func testgetSomethingReturnsSameInstance() {
let expectation1 = self.expectation(description: "getSomething 1 completed")
let expectation2 = self.expectation(description: "getSomething 2 completed")
let expectation3 = self.expectation(description: "getSomething 3 completed")
var client1: ClassA?
var client2: ClassA?
var client3: ClassA?
ClassA.getSomething() { (client, error) in
client1 = client
expectation1.fulfill()
}
ClassA.getSomething() { (client, error) in
client2 = client
expectation2.fulfill()
}
ClassA.getSomething() { (client, error) in
client3 = client
expectation3.fulfill()
}
waitForExpectations(timeout: 10) { (error) in
XCTAssertEqual(client1, client2)
XCTAssertEqual(client2, client3)
XCTAssertEqual(client1, client3)
}
}
ClassB中的doSomething发送一个网络请求并返回一个对象,我需要模拟这个方法来完成以下操作:
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
completionHandler(response: ClassB(), error: nil)
}
但找不到任何方法。有人能解决这个问题吗?
1条答案
按热度按时间7kqas0il1#
如果使用合理的名称,代码会更容易理解。我怀疑
ClassB
Package 了一个API,并且ClassA
保存了一些值(如数据库),您希望从API中只提取一次。因此,让我们相应地重命名ClassBProtocol
和ClassB
:然后,让我们将
ClassA
重命名为Database
,并暂停getSomething
,只留下以下内容:现在,如何在第一次请求数据库时只取一次数据库?
Apple工程师的一般建议是避免在发送到调度队列的块中潜在地阻塞操作。在这种情况下,
semaphore.wait()
是潜在地阻塞操作。此外,同步代码比异步代码更容易测试,但您已经将所有内容都设置为异步。您的
getSomething
所做的第一件事是异步调度,并且大量状态(挂起的完成处理程序集)隐藏在Dispatch数据结构中,我们无法访问。我们不使用
semaphore
和initQueue
,而是手动地同步跟踪在数据库被提取时需要调用的完成处理程序。我们将使用
enum
存储这三个互斥状态,并使用DispatchQueue
保护对存储状态的访问:当请求数据库时,我们检查状态并采取适当的行动:
请注意,在
q
下没有执行阻塞操作,因此使用q.sync
代替q.async
是安全和高效的,稍后我们将看到它使函数更易于测试。好吧,现在回到你真正的问题,我解释为:既然我们已经有了一个
API
协议,我们想让getDatabase
泛型化一个符合API
的类型,并让它接受该类型的一个示例:这些更改意味着该方法不再与Objective-C兼容。因此,让我们添加一个带有旧的Objective-C兼容签名的重载:
现在我们准备编写一个
API
的模拟实现,根据您发布的代码,它看起来如下所示:但我不喜欢这种实现,至少有三个原因:
fetch
只被调用一次并不容易。相反,让我们这样编写模拟实现:
现在我们可以编写测试用例来使用这个实现:
以下是本测试用例验证的一些内容:
Database.getDatabase
之前不调用api.fetch
。api.fetch
只被调用一次。getDatabase
完成处理程序在api.fetch
完成处理程序之后调用。