O que a Apple quer dizer quando diz que um NSManagedObjectContext é de propriedade do thread ou da fila que o criou?

Parece que, em novembro, a Apple atualizou os documentos NSManagedObjectContext Class Reference e Core Data Programming Guide para abençoar explicitamente as GCD Dispatch Queues e NSOperationQueues como mecanismos aceitáveis ​​para a synchronization do access a um NSManagedObjectContext . Mas o conselho deles parece ambíguo e possivelmente contraditório, e quero ter certeza de que entendi direito.

Anteriormente, o entendimento aceito parecia ser que um NSManagedObjectContext só podia ser acessado a partir do encadeamento que o criava, e que usar uma fila serial para synchronization não era suficiente; embora as filas seriais realizem apenas uma operação por vez, essas operações podem ser programadas em diferentes threads, e um MOC não gosta disso.

Mas agora, a partir do guia de programação, temos:

Você pode usar encadeamentos, filas de operações seriais ou distribuir filas para simultaneidade. Por uma questão de concisão, este artigo usa “thread” para se referir a qualquer um deles.

Até agora, tudo bem (embora sua fusão de threads e filas seja inútil). Assim, posso usar com segurança um único contexto por fila (serial), em vez de um por operação / bloco, certo? A Apple ainda tem uma representação visual disso nas sessões de WWDC do Core Data.

Mas … onde você cria o contexto para a fila? Na documentação do NSManagedObjectContext , o estado da Apple:

[Um contexto] assume que o proprietário padrão é o encadeamento ou a fila que o alocou – isso é determinado pelo encadeamento que chama seu método init. Você não deve, portanto, inicializar um contexto em um thread e passá-lo para um thread diferente.

Então, agora temos a ideia de um NSManagedObjectContext precisar saber quem é seu proprietário. Estou assumindo que isso significa que a primeira operação a ser executada na fila deve criar o MOC e salvar uma referência a ele para que as operações restantes sejam usadas.

Isto está certo? A única razão pela qual estou hesitante é que o artigo NSManagedObjectContext prossegue:

Em vez disso, você deve passar uma referência a um coordenador de armazenamento persistente e fazer com que o segmento / fila de recebimento crie um novo contexto derivado disso. Se você usar NSOperation, deverá criar o contexto em main (para uma fila serial) ou iniciar (para uma fila simultânea).

A Apple agora parece estar combinando as operações com as filas que agendam sua execução. Isso faz a minha cabeça, e me faz pensar se eles realmente querem que você crie um novo MOC para cada operação, afinal. o que estou perdendo?

O NSManagedObjectContext e quaisquer objects gerenciados associados a ele devem ser fixados em um único ator (thread, fila serializada, NSOperationQueue com max simultaneidade = 1).

Esse padrão é chamado de confinamento ou isolamento de thread. Não existe uma frase ótima para (thread || fila serializada || NSOperationQueue com max simultaneidade = 1), então a documentação continua dizendo “nós apenas usaremos ‘thread’ para o resto do documento Core Data quando queremos dizer” thread ” qualquer uma dessas 3 maneiras de obter um stream de controle serializado ”

Se você criar um MOC em um encadeamento e usá-lo em outro, violou o confinamento do encadeamento, expondo a referência do object MOC a dois encadeamentos. Simples. Não faça isso. Não cruze os streams.

Chamamos NSOperation explicitamente porque, diferentemente de threads e GCD, ele tem esse problema estranho em que -init é executado no encadeamento que cria a NSOperation, mas -main é executado no encadeamento que executa a NSOperation. Faz sentido se você apertar os olhos corretamente, mas não é intuitivo. Se você criar seu MOC em – [NSOperation init], NSOperation violará o confinamento do encadeamento antes que o seu método -main seja executado e você seja consertado.

Desencorajamos ativamente / descontinuamos o uso de MOCs e encadeamentos de qualquer outra forma. Embora, teoricamente, seja possível fazer o que o álbum menciona, ninguém nunca acertou. Todo mundo tropeçou, esqueceu uma chamada necessária para bloquear em um lugar, “init corre onde?”, Ou de outra forma fora-se. Com pools de autorelease e o loop de events do aplicativo e o undo manager e cacau bindings e o KVO, há apenas muitas maneiras de um thread manter uma referência a um MOC depois de tentar passá-lo em outro lugar. É muito mais difícil do que imaginam os desenvolvedores avançados do Cocoa até que eles comecem a depurar. Então essa não é uma API muito útil.

A documentação foi alterada para esclarecer e enfatizar o padrão de confinamento de thread como o único caminho sensato a ser seguido. Você deve considerar tentar ser mais extravagante usando -lock e -unlock em NSManagedObjectContext para ser (a) impossível e (b) de fato depreciado. Não é literalmente obsoleto porque o código funciona tão bem quanto ele. Mas o seu código usando isso está errado.

Algumas pessoas criaram MOCs em 1 thread e passaram-nas para outro sem chamar -lock. Isso nunca foi legal. O encadeamento que criou o MOC sempre foi o proprietário padrão do MOC. Isso se tornou um problema mais freqüente para os MOCs criados no thread principal. MOCs do thread principal interagem com o loop de events principal do aplicativo para desfazer, gerenciamento de memory e algumas outras razões. No 10.6 e no iOS 3, os MOCs têm uma vantagem mais agressiva em pertencer ao segmento principal.

Embora as filas não estejam vinculadas a segmentos específicos, se você criar um MOC no contexto de uma fila, as coisas certas acontecerão. Sua obrigação é seguir a API pública.

Se a fila for serializada, você poderá compartilhar o MOC com blocos subsequentes executados nessa fila.

Portanto, não exponha um NSManagedObjectContext * a mais de um thread (actor, etc) sob qualquer circunstância. Existe uma ambiguidade. Você pode passar o NSNotification * da notificação didSave para o método -mergeChangesFromContextDidSaveNotification: do MOC de outro segmento.

  • Ben

Parece que você estava certo. Se você estiver usando encadeamentos, o encadeamento que deseja o contexto precisa criá-lo. Se você estiver usando filas, a fila que deseja o contexto deve criá-lo, provavelmente como o primeiro bloco a ser executado na fila. Parece que a única parte confusa é o pouco sobre NSOperations. Eu acho que a confusão lá é NSOperations não fornecem qualquer garantia sobre o segmento / fila subjacente em que são executados, portanto, pode não ser seguro compartilhar um MOC entre as operações, mesmo se todas elas forem executadas no mesmo NSOperationQueue. Uma explicação alternativa é que é apenas uma documentação confusa.

Resumindo:

  • Se você estiver usando threads, crie o MOC no thread que deseja
  • Se você estiver usando o GCD, crie o MOC no primeiro bloco executado em sua fila serial
  • Se você estiver usando o NSOperation, crie o MOC dentro do NSOperation e não o compartilhe entre as operações. Isso pode ser um pouco paranóico, mas o NSOperation não garante o encadeamento / fila subjacente em que ele é executado.

Edit : De acordo com o álbum, o único requisito real é o access precisa ser serializado. Isso significa que você pode compartilhar um MOC entre NSOperations, desde que todas as operações sejam adicionadas à mesma fila e a fila não permita operações simultâneas.