Jemand teilt ein Bild aus der Fotomediathek mit einer iOS-App. Die Teilen-Erweiterung erscheint im Menü, meldet nach dem Öffnen aber „Lesen nicht möglich“. Ob die Erweiterung erfolgreich kompiliert wurde, reicht zur Fehlersuche nicht aus: Die Quell-App liefert einen NSItemProvider, der mehrere Typen deklarieren kann. Bei einer Datei kann der bereitgestellte Pfad zudem nur während des Lade-Callbacks gültig sein. Wer getrennt prüft, ob die Erweiterung angezeigt wird, die Eingabe lesen kann und sie an die Haupt-App übergeben kann, findet die Fehlerstelle leichter.
Zuerst den Vertrag für Eingaben festlegen
Legen Sie fest, welche Quellen und Daten tatsächlich unterstützt werden sollen. „Alle Bilder“ ist als Testfall zu ungenau. Halten Sie mindestens die Quell-App, den geteilten Inhalt, die registeredTypeIdentifiers, die erwartete Lademethode und das gewünschte Ergebnis in der Haupt-App fest. Je nach Quelle kann dasselbe Bild als Datei, als Daten oder in beiden Formen bereitgestellt werden. Die Erweiterung darf sich nicht auf den ersten Typ im Array verlassen.
| Eingabe | Was die Erweiterung prüfen soll | Abnahmekriterium |
|---|---|---|
| Ein Bild | Ob eine zum Bildtyp passende Repräsentation vorhanden ist | Inhalt wird gelesen und ein Übergabeeintrag erstellt |
| Mehrere Bilder | Die von jedem Anhang einzeln deklarierten Typen | Die Anzahl der Anhänge entspricht der Summe aus erfolgreichen und fehlgeschlagenen Vorgängen |
| Reiner Text | Ob eine Textrepräsentation vorhanden ist | Wird je nach Produktanforderung angenommen oder ausdrücklich abgelehnt |
| Nicht unterstützter Inhalt | Kein passender Typ vorhanden | Verständliche Fehlermeldung, kein leerer Eintrag |
Die Aktivierungsregel der Teilen-Erweiterung bestimmt, wann sie im Menü erscheint. Die Typprüfung zur Laufzeit bestimmt, ob sie die aktuellen Anhänge verarbeiten kann. Beides muss getestet werden: Ein Menüeintrag ist kein Nachweis dafür, dass sich die Eingabe lesen lässt.
Erst die deklarierten Typen prüfen, dann die Repräsentation wählen
Schreiben Sie während der Entwicklung die Typkennungen der Anhänge in lokale Diagnoseeinträge, laden Sie dabei aber keine Nutzerinhalte hoch. Prüfen Sie bei der Auswahl, ob ein Typ dem Zieltyp entspricht, statt die vollständige Kennung einer bestimmten Quell-App fest zu codieren. Wenn das Produkt nur Bilder annimmt, muss Text über einen ausdrücklichen Ablehnungspfad laufen. Versuchen Sie nicht, unbekannte Inhalte zwangsläufig als Bild zu decodieren.
Dateien im Callback kopieren
Besonders riskant ist es, die von loadFileRepresentation zurückgegebene URL zu speichern und die Haupt-App erst nach Ende der Erweiterung darauf zugreifen zu lassen. Die URL verweist auf eine beim Laden bereitgestellte temporäre Datei und eignet sich nicht als dauerhafter Übergabeort.
Das Abnahmekriterium lautet nicht „Im Callback lässt sich ein Pfad ausgeben“, sondern „Nach Ende des Callbacks kann die Haupt-App weiterhin eine Kopie lesen, für die sie Zugriffsrechte hat“.
Der folgende Swift-Ausschnitt zeigt die entscheidende Grenze beim Umgang mit einer Bilddatei. Sollen Erweiterung und Haupt-App Dateien gemeinsam nutzen, müssen beide für dieselbe App Group konfiguriert sein; deren Kennung sollte über die Deployment-Konfiguration übergeben werden. Schreiben Sie keine Beispielkennung direkt ins Projekt. Für die Kopie wird ein neuer Dateiname verwendet, damit ein anderer Teilen-Vorgang nicht überschrieben wird.
import Foundation
import UniformTypeIdentifiers
enum ShareCopyError: Error {
case unsupportedType
case containerUnavailable
case missingFile
}
func copySharedImage(
from provider: NSItemProvider,
groupID: String,
completion: @escaping (Result<URL, Error>) -> Void
) {
guard let typeID = provider.registeredTypeIdentifiers.first(where: {
UTType($0)?.conforms(to: .image) == true
}) else {
completion(.failure(ShareCopyError.unsupportedType))
return
}
provider.loadFileRepresentation(forTypeIdentifier: typeID) { source, error in
if let error {
completion(.failure(error))
return
}
guard let source else {
completion(.failure(ShareCopyError.missingFile))
return
}
guard let container = FileManager.default
.containerURL(forSecurityApplicationGroupIdentifier: groupID) else {
completion(.failure(ShareCopyError.containerUnavailable))
return
}
do {
let directory = container.appendingPathComponent(
"Incoming", isDirectory: true
)
try FileManager.default.createDirectory(
at: directory, withIntermediateDirectories: true
)
let ext = UTType(typeID)?.preferredFilenameExtension ?? "bin"
let target = directory.appendingPathComponent(
UUID().uuidString + "." + ext
)
try FileManager.default.copyItem(at: source, to: target)
completion(.success(target))
} catch {
completion(.failure(error))
}
}
}
Dieser Code verarbeitet nur einen einzelnen Bildanhang. In der tatsächlichen Implementierung müssen außerdem die Ergebnisse mehrerer Anhänge zusammengeführt und zulässige Dateigrößen begrenzt werden. Legen Sie auch fest, ob bei einem Teilerfolg bereits kopierte Dateien erhalten bleiben oder der gesamte Vorgang rückgängig gemacht wird. Die Haupt-App sollte eine Dateikennung für ein kontrolliertes Verzeichnis erhalten. Nach dem Lesen muss sie die Kopie gemäß den Produktregeln bereinigen, statt unbegrenzt Dateien anzusammeln.
Die Ladegrenze automatisiert testen
Testen Sie zunächst die Funktionen zur Typauswahl und zum Kopieren, danach die vollständige Teilen-Oberfläche. Die Tests müssen mindestens Bildtypen, nicht unterstützte Typen, Ladefehler, einen nicht verfügbaren gemeinsamen Container, Teilausfälle bei mehreren Anhängen sowie gleichnamige Eingaben abdecken, die einander nicht überschreiben dürfen. Ein Test-Double sollte den Zeitpunkt des Callbacks steuern können: Stellen Sie die Datei innerhalb des Callbacks bereit, entfernen Sie die Quelldatei nach dessen Rückkehr und prüfen Sie anschließend, ob die Kopie weiterhin lesbar ist. So testen Sie die Lebensdauer der Datei und nicht bloß eine URL, die nicht leer ist.
Prüfen Sie auf einem Cloud-Mac von CDNMac zunächst die vom Projekt bereitgestellten Schemes und die verfügbaren Simulatoren. Für die folgenden Befehle müssen drei Umgebungsvariablen auf Werte gesetzt werden, die im aktuellen Projekt tatsächlich vorhanden sind. Das Ergebnispaket wird in einem neu angelegten Verzeichnis abgelegt, damit es bei einem erneuten Lauf nicht mit einem vorhandenen Pfad kollidiert.
: "${PROJECT:?set PROJECT to an existing .xcodeproj path}"
: "${SCHEME:?set SCHEME to a shared test scheme}"
: "${SIMULATOR_ID:?set SIMULATOR_ID to an available device ID}"
xcodebuild -list -project "$PROJECT"
xcrun simctl list devices available
RESULT_ROOT="$(mktemp -d)"
xcodebuild -project "$PROJECT" \
-scheme "$SCHEME" \
-destination "platform=iOS Simulator,id=$SIMULATOR_ID" \
-resultBundlePath "$RESULT_ROOT/share-input.xcresult" \
test
Die CI sollte das Testergebnispaket aufbewahren und den Exit-Status des Testprozesses über Erfolg oder Fehlschlag des Jobs entscheiden lassen. Suchen Sie nicht nur nach „passed“ im Log: Auch ein fehlgeschlagener Start der Erweiterung oder Tests, die im Scheme nicht enthalten sind, müssen auffallen. Lassen Sie bei der ersten Einrichtung absichtlich eine Assertion fehlschlagen, um zu prüfen, ob die Pipeline den Vorgang tatsächlich stoppt.
Testlücken durch echtes Teilen schließen
Unit-Tests können die eigene Ladelogik prüfen, aber nicht garantieren, welche Repräsentation eine Quell-App bereitstellt. Teilen Sie auf einem Testgerät nacheinander Inhalte aus allen Quellen, die unterstützt werden sollen. Prüfen Sie zuerst, ob die Erweiterung erscheint, und anschließend die tatsächlich deklarierten Typen, das Ladeergebnis und die Kopie in der Haupt-App. Achten Sie bei großen Bildern und mehreren Bildern besonders auf Speicherbedarf, Rückmeldungen während des Wartens und das Verhalten beim Abbrechen. Die erfolgreiche Verarbeitung eines kleinen Bildes reicht für die Abnahme nicht aus.
Gehen Sie bei der Fehlersuche der Reihe nach vor: Erscheint die Erweiterung nicht, prüfen Sie die Aktivierungsregel. Erscheint sie, aber kein Typ passt, erfassen Sie die von der Quelle gelieferten Kennungen. Scheitert das Laden, halten Sie die Fehlerkategorie fest. Kann die Erweiterung den Inhalt lesen, die Haupt-App aber nicht, prüfen Sie die App-Group-Berechtigungen, ob das Kopieren innerhalb des Callbacks abgeschlossen wurde und ob die Haupt-App den Pfad der Kopie statt des Quellpfads erhalten hat. Bewahren Sie in Testaufzeichnungen nur die nötigen Angaben zu Typ, Verarbeitungsschritt und Fehler auf. Geteilte Inhalte gehören nicht ins Build-Log.
Checkliste vor der Veröffentlichung
Prüfen Sie vor der Veröffentlichung vier Punkte: Die deklarierten Typen der Erweiterung entsprechen den tatsächlich unterstützten; jede Datei wird innerhalb ihres Lade-Callbacks kopiert; die Haupt-App liest ausschließlich Kopien aus einem Verzeichnis, auf das sie zugreifen darf; und Ablehnung, Abbruch sowie Teilausfälle führen jeweils zu einem sichtbaren Ergebnis. Wurde die App-Group-Konfiguration geändert, testen Sie die Übergabe erneut auf einem frisch eingerichteten Testgerät. Ein bereits vorhandener Container auf dem Entwicklungsgerät genügt als Nachweis nicht.
Halten Sie Typmatrix, Ergebnisse der automatisierten Tests und Aufzeichnungen der Tests auf dem Gerät zusammen mit derselben Änderung fest. Für ein neues Eingabeformat gibt es damit einen klaren Ausgangspunkt: erst den Vertrag ergänzen und einen fehlschlagenden Test schreiben, dann die Ladelogik implementieren. Warten Sie nicht, bis jemand meldet, dass nach dem Teilen keine Datei vorhanden ist, und Sie nachträglich über die gelieferte Repräsentation rätseln müssen.
Häufig gestellte Fragen
Darf die Erweiterung die empfangene Datei-URL direkt an die App übergeben?
Nein. Kopieren Sie die Datei innerhalb des loadFileRepresentation-Callbacks an einen für die App lesbaren Ort und übergeben Sie die Kennung der Kopie.
Was muss nach erfolgreichen Simulator-Tests am Gerät geprüft werden?
Teilen Sie aus der Quell-App und prüfen Sie Sichtbarkeit, tatsächlich angebotenen Typ, große Dateien sowie den Zugriff der App auf die Kopie.
Lassen Sie Ihren nächsten iOS-Build auf einem dedizierten Cloud-Mac laufen
Wählen Sie aus sechs Standorten und vier Mietlaufzeiten die passende Lösung für Ihre Aufgabe – mit M4, 16 GB RAM und 256 GB SSD auf einer festen physischen Maschine.