ios - awakeFromNib, puntos de venta y guiones gráficos: ¿está mal la documentación?
uikit uistoryboard (2)
Respuesta corta
Su controlador de vista y su jerarquía de vista se cargan desde archivos nib separados en tiempo de ejecución.
Su controlador de vista se carga primero y recibe awakeFromNib
cuando se carga su punta, pero su jerarquía de vista aún no se ha cargado, por lo que en awakeFromNib
no debe asumir que awakeFromNib
se haya establecido ninguna salida a la jerarquía de vista.
El controlador de vista recibe viewDidLoad
después de que se haya cargado la punta de la jerarquía de vistas, por lo que en viewDidLoad
puede asumir que todos los puntos de venta se han configurado.
Respuesta larga
Cuando Xcode construye tu aplicación, compila tu guión gráfico. El resultado es un paquete (una carpeta que el Finder trata como un archivo) que contiene un Info.plist
y un montón de archivos .nib
. Ejemplo de uno de mis proyectos:
:; pwd
/Users/mayoff/Library/<snip>/Pinner.app/Base.lproj/Main.storyboardc
:; ll
total 80
drwxr-xr-x 10 mayoff staff 340 May 11 22:13 ./
drwxr-xr-x 4 mayoff staff 136 May 11 22:13 ../
-rw-r--r-- 1 mayoff staff 1700 May 11 22:13 AccountCollection.nib
-rw-r--r-- 1 mayoff staff 1110 May 11 22:13 AccountEditor.nib
-rw-r--r-- 1 mayoff staff 2999 May 11 22:13 BYZ-38-t0r-view-8bC-Xf-vdC.nib
-rw-r--r-- 1 mayoff staff 439 May 11 22:13 Info.plist
-rw-r--r-- 1 mayoff staff 7621 May 11 22:13 LqH-9K-CeF-view-OwT-Ts-HoG.nib
-rw-r--r-- 1 mayoff staff 6570 May 11 22:13 OZq-QF-pn5-view-xSR-gK-reL.nib
-rw-r--r-- 1 mayoff staff 2473 May 11 22:13 UINavigationController-ZKB-z3-xgf.nib
-rw-r--r-- 1 mayoff staff 847 May 11 22:13 UIPageViewController-ufv-JN-y6U.nib
Info.plist
asigna los nombres de las escenas en su guión gráfico a las puntas correspondientes:
:; plutil -p Info.plist
{
"UIViewControllerIdentifiersToNibNames" => {
"AccountCollection" => "AccountCollection"
"UINavigationController-ZKB-z3-xgf" => "UINavigationController-ZKB-z3-xgf"
"UIPageViewController-ufv-JN-y6U" => "UIPageViewController-ufv-JN-y6U"
"AccountEditor" => "AccountEditor"
}
"UIStoryboardDesignatedEntryPointIdentifier" => "UINavigationController-ZKB-z3-xgf"
"UIStoryboardVersion" => 1
}
Una escena solo aparece en esta lista si tiene una ID de guión gráfico, o si un segmento se conecta a ella, o si es la escena inicial.
La lista de archivos de plumillas en Info.plist
no contiene las jerarquías de vista de esos controladores de vista. Cada uno de esos archivos de plumilla contiene el controlador de vista de su escena y cualquier otro objeto de nivel superior en la escena, pero no la vista del controlador de vista ni ninguna de sus subvistas.
Un archivo de plumilla separado contiene la jerarquía de vistas para la escena. El nombre de la punta de jerarquía de vista derivado de los ID de objeto del controlador de vista y su vista de nivel superior. Puede ver el ID de objeto de cualquier objeto en su guión gráfico en el "Inspector de identidad" en Xcode. Por ejemplo, la identificación del controlador de la vista de mi escena "AccountCollection" es BYZ-38-t0r
y la identificación de su vista es 8bC-Xf-vdC
, por lo que la jerarquía de vistas para la escena está en el archivo BYZ-38-t0r-view-8bC-Xf-vdC.nib
. El archivo de punta de escena contiene el nombre de su archivo de punta de jerarquía de vistas:
:; strings - AccountCollection.nib |grep -e ''-.*-''
UIPageViewController-ufv-JN-y6U
BYZ-38-t0r-view-8bC-Xf-vdC <---------
UpstreamPlaceholder-5Hn-fK-fqQ
UpstreamPlaceholder-8GL-mk-Rao
q1g-aL-SLo.title
Si una escena no tiene una jerarquía de vistas, solo habrá un archivo de plumilla para el controlador de vista y no habrá un archivo de plumilla separado para la jerarquía de vistas. Por ejemplo, una escena UIPageViewController
no tiene una jerarquía de vistas en el guión gráfico, por lo que no hay una punta de jerarquía de vistas correspondiente a UIPageViewController-ufv-JN-y6U.nib
.
Entonces, ¿qué tiene todo esto que ver con tu pregunta? Esto es lo que: cuando su aplicación carga una escena desde el "guión gráfico", está cargando el archivo nib que contiene el controlador de vista (y otros objetos de nivel superior). Cuando el cargador de plumillas termina de cargar ese archivo, envía awakeFromNib
a todos los objetos que acaba de cargar. Esto incluye su controlador de vista, pero no incluye sus vistas, porque sus vistas no estaban en ese archivo de plumilla.
Más tarde, cuando se le pide a su controlador de view
su propiedad de view
, carga el archivo nib que contiene su jerarquía de vista. El controlador de vista se pasa a -[UINib instantiateWithOwner:options:]
como el argumento del owner
. Así es como el cargador de plumillas puede conectar objetos en la jerarquía de vista a los puntos de venta y acciones del controlador de vista.
Cuando el cargador de plumillas finaliza la carga de la jerarquía de vistas, envía awakeFromNib
a todos los objetos que acaba de cargar. Como su controlador de vista no era uno de esos objetos, su controlador de vista no recibe un mensaje awakeFromNib
en este momento.
Cuando instantiateWithOwner:options:
devuelve, el controlador de vista envía el mensaje viewDidLoad
. Esta es su oportunidad de realizar cambios en la jerarquía de vistas.
De acuerdo con la Referencia de adiciones de UIKit de NSObject , las variables de salida deben establecerse en el momento en que se llama awakeFromNib
(el énfasis es mío):
La infraestructura de carga de plumillas envía un mensaje awakeFromNib a cada objeto recreado desde un archivo de plumillas, pero solo después de que todos los objetos en el archivo se hayan cargado e inicializado. Cuando un objeto recibe un mensaje awakeFromNib, se garantiza que ya tiene establecidas todas sus conexiones de salida y acción.
...
Importante: debido a que no se garantiza el orden en el que se crea una instancia de los objetos desde un archivo, sus métodos de inicialización no deben enviar mensajes a otros objetos en la jerarquía. Los mensajes a otros objetos se pueden enviar de forma segura desde un método awakeFromNib.
Por lo general, implementa awakeFromNib para los objetos que requieren una configuración adicional que no se puede realizar en el momento del diseño. Por ejemplo, puede usar este método para personalizar la configuración predeterminada de cualquier control para que coincida con las preferencias del usuario o los valores de otros controles. También puede usarlo para restaurar controles individuales a algún estado anterior de su aplicación.
Sin embargo, esto no coincide con mis pruebas, al menos usando Storyboards. Los resultados de la siguiente prueba parecen contradecir la documentación:
- Crear una nueva aplicación de vista única en Xcode.
- Arrastra un segundo ViewController al guión gráfico.
- Dé al primer ViewController un botón y cree un segmento modal a partir de ese botón que muestra el segundo ViewController.
- Cree un archivo de clase ViewController para el segundo ViewController.
- Cree una etiqueta en el segundo ViewController en el guión gráfico y cree una salida llamada
someLabel
desde la clase correspondiente a ViewController. - Agregue la siguiente implementación de
awakeFromNib
al segundo ViewController:
.
- (void) awakeFromNib {
[super awakeFromNib];
if (self.someLabel == nil) {
NSLog(@"someLabel property is nil");
}
else {
NSLog(@"someLabel property is not nil");
}
if (_someLabel == nil) {
NSLog(@"_someLabel is nil");
}
else {
NSLog(@"_someLabel is not nil");
}
}
- Ejecute la aplicación en el simulador y haga clic en el botón.
Cuando hago esto, observo lo siguiente registrado:
2013-07-01 09:24:35.755 test[498:c07] someLabel property is nil
2013-07-01 09:24:35.758 test[498:c07] _someLabel is nil
Como consecuencia de este comportamiento, cuando necesito que mis ViewControllers tengan alguna lógica de inicialización que involucre a sus puntos de venta, tengo que usar un truco como el que se sugiere en la respuesta here para poder usar los puntos de venta. Si entiendo la documentación correctamente, el hecho de que me obliguen a usar este truco es un error en el comportamiento de UIKit, y debería poder poner esa inicialización en awakeFromNib
y simplemente usar los puntos de venta sin ningún tipo de pirateo.
Sin embargo, no puedo encontrar ninguna otra mención de este problema en Internet, lo que parece extraño dado lo fundamental que parece ser (para mí) la funcionalidad fundamental. Tampoco he usado archivos de plumilla reales, solo guiones gráficos, por lo que me falta algo de perspectiva sobre esto, y la documentación sobre este tema es bastante detallada y lo suficientemente difícil como para ser un novato en iOS, no estoy seguro de haberlo entendido. correctamente. ¿Se trata de un error de UIKit genuino o he malinterpretado la documentación de alguna forma? ¿Quizás este método ni siquiera esté destinado a usarse junto con los guiones gráficos?
Los controladores de vista esperan hasta que se acceda a su vista para crear realmente su vista. Dado que el botón está en la vista del controlador de vista, no se creará una instancia todavía.