Reputation: 43
I'm using an OperationQueue to upload files one by one to a remote server using URLSession.dataTask. A delegate is used to update a progress bar but after implementing OperationQueue my delegate becomes nil. It worked without OperationQueues. Looking at the stack while the program is running I don't see my progress bar's view controller. It's been a few days and I still can't quite figure it out. I'm guessing the view controller is getting deallocated but I'm not sure how to prevent it from getting deallocated. Thank you.
I have my delegate set to self in NetWorkViewController but inside my NetworkManager class's urlSession(didSendBodyData), the delegate becomes nil. The delegate is not weak and is a class variable.
However, my delegate becomes none-nil again within the completion block of my BlockOperation. This works for dismissing the ViewController via delegation. But the delegate is nil when I'm trying to update inside urlSession(didSendBodyData)...
UPDATE 10/30/2018
It seems that my urlSessions delegates are on a separate thread and is enqueued to the main thread when called but I lose reference to my custom delegate that updates the UI. I'm trying to read more about multithreading but any help would be appreciated!
UPDATE 2 10/30/2018
Solution found The issue was that I was creating another instance of NetworkManager
inside each operation. This causes the delegate
to be nil
because a new instance of NetworkManager
is being created for each operation. The fix is to pass self
from the original NetworkManager
so the delegate
is retained.
uploadFiles
func uploadFiles(item: LocalEntry) {
let mainOperation = UploadMainFileOperation(file: item)
// This is where I need to give the operation its
// networkManager so the proper delegate is transferred.
mainOperation.networkManager = self
mainOperation.onDidUpload = { (uploadResult) in
if let result = uploadResult {
self.result.append(result)
}
}
if let lastOp = queue.operations.last {
mainOperation.addDependency(lastOp)
}
queue.addOperation(mainOperation)
....
....
let finishOperation = BlockOperation { [unowned self] in
self.dismissProgressController()
for result in self.result {
print(result)
}
self.delegate?.popToRootController()
}
if let lastOp = queue.operations.last {
finishOperation.addDependency(lastOp)
}
queue.addOperation(finishOperation)
queue.isSuspended = false
}
UploadMainFileOperation
class UploadMainFileOperation: NetworkOperation {
let file: LocalEntry
// First issue is here. I re-declared another NetworkManager that doesn't have its delegate properly set.
private let networkManager = NetworkManager()
// I have since have this class receive the original networkManager after it's declared.
var networkManager: NetworkManager?
var onDidUpload: ((_ uploadResult: String?) -> Void)!
init(file: LocalEntry) {
self.file = file
}
override func execute() {
uploadFile()
}
private func uploadFile() {
networkManager.uploadMainFile(item: file) {
(httpResult) in
self.onDidUpload(httpResult)
self.finished(error: "upload main")
}
}
}
urlSession(didSendBodyData)
func urlSession(_ session: URLSession, task: URLSessionTask, didSendBodyData bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64) {
// This is wrong.
let uploadProgress: Float = Float(totalBytesSent) / Float(totalBytesExpectedToSend)
updateDelegateWith(progress: uploadProgress)
// This is the correct way for my situation.
// Because each operation on the queue is on a separate thread. I need to update the UI from the main thread.
DispatchQueue.main.async {
let uploadProgress: Float = Float(totalBytesSent) / Float(totalBytesExpectedToSend)
self.updateDelegateWith(progress: uploadProgress)
}
}
updateDelegateWith(progress: Float)
func updateDelegateWith(progress: Float) {
delegate?.uploadProgressWith(progress: progress)
}
NetworkManagerViewController where the progress bar lives
class NetworkViewController: UIViewController, NetWorkManagerDelegate {
var localEntry: LocalEntry?
var progressBackground = UIView()
var progressBar = UIProgressView()
func uploadProgressWith(progress: Float) {
progressBar.progress = progress
view.layoutSubviews()
}
deinit {
print("deallocate")
}
override func viewDidLoad() {
super.viewDidLoad()
let networkManager = NetworkManager()
networkManager.delegate = self
networkManager.uploadFiles(item: self.localEntry!)
....
....
}
}
Upvotes: 3
Views: 406
Reputation: 15238
With the latest code shared, i would suggest to keep NetworkManager
instance at the class
level instead of a function
level scope as this will ensure the networkManager
instance is not deallocated.
class NetworkViewController: UIViewController, NetWorkManagerDelegate {
var localEntry: LocalEntry?
var progressBackground = UIView()
var progressBar = UIProgressView()
let networkManager = NetworkManager()
func uploadProgressWith(progress: Float) {
progressBar.progress = progress
view.layoutSubviews()
}
deinit {
print("deallocate")
}
override func viewDidLoad() {
super.viewDidLoad()
networkManager.delegate = self
networkManager.uploadFiles(item: self.localEntry!)
}
...
Also, you need to be careful for retain-cycles
that cause memory leaks. To avoid retain cycles, you need to declare your delegate
variable as weak
.
Upvotes: 1
Reputation: 43
As user @Kamran pointed out, I was creating a class level instance of networkManager
inside UploadMainFileOperation
. The issue has been fixed by changed that variable to an Optional
and giving it an instance of NetworkManager
, as self
,that was queueing up the operations. The code blocks as been updated with comments of the correct code along with the incorrect code.
Upvotes: 1
Reputation: 824
If you set a delegate and later it becomes nil
, this means your delegate has been deallocated.
I would recommend to create an (empty) deinit
in your delegate class and set a breakpoint for the debugger in that method. This will help you find out where you're losing the reference to said delegate.
You can probably avoid this by assigning your delegate to a property of one of your classes or make it a strong reference in one of your completion blocks.
Upvotes: 0