Hvordan bygger du en synkroniseringsprotokoll mellom Apple Watch og iPhone?
Du låner den. Musklr henter client-side prediction fra nettverkskoden i flerspillerspill, sekvensnumre med en eksklusiv kumulativ kvittering fra TCP, og et inkarnasjonstoken per app-start fra Kafkas idempotente producer. Kommandoer fra klokka er hendelser og leveres nøyaktig én gang, i riktig rekkefølge. Øyeblikksbilder fra telefonen er tilstand og får et revisjonsnummer, der nyeste rett og slett vinner.
Hvorfor blir Apple Watch og iPhone uenige om en pågående økt?
Løfteren står midt i et sett. Telefonen ligger i et skap, på en benk eller med forsiden ned mot gulvet, og klokka er skjermen som faktisk blir lest. Så fullfører løfteren settet og trykker.
Tre ting kan være sanne i det øyeblikket:
- telefonen er innen rekkevidde og svarer på millisekunder
- telefonen er innen rekkevidde, men svaret bruker sekunder
- telefonen er ikke innen rekkevidde i det hele tatt, og blir det ikke resten av økta
Håndleddet må oppføre seg likt i alle tre. Det er hele designoppgaven, og det er den samme oppgaven et flerspillerspill har når en spiller trykker på avtrekkeren.
Tjue centimeter luft er ikke det som gjør at to Apple-enheter blir uenige. Begge holder hver sin kopi av den samme økta, og meldingene mellom dem kan komme sent, i feil rekkefølge, to ganger eller aldri.
Del I, om å holde Apple Watch og iPhone i synk gjennom en økt, listet fem av de uenighetene og hvordan hver enkelt ble fikset. Hver fiks var riktig i seg selv, og ingen av dem hindret at nummer seks dukket opp, for fem enkeltfikser er ikke en protokoll. En protokoll er et lite sett regler begge enhetene følger, slik at neste uenighet blir forhindret i stedet for oppdaget.
Vi fant ikke opp vår egen. Den kom fra tre steder som løste det samme problemet flere tiår tidligere, og det interessante er hvordan den lar seg overføre, pluss stedene der overføringen slutter å holde. Alt sammen ligger på én branch som ikke er flettet inn, holdt på plass av enhetstester og ikke av to enheter på et treningssenter, og siste seksjon er tydelig på hva det lar stå åpent.
Hva er client-side prediction, og hvordan brukes det i en Apple Watch-app?
Den kanoniske gjennomgangen er Gabriel Gambettas Fast-Paced Multiplayer, og andre del, Client-Side Prediction and Server Reconciliation, er den dette innlegget låner fra. Klienten venter ikke på at serveren skal si seg enig før den viser resultatet av et trykk. Den forutsier utfallet, tegner det med en gang, og avstemmer når det autoritative svaret kommer etter.
Overføringen til en treningsapp er nesten én til én:
| Gambetta | Musklr |
|---|---|
| spillerinndata | et trykk på klokka |
| klientens prediksjon | håndleddet bekrefter settet umiddelbart |
| server | telefonen, som eier databasen |
| serveravstemming | telefonens kvittering, som følger med neste øyeblikksbilde |
| autoritativ tilstand | WorkoutSnapshot |
Telefonen forblir autoritativ. Den eier databasen og lagrer hvert eneste sett, og klokka skriver aldri til treningsdatabasen. Det som er endret, er at klokka har sluttet å vente på å få vite at trykket talte.
Det er Musklr på et ekte håndledd, ikke en illustrasjon. Appen er gratis å bruke så lenge du vil, krever ingen konto, og et sett logges med to trykk.
Last ned Musklr gratis Se alt Musklr kanHer stopper analogien. Et spill forkaster en prediksjon som bommet, simulerer på nytt fra siste autoritative tilstand, og spilleren ser en liten korreksjon. Et loggført sett kan ikke gjøres uløftet for noen som står ved en stang. Det finnes ingen vei tilbake på håndleddet, og det skal det med vilje ikke komme.
Derfor er prediksjonen avgrenset til det leseren allerede kan se: trykket, og verdien som står på kronen der og da, som er på skjermen foran dem uansett om telefonen har sagt seg enig ennå eller ikke. Der et sant tall ikke kan oppgis, dropper bekreftelsen tallet og beholder bekreftelsen. Settnummeret utelates så snart forskyvningen går forbi antall sett på øvelsen, og et sett klokka ikke kan navngi, blir ikke bekreftet i det hele tatt. Å vise et løft leseren ikke har tatt, er verre enn å vise mindre.
Hvordan sikrer TCP at hver melding kommer fram nøyaktig én gang, i riktig rekkefølge?
Kommandoene fra klokka er en hendelsesstrøm. Hver eneste én må komme fram, nøyaktig én gang, i den rekkefølgen den ble sendt. Det er akkurat TCPs problem, så vi tok TCPs svar og beholdt RFC-ens egne navn på delene. Gjeldende spesifikasjon er RFC 9293, publisert i 2022, som erstatter RFC 793.
Fire mekanismer gjør jobben.
Hver kommando klokka sender bærer et sekvensnummer som bare går
oppover. Telefonen svarer med hvor langt den har kommet, en
kumulativ kvittering som TCP kaller RCV.NXT, og som
hele neste seksjon handler om.
En kommando med lavere nummer enn det telefonen venter på, blir forkastet i stedet for utført en gang til. Den duplikatavvisningen er det som gjør det trygt å forsøke resending i det hele tatt: uten den loggfører ett nytt forsøk et sett ingen har løftet.
En kommando som kommer for tidlig, blir bufret i stedet for
utført. Rekkefølgen betyr noe fordi effekten av en kommando
avhenger av tilstanden den forrige etterlot seg: en
.completeSet som har gått tom for sett-id-er å peke
på, faller tilbake på å løse det posisjonelt, og en pausejustering
legger differansen sin oppå det den forrige produserte.
WCSession.sendMessage gir ingen garanti for
rekkefølge i seg selv, så en tidlig kommando holdes tilbake til
hullet foran den lukkes, og utføres så sammen med resten av køen i
én sammenhengende serie.
Ingenting av dette er smart. Det er et førti år gammelt design som blir lest nøye og kopiert med vilje.
Hvorfor kvitterer TCP for neste forventede sekvensnummer i stedet for det siste mottatte?
Dette er det viktigste valget på branchen, og den første implementasjonen tok feil på den opplagte måten: et inklusivt vannmerke, der telefonen sender tilbake det siste sekvensnummeret den utførte, og klokka forkaster alt som ligger på eller under. Det virker, det er lett å resonnere om, og det besvarer nøyaktig ett spørsmål: det beviser at telefonen har utført minst kommando N.
Det er ikke spørsmålet en avsender trenger svar på. Den trenger å vite hvilken kommando som forsvant, og et inklusivt vannmerke kan aldri si at akkurat kommando K gikk tapt. Det rapporterer framdrift, og framdrift sier ingenting om hull.
TCP kvitterer eksklusivt. RCV.NXT er, med RFC-ens
egne ord, "the next sequence number expected on an incoming
segment, and is the left or lower edge of the receive window" (RFC 9293, avsnitt 3.4). Mottakeren rapporterer ikke hva den har gjort. Den navngir den
ene tingen den venter på.
Et hull stopper derfor kvitteringen på flekken. Kommando 41 kommer fram, telefonen svarer 42, og hver senere kommando hoper seg opp i bufferet mens svaret blir stående på 42. Det kryper ikke framover. Det blir stående og navngi den manglende kommandoen til den kommer, og så tømmes den bufrede serien i rekkefølge og kvitteringen hopper forbi alt sammen i ett steg.
Vendingen fra inklusiv til eksklusiv er ikke en kosmetisk omdøping. Den er forskjellen på en kanal som vet at den står fast, og en som bare vet at den ligger etter. I Musklr setter telefonen verdien til én forbi det den nettopp utførte, på alle tre stedene verdien kan flytte seg, og klokka forkaster alt som er strengt lavere: en kommando som er lik kvitteringen, er fortsatt ventet, én under er et duplikat. Begge grensene har en test som ikke gjør annet enn å sitte på dem.
Hvorfor må en resendt melding beholde det opprinnelige sekvensnummeret?
Fordi nummeret identifiserer en posisjon i strømmen og ikke et sendeforsøk, og mottakeren holder den posisjonen åpen.
Vår første implementasjon stemplet på nytt.
transmit() tildelte sekvensnummer K, og da sendingen
feilet, la feilhåndteringen kommandoen tilbake i køen uten nummer
i det hele tatt, så replayen ga den et helt nytt nummer. Commiten
som fikset det, sier hva det koster: "K was burned and never
arrived", og da streng rekkefølge var på plass, "one failed send
froze the whole control path". Mottakeren venter i det uendelige
på et nummer avsenderen ikke har lenger, hver senere kommando
stiller seg i kø bak et hull som aldri kan lukkes, og klokka
slutter å virke som inndataenhet.
Så en resending går ut under det nummeret den gikk ut under første gang, og en kommando som legges tilbake i køen etter en feil, legges tilbake under nummeret som allerede er brukt opp på den. Det høyeste nummeret avsenderen har brukt, er beskyttet på samme måte, så det å registrere et eldre nummer på nytt kan ikke dra det bakover.
Gjenopprettingen er med vilje liten. Når kvitteringen ikke har flyttet seg på fem sekunder, tilbyr avsenderen nøyaktig én kommando for resending: den kvitteringen navngir. Alt annet er allerede utført eller allerede bufret på den andre siden, og å sende det på nytt koster radio uten å gi ny informasjon.
Hvorfor slutter exactly-once å virke når avsenderen starter på nytt?
Sekvensnumre er bare unike innenfor én kjøring av avsenderen. Start den på nytt, og telleren må komme tilbake fra et eller annet sted, og uansett hvor den kommer fra er det en sjanse for å kollidere med numre mottakeren allerede har sett.
TCP svarer på dette i RFC 9293, avsnitt 3.4.1, og setter det innledende sekvensnummeret ut fra en klokke slik at en gjenopprettet forbindelse ikke kan gjenbruke numre en tidligere allerede har sendt. Musklr gjør det samme: telleren på klokka settes ut fra et millisekundstidsstempel, med den siste verdien den enheten noen gang har sendt som gulv, så en klokke som går bakover kan heller ikke trekke den tilbake.
Det løser gjenbruken og lager et problem til. En klokkesatt teller fortsetter ikke, den hopper, med så mye faktisk tid som gikk mens appen var nede, og en mottaker som er streng på rekkefølge leser det hoppet som et enormt hull. En av testene våre heter
testNewIncarnationResynchronisesInsteadOfReportingATrillionSeqGap
som er feilen den ble skrevet mot.
TCP avklarer identitet under oppkoblingen. Vi har ikke noe håndtrykk, så identiteten reiser med nyttelasten. Kafkas idempotente producer løser det på samme måte: KIP-98 dedupliserer på en producer-id, en epoke og en sekvens, og brokeren avviser en foreldet epoke. Kafkas egen dokumentasjon nevner forbeholdet som betyr noe her: uten en transaksjons-id er "the producer is limited to idempotent delivery", avgrenset til én producer-økt.
Musklr løser den samme jobben i en mindre form, to verdier i stedet for tre: ett tilfeldig token generert per oppstart av klokke-appen og aldri lagret, som slår Kafkas producer-id og epoke sammen til ett felt stemplet på hver kommando. Når telefonen ser et token den ikke har sett før, bufrer den ikke og venter ikke på et hull som hører til en tidligere oppstart. Den resynkroniserer, tar imot kommandoen som kommer som nytt utgangspunkt, og forkaster alt som er bufret fra den gamle inkarnasjonen. Det koster et felt på hver melding, og det er verdt det.
Når trenger en synkroniseringsprotokoll garantert levering, og når holder det at nyeste vinner?
Den nyttigste innsikten i hele øvelsen er å vite hvor man skal slutte å bruke resten av den.
To ting reiser mellom disse enhetene, og de er ikke samme slags ting:
| Egenskap | klokke til telefon | telefon til klokke |
|---|---|---|
| bærer | en kommando, en hendelse | et øyeblikksbilde, hele tilstanden |
| eksempel | «jeg er ferdig med et sett» | «her er hele økta akkurat nå» |
| å miste én koster | et sett løfteren faktisk tok | ingenting, neste sending bærer alt |
| trenger | nøyaktig én gang, i rekkefølge, resending | bare rekkefølge, nyeste vinner |
Kommandoer får hele TCP-apparatet over. Øyeblikksbilder får ett revisjonsnummer og en regel som avviser alt som er strengt eldre, og det er alt: ingen kvittering, ingen resending, ingen hulldeteksjon, fordi telefonen sender et fullt øyeblikksbilde som livstegn omtrent hvert femtende sekund og ved hvert faseskifte. Et tapt øyeblikksbilde repareres av det neste uten at noen ber om det.
Å sende et foreldet øyeblikksbilde på nytt ville vært direkte feil. Det beskriver en økt som allerede har gått videre, så å levere det for sent kan bare gjøre håndleddet eldre enn det var.
De motsatte feilene koster ulikt. Legg leveringsgarantier på tilstand, og du bruker radio og batteri på å sende data som allerede var utdatert da det feilet. Legg nyeste-vinner på hendelser, og du mister et sett i stillhet, som er den ene feilen en treningslogg ikke kan ha, fordi løfteren gjorde jobben og appen er uenig.
Så er det to tellere, én per retning, og en detalj faller ut av delingen: en lik revisjon blir fortsatt tatt i bruk i stedet for forkastet som ikke-nyere, fordi bare fulle leveringer øker nummeret. Nyeste vinner betyr å avvise eldre. Det betyr ikke å avvise på nytt.
Hvilken halvdel av trafikken som kan droppes, og hvilken som ikke kan det, er mesteparten av jobben bak en treningslogg. Selve loggen er Musklr: gratis på iPhone og Apple Watch, og et sett logges med to trykk.
Last ned Musklr gratis Se alt Musklr kanHvorfor knekker en teller som teller per prosess når den sendes over nettverket?
Fordi en teller som starter på null ved hver oppstart gir et løfte bare dens egen prosess kan holde.
Vi ga øyeblikksbildene et revisjonsnummer slik at en leser kunne skille nyere fra eldre, og det var en enkel teller per prosess som startet på null. Det er riktig for den opprinnelige jobben, å avlyse en sending som en nyere har kjørt forbi inne i én kjøring av appen. Ingenting ved den jobben bryr seg om hva nummeret var i går.
Så gikk det på nettverket og fikk en jobb til, og bar med seg et krav den første aldri hadde. En telefon som starter på nytt midt i en økt kommer tilbake og publiserer revisjon 1 mens klokka holder revisjon 40. Klokka bruker regelen den fikk, helt korrekt, bestemmer at tilstanden som kommer inn er eldre enn den den har, og forkaster den. Så fortsetter den å forkaste resten av økta, fordi telefonen må klatre gjennom 39 øyeblikksbilder først.
Avbrudd midt i økta er ikke hypotetiske. Musklr rapporterer dem til Sentry, som "Workout session resumed after unexpected termination", nettopp for at de skal kunne telles.
Fiksen er den samme som i forrige seksjon: samme klokke-og-gulv-utgangsverdi som kommandotelleren, under sin egen nøkkel, med en test som ikke gjør annet enn å bevise at de to aldri deler en verdi.
Det som gjorde dette vanskelig å se, er ikke telleren. Én variabel hadde to jobber, bare den ene tålte en omstart, og jobben med det strengeste kravet ble lagt til senere av noen som ikke hadde den første jobben i tankene.
Kan én avvist melding låse hele kommandokanalen?
Ja, og vår gjorde det.
Hver kommando bærer med seg økta den ble laget for, og telefonen avviser en kommando som navngir en økt som er avsluttet. Den vakten fortjener plassen sin: uten den renner en kommando som ble lagt i kø på slutten av én økt over i den neste og skriver en foreldet verdi inn på det første settet der.
Den kjørte opprinnelig før sekvensmottakeren, med vilje, ut fra resonnementet at en kommando telefonen er i ferd med å kaste, ikke burde flytte kvitteringen. Isolert sett er det fornuftig, og isolasjonen er problemet.
Så kom streng rekkefølge. Avvisningen var nå permanent, så kvitteringen parkerte på nummeret til den avviste kommandoen og ble stående. Klokka gjorde det en resendende avsender skal gjøre og sendte kommandoen kvitteringen navnga på nytt, og telefonen avviste den igjen, hver gang. Hver senere kommando, inkludert hver kommando for økta som faktisk var aktiv, ble stående bufret bak et hull som aldri kunne lukkes. Commiten som fikset det, sier det rett ut: "The Watch stopped working as an input device for the rest of its launch."
Fiksen er en rekkefølgeendring. Øktvakten kjører nå inne i utførelsessteget, etter at sekvensmottakeren allerede har gått videre, så en avvist kommando bruker opp sekvensnummeret sitt og strømmen går videre uten den. To tester holder det på plass: én sender sekvens 42 med navnet på en økt som er avsluttet og krever at kvitteringen kommer tilbake som 43 uten at noe er utført, den andre at neste kommando for den levende økta utføres som normalt, og en kommando som tømmes ut av bufferet går nå gjennom vakten den også.
To mekanismer, hver for seg riktige, dødelige sammen.
Hvorfor skal et optimistisk grensesnitt aldri hente bekreftelsen sin fra serverens tilstand?
Fordi serverens tilstand per definisjon er akkurat det et optimistisk grensesnitt ikke venter på.
Håndleddet viser verdien du skrur inn, før telefonen har sendt den tilbake. Bekreftelsesoverlayet hentet derimot tallene sine fra telefonens siste øyeblikksbilde.
Altså: skru 50 kg opp til 55, trykk for å fullføre settet før svaret rekker fram, og hakemerket og den bekreftende vibrasjonen bekrefter 50 kg. Appen fortalte løfteren, med et fysisk trykk på håndleddet, at noe hadde skjedd som ikke hadde skjedd.
Hver del av det er riktig i seg selv. Kroneredigeringen lever lokalt til telefonen sier seg enig, som er hele poenget med prediksjon. Øyeblikksbildet er den autoritative tilstanden, som er grunnen til at overlayet leste det. Still dem opp ved siden av hverandre, og appen sier noe usant til noen som står ved en stang.
Fiksen flytter avgjørelsen til den som kaller. Formatereren strekker seg ikke lenger etter øyeblikksbildet, den får tallene levert, og kallstedet gir den verdiene håndleddet viser.
Dette er den skarpe kanten på client-side prediction og grunnen til at vår er avgrenset til det leseren allerede kan se. En prediksjon som bommer i et spill, koster ett bilde, og bildet blir tegnet på nytt. En falsk bekreftelse på et håndledd koster et sett i noens treningslogg, og deretter koster den troen på resten av loggen.
Hva er størrelsesgrensen for innholdet i en Live Activity med ActivityKit?
4 KB, og det er mindre av det enn det høres ut som, fordi Apple setter taket på de statiske attributtene og den dynamiske innholdstilstanden til sammen, ikke på innholdstilstanden alene.
ActivityKit setter taket for dataene i en Live Activity på 4 KB,
og Activity.update(_:) er ikke et kastende kall, så
det finnes ingen feil å fange opp. Apple dokumenterer ikke hva som
skjer når du går over. Det vi så skje, var at kortet på
låseskjermen rett og slett sluttet å oppdatere seg.
Så den delte typen. WorkoutSnapshot er
overføringstypen både for klokka og for Live Activity-en, så hvert
eneste felt for kommandokanalen som ble lagt til for klokka,
sekvensnumrene, kvitteringen, økt-id-en, gikk rett inn i
budsjettet til ActivityKit uten at noen bestemte at det skulle
skje. På en økt med 20 øvelser måler innholdstilstanden mellom
øvelsene 4211 byte når ingenting er strippet bort. Å strippe bort
feltene bare klokka bruker på grensen tar den ned til 1435, og
attributtene skal fortsatt få plass ved siden av.
Nå finnes det to tester. Den ene krever at det strippede øyeblikksbildet får plass innenfor taket, som er den betryggende. Den andre krever at det ustrippede øyeblikksbildet går over, som er den nyttige, fordi den ryker i det øyeblikket strippingen slutter å virke og ikke i det øyeblikket et kort blir stille på låseskjermen til noen. Før denne branchen fantes det ingen størrelsestest i det hele tatt, så ingen visste.
En delt type er et delt budsjett.
Er Swifts Int 32-bit på Apple Watch?
Ja, på arm64_32-slicen watchOS leverer til eldre Apple Watch-modeller. Verdt å sjekke i stedet for å tro noen på ordet, og sjekken er én linje:
xcrun --sdk watchos clang -arch arm64_32 -dM -E -x c /dev/null
Den rapporterer __ILP32__ 1 og en peker på fire byte.
Sett så det ved siden av en teller satt ut fra et millisekundstidsstempel, som er et tall rundt 1,7 billioner. Det får ikke plass i 32 bit. Typene er eksplisitt 64-bit overalt der det betyr noe, på nettverket og i lagringen, og det var bevisst fra starten.
Fella var det ene stedet verdien gikk tilbake gjennom en innebygd
Int. Å lese det lagrede gulvet med
UserDefaults.integer(forKey:) gir deg en
Int, og på klokka utløser det en trap, ved oppstart,
ved hver eneste oppstart, på hver enhet med den slicen.
Utgangsverdien blir nå skrevet og lest uten at en
Int er innom.
Ingen løftere har noen gang truffet denne. Den ble fanget i
kodegjennomgang, på denne branchen, og fikset før noe ble sendt
ut. Det ubehagelige er at det var gjennomgangen som fanget den.
Det fantes en test som dekket utgangsverdien, og den var grønn,
fordi den kjørte i et target der Int er 64 bit. En
grønn test fortalte oss at koden var i orden på en arkitektur
testen ikke kunne nå.
Hva skjer i Musklr når du logger et sett uten iPhone i nærheten?
Du trykker, og håndleddet bekrefter: et hakemerke, en bekreftende vibrasjon, og tallene på skjermen foran deg. Ingenting av det venter på telefonen, fordi ingenting av det er telefonens å si. Du tok settet, og klokka viser deg det du nettopp gjorde.
Bak det går kommandoen i kø under sekvensnummeret sitt og beholder det nummeret uansett hvor mange ganger den må tilbys på nytt. Så snart telefonen er innen rekkevidde tømmes køen i rekkefølge, hver kommando utføres nøyaktig én gang, telefonen skriver settene til databasen den eier, og neste øyeblikksbilde tar kvitteringen tilbake. En kommando som dukker opp to ganger blir forkastet i stedet for løftet to ganger.
Det er det protokollen er bygget for å gjøre, og det testene holder fast. Det er ikke noe noen har sett skje mellom to ekte enheter, og den forskjellen er den ærlige slutten på dette innlegget.
Enhetstestene er konkrete: kvitteringsgrensen fra begge sider,
bufring av en kommando som kommer for tidlig, å fylle et hull
delvis og så helt, resending under det opprinnelige nummeret,
resynkronisering over en omstart, en avvist kommando som bruker
opp sekvensen sin. Det ingen av dem prøver ut, er nettverket.
WCSession melder aldri at klokke-appen er installert
på en paret simulator, så sendevakten på telefonen avviser hver
eneste sending, helt korrekt, og ingenting denne branchen har
sendt over nettverket, har kjørt mellom to enheter i det hele
tatt.
To av tilfellene en simulator strukturelt ikke kan lage, siden den ikke har noen radio å komme utenfor rekkevidde av, er nettopp dem denne seksjonen handler om: en dobbel settfullføring med telefonen borte, og en økt med flere øvelser der telefonen virkelig er utenfor rekkevidde.
En protokoll er et sett regler, og regler er akkurat den slags ting en enhetstest er god på. To enheter som snakker med hverandre er ikke det. Verifisering på ekte enheter er neste steg, og det som fortsatt er galt, ligger der.
Ofte stilte spørsmål
Kan et sett som logges på Apple Watch gå tapt før iPhone har registrert det?
Protokollen er bygget slik at det ikke kan forsvinne i stillhet. Hver kommando fra håndleddet bærer et sekvensnummer, telefonen kvitterer for det neste nummeret den venter på i stedet for det siste den utførte, og en kommando kvitteringen fortsatt navngir, blir sendt på nytt under sitt opprinnelige nummer til den lander. Det er garantien TCP gir en byte-strøm, brukt på trykk. De reglene er i dag bevist av enhetstester, ikke av to enheter på et treningssenter.
Leverer WCSession.sendMessage meldinger i den
rekkefølgen de ble sendt?
Nei. WCSession.sendMessage gir ingen garanti for
rekkefølge, så alt som har en effekt som avhenger av det som kom
før, trenger sin egen rekkefølge. Musklr nummererer hver kommando,
bufrer en som kommer for tidlig i stedet for å utføre den, og
tømmer bufferet i én serie så snart den manglende kommandoen
dukker opp.
Hvordan hindrer du at en resendt kommando loggfører det samme settet to ganger?
Ved å la mottakeren forkaste alt som er nummerert under det den venter på, slik at en resendt kommando som faktisk kom fram første gang, blir kastet i stedet for utført på nytt. Duplikatavvisning er det som gjør det trygt å forsøke resending, og uten den loggfører ett nytt forsøk et sett ingen har løftet.
Hvorfor er det iPhone og ikke Apple Watch som eier treningsdatabasen?
Fordi to skrivere trenger konflikthåndtering og én skriver gjør ikke det. Klokka skriver aldri til treningsdatabasen. Den sender kommandoer og tegner det den får tilsendt, og telefonen lagrer hvert eneste sett. Den holder på to små biter egen tilstand, det høyeste sekvensnummeret den har sendt og en HealthKit-treningsøkt, og ingen av dem er treningsloggen din.
Krever det Musklr Pro å synkronisere en økt mellom Apple Watch og iPhone i Musklr?
Nei. Å logge fra håndleddet og synkronisere med telefonen er en del av gratisappen, det virker offline, og du trenger ingen konto. Musklr er gratis å bruke så lenge du vil. Pro er valgfritt, rundt 25 kr i måneden, og dekker ekstrafunksjoner som ubegrenset antall rutiner og skysynkronisering, ikke tilgang til klokka.
Prøv å logge fra håndleddet
Musklr logger et sett med to trykk, på iPhone og Apple Watch, og det virker offline. Den er gratis å bruke så lenge du vil, uten konto. Pro er valgfritt, rundt 25 kr i måneden, for ekstrafunksjoner som ubegrenset antall rutiner og skysynkronisering. Den er gratis i App Store.