iOS Share Extension Si Chiudeva Dopo 2 Secondi: La Soluzione all'OOM Kill
Durante lo sviluppo di SnapPress (un'app per caricare foto dall'iPhone direttamente su WordPress), ho aggiunto una Share Extension per permettere agli utenti di condividere foto dall'app Foto senza aprire prima SnapPress.
L'extension funzionava. La maggior parte delle volte. A volte si apriva, mostrava uno spinner di caricamento per circa 2 secondi, e poi spariva senza lasciare traccia. Nessun crash log in Xcode. Nessuno stack trace. Nessun messaggio di errore. Spariva e basta.
Share Extension vs Action Extension
Una Action Extension (com.apple.ui-services) appare nella riga delle icone app del share sheet. È progettata per trasformare o agire sul contenuto sul posto.
Una Share Extension (com.apple.share-services) appare nell'elenco delle attività del share sheet. È progettata per inviare contenuti da qualche parte, che sia un social network, un CMS o un'app per appunti. Viene eseguita come una mini-app indipendente con la propria UI, il proprio processo e i propri limiti di memoria.
Il Crash: "Invalidation Requested"
Il primo indizio è arrivato da Console.app su Mac. Con l'iPhone connesso e filtrando per il nome del processo dell'extension, ho trovato questo in sharingd:
MetricEvent 'com.apple.sharing.sharesheetCompleted' : {
"success" : false,
"totalShareTimeMs" : 2700,
"activityType" : "app.snappress.SnapPress.ShareExtension",
} E da Photos:
View service session ended with error:
_UIViewServiceHostSessionErrorDomain Code=4
UserInfo={Message=Invalidation requested} Invalidation requested significa che il processo dell'extension è terminato inaspettatamente. totalShareTimeMs: 2700 significa che è vissuto esattamente 2,7 secondi. Questa è la firma di un OOM kill. iOS termina silenziosamente i processi che superano il limite di memoria, senza crash log né segnali.
Causa Principale: UIImage(data:) Decodifica in Piena Risoluzione in Memoria
La Share Extension carica immagini da NSItemProvider, le elabora e le carica su WordPress. Il passo di elaborazione chiamava questo metodo:
static func process(_ imageData: Data, quality: ImageQuality) -> Data? {
guard let image = UIImage(data: imageData) else { return nil }
let resized = resize(image, maxDimension: quality.maxDimension)
return resized.jpegData(compressionQuality: quality.compressionQuality)
} Il problema è UIImage(data: imageData). Questo decodifica il file immagine compresso in un buffer di pixel a piena risoluzione prima di fare qualsiasi altra cosa. Una foto iPhone da 12 megapixel (4032 × 3024 pixel) si espande a:
4032 × 3024 × 4 byte (RGBA) = ~47 MB Con la copia ridimensionata, il buffer di output JPEG, il container SwiftData e le miniature, un singolo upload può raggiungere 60-80 MB. iOS dà alle Share Extension circa 120-150 MB. Con alcune grandi foto HEIC, quel limite viene facilmente superato.
La Soluzione: Decodificare Direttamente alla Dimensione Target con CGImageSource
CGImageSource può fare downsampling durante la decodifica, producendo l'immagine alla dimensione target senza mai creare il buffer a piena risoluzione in memoria.
static func process(_ imageData: Data, quality: ImageQuality) -> Data? {
let maxDim = Int(quality.maxDimension)
let options: [CFString: Any] = [
kCGImageSourceCreateThumbnailFromImageAlways: true,
kCGImageSourceThumbnailMaxPixelSize: maxDim,
kCGImageSourceCreateThumbnailWithTransform: true,
kCGImageSourceShouldCacheImmediately: false,
]
guard
let source = CGImageSourceCreateWithData(imageData as CFData, nil),
let cgImage = CGImageSourceCreateThumbnailAtIndex(source, 0, options as CFDictionary)
else { return nil }
return UIImage(cgImage: cgImage).jpegData(compressionQuality: quality.compressionQuality)
} L'utilizzo di memoria per immagine scende da ~60 MB a ~6 MB. Una riduzione di 10x. La Share Extension non è più andata in crash da allora.
Riepilogo
Se la tua Share Extension iOS va in crash dopo 2-3 secondi senza crash log, è quasi certamente un OOM kill. Sostituisci UIImage(data:) con CGImageSourceCreateThumbnailAtIndex e imposta kCGImageSourceThumbnailMaxPixelSize alla tua dimensione target.
SnapPress è disponibile sull'App Store.