Делегат становится нулевым в Операции urlSession. Как сохранить делегат переменную в отдельном потоке? - PullRequest
0 голосов
/ 30 октября 2018

Я использую OperationQueue для загрузки файлов по одному на удаленный сервер, используя URLSession.dataTask. Делегат используется для обновления индикатора выполнения, но после реализации OperationQueue мой делегат становится равным нулю. Это работало без OperationQueues. Глядя на стек во время работы программы, я не вижу контроллер представления моего индикатора выполнения. Прошло несколько дней, и я до сих пор не могу понять это. Я предполагаю, что контроллер представления освобождается, но я не уверен, как предотвратить его освобождение. Спасибо.

Мой делегат установлен в self в NetWorkViewController, но внутри urlSession моего класса NetworkManager (didSendBodyData) делегат становится нулевым. Делегат не является слабым и является переменной класса.

Однако мой делегат снова становится не равным нулю в блоке завершения моей операции BlockOperation. Это работает для отклонения ViewController через делегирование. Но делегат равен нулю, когда я пытаюсь обновить внутри urlSession (didSendBodyData) ...

ОБНОВЛЕНИЕ 10/30/2018

Кажется, что мои делегаты urlSessions находятся в отдельном потоке и записываются в основной поток при вызове, но я теряю ссылку на мой пользовательский делегат, который обновляет пользовательский интерфейс. Я пытаюсь прочитать больше о многопоточности, но любая помощь будет признательна!

ОБНОВЛЕНИЕ 2 10/30/2018

Решение найдено Проблема заключалась в том, что я создавал еще один экземпляр NetworkManager внутри каждой операции. Это заставляет delegate быть nil, потому что для каждой операции создается новый экземпляр NetworkManager. Исправление должно передать self от исходного NetworkManager, поэтому delegate сохраняется.

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 (прогресс: Float)

func updateDelegateWith(progress: Float) {
    delegate?.uploadProgressWith(progress: progress)
}

NetworkManagerViewController где находится индикатор выполнения

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!)
....
....
}

}

Ответы [ 3 ]

0 голосов
/ 30 октября 2018

Как указал пользователь @Kamran, я создавал экземпляр уровня класса networkManager внутри UploadMainFileOperation. Проблема была исправлена ​​изменением этой переменной на Optional и присвоением ей экземпляра NetworkManager, как self, который ставил в очередь операции. Блоки кода обновлены с комментариями правильного кода вместе с неверным кодом.

0 голосов
/ 30 октября 2018

С последним общим кодом я бы предложил сохранить экземпляр NetworkManager на уровне class вместо области действия уровня function, поскольку это обеспечит освобождение экземпляра networkManager.

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!)
}

...

Кроме того, вы должны быть осторожны с retain-cycles, которые вызывают утечки памяти. Чтобы избежать сохранения циклов, вам нужно объявить переменную delegate как weak.

0 голосов
/ 30 октября 2018

Если вы установите делегата, а позже он станет nil, это означает, что ваш делегат был освобожден.

Я бы порекомендовал создать (пустой) deinit в вашем классе делегатов и установить точку останова для отладчика в этом методе. Это поможет вам узнать, где вы теряете ссылку на упомянутого делегата.

Вероятно, вы можете избежать этого, назначив свой делегат свойству одного из ваших классов или сделав его сильной ссылкой в ​​одном из блоков завершения.

...