Подтвердить что ты не робот

Почему само освобождается при вызове [unowned self]

Я пытаюсь написать простое закрытие как обработчик завершения, а внутри замыкания задается текстовое значение текстового поля:

class ViewController: UIViewController {

    @IBOutlet var textArea : UITextView

    let sessionConfig = NSURLSessionConfiguration.defaultSessionConfiguration()

    let session:NSURLSession?


    init(coder aDecoder: NSCoder!)  {
        super.init(coder: aDecoder)

        session = NSURLSession(configuration: sessionConfig)
    }


    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
    }

    @IBAction func btnSendRequestTapped(sender : AnyObject) {

        let url:NSURL  = NSURL(string: "https://www.google.com")

        let sessionTask:NSURLSessionTask =
        session!.dataTaskWithURL(url, completionHandler: {
            [unowned self]
            (data:NSData!,response:NSURLResponse!,error:NSError!) -> Void in
            let st:String = NSString(data: data,encoding: NSUTF8StringEncoding)

            println("\(st)")

            NSOperationQueue.mainQueue().addOperationWithBlock({
                () -> Void in
                self.textArea!.text = st
                })
            })

        sessionTask.resume()
    }
}

но в строке, где я определил [unowned self], я получаю EXC_BREAKPOINT(code=EXC_I386_BPT,subcode=0x0), и он показывает некоторый код сборки следующим образом:

libswift_stdlib_core.dylib`_swift_abortRetainUnowned:
0x1001bb980:  pushq  %rbp
0x1001bb981:  movq   %rsp, %rbp
0x1001bb984:  leaq   0x176a7(%rip), %rax       ; "attempted to retain deallocated object"
0x1001bb98b:  movq   %rax, 0x792ce(%rip)       ; gCRAnnotations + 8
0x1001bb992:  int3   
0x1001bb993:  nopw   %cs:(%rax,%rax)

Я не уверен, что я сделал неправильно здесь, на основе документации. Я обновил вопрос до всего класса. Также я обновил вопрос, чтобы обновить свойство text TextView в основном потоке

4b9b3361

Ответ 1

Просто, чтобы быть в курсе всех ответов здесь - как вы должны исправить эту ошибку, зависит от ваших потребностей.

Проблема с кодом в исходном вопросе заключается в том, что self, который является UIViewController, обращается к асинхронно выполняемому блоку NSOperation, и это выполняется внутри обработчика завершения асинхронного NSURLSessionTask.

К тому времени, когда время выполнения достигнет self.textArea!.text, self стало nil. При каких обстоятельствах будет отменен UIViewController? Когда вы выталкиваете его из стека навигации, когда его отклоняют и т.д. Я предполагаю, что код для btnSendRequestTapped(:) выше не является полным и имеет popViewController() где-то.

Решения:

1: Использовать weak вместо unowned. Разница между ними заключается в том, что weak означает, что захваченный объект (self) может быть nil и превращает его в необязательный, тогда как unowned означает, что вы уверены, что self никогда не будет nil и вы можете получить доступ к self как есть. Использование weak означает разворачивание self перед его использованием. Если он равен нулю, ничего не делайте в коде.

{[weak self] in
    if let strongSelf = self {
        // ...
        strongSelf.textArea!.text = "123"
    }
}

2: Другое решение - отменить NSURLSessionTask и его обработчик завершения (который отправлен в свойство NSOperationQueue NSURLSession, называемый delegateQueue), если UIViewController выгружается из навигационного стека.

func btnSendRequestTapped() {
    // pop the view controller here...
    sessionTask.cancel()
    sessionTask.delegateQueue.cancelAllOperations()
}

3: Продолжайте использовать [unowned self], но не добирайте контроллер вида до тех пор, пока не будет выполнен рабочий блок. Я лично предпочитаю этот подход.

Короче говоря, держите self от освобождения, пока вы на самом деле не закончите с ним.

Ответ 2

Я не уверен, почему, но я думаю, что он работает с weak вместо unowned. Это может быть ошибка.

session.dataTaskWithURL(url, completionHandler: {
        [weak self]
        (data:NSData!,response:NSURLResponse!,error:NSError!) -> Void in
        let st:String = NSString(data: data,encoding: NSUTF8StringEncoding)

        self!.txtArea!.text = "123"
        }
    )

Ответ 3

Ваш self получает освобождение до запуска блока завершения. Не забывайте, что unowned совпадает с unsafe_unretained и не будет обнулено. Вы можете попробовать [weak self], но вам нужно будет получить доступ к нему следующим образом:

self?.txtArea!.text = "123"

Ответ 4

Я обнаружил, что если "unowned object" является объектом "Objective-C Class", например UIViewController, программа будет сбой. Хотя если "незанятый объект" является объектом "Swift Class", все будет в порядке.

Ответ 5

Я обнаружил, что это происходит со мной, когда я игнорирую последнюю сильную ссылку на переменную, находясь внутри закрытия уведомления, в результате чего этот объект будет удален на месте, но тогда тот же уведомление продолжает распространяться на другие объекты - и один из объектов, который наблюдает за тем же уведомлением, является объектом deinit'd, который ссылался на себя как [незанятое я]. По-видимому, хотя объект деинирован, он по-прежнему пытается выполнить блок кода, связанный с уведомлением, и сбой. Я также видел, как это происходит, когда последняя сильная ссылка отключена и будет создано уведомление, которое будет распространяться на этот объект. Он по-прежнему сбой, хотя deinit сделал NSNotificationCenter.defaultCenter(). RemoveObserver (self).

В этих случаях одно решение, которое я использовал с некоторым успехом, - это обернуть dispatch_async вокруг нулевого значения, например:

dispatch_async(dispatch_get_main_queue(), { () -> Void in
   someVariable = nil
})

Это приводит к тому, что переменная, которая получает nil'd, будет уничтожена после того, как уведомление будет полностью распространено. Хотя это сработало для меня один раз, кажется, все еще будет обидно.

Нижняя строка: Единственное решение, которое, казалось бы, было полноправным для меня, заключалось в том, чтобы устранить использование закрытия, заменив наблюдателя уведомлений на более старый объект-наблюдатель типа/селектора.

Я думаю, что это очень возможно, это ошибка с тем, как работает [unowned self], и что Apple просто потребуется исправить это. Я видел, как другие люди говорили о проблемах, которые у них были с [незадействованным я], но вместо этого нужно использовать [слабое "я" ] в качестве обходного пути. Я думаю, что та же проблема.

Ответ 6

Я столкнулся с той же проблемой, запустив Xcode 6.4. Вероятно, изменение проблемы с владением до слабого решило проблему.

Две вещи:

  • _swift_abortRetainUnowned не происходит все время.
  • _swift_abortRetainUnowned происходит, когда закрытие назначается, а не когда оно выполняется, поэтому в моем случае я уверен, что сам не освобожден.

Ответ 7

Лучше всего использовать weak self в этом случае на всякий случай, если вы вытащите представление из стека навигации.

Обновление: Swift 3.0

let task:URLSessionTask = session.dataTask(with: url, completionHandler: {
        [weak self] (data:Data?, response: URLResponse?, error:Error?) in

        /// Cool tip instead of using *strongSelf*, use ` as:
            if let `self` = self {
               /// Run your code here, this will ensure if self was deallocated, it won't crash.
            }
    })

Swift 2.2

let sessionTask:NSURLSessionTask =
        session!.dataTaskWithURL(url, completionHandler: {
            [weak self] (data:NSData!,response:NSURLResponse!,error:NSError!) -> Void in
                /// Cool tip instead of using *strongSelf*, use ` as:
                if let `self` = self {
                   /// Run your code here, this will ensure if self was deallocated, it won't crash.
                }
            })