Architecture Overview - MKS2508/MKS-IPTV-App GitHub Wiki

<style> body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; line-height: 1.6; color: #333; max-width: 960px; margin: 0 auto; padding: 20px; } h1, h2, h3, h4, h5, h6 { font-weight: 600; color: #111; } a { color: #0366d6; text-decoration: none; } a:hover { text-decoration: underline; } code { font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace; background-color: #f6f8fa; padding: .2em .4em; margin: 0; font-size: 85%; border-radius: 3px; } pre { background-color: #f6f8fa; padding: 16px; overflow: auto; line-height: 1.45; border-radius: 3px; } pre code { padding: 0; margin: 0; font-size: 100%; background-color: transparent; border: 0; } footer { margin-top: 40px; padding-top: 20px; border-top: 1px solid #eee; font-size: 12px; color: #586069; } </style>

Home > Architecture > Architecture Overview

πŸ—οΈ Architecture Overview

A comprehensive guide to MKS-IPTV-App's architecture, design patterns, and implementation strategies.


🎯 Architectural Philosophy

MKS-IPTV-App is built with modern Swift development principles, emphasizing:

  • Type Safety with Swift 6's strict concurrency
  • Reactive Programming using SwiftUI and Combine
  • Actor-based Concurrency for thread-safe operations
  • Platform Adaptation while maintaining code reuse
  • Testability through dependency injection and protocols

πŸ›οΈ High-Level Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                         UI Layer                            β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚  β”‚   iOS Views     β”‚ β”‚   macOS Views   β”‚ β”‚   tvOS Views    β”‚ β”‚
β”‚  β”‚   (SwiftUI)     β”‚ β”‚   (SwiftUI)     β”‚ β”‚   (SwiftUI)     β”‚ β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                     ViewModel Layer                         β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚  β”‚ StreamViewModel β”‚ β”‚DownloadViewModelβ”‚ β”‚  SearchViewModelβ”‚ β”‚
β”‚  β”‚(@MainActor)     β”‚ β”‚(@MainActor)     β”‚ β”‚ (@MainActor)    β”‚ β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                     Service Layer                           β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚  β”‚  StreamManager  β”‚ β”‚ DownloadManager β”‚ β”‚ HTTPStreamServerβ”‚ β”‚
β”‚  β”‚    (Actor)      β”‚ β”‚    (Actor)      β”‚ β”‚    (Actor)      β”‚ β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                     Data Layer                              β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚  β”‚   Core Data     β”‚ β”‚   UserDefaults  β”‚ β”‚   File System   β”‚ β”‚
β”‚  β”‚  (Persistence)  β”‚ β”‚  (Settings)     β”‚ β”‚  (Downloads)    β”‚ β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

πŸ”„ MVVM Pattern Implementation

Model

Data structures and business logic

// Pure data models
struct IPTVStream: Codable, Identifiable {
    let id: UUID
    let name: String
    let url: URL
    let category: StreamCategory
}

// Business logic protocols
protocol StreamService {
    func fetchStreams() async throws -> [IPTVStream]
    func validateStream(_ stream: IPTVStream) async throws -> Bool
}

View

SwiftUI views with platform adaptations

struct StreamListView: View {
    @StateObject private var viewModel = StreamListViewModel()
    
    var body: some View {
        PlatformNavigationView {
            StreamGridView(streams: viewModel.streams)
                .searchable(text: $viewModel.searchText)
        }
        .task {
            await viewModel.loadStreams()
        }
    }
}

ViewModel

Observable objects managing view state

@MainActor
class StreamListViewModel: ObservableObject {
    @Published var streams: [IPTVStream] = []
    @Published var searchText: String = ""
    @Published var isLoading: Bool = false
    
    private let streamService: StreamService
    
    init(streamService: StreamService = StreamManager.shared) {
        self.streamService = streamService
    }
    
    func loadStreams() async {
        isLoading = true
        defer { isLoading = false }
        
        do {
            streams = try await streamService.fetchStreams()
        } catch {
            // Handle error
        }
    }
}

🎭 Actor-Based Concurrency

Core Principles

  • Data isolation using actors for thread-safe operations
  • Structured concurrency with async/await patterns
  • MainActor for UI-related operations
  • Global actors for shared resources

StreamManager Actor

actor StreamManager {
    private var activeStreams: [UUID: AVPlayer] = [:]
    private var httpProxyServer: HTTPProxyServer?
    
    func loadStream(_ url: URL) async throws -> UUID {
        let streamId = UUID()
        let proxiedURL = try await setupProxy(for: url)
        let player = AVPlayer(url: proxiedURL)
        
        activeStreams[streamId] = player
        return streamId
    }
    
    nonisolated func getCurrentPlayer(for id: UUID) -> AVPlayer? {
        // Safe access to actor-isolated data
        return activeStreams[id]
    }
}

DownloadManager Actor

actor DownloadManager {
    private var activeDownloads: [UUID: URLSessionDownloadTask] = [:]
    private var downloadProgress: [UUID: Double] = [:]
    
    func startDownload(for item: DownloadableItem) async throws -> UUID {
        let downloadId = UUID()
        let task = URLSession.shared.downloadTask(with: item.downloadURL)
        
        activeDownloads[downloadId] = task
        task.resume()
        
        return downloadId
    }
    
    func getProgress(for downloadId: UUID) async -> Double {
        return downloadProgress[downloadId] ?? 0.0
    }
}

🌐 Networking Architecture

HTTP Proxy Implementation

actor HTTPProxyServer {
    private var server: NWListener?
    private let port: NWEndpoint.Port
    
    func start() async throws {
        let parameters = NWParameters.tcp
        server = try NWListener(using: parameters, on: port)
        
        server?.newConnectionHandler = { [weak self] connection in
            await self?.handleConnection(connection)
        }
        
        server?.start(queue: .global())
    }
    
    private func handleConnection(_ connection: NWConnection) async {
        // Handle incoming HTTP requests
        // Proxy to IPTV endpoints with custom headers
    }
}

Stream URL Resolution

extension StreamManager {
    func resolveStreamURL(_ originalURL: URL) async throws -> URL {
        // 1. Check for redirects
        let resolvedURL = try await followRedirects(originalURL)
        
        // 2. Determine if proxy is needed
        if requiresProxy(resolvedURL) {
            return try await setupProxy(for: resolvedURL)
        }
        
        // 3. Return direct URL if no proxy needed
        return resolvedURL
    }
    
    private func requiresProxy(_ url: URL) -> Bool {
        // Logic to determine if URL needs HTTP proxy
        return url.scheme == "http" || hasCustomHeaders(url)
    }
}

πŸ“± Platform-Specific Adaptations

Navigation Patterns

iOS Implementation

struct iOSNavigationView: View {
    var body: some View {
        TabView {
            LiveTVView()
                .tabItem { Label("Live TV", systemImage: "tv") }
            
            MoviesView()
                .tabItem { Label("Movies", systemImage: "film") }
            
            DownloadsView()
                .tabItem { Label("Downloads", systemImage: "arrow.down.circle") }
        }
    }
}

macOS Implementation

struct macOSNavigationView: View {
    var body: some View {
        NavigationSplitView {
            SidebarView()
        } detail: {
            ContentView()
        }
        .toolbar {
            TouchBarControls()
        }
    }
}

tvOS Implementation

struct tvOSNavigationView: View {
    @FocusState private var focusedTab: Tab?
    
    var body: some View {
        TabView(selection: $focusedTab) {
            LiveTVView()
                .tag(Tab.liveTV)
                .focused($focusedTab, equals: .liveTV)
        }
        .focusGuide(.automatic)
    }
}

TouchBar Integration (macOS)

extension NSTouchBarItem.Identifier {
    static let playPause = NSTouchBarItem.Identifier("com.mks.iptv.playPause")
    static let volume = NSTouchBarItem.Identifier("com.mks.iptv.volume")
    static let search = NSTouchBarItem.Identifier("com.mks.iptv.search")
}

class TouchBarManager: NSObject, NSTouchBarDelegate {
    @MainActor
    func makeTouchBar() -> NSTouchBar {
        let touchBar = NSTouchBar()
        touchBar.delegate = self
        touchBar.defaultItemIdentifiers = [.playPause, .volume, .search]
        return touchBar
    }
}

πŸ’Ύ Data Management

Persistence Strategy

// Core Data for complex relationships
@Model
class DownloadedContent {
    @Attribute(.unique) var id: UUID
    var title: String
    var localPath: URL
    var originalURL: URL
    var downloadDate: Date
    var fileSize: Int64
}

// UserDefaults for simple settings
extension UserDefaults {
    @objc dynamic var preferredQuality: String {
        get { string(forKey: "preferredQuality") ?? "auto" }
        set { set(newValue, forKey: "preferredQuality") }
    }
}

// File system for downloaded content
actor FileManager {
    static let downloadsDirectory = FileManager.default
        .urls(for: .documentDirectory, in: .userDomainMask)
        .first!
        .appendingPathComponent("Downloads")
    
    func saveDownload(_ data: Data, filename: String) async throws -> URL {
        let fileURL = Self.downloadsDirectory.appendingPathComponent(filename)
        try data.write(to: fileURL)
        return fileURL
    }
}

πŸ”„ Reactive Data Flow

State Management

// Centralized app state
@MainActor
class AppState: ObservableObject {
    @Published var selectedTab: Tab = .liveTV
    @Published var isDownloading: Bool = false
    @Published var currentlyPlaying: IPTVStream?
    
    // Computed properties
    var hasActiveDownloads: Bool {
        // Logic to check for active downloads
    }
}

// View models subscribe to app state
@MainActor
class ContentViewModel: ObservableObject {
    @Published var content: [ContentItem] = []
    
    private let appState: AppState
    private var cancellables = Set<AnyCancellable>()
    
    init(appState: AppState) {
        self.appState = appState
        
        // React to app state changes
        appState.$selectedTab
            .sink { [weak self] tab in
                Task { await self?.loadContent(for: tab) }
            }
            .store(in: &cancellables)
    }
}

πŸ§ͺ Testing Architecture

Protocol-Based Testing

// Service protocols for dependency injection
protocol StreamServiceProtocol {
    func fetchStreams() async throws -> [IPTVStream]
}

// Mock implementations for testing
class MockStreamService: StreamServiceProtocol {
    var mockStreams: [IPTVStream] = []
    var shouldThrowError = false
    
    func fetchStreams() async throws -> [IPTVStream] {
        if shouldThrowError {
            throw StreamError.networkError
        }
        return mockStreams
    }
}

// Testable view models
@MainActor
class StreamListViewModel: ObservableObject {
    private let streamService: StreamServiceProtocol
    
    init(streamService: StreamServiceProtocol = StreamManager.shared) {
        self.streamService = streamService
    }
}

πŸ› οΈ Build Configuration

Target-Specific Builds

#if os(iOS)
    // iOS-specific implementations
    import UIKit
    typealias PlatformViewController = UIViewController
#elseif os(macOS)
    // macOS-specific implementations
    import AppKit
    typealias PlatformViewController = NSViewController
#elseif os(tvOS)
    // tvOS-specific implementations
    import TVUIKit
    typealias PlatformViewController = UIViewController
#endif

// Shared interface
protocol PlatformAdaptable {
    func configureForPlatform()
}

πŸ”§ Dependency Injection

Service Container

@MainActor
class ServiceContainer: ObservableObject {
    static let shared = ServiceContainer()
    
    lazy var streamManager = StreamManager()
    lazy var downloadManager = DownloadManager()
    lazy var httpServer = HTTPStreamServer()
    
    private init() {}
    
    // Factory methods for testing
    func makeStreamService() -> StreamServiceProtocol {
        #if DEBUG
        if ProcessInfo.processInfo.environment["TESTING"] == "1" {
            return MockStreamService()
        }
        #endif
        return streamManager
    }
}

πŸ“ˆ Performance Considerations

Memory Management

  • Weak references to prevent retain cycles
  • Actor isolation to prevent data races
  • Lazy loading for expensive operations
  • Resource cleanup in deinitializers

Concurrency Optimization

  • Task groups for parallel operations
  • AsyncSequence for streaming data
  • Sendable conformance for cross-actor communication
  • MainActor isolation for UI updates

Network Efficiency

  • Connection pooling for HTTP requests
  • Request deduplication for identical URLs
  • Caching strategies for frequently accessed data
  • Bandwidth adaptation based on network conditions

This architecture ensures scalability, maintainability, and optimal performance across all Apple platforms while leveraging the latest Swift 6 concurrency features.


{{< include _Footer.md >}}

⚠️ **GitHub.com Fallback** ⚠️