# macOS

## Hva det er

macOS er operativsystemet Apple leverer med hver Mac. Det du ser, er skrivebordet,
Finder og appene som følger med; under dem ligger et fullt Unix-system med terminal,
skall og de kjente kommandolinjeverktøyene. Apple lager systemet for sin egen maskinvare
og selger de to samlet, så den som velger macOS, velger i praksis også en Mac.

Grunnmuren heter Darwin, og kildekoden til den publiserer Apple selv. Darwin hviler på
en systemkjerne kalt XNU, en hybrid som forener Mach-kjernen fra Carnegie Mellon
University med komponenter fra FreeBSD og et driverrammeverk i C++ som heter IOKit.
Navnet står for «X is Not Unix». Også mye av userland, programmene som kjører utenfor
kjernen, kommer fra FreeBSD: manualsidene for `sed` og `date` på en Mac beskriver
FreeBSD-utgavene av de to verktøyene. Siden macOS 10.15 Catalina har zsh vært
standardskallet.

Under systemet har maskinvaren nylig byttet prosessorarkitektur. Mot slutten av 2020
begynte Apple å flytte Macen fra Intel-prosessorer til egne brikker, Apple silicon, og i
dag finnes hver Mac-serie fra MacBook Air til Mac Pro med Apple silicon. Brikkene er
arm64, og det merkes i samme øyeblikk som programvare bygget på en Mac skal kjøre på en
x86-64-server.

På bærbare og stasjonære maskiner er det Windows og Linux macOS blir veid opp mot.

## Hvorfor det er viktig

For et selskap som lager programvare, har macOS to roller som lett flyter over i
hverandre: det er maskinen mange utviklere jobber på, og det er en plattform kundene
kjører programvare på.

Som arbeidsstasjon skiller en Mac seg på tre punkter fra Linux-serverne koden skal kjøre
på i produksjon. Kommandolinjeverktøyene kommer fra FreeBSD og ikke fra GNU, så det
samme flagget kan bety to ulike ting i et skript. Prosessoren er arm64, mens serveren
gjerne er x86-64. Og en Mac har ingen Linux-kjerne, mens containere deler kjernen med
verten sin. Derfor starter containerverktøy på en Mac først en virtuell Linux-maskin.
Apples eget verktøy `container`, som krever Apple silicon og macOS 26, gir hver
container sin egen lette virtuelle maskin. Ingenting av dette diskvalifiserer en Mac.
Det betyr at dere tester på systemet dere produksjonssetter til, i stedet for å stole på
en grønn kjøring på en bærbar.

Som plattform reiser macOS spørsmålet om hvordan dere skal bygge for den. Et skall på
tvers av plattformer, altså én kodebase med en innebygd nettlesermotor som leveres til
Windows, Linux og Mac, holder flere systemer i takt til en lav pris. På Macen drar det
likevel med seg en nettleserkjøretid som en native applikasjon ikke har bruk for, og det
tegner sine egne kontroller i stedet for systemets. En native applikasjon i Swift og
SwiftUI bruker systemets kontroller og materialer, og den følger brukerens innstillinger
for utseende, aksentfarge, kontrast, tekststørrelse og redusert bevegelse uten ekstra
kode. Prisen er en egen klient og folk som kan plattformen. Native lønner seg når
Mac-brukerne er kundene.

Leveranser til Mac må også gjennom et kontrollpunkt. Programvare som distribueres
utenfor Mac App Store, signeres med et Developer ID-sertifikat og sendes til Apples
notariseringstjeneste, en automatisk skanning etter skadelig innhold og feil i
kodesigneringen. Den er noe annet enn App Review. Siden macOS 10.15 må all programvare
som er bygget etter 1. juni 2019 og distribueres med Developer ID, være notarisert.
Apple sender tilbake en notariseringsbillett (ticket) som festes til programvaren, og
publiserer den samme billetten på nettet, der Gatekeeper slår den opp første gang
brukeren åpner appen. Sertifikatet følger med Apple Developer Program, som koster 99
dollar i året. Ta med medlemskapet og signeringen i planen for den første utgivelsen.

## Slik ser det ut i praksis

Et meldingsprodukt hadde en skrivebordsklient på macOS som var et skall på tvers av
plattformer, og den verken så ut eller oppførte seg som en Mac-app. Produktets godt
testede Rust-kjerne har et C-grensesnitt, og Swift kan kalle C, så
[en native klient i Swift og SwiftUI](/nb/portfolio/built-a-native-macos-messaging-client-in-swift-6-154/)
ble bygget rett på det grensesnittet. Kjernen er lenket inn som et statisk arkiv fra en
fastlåst oppstrømsrevisjon, noe som holder byggingen reproduserbar, og en tynn wrapper
eier hver peker som krysser mellom språkene. Klienten starter som en Mac-app, bruker
plattformens egne materialer og kontroller og har ingen innebygd nettleser. Siden er
ærlig om avstanden: det finnes verken notarisert distribusjon eller oppdateringskanal,
så dette er en fungerende app og ikke et produkt andre kjører. Notariseringen beskrevet
over er nettopp det som skiller de to.

Plattformens utseende kan håndheves i stedet for å etterlignes.
[Et designsystem med 48 navngitte tokens](/nb/portfolio/built-a-48-token-design-system-160/)
i seks grupper bygger farge og materiale på plattformens semantiske farger og
glassmaterialet, ikke på faste verdier. Dermed følger grensesnittet systemets utseende,
aksentfargen og kontrastinnstillingene uten at noen kode passer på dem, og tekststilene
skalerer med brukerens foretrukne størrelse. En linter avviser et rått tall der et token
hører hjemme, en hardkodet skriftstørrelse og en animasjon som overser innstillingen for
redusert bevegelse. Siden sier også hvor grensen går: tokens gjør et brukergrensesnitt
konsistent, og det er noe annet enn å gjøre det godt.

Hvordan macOS håndterer minne, viste seg i
[arbeid på en macOS-applikasjon](/nb/portfolio/stopped-an-application-filling-memory-at-41mb-a-second-155/)
der en app vokste med rundt 41 MB i sekundet. Komprimert minne og swap lot den vokse
langt forbi de fysiske 36 GB i maskinen: hendelsen som ble registrert, viste 111 GB
komprimerte sider. Da operativsystemet til slutt drepte appen, hadde resten av
skrivebordet for lengst sluttet å svare. Tre årsaker multipliserte hverandre: en
ubegrenset hendelsesstrøm, abonnenter som hver fikk hver eneste hendelse, og logging
uten budsjett. Rettingen ga bufferne en grense, lot hver lytter abonnere bare på sine
egne hendelsestyper og satte et tak på hvor ofte loggingen skriver, og en
regresjonstest holder minnetaket. Økten som skrev
610 996 logglinjer, skriver nå 1 411, og siden er åpen om prisen: når noe går galt fort,
er loggen med vilje ufullstendig.

## Hvor det går galt

**Aktivitetsmonitor viser nesten ikke noe ledig minne på en Mac som går helt fint.**
macOS bruker ellers ubrukt minne til caching av nylig brukte filer, og etter hvert som
minnet fylles, komprimerer systemet inaktive apper for å gi plass til de aktive. Når
brukt minne ligger nær det fysiske og tallet for komprimert minne er høyt, er det altså
normalt. Apple skriver selv at ledig eller ubrukt minne ikke nødvendigvis gir bedre
ytelse. Les heller Minnebelastning-diagrammet i Minne-panelet: grønt betyr at maskinen
bruker all RAM effektivt, gult at den etter hvert kan trenge mer, og rødt at den trenger
mer. Swap er det tydeligste signalet:

```sh
sysctl vm.swapusage
vm_stat | grep -E '^Swap(ins|outs)'
```

Tellerne i `vm_stat` løper fra oppstart, så sammenlign to avlesninger med ett minutts
mellomrom. Swapouts som fortsetter å stige, betyr at arbeidslasten har vokst ut av
RAM-en.

**Et skript som virker på Macen, stopper med en merkelig feil på Linux-serveren, eller
omvendt.** Den FreeBSD-avledede `sed` på en Mac leser ordet etter `-i` som et suffiks
for sikkerhetskopien. GNU-vanen `sed -i 's/a/b/' file` får dermed uttrykket tolket som
suffiks og filnavnet som skript. Mac-vanen `sed -i '' …` får på sin side GNU `sed` til å
lese uttrykket som et filnavn. Et suffiks skrevet tett inntil flagget virker begge
steder:

```sh
sed -i.bak 's/^port=80$/port=8080/' app.conf && rm app.conf.bak
```

Den varige løsningen er å kjøre skriptene i CI på det systemet de møter i produksjon.

**Et bygg som deles utenfor teamet, blir stoppet av Gatekeeper når mottakeren åpner
det.** Appen er signert for utvikling, eller den er signert, men aldri notarisert.
Signer den med et Developer ID-sertifikat, med hardened runtime slått på og et sikkert
tidsstempel, send den til notariseringstjenesten, og fest billetten til appen, så
Gatekeeper finner den også uten nettforbindelse. Et ZIP-arkiv kan notariseres, men har
ikke plass til en billett, så fest den til appen inni arkivet:

```sh
ditto -c -k --keepParent App.app App.zip
xcrun notarytool submit App.zip --keychain-profile notary --wait
xcrun stapler staple App.app
```

**Et containerimage kjører på utviklerens Mac og dør på serveren med
`exec format error`.** Docker bygger som standard for plattformen til BuildKit-daemonen
som utfører byggingen, og på en Mac med Apple silicon kjører den daemonen i en virtuell
arm64-maskin. En container deler kjernen med verten, så uten emulering kjører en
x86-64-vert bare x86-64-kode. Sjekk serveren med `uname -m`, og bygg deretter for den
med `docker build --platform linux/amd64`, eller bygg i CI på serverens
prosessorarkitektur.

## Les mer

- [Mac User Guide](https://support.apple.com/guide/mac-help/welcome/mac) — Apples egen
  brukerveiledning for macOS, oppdatert for hver gjeldende versjon.
- [Check if your Mac needs more RAM](https://support.apple.com/guide/activity-monitor/check-if-your-mac-needs-more-ram-actmntr34865/mac)
  — Minnebelastning-diagrammet og hva de tre fargene betyr.
- [Notarizing macOS software before distribution](https://developer.apple.com/documentation/security/notarizing-macos-software-before-distribution)
  — Developer ID, hardened runtime og hvordan Gatekeeper bruker billetten.
- [Multi-platform builds](https://docs.docker.com/build/building/multi-platform/) —
  hvorfor arm64- og amd64-verter trenger hvert sitt image, og hvordan ett bygg lager
  begge.
- [container](https://github.com/apple/container) — Apples verktøy for å kjøre
  Linux-containere på en Mac, hver i sin egen lette virtuelle maskin.
- [XNU](https://github.com/apple-oss-distributions/xnu) — kildekoden til systemkjernen i
  macOS, med en kort forklaring på hvordan Mach, FreeBSD og IOKit henger sammen.

<https://engineer.company/nb/glossary/macos/>
