Lista de verificación para la revisión del código de iOS: publique aplicaciones que superen las pruebas y permanezcan en la tienda

La App Store no es un lugar indulgente. Solo en 2024, Apple revisó 7,77 millones de envíos de apps y rechazó casi 1,93 millones, aproximadamente uno de cada cuatro. Los problemas de rendimiento, las violaciones de privacidad y las inconsistencias de diseño fueron las principales razones por las que se rechazaron las apps, y muchos de esos problemas habrían sido detectados en una buena revisión de código antes de que se realizara el envío.

Según Business of Apps, el gasto de los consumidores en iOS alcanzó los $117.600 millones en 2025 y los usuarios de iOS gastan casi el doble por app que los usuarios de Android. Este tipo de ecosistema recompensa las apps construidas con un alto estándar y penaliza las que recortan calidad o seguridad.

Una revisión de código estructurada es una de las inversiones más directas que un equipo de iOS puede realizar antes del lanzamiento o de una ronda de financiación. También es imprescindible si se planea entregar el código fuente a un nuevo socio de desarrollo. Es el paso que convierte una aplicación funcional en una que se mantiene funcionando.

Hoy compartiremos una lista de verificación que recoge los problemas que nuestros ingenieros detectan en cada revisión, estructurada en torno a los problemas que realmente perjudican a los proyectos de iOS en producción y en la cola de revisión de la App Store. Ten en cuenta que Apple es aún más exigente con las aplicaciones codificadas con Vibe. Por lo tanto, si quieres que la tuya tenga éxito, una auditoría profesional podría ser clave para que la App Store la apruebe.

Preparación previa a la revisión

Una revisión de código productiva comienza antes de leer una sola línea. Obtener el contexto correcto desde el principio significa que la revisión en sí se centra en problemas reales en lugar de en confusiones de configuración.

Define el alcance y los objetivos:

  • Identificar si la revisión cubre la base de código completa, un módulo de funcionalidad específico, un envío pendiente a la App Store o la preparación para la diligencia debida de inversores
  • Establecer criterios de éxito medibles: menos bloqueos, problemas de seguridad resueltos, cobertura de pruebas mejorada o preparación para una versión específica de iOS
  • Aclarar los dispositivos objetivo de la app y la versión mínima de iOS, ya que las diferentes versiones tienen distintas APIs, requisitos de permisos y peculiaridades de comportamiento
  • Identificar qué extensiones están en el alcance: App Clips, extensiones de WidgetKit, Live Activities y Share Extensions tienen cada una su propia superficie de revisión

Verifica el entorno y las herramientas:

  • Confirmar que el proyecto se compila limpiamente en Xcode sin advertencias ni errores tanto en las configuraciones de depuración como de lanzamiento
  • Verificar que se está usando la versión correcta y estable de Xcode. Apple aplica los requisitos del SDK y usar una versión desactualizada puede impedir completamente los envíos a la App Store
  • Asegurarse de que todos los miembros del equipo usen las mismas versiones de Swift y dependencias, ya que las discrepancias de versión introducen errores sutiles difíciles de rastrear
  • Ejecutar el proyecto en un dispositivo físico, no solo en el simulador, porque ciertos problemas de memoria, rendimiento y hardware solo aparecen en hardware real

Revisa las dependencias y la gestión de paquetes:

  • Confirmar que todos los paquetes de terceros se gestionan de forma consistente a través de Swift Package Manager, CocoaPods o Carthage, no una mezcla de los tres sin justificación documentada
  • Ejecutar una auditoría de dependencias y verificar que ningún paquete tenga vulnerabilidades de seguridad conocidas
  • Verificar la fecha de última publicación y la actividad de mantenimiento de cada dependencia. Una biblioteca abandonada es un riesgo futuro de compatibilidad y seguridad
  • Confirmar que los archivos de bloqueo de dependencias (Package.resolved, Podfile.lock) están confirmados en el control de versiones, para que todos los miembros del equipo compilen contra versiones idénticas
  • Verificar que ninguna dependencia introduzca un paquete transitivo con una licencia restrictiva o incompatible. Una dependencia transitiva con licencia GPL en una app comercial de la App Store es un problema legal
  • Verificar que las dependencias de Swift Package Manager estén fijadas a versiones exactas o hashes de commit en proyectos sensibles a la seguridad, no en rangos flotantes como .upToNextMajor

Verifica la documentación e historial del proyecto:

  • Asegurarse de que el README describa claramente cómo configurar, compilar, ejecutar y probar el proyecto desde una máquina limpia
  • Revisar los commits recientes para entender qué cambió y por qué antes de revisar el propio código
  • Verificar que los pull requests abiertos estén rebasados contra la rama principal más reciente

Organización del código y arquitectura

La estructura de un proyecto iOS te dice mucho sobre el equipo que lo construyó. Un proyecto bien organizado es más fácil de extender, más fácil de probar y mucho más económico de mantener a medida que cambian los requisitos.

Estructura de carpetas y separación de módulos:

  • Verificar que el proyecto siga una estructura de carpetas consistente: directorios separados para modelos, vistas, view models o controladores, servicios, utilidades y recursos
  • Confirmar que la app usa un patrón arquitectónico definido de manera consistente en toda la base de código; MVVM, MVC, VIPER o Clean Architecture son opciones válidas, pero cualquier patrón seleccionado debe aplicarse de manera uniforme, no abandonarse a mitad del proyecto
  • Asegurarse de que la lógica de negocio no resida dentro de las subclases de UIViewController. Los view controllers deben manejar solo la navegación y la interacción del usuario, delegando el procesamiento de datos y las reglas de negocio a capas dedicadas de servicio o view model

Mala práctica:

// ViewController doing too much
class OrderViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        let request = NSFetchRequest<Order>(entityName: "Order")
        request.predicate = NSPredicate(format: "status == %@", "pending")
        let orders = CoreDataStack.shared.context.fetch(request)
        let total = orders.reduce(0) { $0 + $1.price * $1.quantity * taxRate }
        sendConfirmationEmail(to: currentUser.email, total: total)
    }
}

Buena práctica:

// ViewController delegates to a service
class OrderViewController: UIViewController {
    private let orderService: OrderService

    override func viewDidLoad() {
        super.viewDidLoad()
        Task {
            let summary = await orderService.fetchPendingSummary()
            updateUI(with: summary)
        }
    }
}

Convenciones de nombres:

  • Usar camelCase para variables, funciones, propiedades y constantes globales declaradas con let; PascalCase para tipos, protocolos y enumeraciones
  • UPPER_SNAKE_CASE es una convención de Objective-C y C y no es idiomático en Swift: evítalo en el nuevo código Swift
  • Asegurarse de que los nombres de archivo coincidan con el tipo que contienen: un archivo llamado UserProfileViewModel.swift debe contener el tipo UserProfileViewModel y nada más
  • Evitar nombres de variables de una sola letra fuera de closures muy cortos. Por ejemplo, user siempre es más claro que u, y completion siempre es más claro que c

Uso de protocolos y extensiones:

  • Verificar que los protocolos se usen para definir comportamiento compartido en lugar de cadenas de subclases. Las jerarquías de clases profundas en el código iOS son más difíciles de probar y de cambiar
  • Verificar que las extensiones se usen para organizar la funcionalidad en grupos lógicos en lugar de crear archivos de miles de líneas
  • Confirmar que los archivos de extensión tengan nombres descriptivos, como String+Validation.swift o UIColor+BrandColors.swift, para que su propósito sea inmediatamente claro

Lenguaje Swift y calidad del código

Swift te ofrece muchas herramientas para escribir código seguro y expresivo. Una revisión de código es el lugar ideal para confirmar que esas herramientas se están utilizando correctamente, sin buscar soluciones alternativas.

Opcionales y desempaquetado forzado:

  • Revisar cada aparición del operador de desempaquetado forzado (!) y confirmar que cada uno está justificado; desempaquetar forzosamente un valor nil bloquea la app inmediatamente, y la mayoría de los usos pueden reemplazarse con alternativas más seguras
  • Verificar que guard let e if let se usen para desempaquetar opcionales de forma segura, y que las rutas de fallo realmente manejen el valor ausente en lugar de simplemente retornar silenciosamente

Mala práctica:

let user = userRepository.findUser(by: id)!  // crashes if not found
let name = user.profile!.displayName!        // crashes on nil profile or name

Buena práctica:

guard let user = userRepository.findUser(by: id) else {
    logger.warning("User not found for id: \(id)")
    showErrorState()
    return
}
let name = user.profile?.displayName ?? "Anonymous"

Manejo de errores:

  • Verificar que las funciones que pueden fallar usen el mecanismo throws de Swift en lugar de devolver nil o depender de opcionales para señalar errores. Los llamadores merecen saber qué salió mal
  • Check that all try calls inside do-catch blocks have specific catch clauses for known error types, as a single catch-all block that ignores errors is not error handling but rather ‘error hiding’
  • Confirmar que los errores se registren con suficiente contexto para que un desarrollador entienda qué ocurrió, qué entrada lo desencadenó y dónde en la base de código ocurrió el fallo

Tipos de valor frente a tipos de referencia:

  • Revisar el uso de struct frente a class; los structs se copian, lo que los hace más seguros en código concurrente y más fáciles de razonar; usar clases cuando se necesitan explícitamente semánticas de referencia o es necesario crear subclases
  • Verificar que los structs conformen con Equatable y Hashable donde esas conformidades sean útiles, como cuando el tipo se almacena en conjuntos o se usa como claves de diccionario
  • Verificar que los structs grandes no se pasen a través de rutas de código sensibles al rendimiento donde la copia sería costosa; perfilar primero, optimizar segundo

Concurrencia:

  • Confirmar que la base de código usa el modelo de concurrencia moderno de Swift (async/await, actor, Task) en lugar de una mezcla de DispatchQueue, OperationQueue y manejadores de completado que producen efectos secundarios impredecibles
  • Verificar que todas las actualizaciones de UI ocurran en el hilo principal; actualizar un UILabel o llamar a tableView.reloadData() desde un hilo de fondo produce bloqueos intermitentes y notoriamente difíciles de reproducir

Mala práctica:

URLSession.shared.dataTask(with: url) { data, _, _ in
    self.titleLabel.text = parseTitle(from: data) // UI update on background thread
}.resume()

Buena práctica:

Task {
    let title = try await fetchTitle(from: url) // runs off main thread
    await MainActor.run {
        self.titleLabel.text = title            // UI update on main thread
    }
}
  • Revisar los usos de @MainActor para asegurarse de que los view models y el código relacionado con la UI estén correctamente aislados en el hilo principal

Preparación para la concurrencia de Swift 6:

Swift 6 introduce la verificación estricta de carreras de datos. Los proyectos deben auditarse para la preparación ahora, incluso si la migración es por fases.

  • Verificar la configuración de compilación SWIFT_STRICT_CONCURRENCY del proyecto. Un valor de complete habilita la verificación de carreras de datos de nivel Swift 6 y es el estado objetivo para todo el código nuevo. Los proyectos aún en minimal o targeted deben tener un plan de migración documentado
  • Revisar todos los tipos que se pasan a través de los límites de concurrencia para la conformidad con Sendable. El compilador señala los tipos no Sendable en modo estricto, pero en modo permisivo estas son carreras de datos silenciosas que emergen bajo carga
  • Auditar los usos de Task.detached. A diferencia de Task { } estructurado, las tareas desconectadas no heredan el contexto del actor del llamador ni los valores locales de la tarea. Cada llamada a Task.detached debe tener un comentario que explique por qué es intencionalmente no estructurado

Auditoría de deprecaciones:

Deprecation warnings compound over time and block future SDK adoption. Therefore, it’s imperative to address these issues before they impair your app development progress.

  • Ejecutar una compilación con -warn-deprecated-declarations y resolver todas las advertencias antes del envío
  • Verificar el uso de UIWebView (completamente prohibido de la App Store desde 2023), OpenGL ES (deprecado) y patrones de puente Objective-C que tienen equivalentes modernos en Swift
  • Verificar que el objetivo de despliegue mínimo esté establecido intencionalmente y no dejado en una versión antigua de iOS que fuerza el uso de APIs deprecadas

Gestión de memoria

Los problemas de gestión de memoria son la causa principal de bloqueos en producción en apps iOS. Swift usa el Conteo Automático de Referencias (ARC) para gestionar la memoria, pero ARC aún requiere que los desarrolladores tomen decisiones deliberadas sobre cómo los objetos se referencian entre sí.

Ciclos de retención:

  • Buscar closures que capturen self con fuerza. Esta es una de las fuentes más comunes de fugas de memoria en apps iOS, particularmente en callbacks de red, temporizadores y bloques de animación

Mala práctica:

// Strong capture of self creates a retain cycle
networkClient.fetchData { response in
    self.processResponse(response)  // self retains networkClient, networkClient retains self
}

Buena práctica:

networkClient.fetchData { [weak self] response in
    guard let self else { return }
    self.processResponse(response)
}
  • Verificar que las propiedades delegate estén declaradas como weak, ya que una referencia delegate fuerte casi siempre es un ciclo de retención esperando ocurrir
  • Revisar los usos de unowned para confirmar que el objeto referenciado siempre sobrevivirá al objeto que referencia. En caso de duda, usar weak en su lugar, ya que una referencia unowned pendiente bloquea la app

Detección de fugas de memoria:

  • Confirmar que el equipo use el Depurador de Gráficos de Memoria de Xcode y la herramienta Leaks de Instruments como parte del proceso de desarrollo y revisión
  • Verificar que los métodos deinit estén implementados en las clases clave y que se ejecuten como se espera; una clase cuyo deinit nunca se llama es una clase que está filtrando memoria
  • Verificar que los observadores de NotificationCenter se eliminen cuando un view controller o un objeto se desasigna, ya que los observadores no liberados mantienen el objeto vivo y se activan en objetivos desasignados

Gestión de recursos:

  • Revisar la carga de imágenes y activos para confirmar que las imágenes grandes no se mantienen en memoria más tiempo del necesario. Usar UIImage(named:) para imágenes reutilizadas con frecuencia y UIImage(contentsOfFile:) para imágenes grandes que no deben almacenarse en caché
  • Verificar que los datos cargados desde la red o el disco no se retengan indefinidamente en memoria cuando podrían almacenarse en un caché de disco o recuperarse bajo demanda

Redes y manejo de datos

El código de red es donde muchas apps iOS acumulan deuda técnica. Las capas de red mal estructuradas se vuelven costosas de mantener a medida que crece la superficie de la API.

Organización de la capa de red:

  • Confirmar que todas las solicitudes de red estén centralizadas en una capa de red dedicada en lugar de dispersas por view controllers y view models
  • Verificar que los endpoints de API, las URLs base y los tokens de autenticación no estén codificados de forma fija en archivos individuales, porque deben gestionarse mediante configuración del entorno
  • Verificar que las solicitudes de red usen URLSession o una biblioteca bien mantenida como Alamofire con una estrategia de manejo de errores consistente

Análisis y validación de respuestas:

  • Revisar el uso de Codable para el análisis de JSON, ya que los modelos deben decodificarse correctamente y manejar campos faltantes o inesperados sin bloquearse
  • Confirmar que las respuestas del servidor se validen antes de su uso, y nunca asumir que un campo marcado como opcional en la documentación de la API es realmente opcional en la práctica
  • Verificar que la paginación, el manejo de tiempos de espera y la lógica de reintento estén implementados para cualquier solicitud que pueda enfrentar condiciones de red poco confiables

SwiftData y Core Data:

Actualmente, la capa de persistencia recomendada para las apps que se dirigen a iOS 17 y versiones posteriores es SwiftData. Sin embargo, Core Data sigue siendo completamente compatible para las apps que lo usan. Por lo tanto, tienes libertad para elegir la opción que mejor se adapte a tu caso, siempre que lo hagas de forma segura.

  • Si la app se dirige a iOS 17 o posterior, revisar si SwiftData se usa o está planificado. La macro @Model de SwiftData reemplaza a NSManagedObject y se integra con la concurrencia de Swift de forma nativa
  • Para proyectos con Core Data, confirmar que las solicitudes de fetch usen NSFetchRequest con cadenas de formato NSPredicate parametrizadas, nunca interpolación de cadenas, para prevenir la inyección de predicados
  • Verificar que los contextos de objetos gestionados de Core Data se usen en el hilo correcto. NSManagedObjectContext no es seguro para subprocesos; usar performAndWait o contextos en segundo plano para el trabajo fuera del hilo principal
  • Verificar que las estrategias de migración de SwiftData o Core Data estén documentadas y probadas antes de cada lanzamiento que cambie el modelo

Datos sensibles en tránsito:

  • Confirmar que App Transport Security (ATS) esté habilitada y que no se hayan añadido excepciones a Info.plist sin una razón documentada. Las excepciones de ATS permiten conexiones HTTP simples y son un camino directo a la interceptación de datos
  • Para aplicaciones de alta seguridad como apps bancarias o de salud, considera implementar SSL pinning para garantizar que la app solo se comunique con servidores de confianza incluso si una autoridad de certificación está comprometida. Prefiere el pinning de clave pública (SPKI) sobre el pinning de certificado completo, ya que fijar el certificado completo se rompe cada vez que el servidor rota su cert y requiere una actualización de emergencia de la app. Para la mayoría de las apps, ATS combinado con TLS moderno proporciona suficiente protección sin el riesgo operacional

Mejores prácticas de seguridad

iOS tiene sólidas capacidades de seguridad integradas, lo que es una ventaja definitiva. Sin embargo, ayudan solo si las implementas correctamente. Las directrices de Apple sitúan la privacidad y la seguridad en la cima de sus criterios de revisión. Puedes ver prueba de esto en el informe de transparencia de la App Store de 2024, que confirma que las violaciones de privacidad siguieron siendo una de las principales causas de rechazo y eliminación.

Keychain y almacenamiento seguro:

  • Confirmar que los datos sensibles como tokens de autenticación, contraseñas y claves criptográficas se almacenen en el Keychain de iOS, no en UserDefaults, archivos simples o NSUserDefaults. Los elementos del Keychain están cifrados y vinculados a la identidad de la app
  • Verificar que los elementos del Keychain usen configuraciones de accesibilidad apropiadas: kSecAttrAccessibleWhenUnlockedThisDeviceOnly es apropiado para la mayoría de los datos sensibles y previene la copia de seguridad de iCloud y la migración a nuevos dispositivos
  • Revisar cualquier uso del Secure Enclave para operaciones criptográficas en dispositivos que lo admitan. Secure Enclave realiza trabajo criptográfico en hardware sin exponer nunca la clave a la memoria de la app

Protección de datos:

  • Confirmar que los archivos que contienen datos sensibles se creen con la clase de protección de datos NSFileProtectionComplete, que los cifra cuando el dispositivo está bloqueado
  • Verificar que los datos sensibles se borren de la memoria tan pronto como ya no sean necesarios; después de usar una contraseña o token, sobrescribir la variable y establecerla en nil en lugar de dejarla en memoria hasta la siguiente recolección de basura

Validación de entradas y prevención de inyección:

  • Revisar todos los puntos donde la entrada suministrada por el usuario llega a una consulta de base de datos, ruta de archivo o construcción de URL, ya que estos son puntos de inyección potenciales y deben validarse y sanearse antes de su uso
  • Confirmar que las web views no ejecuten JavaScript de fuentes no confiables. Si un WKWebView carga contenido externo, deshabilitar JavaScript a menos que sea necesario

Autenticación y gestión de sesiones:

  • Verificar que la app use Sign in with Apple o un flujo OAuth 2.0 seguro en lugar de implementar una autenticación personalizada
  • Verificar que los tokens de sesión se actualicen apropiadamente y que los tokens expirados se borren del almacenamiento. Un token que era válido hace seis meses no debería seguir concediendo acceso hoy
  • Confirmar que la autenticación biométrica (Face ID, Touch ID) se use a través de LocalAuthentication con un respaldo al código de acceso del dispositivo, no como sustituto de la autenticación del lado del servidor

Optimización del rendimiento

Los usuarios de iOS esperan que sus aplicaciones respondan al instante. Por lo tanto, una aplicación lenta se desinstala. La buena noticia es que la mayoría de los problemas de rendimiento surgen de patrones específicos e identificables que una revisión de código puede detectar antes de que afecten a los usuarios.

Rendimiento del hilo principal:

  • Identificar cualquier E/S de archivo, lecturas de base de datos, llamadas de red o cómputo pesado que se ejecute en el hilo principal. Todo esto debe moverse a hilos en segundo plano y los resultados enviados de vuelta al hilo principal para las actualizaciones de UI
  • Verificar que viewDidLoad y viewWillAppear no contengan trabajo sincrónico costoso. Estos métodos se ejecutan en el hilo principal y cualquier retraso bloquea directamente la UI

Optimización de table view y collection view:

  • Verificar que la reutilización de celdas de UITableView y UICollectionView esté implementada correctamente y que las celdas no retengan el estado de elementos anteriores después de ser sacadas de la cola
  • Verificar que las imágenes mostradas en las celdas se carguen de forma asíncrona y que se muestre un marcador de posición mientras se carga la imagen, porque la carga sincrónica de imágenes en una vista de desplazamiento causa tartamudeo visible
  • Confirmar que las alturas de las celdas se calculen de manera eficiente; llamar a systemLayoutSizeFitting en cada celda en tableView(_:heightForRowAt:) es una causa común de inestabilidad en el desplazamiento

Tiempo de lanzamiento y rendimiento en el inicio:

  • Revisar la secuencia de inicio de la app y confirmar que solo el trabajo esencial ocurra antes de que aparezca la primera pantalla. La carga diferida y la inicialización perezosa deben usarse para cualquier cosa que no se necesite al lanzar
  • Verificar que los activos y recursos grandes no se carguen sincrónicamente durante el lanzamiento de la app y usar colas en segundo plano y mostrar un estado de carga si es necesario
  • Verificar que la app haya sido perfilada con el instrumento Time Profiler de Xcode, ya que identificar los cuellos de botella reales siempre es más productivo que adivinar

Eficiencia de batería y recursos:

  • Revisar el uso de tareas en segundo plano y confirmar que los modos en segundo plano declarados en los entitlements de la app sean realmente necesarios. Los modos en segundo plano innecesarios agotan la batería e invitan a un escrutinio más cercano de la revisión de la App Store
  • Verificar que las actualizaciones de ubicación usen el nivel de precisión más bajo que requiere la funcionalidad; solicitar kCLLocationAccuracyBest para una funcionalidad que solo necesita la ciudad del usuario es un drenaje significativo de batería

Instrumentos de Xcode

  • Perfilador de tiempo: identifica los puntos críticos de la CPU en el hilo principal. Busca cualquier tarea que supere los 16 ms y que pueda bloquear un fotograma
  • Asignaciones: realiza un seguimiento del crecimiento del montón durante una sesión de usuario típica. Un gráfico que nunca se estabiliza durante el uso normal indica una fuga o una caché ilimitada
  • Fugas: detección automatizada del ciclo de retención. Ejecutar después de cada característica importante, no solo antes de la entrega
  • Depurador de gráficos de memoria: integrado en Xcode, muestra el gráfico de objetos en tiempo real. La forma más rápida de demostrar que existe un ciclo de retención y rastrear exactamente qué referencia lo está reteniendo.
  • Bloqueos: detecta bloqueos del hilo principal por encima de un umbral. Apple utiliza estos mismos datos para la métrica Tasa de bloqueo visible en Xcode Organizer.
  • Registro de energía / Uso de CPU: mide el impacto en la batería de las tareas en segundo plano, las actualizaciones de ubicación y Bluetooth. Las aplicaciones que consumen mucha batería se eliminan y reciben malas calificaciones.
  • Instrumento de red: simula conexiones deficientes (Edge, 3G, pérdida de paquetes) para detectar fallos en el manejo de tiempos de espera que solo aparecen en el mundo real.
  • Plantilla de Core Data: inspecciona el rendimiento de las solicitudes de obtención, los patrones de activación de fallos y la E/S del almacenamiento persistente para aplicaciones que utilizan Core Data.
  • Instrumento SwiftUI: recuento de renderizados del cuerpo por vista. Un problema de rendimiento invisible en las aplicaciones SwiftUI donde la identidad de la vista está mal configurada.

Tamaño de la app y análisis binario

  • Revisar el tamaño binario sin comprimir de la app y confirmar que se mantiene por debajo del umbral de advertencia de descarga celular de Apple (actualmente 200 MB)
  • Usar el Informe de Tamaño de App de Xcode, generado durante el archivado, para identificar qué frameworks, activos y recursos contribuyen más al tamaño del binario
  • Verificar que los activos no utilizados no estén incluidos en el paquete
  • Verificar que los activos multimedia grandes usen Recursos Bajo Demanda si no son necesarios en el primer lanzamiento

Configuración de compilación y firma de código

Los problemas de configuración de compilación son una causa frecuente de errores en producción y rechazos en la App Store. Rara vez se abordan en las revisiones de código, pero su ausencia resulta muy perjudicial.

Configuraciones de compilación Debug vs Release

  • Confirmar que las configuraciones de compilación Debug y Release difieran significativamente: aserciones habilitadas en Debug, eliminadas en Release; logging de depuración deshabilitado en Release
  • Verificar que el flag DEBUG se use de manera consistente y que ninguna ruta de código solo de depuración (cuentas de prueba, endpoints de logging, feature flags) pueda llegar a un binario de producción
  • Verificar que las optimizaciones del compilador estén configuradas correctamente: None [-O0] para Debug (compilaciones más rápidas, mejor depurador) y Optimize for Speed [-O] u Optimize for Size [-Oz] para Release
  • Verificar que la eliminación de código muerto esté habilitada para las compilaciones Release para reducir el tamaño del binario y eliminar las rutas de código inalcanzables

Firma de código y aprovisionamiento

  • Confirmar que los perfiles de aprovisionamiento se gestionen a través de la firma automática en Xcode o una herramienta como Fastlane Match, no perfiles descargados manualmente que expiran sin advertencia y rompen los pipelines de CI
  • Verificar que los entitlements en el archivo .entitlements coincidan exactamente con lo que está habilitado en la configuración de capacidades de App Store Connect. Las discrepancias causan fallos de archivado o rotura silenciosa de capacidades en tiempo de ejecución
  • Verificar que los certificados de notificación push o las claves de autenticación se roten antes de su vencimiento y que el equipo tenga un proceso de renovación documentado

Pruebas y cobertura de código

Las pruebas son el contrato escrito entre tu código y el comportamiento que promete. Por lo tanto, las pruebas exhaustivas son un paso obligatorio si quieres que tu app sea aprobada para la App Store y tenga la posibilidad de atraer usuarios. Además, sin pruebas recurrentes, cada cambio conlleva riesgos ocultos.

Pruebas unitarias y de integración:

  • Verificar que toda la lógica de negocio, las funciones de transformación de datos y las clases de servicio tengan pruebas unitarias que cubran el camino feliz, los casos extremos y las condiciones de fallo
  • Confirmar que las pruebas usen inyección de dependencias o abstracciones basadas en protocolos para que las llamadas de red reales, bases de datos y hardware sean reemplazados por dobles de prueba
  • Verificar que las pruebas de integración cubran flujos de usuario críticos de extremo a extremo, particularmente autenticación, manejo de pagos y persistencia de datos

Pruebas de UI:

  • Confirme que los flujos de usuario críticos tengan pruebas de interfaz de usuario que se ejecuten sobre estados de dispositivo reales o simulados, no solo aserciones a nivel de unidad
  • Verifique que las pruebas de interfaz de usuario utilicen identificadores de accesibilidad estables y únicos en lugar de consultas posicionales como “el tercer botón en la segunda sección”, que fallan cada vez que cambia el diseño

Calidad de las pruebas:

  • Revisar los nombres de las pruebas para confirmar que describen el comportamiento esperado, por ejemplo, test_login_withExpiredToken_shouldRedirectToLoginScreen() en lugar de testLogin()
  • Confirmar que ninguna prueba esté permanentemente deshabilitada con .skip o XCTSkip sin una razón documentada y rastreada para la exclusión
  • Verificar que el pipeline de CI ejecute la suite completa de pruebas en cada pull request y bloquee las fusiones en caso de fallos de prueba

Informes de bloqueos y observabilidad

Las pruebas te dicen lo que hace el código en un entorno controlado. Sin embargo, necesitas informes de bloqueos y observabilidad para saber lo que hace en el mundo real, en dispositivos reales, bajo condiciones reales.

Informes de bloqueos:

  • Confirmar que un SDK de informes de bloqueos esté integrado (Firebase Crashlytics, Sentry o similar) y que los archivos dSYM se carguen automáticamente para cada compilación de lanzamiento para que los bloqueos sean simbolizados y legibles
  • Verificar que el propio SDK de informes de bloqueos no introduzca violaciones del manifiesto de privacidad. Varios SDKs populares añadieron APIs de razón requerida en versiones recientes que deben declararse en PrivacyInfo.xcprivacy
  • Verificar que los errores no fatales y los estados inesperados se registren como eventos no fatales, no solo los bloqueos. Un fallo silencioso es más difícil de depurar que un bloqueo

Métricas de Xcode Organizer y App Store Connect:

  • Revisar los informes de Crash de la app en Xcode Organizer antes de cualquier revisión de una app en vivo. Estos agregan registros de bloqueos simbolizados de usuarios reales y son la forma más rápida de identificar los principales problemas en producción
  • Verificar el Hang Rate y Scroll Hitch Rate de la app en Organizer. Apple marca las apps en el peor 25% de su categoría, y estas métricas alimentan directamente la visibilidad en la App Store
  • Revisar los diagnósticos de escrituras en disco. El exceso de E/S de disco es un problema común en las apps de Core Data que guardan en cada pulsación de tecla
  • Verificar el percentil de Uso de Batería relativo a apps similares. Esta es la línea base para cualquier auditoría energética y está disponible sin ejecutar Instruments

Cumplimiento de la App Store y privacidad

Apple revisó 7,77 millones de envíos en 2024, y casi dos millones fueron rechazados. Los equipos que pasan en el primer envío tratan las directrices de la App Store como requisitos de ingeniería, no como consideraciones de último momento.

Manifiesto de privacidad y declaraciones de datos:

  • Confirmar que existe un archivo de Manifiesto de Privacidad (PrivacyInfo.xcprivacy) y que declara con precisión todas las APIs utilizadas, los datos recopilados y las razones de su uso. Apple aplica esto para todos los SDKs de terceros incluidos en la app, así como para la propia app
  • Verificar que la etiqueta nutricional de privacidad de App Store Connect refleje con precisión el comportamiento real de recopilación de datos de la app, ya que las inconsistencias entre el comportamiento declarado y el real son una causa principal de eliminación
  • Revisar todas las llamadas a APIs sensibles a la privacidad: ubicación, cámara, micrófono, contactos, fotos, salud y seguimiento. Cada una debe tener una cadena de propósito clara orientada al usuario en Info.plist que explique en lenguaje sencillo por qué se necesita el permiso

Momento de la solicitud de permisos:

  • Revisar el momento de todas las llamadas de solicitud de permisos. Cada permiso debe solicitarse en el momento en que el usuario realiza una acción que lo requiere, no al lanzar la app
  • Mostrar una breve explicación dentro de la app inmediatamente antes del prompt del sistema para que el usuario entienda por qué se necesita el permiso antes de que aparezca el diálogo del sistema
  • Una cadena de descripción de uso en Info.plist es obligatoria, pero no es un sustituto del buen momento de solicitud

Compras dentro de la app y entitlements:

  • Confirma que cualquier contenido digital o función desbloqueada dentro de la aplicación utilice el sistema de compras integradas de Apple. Eludir este sistema mediante enlaces de pago externos constituye una infracción de las normas que conlleva el rechazo y la posible eliminación de la aplicación
  • Verifique que el botón “Restaurar compras” sea accesible para los usuarios y que funcione correctamente, ya que los revisores de Apple lo prueban explícitamente
  • Comprueba que los permisos declarados en la aplicación coincidan con los habilitados en la configuración de App Store Connect, ya que las discrepancias provocan fallos de firma

Eliminación de cuenta:

  • Confirmar que cualquier app que ofrezca creación de cuenta también proporcione una forma de eliminar esa cuenta directamente dentro de la app. Este ha sido un requisito obligatorio desde junio de 2023 y se verifica durante cada revisión

Accesibilidad y localización

Una app utilizada por la mayoría de las personas se construye con la accesibilidad y la localización en mente desde el principio, no se añaden como un último paso antes de publicarla.

Accesibilidad:

  • Verificar que todos los elementos interactivos tengan etiquetas de accesibilidad significativas. Un botón con un icono y sin etiqueta es invisible para los usuarios de VoiceOver
  • Verificar que la app respete la configuración de Tipo Dinámico del sistema y que el texto escale apropiadamente sin romper los diseños
  • Confirmar que el color nunca sea el único medio de transmitir información. Los usuarios con daltonismo deben poder entender todos los estados y mensajes también a través de etiquetas o iconografía
  • Verificar que los tamaños mínimos de los objetivos táctiles cumplan la recomendación de las Directrices de Interfaz Humana de Apple de al menos 44 por 44 puntos

Localización:

  • Confirme que todas las cadenas visibles para el usuario estén envueltas en NSLocalizedString y que ninguna esté codificada directamente en los archivos fuente
  • Revise el archivo Localizable.strings para asegurarse de que esté completo; las traducciones faltantes recurren silenciosamente al idioma de desarrollo y pueden producir pantallas confusas con idiomas mezclados
  • Compruebe que el formato de fecha, hora, moneda y número utilice formateadores que tengan en cuenta la configuración regional en lugar de cadenas de formato fijas. Una fecha con formato ‘MM/DD/YYYY’ significa algo diferente en Europa que en Estados Unidos

De derecha a izquierda y pseudolocalización

  • Verificar que la app maneje correctamente los diseños de derecha a izquierda para los mercados que usan árabe, hebreo o persa. Usar NSTextAlignment.natural y restricciones de Auto Layout leading/trailing en lugar de left/right
  • Verificar que el formato de cadenas use String(format:locale:) en lugar de solo String(format:) cuando la salida está orientada al usuario
  • Confirmar que la app haya sido probada con pseudolocalización para detectar etiquetas truncadas y suposiciones de diseño codificadas de forma fija antes de que se entreguen las traducciones reales. Xcode admite esto de forma nativa en la configuración del esquema

Por qué confiar la revisión de código iOS a Redwerk

La mayoría de los problemas de calidad de iOS no son problemas misteriosos que aparecen de la nada. Son los mismos patrones que aparecen en diferentes bases de código:

  • Ciclos de retención que nadie perfiló
  • Secretos del Keychain reemplazados con UserDefaults
  • Actualizaciones de UI que se activan fuera del hilo principal
  • Envíos a la App Store enviados sin verificar el manifiesto de privacidad

Hemos intentado abarcar todos los puntos en esta lista de verificación. Sin embargo, revisarla solo te llevará hasta cierto punto. La verdadera pregunta es si tu equipo tiene la capacidad y la experiencia necesarias para analizarla a fondo antes de que la situación se complique.

Puedes ver un ejemplo de por qué esto importa desde nuestra propia práctica. Echa un vistazo a Gooroo, una plataforma de tutoría iOS que construimos desde cero en Swift. La app conectó tutores y estudiantes en toda Nueva York y se convirtió en socio proveedor oficial del Departamento de Educación de Nueva York. También obtuvo una calificación de 5 estrellas en la App Store y clasificó en el número tres en Product Hunt.

Sin embargo, para alcanzar ese nivel de reconocimiento tuvimos que asegurarnos de que el código fuera correcto en cada capa:

  • Arquitectura MVVM limpia
  • Gestión de memoria que se mantuvo bajo carga real de usuarios
  • Manejo de privacidad que satisfizo a los revisores de la App Store en el primer envío
  • Suite de pruebas que permitió al equipo iterar rápidamente sin regresiones

No se puede lograr ese tipo de resultado sin una dedicación absoluta y una atención meticulosa a los detalles. Se trata de considerar la calidad del código como una característica, no como una tarea que se pospone hasta después del lanzamiento.

Nuestro equipo ha estado publicando y auditando aplicaciones iOS durante más de 20 años. Cubrimos arquitectura, análisis de memoria, refuerzo de seguridad, cumplimiento de la App Store y cobertura de pruebas. Recibes un informe específico, priorizado y escrito tanto para los tomadores de decisiones como para los desarrolladores. Nunca proporcionamos observaciones vagas o recomendaciones genéricas.

Si quieres tener una idea clara del estado actual de tu código base de iOS, nuestro servicio de revisión de código es el punto de partida ideal. Hablemos de lo que has desarrollado y te diremos exactamente lo que encontramos.

Consulta los resultados reales de la revisión de código: más de 80 mejoras y riesgos de seguridad identificados para un mercado móvil

Este campo es obligatorio. no es un correo electrónico comercial