Das Projektrepository ist fast 40 GB groß, und Pods, node_modules und DerivedData zusammen nehmen mehr Platz ein als der eigentliche Quellcode. Öffnet man Xcode lokal, während das Projekt über eine SMB-Freigabe auf einem entfernten Cloud Mac gemountet ist, dauert die Indexierung schon mal über eine Minute, und ein Branch-Wechsel hängt so lange, dass man an einen Netzwerkabbruch glaubt – das ist die erste Mauer, an die viele stoßen, sobald sie ihre Entwicklungsumgebung auf einen Cloud Mac verlegen. Das Mounten einer Netzwerkfreigabe wirkt zunächst bequem, doch in der Praxis zeigt sich schnell: Die Dateizugriffsmuster großer iOS-Projekte passen von Natur aus nicht zu Netzwerkdateisystemen. Dieser Beitrag beschreibt, wie wir auf HireVPS-Knoten mit Mutagen eine bidirektionale Synchronisation zwischen lokalem Rechner und Remote-Mac aufgebaut haben, um die Erfahrung wieder nahe an ein lokales Setup heranzuführen.
Mounten von Netzwerkfreigaben eignet sich nicht für große iOS-Projekte
Netzwerkdateisysteme wie NFS oder SMB sind für „gelegentlichen Zugriff auf große Dateien" ausgelegt. Xcodes Indexierung, SourceKit und der CocoaPods-Cache-Scan machen jedoch genau das Gegenteil: Sie führen innerhalb kurzer Zeit stat-, open- und close-Aufrufe auf Zehntausende kleiner Dateien aus. Jede einzelne Operation erfordert einen Netzwerk-Roundtrip – selbst bei nur 20 ms Latenz summiert sich das bei Zehntausenden Aufrufen zu Verzögerungen im Minutenbereich. Noch ärgerlicher ist das Verhalten bei Verbindungsabbrüchen: Der Mount-Punkt friert bei instabiler Verbindung einfach ein, Terminal und Editor hängen gemeinsam fest, und es bleibt nur das erzwungene Unmounten und erneute Mounten.
Ein Vergleich der drei gängigen Ansätze macht das anschaulicher:
| Lösung | Echtzeitfähigkeit | Verhalten bei vielen kleinen Dateien | Wiederherstellung nach Verbindungsabbruch | Einsatzszenario |
|---|---|---|---|---|
| SMB/NFS-Mount | Stark (sofort aktuell) | Schlecht, Netzwerk-Roundtrip pro Datei | Schlecht, neigt zum Einfrieren | Gelegentliches Lesen/Schreiben großer Dateien |
| rsync-Zeitplan | Schwach, mit Synchronisationsfenster | Mittel, effizient bei Batch-Übertragung | Gut, einfach erneut ausführen | Regelmäßige Backups, unidirektionaler Push |
| Mutagen Bidirektionale Sync | Nahezu Echtzeit | Gut, lokaler Cache + inkrementelle Übertragung | Gut, automatische Wiederverbindung und Fortsetzung | Fortlaufende bidirektionale Entwicklungszusammenarbeit |
Eine Lehre, die wir schmerzhaft gezogen haben: Aus Bequemlichkeit haben wir das Projekt zunächst einfach gemountet – der Schritt „Building workspace" in Xcode dauerte dadurch im Schnitt 15 bis 25 Sekunden länger. Allein die tägliche Wartezeit beim Indexieren hat das Team spürbar Zeit gekostet; nach dem Wechsel zu Mutagen ist diese Wartezeit praktisch verschwunden.
Mit Mutagen eine bidirektionale Sync-Session einrichten
Installation und erste Verbindung
Mutagen selbst ist eine einzelne Binärdatei; weder lokal noch auf dem Cloud Mac ist ein zusätzlicher Hintergrunddienst nötig – beim Verbindungsaufbau wird der Agent automatisch auf die entfernte Maschine verteilt. Zunächst lokal installieren:
brew install mutagen-io/mutagen/mutagen
mutagen version
Angenommen, SSH ist auf deinem Cloud Mac bereits aktiviert und die Zugangsdaten stehen in der Willkommens-E-Mail beim ersten Start. Jetzt eine Sync-Session erstellen:
mutagen sync create \
--name=ios-app \
--ignore-vcs \
--symlink-mode=posix-raw \
/Users/me/Projects/ios-app \
ssh://devuser@your-cloud-mac-host/Users/devuser/Projects/ios-app
--ignore-vcs überspringt automatisch die internen .git-Objekte (Git synchronisiert sich besser über sein eigenes Protokoll, Mutagen muss das nicht doppelt erledigen), und --symlink-mode=posix-raw sorgt dafür, dass die in CocoaPods üblichen symbolischen Links unverändert erhalten bleiben und nicht konvertiert werden.
Ignore-Regeln: Welche Verzeichnisse niemals synchronisiert werden dürfen
Im Projektverzeichnis sollte unbedingt eine .mutagenignore angelegt werden – sonst überträgt die erste Synchronisation gleich mehrere GB an Cache-Verzeichnissen mit:
DerivedData/
Pods/
.build/
node_modules/
*.xcuserstate
xcuserdata/
.DS_Store
Das Gemeinsame dieser Verzeichnisse ist, dass sie sich auf jeder Seite lokal neu erzeugen lassen. Sie zu synchronisieren verschwendet nicht nur Bandbreite, sondern kann auch die im nächsten Abschnitt beschriebenen Probleme verursachen.
Typische Xcode-Fallstricke
- DerivedData darf niemals synchronisiert werden: Der Modul-Cache (ModuleCache.noindex) und der Build-Index sind fest an absolute Pfade und die Architektur der jeweiligen Maschine gebunden. Synchronisiert man sie auf eine andere Maschine, erzwingt man praktisch eine vollständige Neuindexierung – im schlimmsten Fall treten sogar rätselhafte Compilerfehler auf. Am besten lokal und in der Cloud jeweils unabhängig neu generieren lassen, schließlich ist DerivedData ohnehin ein wegwerfbares Build-Artefakt.
- Pods und node_modules jeweils separat installieren: Was tatsächlich synchronisiert werden muss, sind
Podfile.lockundpackage-lock.jsonim Quellcode. Die Abhängigkeitsverzeichnisse selbst lässt man auf jeder Seite einmal perpod installbzw.npm installerzeugen – das spart Bandbreite und vermeidet Inkompatibilitäten binärer Artefakte zwischen unterschiedlichen Architekturen. .gitignoreund.mutagenignoresynchron halten: Widersprechen sich die beiden Ausschlusslisten, zeigt Git „keine Änderungen" an, während Mutagen im Hintergrund trotzdem einen Haufen sinnloser Dateien überträgt – die Fehlersuche kostet dann viel Zeit. Am besten ein kleines Skript schreiben, das die beiden Listen nach jeder Änderung gegeneinander abgleicht.- Unterschiede bei den Berechtigungsbits: Da die UID des Cloud-Mac-Kontos von der lokalen UID abweicht, sollte man bei Berechtigungsfehlern zunächst prüfen, ob die Synchronisation das Ausführungsbit verloren hat.
--permissions-mode=portableschafft in den meisten Fällen Abhilfe.
Praxiswerte und Konfliktbehandlung
Im Alltag ist eine durch das Speichern ausgelöste inkrementelle Synchronisation (Änderungen an ein paar Swift-Dateien) in der Regel innerhalb von ein bis zwei Sekunden abgeschlossen – gefühlt kaum langsamer als ein lokaler Speichervorgang. Die erste vollständige Synchronisation (Zehntausende Quelldateien) dauert dagegen deutlich länger, weil dabei der komplette Verzeichnisbaum gescannt werden muss. Es empfiehlt sich, diese initiale Synchronisation vor Arbeitsbeginn einmal laufen zu lassen, statt währenddessen schon mit dem Programmieren zu beginnen.
Beim Konfliktmanagement ist der standardmäßige bidirektionale Modus two-way-safe: Bei einem Konflikt wird nicht automatisch überschrieben, sondern die Synchronisation pausiert und wartet auf manuelles Eingreifen. Das ist sicherer als ein Modus, der Änderungen erzwungen überschreibt, führt aber zu häufigen Pausen, wenn beide Seiten dieselbe Datei bearbeiten. Für die Teamzusammenarbeit empfiehlt sich eher:
mutagen sync create \
--name=ios-app \
--sync-mode=two-way-resolved \
--default-file-mode-alpha=0644 \
/Users/me/Projects/ios-app \
ssh://devuser@your-cloud-mac-host/Users/devuser/Projects/ios-app
two-way-resolved lässt bei Konflikten standardmäßig die lokale Seite (Alpha) gewinnen. Kombiniert mit der Regel, dass jede Person zu jedem Zeitpunkt nur auf einer Seite Code ändert, gehen so praktisch keine Änderungen verloren. Bei echten Konflikten binärer Ressourcen (Bilder, Schriften) listet mutagen sync list die betroffenen Pfade auf – dann vergleicht man Zeitstempel und Dateigröße manuell, um zu entscheiden, welche Version erhalten bleibt.
Eine fertige Konfiguration zum Übernehmen
Mutagen unterstützt projektbezogene Konfigurationsdateien. Legt man eine mutagen.yml im Projektstammverzeichnis ab, kann jedes Teammitglied nach dem Klonen des Repositorys die Synchronisation mit einem einzigen Befehl starten, ohne alle Parameter selbst eintippen zu müssen:
sync:
ios-app:
alpha: "."
beta: "ssh://devuser@your-cloud-mac-host/Users/devuser/Projects/ios-app"
mode: "two-way-resolved"
ignore:
vcs: true
paths:
- "DerivedData"
- "Pods"
- "node_modules"
- "xcuserdata"
- ".build"
symlink:
mode: "posix-raw"
Für den Alltag reichen diese Befehle:
mutagen sync list
mutagen sync monitor ios-app
mutagen sync pause ios-app
mutagen sync resume ios-app
mutagen sync terminate ios-app
monitor ist besonders hilfreich, wenn man der Frage nachgeht, „warum wurde meine Änderung nicht übertragen" – man sieht in Echtzeit, in welcher der drei Phasen (Scannen, Staging, Übertragung) es gerade hakt.
Checkliste vor dem produktiven Einsatz
- Decken
.mutagenignoreund.gitignorealle neu erzeugbaren Verzeichnisse wie DerivedData, Pods, node_modules und xcuserdata ab? - Wurde
two-way-safeodertwo-way-resolvedals Sync-Modus gewählt? Für die Teamzusammenarbeit ist Letzteres vorzuziehen, verbunden mit einer klaren Absprache, wessen Änderungen im Konfliktfall Vorrang haben. - Wurde die erste vollständige Synchronisation zu einem Zeitpunkt ohne dringende Aufgaben durchgeführt? Das initiale Scannen von Zehntausenden Dateien eignet sich nicht dafür, währenddessen weiterzuarbeiten.
- Wurden das konkrete Cloud-Mac-Modell und der Knoten im Control Panel auf die aktuell verfügbare Konfiguration überprüft? Bandbreite und Speicherspezifikationen beeinflussen die Obergrenze der Synchronisationsgeschwindigkeit.
- Ist
mutagen sync monitorbereits als feste Gewohnheit zur Fehlersuche etabliert, statt erst nach dem Verlust von Dateien in die Logs zu schauen?
Geht man diese Punkte durch, kommt das Bearbeitungserlebnis auf dem Cloud Mac dem lokalen Setup schon sehr nahe – und im Team schiebt sich niemand mehr gegenseitig die Schuld zu, weil „wessen Änderung nicht synchronisiert wurde".
Häufig gestellte Fragen
Warum nicht einfach per SMB oder NFS mounten?
Netzwerk-Mounts verwandeln jeden kleinen Dateizugriff (Xcode-Indexierung, Pods-Lookups) in einen Roundtrip; große Projekte brauchen dadurch oft zehn bis zwanzig Sekunden allein zum Öffnen. Mutagen hält einen lokalen Cache und synchronisiert inkrementell, sodass die Reaktionszeit im Editor fast lokal bleibt und eine unterbrochene Verbindung das Dateisystem nicht einfrieren lässt.
Sollte DerivedData mitsynchronisiert werden?
Nein. Der Modul-Cache und die Indizes in DerivedData sind an Architektur und absolute Pfade der Maschine gebunden; eine Synchronisation löst meist eine komplette Neuindizierung oder Build-Fehler aus. In .mutagenignore aufnehmen und jede Maschine ihr eigenes DerivedData generieren lassen.
Was tun bei Konflikten in der Zweiwegesynchronisation?
Mit mutagen sync list die betroffenen Pfade finden. Bei Quellcode reicht meist der Modus two-way-resolved mit Vorrang für die lokale (alpha) Seite; bei Binärdateien manuell per Diff entscheiden. Vor jedem Sync-Start git status auszuführen vermeidet die meisten Konflikte von vornherein.
HireVPS Cloud Mac
Testen Sie noch heute einen dedizierten Cloud Mac mini
Tagesweise mietbar, SSH/VNC-Zugangsdaten in nur 2 Minuten, Konfiguration jederzeit upgradbar.