Hoppa till huvudinnehåll

Advanced Formatting Guidelines

risk

Den här sidan är avsiktligt kvar på engelska så att de avancerade riktlinjerna förblir exakta. Använd den engelska texten tills en granskad svensk översättning finns.

We will go through advanced formatting situations, architectural patterns, and complex Swift/Objective-C features for the SideStore project.

Advanced Swift Patterns

Protocol-Oriented Programming

Protocol Definitions

  • Keep protocols focused and cohesive
  • Use associated types for generic protocols
  • Provide default implementations in extensions when appropriate
// ✅ Good
protocol AppInstalling {
associatedtype AppType: App

func install(_ app: AppType) async throws
func uninstall(_ app: AppType) async throws
}

extension AppInstalling {
func validateApp(_ app: AppType) -> Bool {
return !app.identifier.isEmpty && app.version.isValid
}
}

// ❌ Bad
protocol AppManager {
func install(_ app: Any) -> Bool
func uninstall(_ app: Any) -> Bool
func update(_ app: Any) -> Bool
func backup(_ app: Any) -> Bool
func restore(_ app: Any) -> Bool
// Too many responsibilities
}

Protocol Composition

  • Use protocol composition for complex requirements
  • Keep individual protocols small and focused
// ✅ Good
protocol Downloadable {
var downloadURL: URL { get }
func download() async throws -> Data
}

protocol Installable {
var installationRequirements: InstallationRequirements { get }
func install() async throws
}

typealias DeployableApp = App & Downloadable & Installable

class AppDeployer {
func deploy<T: DeployableApp>(_ app: T) async throws {
let data = try await app.download()
try await app.install()
}
}

Generics and Type Constraints

Generic Type Definitions

  • Use meaningful constraint names
  • Prefer protocol constraints over class constraints
  • Use where clauses for complex constraints
// ✅ Good
struct Repository<Entity: Identifiable & Codable> {
private var entities: [Entity.ID: Entity] = [:]

func save<T>(_ entity: T) where T == Entity {
entities[entity.id] = entity
}

func find<ID>(byId id: ID) -> Entity? where ID == Entity.ID {
return entities[id]
}
}

// ❌ Bad
struct Repository<T> {
private var items: [String: T] = [:]
// No type safety
}

Advanced Generic Constraints

  • Use conditional conformance appropriately
  • Leverage phantom types when needed
// ✅ Good
extension Array: AppCollection where Element: App {
var installedApps: [Element] {
return filter { $0.isInstalled }
}

func sortedByInstallDate() -> [Element] {
return sorted { $0.installDate < $1.installDate }
}
}

// Phantom types for type safety
struct AppState<Status> {
let app: App
}

enum Downloaded {}
enum Installed {}

typealias DownloadedApp = AppState<Downloaded>
typealias InstalledApp = AppState<Installed>

Async/Await Patterns

Async Function Design

  • Use async/await consistently
  • Structure concurrent operations clearly
  • Handle cancellation appropriately
// ✅ Good
actor AppInstallationManager {
private var activeInstallations: [String: Task<Void, Error>] = [:]

func installApp(_ app: App) async throws {
// Prevent duplicate installations
if activeInstallations[app.identifier] != nil {
throw InstallationError.alreadyInstalling
}

let task = Task {
try await performInstallation(app)
}

activeInstallations[app.identifier] = task

defer {
activeInstallations.removeValue(forKey: app.identifier)
}

try await task.value
}

private func performInstallation(_ app: App) async throws {
// Check for cancellation at key points
try Task.checkCancellation()

let data = try await downloadApp(app)

try Task.checkCancellation()

try await installData(data, for: app)
}
}

// ❌ Bad
func installApp(_ app: App, completion: @escaping (Error?) -> Void) {
DispatchQueue.global().async {
// Mixing old completion handler style with new async code
let result = await self.downloadApp(app)
DispatchQueue.main.async {
completion(nil)
}
}
}

Structured Concurrency

  • Use task groups for related concurrent operations
  • Prefer structured concurrency over unstructured tasks
// ✅ Good
func installMultipleApps(_ apps: [App]) async throws {
try await withThrowingTaskGroup(of: Void.self) { group in
for app in apps {
group.addTask {
try await self.installApp(app)
}
}

// Wait for all installations to complete
try await group.waitForAll()
}
}

// For independent results
func downloadMultipleApps(_ apps: [App]) async throws -> [App: Data] {
try await withThrowingTaskGroup(of: (App, Data).self) { group in
for app in apps {
group.addTask {
let data = try await self.downloadApp(app)
return (app, data)
}
}

var results: [App: Data] = [:]
for try await (app, data) in group {
results[app] = data
}
return results
}
}

Result Builders and DSLs

Custom Result Builders

  • Create focused, single-purpose result builders
  • Provide clear syntax for domain-specific operations
// ✅ Good
@resultBuilder
struct AppConfigurationBuilder {
static func buildBlock(_ components: AppConfigurationComponent...) -> AppConfiguration {
return AppConfiguration(components: components)
}

static func buildOptional(_ component: AppConfigurationComponent?) -> AppConfigurationComponent? {
return component
}

static func buildEither(first component: AppConfigurationComponent) -> AppConfigurationComponent {
return component
}

static func buildEither(second component: AppConfigurationComponent) -> AppConfigurationComponent {
return component
}
}

// Usage
func configureApp(@AppConfigurationBuilder builder: () -> AppConfiguration) -> App {
let config = builder()
return App(configuration: config)
}

let app = configureApp {
AppName("SideStore")
AppVersion("1.0.0")
if debugMode {
DebugSettings()
}
Permissions {
NetworkAccess()
FileSystemAccess()
}
}

Advanced Objective-C Patterns

Category Organization

  • Use categories to organize related functionality
  • Keep category names descriptive and specific
// ✅ Good
@interface NSString (SSValidation)
- (BOOL)ss_isValidAppIdentifier;
- (BOOL)ss_isValidVersion;
@end

@interface UIViewController (SSAppInstallation)
- (void)ss_presentAppInstallationViewController:(SSApp *)app;
- (void)ss_showInstallationProgress:(SSInstallationProgress *)progress;
@end

// ❌ Bad
@interface NSString (Helpers)
- (BOOL)isValid; // Too generic
- (NSString *)cleanup; // Unclear purpose
@end

Advanced Memory Management

  • Use proper patterns for delegate relationships
  • Handle complex object graphs correctly
// ✅ Good
@interface SSAppInstaller : NSObject
@property (nonatomic, weak) id<SSAppInstallerDelegate> delegate;
@property (nonatomic, strong) NSOperationQueue *installationQueue;
@end

@implementation SSAppInstaller

- (instancetype)init {
self = [super init];
if (self) {
_installationQueue = [[NSOperationQueue alloc] init];
_installationQueue.maxConcurrentOperationCount = 3;
_installationQueue.name = @"com.sidestore.installation";
}
return self;
}

- (void)installApp:(SSApp *)app completion:(void (^)(NSError *))completion {
__weak typeof(self) weakSelf = self;
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
__strong typeof(weakSelf) strongSelf = weakSelf;
if (!strongSelf) return;

NSError *error = nil;
[strongSelf performInstallationForApp:app error:&error];

dispatch_async(dispatch_get_main_queue(), ^{
completion(error);
});
}];

[self.installationQueue addOperation:operation];
}

@end

Block Usage Patterns

  • Use proper block patterns for asynchronous operations
  • Handle memory management in blocks correctly
// ✅ Good
typedef void (^SSInstallationProgressBlock)(float progress);
typedef void (^SSInstallationCompletionBlock)(SSApp *app, NSError *error);

@interface SSAppDownloader : NSObject
- (NSURLSessionTask *)downloadApp:(SSApp *)app
progress:(SSInstallationProgressBlock)progressBlock
completion:(SSInstallationCompletionBlock)completion;
@end

@implementation SSAppDownloader

- (NSURLSessionTask *)downloadApp:(SSApp *)app
progress:(SSInstallationProgressBlock)progressBlock
completion:(SSInstallationCompletionBlock)completion {

NSURLRequest *request = [NSURLRequest requestWithURL:app.downloadURL];

__weak typeof(self) weakSelf = self;
NSURLSessionDownloadTask *task = [[NSURLSession sharedSession]
downloadTaskWithRequest:request
completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) {
__strong typeof(weakSelf) strongSelf = weakSelf;
if (!strongSelf) return;

if (error) {
dispatch_async(dispatch_get_main_queue(), ^{
completion(nil, error);
});
return;
}

// Process downloaded file
[strongSelf processDownloadedFile:location
forApp:app
completion:completion];
}];

[task resume];
return task;
}

@end

Architectural Patterns

MVVM Implementation

  • Separate concerns clearly between Model, View, and ViewModel
  • Use proper data binding patterns
// ✅ Good
// Model
struct App {
let identifier: String
let name: String
let version: String
let isInstalled: Bool
}

// ViewModel
@MainActor
class AppListViewModel: ObservableObject {
@Published var apps: [App] = []
@Published var isLoading = false
@Published var errorMessage: String?

private let appService: AppService

init(appService: AppService) {
self.appService = appService
}

func loadApps() async {
isLoading = true
errorMessage = nil

do {
apps = try await appService.fetchAvailableApps()
} catch {
errorMessage = error.localizedDescription
}

isLoading = false
}

func installApp(_ app: App) async {
do {
try await appService.installApp(app)
await loadApps() // Refresh the list
} catch {
errorMessage = "Failed to install \(app.name): \(error.localizedDescription)"
}
}
}

// View
struct AppListView: View {
@StateObject private var viewModel: AppListViewModel

init(appService: AppService) {
_viewModel = StateObject(wrappedValue: AppListViewModel(appService: appService))
}

var body: some View {
NavigationView {
List(viewModel.apps, id: \.identifier) { app in
AppRowView(app: app) {
Task {
await viewModel.installApp(app)
}
}
}
.navigationTitle("Available Apps")
.overlay {
if viewModel.isLoading {
ProgressView()
}
}
.alert("Error", isPresented: .constant(viewModel.errorMessage != nil)) {
Button("OK") {
viewModel.errorMessage = nil
}
} message: {
Text(viewModel.errorMessage ?? "")
}
}
.task {
await viewModel.loadApps()
}
}
}

Dependency Injection

  • Use dependency injection for testability and flexibility
  • Consider using a DI container for complex applications
// ✅ Good
protocol AppService {
func fetchAvailableApps() async throws -> [App]
func installApp(_ app: App) async throws
}

protocol NetworkService {
func downloadData(from url: URL) async throws -> Data
}

class DefaultAppService: AppService {
private let networkService: NetworkService
private let deviceService: DeviceService

init(networkService: NetworkService, deviceService: DeviceService) {
self.networkService = networkService
self.deviceService = deviceService
}

func fetchAvailableApps() async throws -> [App] {
let data = try await networkService.downloadData(from: appsURL)
return try JSONDecoder().decode([App].self, from: data)
}

func installApp(_ app: App) async throws {
guard deviceService.hasSpace(for: app) else {
throw InstallationError.insufficientStorage
}

let appData = try await networkService.downloadData(from: app.downloadURL)
try await deviceService.installApp(data: appData)
}
}

// DI Container
class ServiceContainer {
static let shared = ServiceContainer()

private init() {}

lazy var networkService: NetworkService = DefaultNetworkService()
lazy var deviceService: DeviceService = DefaultDeviceService()
lazy var appService: AppService = DefaultAppService(
networkService: networkService,
deviceService: deviceService
)
}

Error Handling Architecture

  • Create comprehensive error handling strategies
  • Use typed errors for better error handling
// ✅ Good
enum SideStoreError: Error {
case network(NetworkError)
case installation(InstallationError)
case device(DeviceError)
case validation(ValidationError)
}

enum NetworkError: Error {
case noConnection
case timeout
case serverError(Int)
case invalidResponse
}

enum InstallationError: Error {
case insufficientStorage
case incompatibleDevice
case corruptedFile
case alreadyInstalled
}

extension SideStoreError: LocalizedError {
var errorDescription: String? {
switch self {
case .network(let networkError):
return "Network error: \(networkError.localizedDescription)"
case .installation(let installError):
return "Installation error: \(installError.localizedDescription)"
case .device(let deviceError):
return "Device error: \(deviceError.localizedDescription)"
case .validation(let validationError):
return "Validation error: \(validationError.localizedDescription)"
}
}
}

// Error handling in services
class AppService {
func installApp(_ app: App) async throws {
do {
try validateApp(app)
} catch {
throw SideStoreError.validation(error as! ValidationError)
}

do {
try await performInstallation(app)
} catch let error as NetworkError {
throw SideStoreError.network(error)
} catch let error as InstallationError {
throw SideStoreError.installation(error)
}
}
}

Performance Considerations

Memory Optimization

  • Use lazy loading for expensive resources
  • Implement proper caching strategies
// ✅ Good
class AppImageCache {
private let cache = NSCache<NSString, UIImage>()
private let downloadQueue = DispatchQueue(label: "image-download", qos: .utility)

init() {
cache.countLimit = 50
cache.totalCostLimit = 50 * 1024 * 1024 // 50MB
}

func image(for app: App) async -> UIImage? {
let key = app.identifier as NSString

// Check cache first
if let cachedImage = cache.object(forKey: key) {
return cachedImage
}

// Download if not cached
return await withCheckedContinuation { continuation in
downloadQueue.async { [weak self] in
guard let self = self else {
continuation.resume(returning: nil)
return
}

do {
let data = try Data(contentsOf: app.iconURL)
let image = UIImage(data: data)

if let image = image {
self.cache.setObject(image, forKey: key)
}

continuation.resume(returning: image)
} catch {
continuation.resume(returning: nil)
}
}
}
}
}

Threading Best Practices

  • Use appropriate queue priorities
  • Minimize context switching
// ✅ Good
actor BackgroundProcessor {
private let processingQueue = DispatchQueue(
label: "background-processing",
qos: .utility,
attributes: .concurrent
)

func processLargeDataSet(_ data: [LargeDataItem]) async -> [ProcessedItem] {
return await withTaskGroup(of: ProcessedItem?.self, returning: [ProcessedItem].self) { group in
let chunkSize = max(data.count / ProcessInfo.processInfo.activeProcessorCount, 1)

for chunk in data.chunked(into: chunkSize) {
group.addTask {
return await self.processChunk(chunk)
}
}

var results: [ProcessedItem] = []
for await result in group {
if let processed = result {
results.append(processed)
}
}

return results
}
}

private func processChunk(_ chunk: [LargeDataItem]) async -> ProcessedItem? {
// CPU-intensive processing
return await withCheckedContinuation { continuation in
processingQueue.async {
let result = chunk.map { self.expensiveOperation($0) }
continuation.resume(returning: ProcessedItem(results: result))
}
}
}
}

Testing Patterns

Protocol-Based Testing

  • Use protocols for dependency injection in tests
  • Create focused test doubles
// ✅ Good
class MockAppService: AppService {
var shouldFailInstallation = false
var installedApps: [App] = []

func fetchAvailableApps() async throws -> [App] {
return [
App(identifier: "test.app1", name: "Test App 1", version: "1.0.0"),
App(identifier: "test.app2", name: "Test App 2", version: "2.0.0")
]
}

func installApp(_ app: App) async throws {
if shouldFailInstallation {
throw InstallationError.insufficientStorage
}
installedApps.append(app)
}
}

class AppListViewModelTests: XCTestCase {
private var mockAppService: MockAppService!
private var viewModel: AppListViewModel!

override func setUp() {
super.setUp()
mockAppService = MockAppService()
viewModel = AppListViewModel(appService: mockAppService)
}

@MainActor
func testLoadAppsSuccess() async {
await viewModel.loadApps()

XCTAssertEqual(viewModel.apps.count, 2)
XCTAssertFalse(viewModel.isLoading)
XCTAssertNil(viewModel.errorMessage)
}

@MainActor
func testInstallAppFailure() async {
mockAppService.shouldFailInstallation = true

let testApp = App(identifier: "test", name: "Test", version: "1.0")
await viewModel.installApp(testApp)

XCTAssertNotNil(viewModel.errorMessage)
XCTAssertTrue(viewModel.errorMessage!.contains("insufficient storage"))
}
}

Documentation Standards

Complex API Documentation

  • Document complex behaviors and edge cases
  • Provide usage examples for non-trivial APIs
/**
* A thread-safe manager for handling app installations with automatic retry logic.
*
* This class manages the installation process for iOS applications, handling
* network downloads, signature verification, and device communication.
* It provides automatic retry functionality for transient failures and
* comprehensive error reporting.
*
* ## Usage Example
* ```swift
* let installer = AppInstallationManager()
*
* do {
* let result = try await installer.installApp(
* from: app.sourceURL,
* identifier: app.bundleID,
* maxRetries: 3
* )
* print("Installation completed: \(result.installedPath)")
* } catch InstallationError.insufficientStorage {
* // Handle storage error
* } catch {
* // Handle other errors
* }
* ```
*
* ## Thread Safety
* This class is thread-safe and can be called from any queue. All completion
* handlers are called on the main queue unless otherwise specified.
*
* ## Error Handling
* Installation failures are categorized into recoverable and non-recoverable
* errors. Recoverable errors (network timeouts, temporary device issues) will
* be automatically retried up to the specified limit. Non-recoverable errors
* (invalid signatures, incompatible devices) will fail immediately.
*/
@MainActor
class AppInstallationManager {
// Implementation
}

Remember: These advanced patterns should be used judiciously. Always prioritize code clarity and maintainability over clever implementations. When in doubt, choose the simpler approach that still meets the requirements.