我正在测试通知和完成处理程序的使用情况,以特定顺序执行多个耗时的任务。一个同事让我检查可能的内存泄漏。
我的问题是:
- 我的LongTasksClass:这是使用通知和闭包的正确方式吗?
- 在iOS 9之后不删除观察者是正确的吗?
- 应该在其他地方发射单颗卫星吗?
在我的视图控制器中我有:
private var longTaskInstance: LongTasksClass? = LongTasksClass()
和在一个按钮:
longTaskInstance?.startOperation()
我班上
class LongTasksClass {
enum TestingEnum: String {
case firstOperation
case secondOperation
case stop
}
//public
let testNotification = NSNotification.Name("test")
//private
private var myStatus: TestingEnum = .firstOperation
private let notificationCenter = NotificationCenter.default
init() {
notificationCenter.addObserver(forName: self.testNotification, object: nil, queue: nil) { [weak self] notification in
guard let self = self else {return}
print(notification.userInfo?["info", default: "N/D"] ?? "no userInfo")
self.doWholeOperation() {
//first calls code in comletion
//then calls this
print("code after last completion")
}
}
}
func startOperation() {
notificationCenter.post(name: self.testNotification, object: nil, userInfo: ["info" : "info start operation"])
}
//MARK: - private section -
private func doWholeOperation(_ completion: @escaping (()) -> () ) {
switch myStatus {
case .firstOperation:
print("very start: (Date())n")
firstTimeConsumingTask { [weak self] in
guard let self = self else {return}
print("end first: (Date())n")
self.myStatus = .secondOperation
self.notificationCenter.post(name: self.testNotification, object: nil, userInfo: ["info" : "info sent from first task"])
}
case .secondOperation:
secondTimeConsumingTask {
print("end second: (Date())n")
self.myStatus = .stop
self.notificationCenter.post(name: self.testNotification, object: nil, userInfo: ["info" : "info sent from second task"])
}
case .stop:
myStatus = .firstOperation
let myCodeForCompletion: () = print("very end")
completion(myCodeForCompletion)
return
}
}
private func firstTimeConsumingTask(_ completion: @escaping () -> ()) {
print("start first task: (Date())")
//simulating long task
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
print("inside first task: (Date())")
completion()
}
}
private func secondTimeConsumingTask(_ completion: @escaping () -> ()) {
print("start second task: (Date())")
//simulating long task
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
print("inside second task: (Date())")
completion()
}
}
//should not be required aymore
deinit {
print("‼️ called deinit")
NotificationCenter.default.removeObserver(self)
}
}
您必须取消注册由addObserver(forName:object:queue:using)
创建的通知观察者。来自文档:
返回值:作为观察者的不透明对象。通知中心将强力保留此返回值,直到移除观察者注册。
在系统释放
addObserver(forName:object:queue:using:)
指定的对象之前,必须调用removeObserver(_:)
或removeObserver(_:name:object:)
。
这个removeObserver
在addObserver
的返回值(必须存储)上被调用。它不在self
上调用。addObserver(forName:object:queue:using)
的要点在于它创建了一个不透明的对象观察者;它不会使self
成为观察者。
关于iOS 9,你想到的是addObserver(_:selector:name:object:)
,它使第一个参数(通常是self
)成为观察者,通常不需要注销:
如果你的应用目标是iOS 9.0及以上版本或macOS 10.11及以上版本,你不需要注销你用这个函数创建的观察者。如果您忘记或无法删除观察者,系统将在下一次发布时清理它。
一如既往,请查阅文档。
你的deinit
是不正确的(如你所怀疑的)。你所做的调用不是必需的,但是你缺少必要的调用来消除你所做的观察。
作为一般规则,基于选择器的API更容易正确使用。人们倾向于喜欢基于块的api,即使它们不太方便。
在您的示例中,所需的代码是:class LongTasksClass {
...
var observer: NSObjectProtocol
...
init() {
observer = notificationCenter.addObserver(forName: self.testNotification, object: nil, queue: nil) { [weak self] notification in
...
}
}
...
deinit {
notificationCenter.removeObserver(observer)
}