Reputation: 404
In my app 2 controller uses same data to display, and server call is made from each controller class to get data from server.
If user navigates back and forth another request that both fetch same data is being made before first one is completed .
I want to optimize this by using only single server call and prevent another if one is in progress.
To do this I am using a singleton class which takes care of getting data and preventing 2 simultaneous request from server for same data.
If controller's ViewWillDisappear
gets call I don't want to notify that controller once data is received from server. Just like we remove observers in viewWillDisappear
and add observer in viewWillAppear
Below is my code -
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)
}
})
}
}
}
}
And Controller class 1 - ViewControllerA
class ViewControllerA {
func getPersons() {
SingletonDataManager.sharedInstance.getData(onCompletion: { [weak self] (persons, error) in
})
override func viewWillAppear(_ animated: Bool) {
getPersons()
}
}
Controller class 2 - ViewControllerB
class ViewControllerB {
func getPersons() {
SingletonDataManager.sharedInstance.getData(onCompletion: { [weak self] (persons, error) in
})
override func viewWillAppear(_ animated: Bool) {
getPersons()
}
}
User can navigates between ViewControllerA and ViewControllerB. Here there are two cases - User may navigates to ViewControllerB from ViewControllerA before network request initiated by controller ViewControllerA completed. OR After moved after request completion.
I want to solve case 1 by preventing multiple server request.
To achieve this I am using one bool variable in my singleton class
isInProgress
and setting its value thread-safe way.
I have one more variable **handler**
which keeps most recent completion handler that needs to be called. This is also thread-safe.
Everything is working as expected but I want to make sure that completionHanlder which are not called won't take extra memory.
Do they released when I assign new completion handler to my **handler
variable?** or will eat up memory contineously?
Is it the right way to solve this problem?
Or Should I use NSNotification
here.
What will be the best approach?
I want to free memory taken by results array in case of low memory warning. How to handle this in ARC.
Hope I explain my problem properly now. I have asked this questions many times but didn't receive good response because of bad explanation of problem. Please let me know if more clarification required.
Thanks.
Upvotes: 1
Views: 313
Reputation: 404
I finally figured out!
@escaping closure gets call even after class deinitialized, But won't it will get nil instance variable if you properly managed memory by using weak self.
If we don't call @escaping closure at all it doesn't occupy any memory.
Sample Code
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")
}
}
And For Low memory warning-
Since in swift
collection types and tuple are value type,
I will either remove person objects or set tuple to nil in case of low memory warning. It won't impact data on my Controller view.
Upvotes: 1