@escaping completionHandler - 内存管理如何为他们工作?
@escaping completionHandler - How does memory management work for them?
在我的应用程序中,2 个控制器使用相同的数据来显示,每个控制器都会调用服务器 class 从服务器获取数据。
如果用户来回导航另一个请求,即在第一个请求完成之前发出两个获取相同数据的请求。
我想通过仅使用单个服务器调用来优化它,如果一个正在进行中则阻止另一个。
为此,我使用了一个单例 class,它负责获取数据并防止来自服务器的 2 个同时请求相同的数据。
如果控制器的 ViewWillDisappear
收到呼叫,我不想在从服务器接收到数据后通知该控制器。就像我们在viewWillDisappear
中移除观察者并在viewWillAppear
中添加观察者一样
下面是我的代码 -
final class SingletonDataManager {
typealias MYCompletionHandler = (_ persons:[Person]?, _ error: NSError?) -> Void
// MARK: Shared Instance
static let sharedInstance = SingletonDataManager()
// MARK: Concurrent queue f
fileprivate let concurrentQ = DispatchQueue(label: "com.test.concurrentQueue",
qos: .userInitiated,
attributes: .concurrent)
fileprivate var _handler: MYCompletionHandler?
fileprivate var handler: MYCompletionHandler? {
get {
return concurrentQ.sync {
return _handler
}
}
set {
concurrentQ.async(flags: .barrier){ [weak self] in
self?._handler = newValue
}
}
}
fileprivate var result:(persons: [Person]?, error: NSError?) {
didSet {
if let hndlr = handler {
hndlr(result.persons, result.error)
self.isInProgress = false
}
}
}
fileprivate var _isInProgress = false
fileprivate var isInProgress: Bool {
get {
return concurrentQ.sync {
return _isInProgress
}
}
set {
concurrentQ.async(flags: .barrier){ [weak self] in
self?._isInProgress = newValue
}
}
}
// MARK:- init()
private init() {
}
deinit {
print(" destroyed")
}
internal func getData(_ onCompletion: @escaping MYCompletionHandler) {
if self.isInProgress == true {
self.handler = onCompletion
} else {
NetworkManager.sharedInstance().fetchDataFromServer({ [weak self] (data, error) in
DispatchQueue.main.async {
self?.result = (data, error)
}
})
}
}
}
}
和控制器class 1 - ViewControllerA
class ViewControllerA {
func getPersons() {
SingletonDataManager.sharedInstance.getData(onCompletion: { [weak self] (persons, error) in
})
override func viewWillAppear(_ animated: Bool) {
getPersons()
}
}
控制器class 2 - ViewControllerB
class ViewControllerB {
func getPersons() {
SingletonDataManager.sharedInstance.getData(onCompletion: { [weak self] (persons, error) in
})
override func viewWillAppear(_ animated: Bool) {
getPersons()
}
}
用户可以在 ViewControllerA 和 ViewControllerB 之间导航。
这里有两种情况——
在控制器 ViewControllerA 发起的网络请求完成之前,用户可以从 ViewControllerA 导航到 ViewControllerB。
或请求完成后移动后。
我想通过阻止多个服务器请求来解决案例 1。
为了实现这一点,我在我的单例中使用了一个 bool 变量 class
isInProgress
并设置其值 线程安全方式.
我还有一个变量 **handler**
用于保存需要调用的最新完成处理程序。这也是 线程安全的。
一切都按预期工作,但我想确保未调用的 completionHanlder 不会占用额外的内存。
当我为我的 **handler
变量分配新的完成处理程序时,它们会释放吗?** 还是会不断消耗内存?
这是解决这个问题的正确方法吗?
或者我应该在这里使用NSNotification
。
什么是最好的方法?
我想在内存不足警告的情况下释放结果数组占用的内存。如何在 ARC 中处理此问题。
希望我现在能正确解释我的问题。
我已经多次问过这个问题,但由于对问题的解释不当而没有得到很好的回应。
如果需要更多说明,请告诉我。
谢谢。
我终于明白了!
@escaping 闭包即使在 class 取消初始化后也会被调用,但是如果你通过使用 weak self 正确管理内存,它不会得到 nil 实例变量。
如果我们根本不调用@escaping闭包,它不会占用任何内存。
示例代码
final class DataSource {
typealias RequestCompleted = (_ data:String?, _ error: NSError?) -> Void
// MARK: Shared Instance
static let sharedInstance = DataSource()
// MARK: Concurrent queue for thread-safe array
fileprivate let concurrentQ = DispatchQueue(label: "com.test.concurrentQueue",
qos: .userInitiated,
attributes: .concurrent)
// MARK:- Local Variable
fileprivate var _dataHandler: RequestCompleted?
fileprivate var dataHandler: RequestCompleted? {
get {
return concurrentQ.sync {
return _dataHandler
}
}
set {
concurrentQ.async(flags: .barrier){ [weak self] in
self?._dataHandler = newValue
}
}
}
fileprivate var result:(data: String?, error: NSError?) {
didSet {
if let handlr = dataHandler {
handlr(result.data, result.error)
self.isRequestInProgress = false
}
}
}
fileprivate var _isRequestInProgress = false
fileprivate var isRequestInProgress: Bool {
get {
return concurrentQ.sync {
return _isRequestInProgress
}
}
set {
concurrentQ.async(flags: .barrier){ [weak self] in
self?._isRequestInProgress = newValue
}
}
}
// MARK:- Private init()
private init() {
}
deinit {
print("Deinitialized")
}
internal func fetchData(_ onCompletion: @escaping RequestCompleted) {
self.dataHandler = onCompletion
if self.isRequestInProgress == true { print("TTT: In Progress")
return
} else {
self.isRequestInProgress = true
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(20)) {
// Code
self.result = ("Done", nil)
}
}
}
}
ViewController
class ViewController: UIViewController {
override func viewWillAppear(_ animated: Bool) {
print("ViewController 1 Function")
DataSource.sharedInstance.fetchData { (name, error) in
print("ViewController 1 Handler")
}
}
}
ViewController2
class ViewController2: UIViewController {
var str = "Test"
var arr = [1, 2, 3]
override func viewWillAppear(_ animated: Bool) {
print("ViewController 2 Function")
DataSource.sharedInstance.fetchData { [weak self] (name, error) in
print("ViewController 2 Handler")
print("CCCC\(self?.arr ?? [0])")
print("SSSS\(self?.str ?? "Happy")")
}
}
deinit {
print("VC2.. deinit")
}
}
ViewController3
class ViewController3: UIViewController {
override func viewWillAppear(_ animated: Bool) {
print("ViewController 3 Function")
DataSource.sharedInstance.fetchData { (name, error) in
print("ViewController 3 Handler")
}
}
deinit {
print("VC3.. deinit")
}
}
以及低内存警告-
自从 swift
collection types and tuple are value type,
如果内存不足警告,我将删除人员对象或将元组设置为 nil。它不会影响我的控制器视图上的数据。
在我的应用程序中,2 个控制器使用相同的数据来显示,每个控制器都会调用服务器 class 从服务器获取数据。
如果用户来回导航另一个请求,即在第一个请求完成之前发出两个获取相同数据的请求。
我想通过仅使用单个服务器调用来优化它,如果一个正在进行中则阻止另一个。
为此,我使用了一个单例 class,它负责获取数据并防止来自服务器的 2 个同时请求相同的数据。
如果控制器的 ViewWillDisappear
收到呼叫,我不想在从服务器接收到数据后通知该控制器。就像我们在viewWillDisappear
中移除观察者并在viewWillAppear
下面是我的代码 -
final class SingletonDataManager {
typealias MYCompletionHandler = (_ persons:[Person]?, _ error: NSError?) -> Void
// MARK: Shared Instance
static let sharedInstance = SingletonDataManager()
// MARK: Concurrent queue f
fileprivate let concurrentQ = DispatchQueue(label: "com.test.concurrentQueue",
qos: .userInitiated,
attributes: .concurrent)
fileprivate var _handler: MYCompletionHandler?
fileprivate var handler: MYCompletionHandler? {
get {
return concurrentQ.sync {
return _handler
}
}
set {
concurrentQ.async(flags: .barrier){ [weak self] in
self?._handler = newValue
}
}
}
fileprivate var result:(persons: [Person]?, error: NSError?) {
didSet {
if let hndlr = handler {
hndlr(result.persons, result.error)
self.isInProgress = false
}
}
}
fileprivate var _isInProgress = false
fileprivate var isInProgress: Bool {
get {
return concurrentQ.sync {
return _isInProgress
}
}
set {
concurrentQ.async(flags: .barrier){ [weak self] in
self?._isInProgress = newValue
}
}
}
// MARK:- init()
private init() {
}
deinit {
print(" destroyed")
}
internal func getData(_ onCompletion: @escaping MYCompletionHandler) {
if self.isInProgress == true {
self.handler = onCompletion
} else {
NetworkManager.sharedInstance().fetchDataFromServer({ [weak self] (data, error) in
DispatchQueue.main.async {
self?.result = (data, error)
}
})
}
}
}
}
和控制器class 1 - ViewControllerA
class ViewControllerA {
func getPersons() {
SingletonDataManager.sharedInstance.getData(onCompletion: { [weak self] (persons, error) in
})
override func viewWillAppear(_ animated: Bool) {
getPersons()
}
}
控制器class 2 - ViewControllerB
class ViewControllerB {
func getPersons() {
SingletonDataManager.sharedInstance.getData(onCompletion: { [weak self] (persons, error) in
})
override func viewWillAppear(_ animated: Bool) {
getPersons()
}
}
用户可以在 ViewControllerA 和 ViewControllerB 之间导航。 这里有两种情况—— 在控制器 ViewControllerA 发起的网络请求完成之前,用户可以从 ViewControllerA 导航到 ViewControllerB。 或请求完成后移动后。
我想通过阻止多个服务器请求来解决案例 1。
为了实现这一点,我在我的单例中使用了一个 bool 变量 class
isInProgress
并设置其值 线程安全方式.
我还有一个变量 **handler**
用于保存需要调用的最新完成处理程序。这也是 线程安全的。
一切都按预期工作,但我想确保未调用的 completionHanlder 不会占用额外的内存。
当我为我的 **handler
变量分配新的完成处理程序时,它们会释放吗?** 还是会不断消耗内存?
这是解决这个问题的正确方法吗?
或者我应该在这里使用NSNotification
。
什么是最好的方法?
我想在内存不足警告的情况下释放结果数组占用的内存。如何在 ARC 中处理此问题。
希望我现在能正确解释我的问题。 我已经多次问过这个问题,但由于对问题的解释不当而没有得到很好的回应。 如果需要更多说明,请告诉我。
谢谢。
我终于明白了!
@escaping 闭包即使在 class 取消初始化后也会被调用,但是如果你通过使用 weak self 正确管理内存,它不会得到 nil 实例变量。
如果我们根本不调用@escaping闭包,它不会占用任何内存。
示例代码
final class DataSource {
typealias RequestCompleted = (_ data:String?, _ error: NSError?) -> Void
// MARK: Shared Instance
static let sharedInstance = DataSource()
// MARK: Concurrent queue for thread-safe array
fileprivate let concurrentQ = DispatchQueue(label: "com.test.concurrentQueue",
qos: .userInitiated,
attributes: .concurrent)
// MARK:- Local Variable
fileprivate var _dataHandler: RequestCompleted?
fileprivate var dataHandler: RequestCompleted? {
get {
return concurrentQ.sync {
return _dataHandler
}
}
set {
concurrentQ.async(flags: .barrier){ [weak self] in
self?._dataHandler = newValue
}
}
}
fileprivate var result:(data: String?, error: NSError?) {
didSet {
if let handlr = dataHandler {
handlr(result.data, result.error)
self.isRequestInProgress = false
}
}
}
fileprivate var _isRequestInProgress = false
fileprivate var isRequestInProgress: Bool {
get {
return concurrentQ.sync {
return _isRequestInProgress
}
}
set {
concurrentQ.async(flags: .barrier){ [weak self] in
self?._isRequestInProgress = newValue
}
}
}
// MARK:- Private init()
private init() {
}
deinit {
print("Deinitialized")
}
internal func fetchData(_ onCompletion: @escaping RequestCompleted) {
self.dataHandler = onCompletion
if self.isRequestInProgress == true { print("TTT: In Progress")
return
} else {
self.isRequestInProgress = true
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(20)) {
// Code
self.result = ("Done", nil)
}
}
}
}
ViewController
class ViewController: UIViewController {
override func viewWillAppear(_ animated: Bool) {
print("ViewController 1 Function")
DataSource.sharedInstance.fetchData { (name, error) in
print("ViewController 1 Handler")
}
}
}
ViewController2
class ViewController2: UIViewController {
var str = "Test"
var arr = [1, 2, 3]
override func viewWillAppear(_ animated: Bool) {
print("ViewController 2 Function")
DataSource.sharedInstance.fetchData { [weak self] (name, error) in
print("ViewController 2 Handler")
print("CCCC\(self?.arr ?? [0])")
print("SSSS\(self?.str ?? "Happy")")
}
}
deinit {
print("VC2.. deinit")
}
}
ViewController3
class ViewController3: UIViewController {
override func viewWillAppear(_ animated: Bool) {
print("ViewController 3 Function")
DataSource.sharedInstance.fetchData { (name, error) in
print("ViewController 3 Handler")
}
}
deinit {
print("VC3.. deinit")
}
}
以及低内存警告-
自从 swift
collection types and tuple are value type,
如果内存不足警告,我将删除人员对象或将元组设置为 nil。它不会影响我的控制器视图上的数据。