objective c - NSKeyedUnarchiver-prueba/captura necesaria?
objective-c exception (2)
Según tengo entendido, se desaconseja el uso de @try/@catch
bloques @try/@catch
, ya que las exceptions
solo deben arrojar errores catastróficos irrecuperables (consulte esta discusión con una buena respuesta de @bbum : Exception Handeling en iOS ).
Así que revisé mi código y encontré un bloque @try/@catch
que no sé cómo deshacerme de:
NSData *fileData = [NSData dataWithContentsOfFile: ....];
NSDictionary *dictionary;
@try {
dictionary = [NSKeyedUnarchiver unarchiveObjectWithData: fileData];
}
@catch (NSException *exception) {
//....
}
@finally {
//...
}
El problema es que (como se indica en la documentation ) +unarchiveObjectWithData:
genera una NSInvalidArchiveOperationException
si el NSData
no contiene un archivo válido.
Dado que los datos son proporcionados por un archivo que el usuario eligió, no se garantiza que contenga un archivo válido y, por lo tanto, la aplicación se bloquearía si se eligiera un archivo incorrecto.
Ahora dos preguntas:
- ¿Por qué no
+unarchiveObjectWithData:
simplemente devuelvenil
( Edit: yNSError**
) si el archivo no es válido (esto no parece calificar como un error catastrófico o irrecuperable). - ¿Es correcto el patrón anterior (usando
@try
)? No he encontrado ningún método que nos permita verificar si los datos contienen un archivo válido de antemano y no he encontrado ninguna posibilidad de manejar este caso utilizando el protocolo de delegado. ¿Algo que pasé por alto?
Tenga en cuenta que el código anterior, por supuesto, funciona, me pregunto si es la mejor práctica.
Hubo un nuevo método agregado en iOS 9 a NSKeyedUnarchiver que ahora devuelve un error:
Rápido:
public class func unarchiveTopLevelObjectWithData(data: NSData) throws -> AnyObject?
C objetivo:
+ (nullable id)unarchiveTopLevelObjectWithData:(NSData *)data error:(NSError **)error;
Sin embargo, esto no es compatible con versiones anteriores de iOS, por lo que deberá verificar la disponibilidad del marco .
NSKeyedArchiver
es construido por Apple. Ellos controlan el código que se realiza mientras que unarchiveObjectWithData:
ejecuta para que también controlen la administración de recursos durante el manejo de excepciones (que es la fuente de problemas detrás de las excepciones en Objective-C).
Si pueden garantizar que entre su llamada a unarchiveObjectWithData:
y el punto en el código en el que generan la excepción no es un código extranjero (ni el código de su aplicación) es en teoría posible usar una excepción de forma segura, siempre y cuando se llame El código se encarga de limpiar correctamente.
El problema es que esta suposición podría no ser el caso: es común utilizar NSKeyedArchiver
para serializar objetos personalizados. Por lo general, la clase personalizada implementa initWithCoder:
para leer los datos de las clases (usando los métodos del archivador como decodeObjectForKey:
.
Si el archivador lanza una excepción en uno de estos métodos, no hay manera de arreglar el manejo de recursos para el archivador. La excepción se lanzará a través del initWithCoder:
del objeto personalizado initWithCoder:
El archivador no sabe si hay más cosas que limpiar que los objetos deserializados. Por lo tanto, en este escenario, la ocurrencia de la excepción significa que el proceso se encuentra en un estado peligroso y puede resultar en un comportamiento no deseado.
Respecto a tus preguntas:
¿Por qué [NSKeyedArchiver no utiliza el manejo adecuado de errores de Cocoa]?
Sólo los ingenieros de Apple que construyeron el archivador lo saben. Mi conjetura es que el manejo de excepciones y el archivo con claves se construyeron aproximadamente al mismo tiempo (alrededor de 2001) y en ese momento aún no estaba claro que el manejo de excepciones nunca sería un ciudadano de primera clase en Objective-C.
¿Es correcto el patrón @try?
Con la limitación de las advertencias descritas anteriormente es correcto. Si el código de Apple maneja los casos de excepción correctamente y su propio código de serialización hace lo mismo, el patrón @try podría ser correcto.
Sin embargo, es muy difícil lograr una corrección total. Debería asegurarse de que todo el código ejecutado tenga conocimiento de las excepciones y realice la limpieza correctamente.
ARC, por ejemplo, no realiza la limpieza de excepciones para las variables locales y los objetos temporales de forma predeterminada (tendría que habilitar las -fobjc-arc-exceptions
para hacer esto).
Además, no hay documentación sobre la seguridad de excepción de los accesores de las propiedades sintetizadas (cuando son atomic
pueden perder un bloqueo).
Conclusión:
Hay una gran variedad de formas sutiles de cómo las excepciones pueden romper las cosas. Es difícil y requiere un conocimiento profundo de la implementación de todas las partes involucradas para crear un código seguro de excepción en Objective-C.
Todo esto lleva a la conclusión. Si desea manejar los errores con gracia mientras carga archivos posiblemente corruptos y continuar luego con la ejecución normal: No use NSKeyedArchiver
.