Ren kod i streamingappar: Varför din favorit-OTT-app förmodligen är 10 gånger större än den behöver vara

En iOS-utvecklares syn på uppsvällda kodbaser, legacy-fällor och 20 MB-testet.


Jag har varit iOS-utvecklare sedan 2008, med fokus på streaming och podcasting. Jag började arbeta med HLS på iPhone 2009, och under åren har jag byggt, underhållit, felsökt och ibland ärvt streamingappar i alla storlekar.

Och jag har utvecklat en ganska stark uppfattning om vad "clean code" faktiskt betyder i den här branschen. Inte läroboksversionen. Den verkliga. Den man upptäcker när man öppnar någon annans Xcode-projekt och ens första instinkt är att stänga locket.

Jag har granskat hundratals jobbannonser och frilanskontrakt för iOS-utvecklare genom åren. Nästan varenda en listar stolt "clean code" och "bästa praxis" som krav. SOLID-principer. MVVM. Enhetsteststäckning. Designmönster. Hela regelboken.

Och sedan öppnar man projektet.

Klyftan mellan jobbannonser och verklighet

Här är vad jag faktiskt har hittat när jag öppnat de där "clean code"-streamingprojekten: XIB-filer från 2014 som fortfarande driver kritiska UI-flöden. Objective-C bridging headers som är längre än vissa hela appar. Tre olika nätverkslager som samexisterar för att ingen vågade ta bort det gamla. Ett Coordinator-mönster staplat ovanpå ett Navigator-mönster staplat ovanpå ett Router-mönster, för att varje ny ledande arkitekt lade till sitt favoritmönster utan att ta bort det föregående.

"Ren kod" i de flesta jobbannonser för streamingappar är aspirerande, inte beskrivande. Det beskriver vad rekryteringsansvarig önskar att kodbasen såg ut som, inte vad som väntar dig i repositoryt.

Här är verklighetskollen som ingen skriver i jobbeskrivningen: det där Coordinator-mönstret de är så stolta över? Det koordinerar tre andra navigeringsmönster som ingen hann ta bort. De där SOLID-principerna? De upplöstes i samma ögonblick som VD:n ville ha en funktion levererad till fredag. Enhetstesterna? De täcker inloggningsskärmen och absolut inget annat.

Jag kastar inte sten i glashus. Jag har själv bidragit till den här typen av entropi. Varje mobilutvecklare har gjort det. Men jag tror att streamingbranschen har en särskilt allvarlig version av det här problemet, och jag har en teori om hur man kan upptäcka det omedelbart.

20 MB-regeln: ett lackmustest för kodkvalitet i streamingappar

Tänk på vad en streamingapplikation faktiskt gör. Den hämtar ett manifest (en HLS-spellista, en DASH MPD), matar segment till en videospelare, renderar lite UI runt det (kanaler, EPG, inställningar), och det är i stort sett allt. Innehållet streamas. Det tunga, videon, ljudet, miniatyrbilderna, allt ligger på ett CDN, inte i din app-binär.

Så här är min teori: om en streamingapp väger mer än 20 MB på App Store är varje megabyte över den tröskeln förmodligen "smutsig" kod. Inte skadlig, inte nödvändigtvis buggig, men kod som inte borde finnas där. Kod som speglar dåliga arkitekturbeslut som ackumulerats under år av kompromisser.

Låt oss titta på några siffror. Min senaste app, My TV Channel, väger 9 MB på App Store. Nio. Den körs nativt på iPhone, iPad, Mac (Apple Silicon), Apple TV och Apple Vision Pro. Den stöder 9 språk. Användare kan skapa och titta på linjära TV-kanaler dygnet runt, med VOD-till-Live-schemaläggning, push-notiser, offline-nedladdningar, bild-i-bild, multivy, avancerad sökning med röstinmatning, ljudläge, privata kanaler och ett komplett innehållshanteringssystem för skapare. Det är fler funktioner än många vanliga streamingappar.

Gå och kolla App Store själv. De flesta stora streamingappar väger mellan 80 och 200 MB. Vissa är ännu tyngre. Det är 10 till 20 gånger tyngre än My TV Channel. Dessa appar har samma primära uppgift: streama video. Ändå bär de på motsvarigheten av dussintals My TV Channels i sina binärer.

Visst, stora streamingtjänster behöver en del av den extra vikten. De stöder breda enhetsmatriser, tillgänglighetsfunktioner, offline DRM, ibland spel. Men 150+ MB för vad som i grunden är en videostreamingapplikation? Den klyftan berättar en historia om legacy-kod, plattformsoberoende kompromisser och år av ackumulerad arkitektonisk skuld.

Varifrån uppsvällningen kommer

Efter år av konsultarbete med streamingprojekt ser jag ständigt samma mönster som blåser upp appbinärer långt bortom vad som är nödvändigt.

Legacy UI-teknologier. XIB- och Storyboard-filer är ett klassiskt exempel. De var standardsättet att bygga iOS-gränssnitt under många år, och de bäddar in serialiserade objektgrafer, layoutbegränsningar, ibland till och med bildreferenser direkt i binären. Många streamingappar bär fortfarande på XIB-filer från sin första release, lappade och underhållna men aldrig migrerade till SwiftUI eller ens moderna UIKit-mönster. Varje XIB-fil är fryst teknisk skuld med filändelsen .xib.

Overhead från plattformsoberoende ramverk. React Native, Flutter, Kotlin Multiplatform: vart och ett lägger till sin egen runtime, sitt eget brygglager, sina egna abstraktioner. För en streamingapp där den mest prestandakritiska komponenten ändå är den nativa videospelaren, är avvägningen mellan plattformsoberoende bekvämlighet och binärstorlek sällan meningsfull. Man skeppar en JavaScript-motor (eller en Dart VM, eller en KMP-runtime) bara för att rendera ett rutnät av miniatyrbilder som SwiftUI eller UIKit hanterar på en bråkdel av storleken.

Det är samma logik som ledde backend-team ner i mikrotjänsternas kaninhål. Minns du när varje startup behövde Kubernetes och 47 Docker-containrar för att servera ett REST API som en enda Django-app kunde hantera? Mobilmotsvarigheten är att skeppa tre ramverksruntimes för att visa en lista med videor. Mönstret är identiskt: att anta arkitektonisk komplexitet som löser problem man inte har, till en kostnad man betalar i åratal.

Ackumulering av designmönster. Den här är mer subtil. Den syns inte direkt som megabyte, men den multiplicerar filer, klasser och abstraktioner, vilket ökar kompileringsberoenden, inbäddad metadata och binärstorlek. Jag har sett streamingprojekt med separata ViewModel, Coordinator, UseCase, Repository, DataSource, Mapper och Entity för varje enskild skärm. Det är sju filer där två skulle räcka. Multiplicera med 30 skärmar och du har 210 filer istället för 60, där varje fil lägger till sin klassmetadata till binären.

VIPER är den värsta syndaren här. Det designades för en UIKit-värld där massiva view controllers var ett verkligt problem. Att porta VIPER till ett SwiftUI-projekt är som att ta med en brandbil till ett stearinljus. SwiftUI-vyer är redan lätta, tillståndslösa och komponerbara av design. Att linda in var och en i en VIPER-stack (View, Interactor, Presenter, Entity, Router) lägger till fem filer och tre lager av indirektion för vad SwiftUI hanterar med en struct och en @Observable-klass. Jag har sett team göra det ändå för att "det är vår arkitektur," och resultatet är alltid detsamma: mer boilerplate än affärslogik.

Inbäddade tillgångar som borde vara fjärrbaserade. Medföljande typsnitt, medföljande animationer (Lottie JSON-filer är ökända för detta), medföljande platshållarbilder i 3x-upplösning för varje enhetsklass. I en streamingapp kan nästan varje visuell tillgång hämtas på begäran från ett CDN. Att bunta ihop dem är den lata vägen, och det märks på skalan.

Död kod från övergivna funktioner. A/B-testningsramverk lämnar efter sig villkorssatser som aldrig städas upp. Misslyckade funktionsexperiment stannar kvar i kodbasen för att "vi kanske tar tillbaka den." Analytics-SDK:n från två leverantörer sedan kompileras fortfarande för att någon glömde att ta bort den från Podfilen.

Den verkliga kostnaden: när smutsig kod bestämmer din roadmap

Uppsvällning av binären är en sak. Men den värsta konsekvensen av en smutsig kodbas är inte nedladdningsstorleken. Det är vad som händer i varje sprintplaneringsmöte.

Du känner till frasen. En produktchef frågar efter en ny funktion, säg bild-i-bild, ljudläge, en enkel UI-uppdatering, och de första orden ur den ledande utvecklarens mun är: "Det kommer att bli komplicerat."

Den meningen är lukttestet för smutsig kod. Inte "det kommer att ta tid," inte "vi behöver tänka på kantfall," utan komplicerat. Kodbasen har blivit så trasslig att ingen kan förutsäga vad det innebär att röra en modul i tre andra.

I rena kodbaser känns det naturligt att lägga till funktioner. Man hittar rätt lager, man utökar det, man levererar. I smutsiga kodbaser känns det som kirurgi på en patient utan journaler. Varje ändring kräver en arkeologisk expedition först. Varje uppskattning kommer med en riskmultiplikator som ingen vågar sätta på papper. Varje sprint bär på det outtalade förbehållet: "förutsatt att inget oväntat går sönder."

Jag har sett streamingprojekt där implementeringen av en enkel "fortsätt titta"-funktion krävde ändringar över sju arkitektoniska lager, två bridging headers och en anpassad händelsebuss som ingen längre fullt ut förstod. Det är inte mjukvaruutveckling. Det är gisslanförhandling med sin egen kodbas.

Och den del som produktteam sällan förstår: smutsig kod saktar inte bara ner dig idag. Den bestämmer vad du kan bygga imorgon. Funktioner avvisas inte för att de är dåliga idéer. De avvisas för att "vår arkitektur stöder det inte," vilket är ett artigt sätt att säga "vi byggde in oss i ett hörn för fem år sedan och ingen vill erkänna det." Kodbasen blir den de facto produktchefen, som lägger veto mot funktioner enbart genom friktion.

Verklighetskoll: dina konkurrenter med renare kodbaser kommer att leverera samma funktion på två veckor medan ditt team fortfarande kartlägger beroendegrafer i Miro. De är inte smartare. De behöver bara inte kämpa mot sin egen kod innan de kämpar mot marknaden.

LLM-testet: ett nytt sätt att mäta kodrenlighet

Utöver binärstorlek finns det ett annat lackmustest jag har använt på sistone, ett som inte existerade för fem år sedan.

Rikta en LLM mot din kodbas. Om den inte kan förstå den, kan inte heller din nästa nyanställning det.

Verktyg som Claude Code, OpenAI Codex och andra AI-assisterade utvecklingsmiljöer är mycket bra på att navigera välstrukturerade kodbaser. De kan läsa ett rent Swift-projekt, förstå dess arkitektur, identifiera buggar, föreslå fixar och implementera nya funktioner med minimal vägledning.

Men försök rikta dem mot en legacy-streamingapp med 15 års ackumulerade mönster, blandat Objective-C och Swift, tre olika metoder för dependency injection och ett byggsystem som hålls ihop av anpassade skalskript. LLM:en kommer att kämpa. Den kommer att hallucinera relationer mellan klasser som inte finns. Den kommer att föreslå fixar som bryter andra delar av systemet. Den kommer att producera fler kraschar än en senior utvecklare skulle göra.

Det är inte en begränsning hos AI. Det är en spegel. Om en LLM tränad på miljontals repositoryn inte kan tolka ditt projekts arkitektur, betyder det att dina abstraktioner läcker, din namngivning är inkonsekvent, dina beroenden är trassliga och din projektstruktur inte följer någon igenkännbar konvention.

Tänk på det som det ultimata "onboarding av ny utvecklare"-testet. En LLM närmar sig din kodbas med noll institutionell kunskap, bara mönsterigenkänning och bred förståelse för programmeringskonventioner. Om den går vilse, kommer din nästa juniora utvecklare också att gå vilse. Och din nästa seniora utvecklare kommer att ägna sina första tre månader åt att reda ut samma knutar, fast de fakturerar dig för det.

Vänd nu på det. När din kodbas är ren händer något intressant: verktyg som Claude Code granskar inte bara din kod, de bygger med dig. Jag har använt Claude Code på My TV Channel, och i en ren kodbas kan det implementera en ny funktion, skriva testerna och öppna en PR på den tid det tidigare tog mig att skriva en Jira-biljett. Det läser arkitekturen, förstår konventionerna och producerar kod som passar in.

Det är här det blir intressant för team som fortfarande kör tvåveckors-sprintar. Sprintar designades för att hantera mänsklig osäkerhet: hur lång tid tar det, vad kan vi åta oss, när demonstrerar vi. Men när en AI-agent tillförlitligt kan navigera din kodbas och leverera fungerande kod på timmar, blir själva sprinten flaskhalsen. Planeringsmötet tar längre tid än implementeringen. Ceremonin kring att uppskatta en funktion kostar mer tid än att bygga den.

Naturligtvis fungerar detta bara om kodbasen är ren. Rikta Claude Code mot ett VIPER-infekterat, XIB-belastat, multi-ramverksmonster och det är tillbaka till hallucinationer. Verktyget skapar inte hastigheten. Den rena arkitekturen gör det. Claude Code avslöjar den bara. Och smutsiga kodbaser? De saktar inte bara ner dina mänskliga utvecklare längre. De blockerar också AI från att hjälpa dig, vilket kommer att bli ett alltmer kostsamt handikapp.

Hur ren kod faktiskt ser ut i en streamingapp

Jag byggde My TV Channel från grunden med dessa principer i åtanke. Här är vad jag anser att ren kod betyder för streamingapplikationer specifikt.

Följ plattformens konventioner. Apples Human Interface Guidelines finns av en anledning. Standard UIKit- eller SwiftUI-komponenter ger dig tillgänglighet, Dynamic Type, Dark Mode och lokalisering gratis. Varje anpassad komponent du bygger istället är en du måste underhålla, testa på olika enheter och felsöka när Apple ändrar något i en ny iOS-version. My TV Channel körs på fem Apple-plattformar med en delad kodbas eftersom den lutar sig mot plattformsnativt UI, inte på grund av något smart abstraktionslager.

Håll beroendegrafen grund. En streamingapp behöver en videospelare, ett nätverkslager och ett persistenslager. Utöver det bör varje tredjepartsberoende ifrågasättas hårt. Alla beroenden är inte dåliga. Jag använder Kingfisher för bildcachning i My TV Channel för att det gör en sak bra, det underhålls aktivt och dess binäravtryck är rimligt i förhållande till värdet det ger. Det är ribban. Löser beroendet ett verkligt problem bättre än du kunde med plattforms-API:er? Underhålls det? Är storleken proportionell mot värdet? Om svaret på någon av dessa frågor är nej, borde det inte finnas i din Podfil. Problemet är inte att använda bibliotek. Problemet är att använda fem av dem när ett skulle räcka, eller att använda ett 5 MB reaktivt ramverk när Swifts nativa async/await och Combine hanterar samma jobb utan extern kod.

Behandla binärstorlek som en funktion, inte ett mätvärde. Användare på mobilnätverk, användare med 64 GB iPhones, användare på marknader där lagringsutrymme är ont om: alla gynnas av en mindre app. Apple visar appstorlek tydligt på App Store av en anledning. En nedladdning på 9 MB installeras på sekunder oavsett uppkoppling. En nedladdning på 150 MB kräver Wi-Fi för många användare och konkurrerar om utrymme med foton, meddelanden och andra appar de bryr sig mer om.

Radera kod. Den svåraste delen av att underhålla en ren kodbas är inte att skriva ny kod. Det är att radera gammal kod. Funktionsflaggor som inte har växlats på sex månader, analyshändelser som ingen kollar i dashboarden, migrationsvägar för dataformat från tre versioner sedan. Allt måste bort. Den bästa koden är den kod som inte finns.

En utmaning till branschen

Streaming är en av de mest konkurrensutsatta kategorierna på App Store. Ändå levererar de flesta OTT-appar binärer som är 5 till 20 gånger större än de behöver vara, bär på år av legacy-kod och följer arkitektoniska mönster som inte ens AI kan reda ut.

Nästa gång du ser en jobbannons för en iOS-utvecklare hos ett streamingföretag som kräver "ren kodpraxis" och "modern arkitektur," ställ en fråga: hur stor är er appbinär?

Om svaret är norr om 50 MB för vad som i grunden är en videospelare med en innehållskatalog, vet du exakt vad som väntar dig i det repositoryt.

Och om du vill se hur en streamingapp ser ut när den är byggd från grunden med principer för ren kod, utan legacy, utan plattformsoberoende kompromisser, utan ackumulerad skuld: ladda ner My TV Channel och kolla storleken själv.

9 MB. Fem plattformar. Fullt utrustad. Det är vad ren kod ser ut som.

Need Help With Your Streaming Project?

This article was written by experienced professionals available through iReplay.tv. Whether you need expertise in streaming, OTT, iOS—our network of specialists can bring your project to life.

Hire a Professional →