Swift iOS Development

Best practices for iOS app development with Swift

# Swift iOS Development Best Practices

## 1. Project Structure & Architecture

Organize your iOS project with clean architecture patterns.

```swift
// MVVM Architecture Example
import UIKit
import Combine

// Model
struct User: Codable {
    let id: Int
    let name: String
    let email: String
    let avatarURL: URL?
}

// View Model
class UserViewModel: ObservableObject {
    @Published var users: [User] = []
    @Published var isLoading = false
    @Published var errorMessage: String?

    private let userService: UserServiceProtocol
    private var cancellables = Set<AnyCancellable>()

    init(userService: UserServiceProtocol = UserService()) {
        self.userService = userService
    }

    func loadUsers() {
        isLoading = true
        errorMessage = nil

        userService.fetchUsers()
            .receive(on: DispatchQueue.main)
            .sink(
                receiveCompletion: { [weak self] completion in
                    self?.isLoading = false
                    if case .failure(let error) = completion {
                        self?.errorMessage = error.localizedDescription
                    }
                },
                receiveValue: { [weak self] users in
                    self?.users = users
                }
            )
            .store(in: &cancellables)
    }
}

// View Controller
class UserListViewController: UIViewController {
    @IBOutlet weak var tableView: UITableView!
    @IBOutlet weak var activityIndicator: UIActivityIndicatorView!

    private let viewModel = UserViewModel()
    private var cancellables = Set<AnyCancellable>()

    override func viewDidLoad() {
        super.viewDidLoad()
        setupUI()
        bindViewModel()
        viewModel.loadUsers()
    }

    private func setupUI() {
        title = "Users"
        tableView.register(UserTableViewCell.self, forCellReuseIdentifier: "UserCell")
        tableView.dataSource = self
        tableView.delegate = self
    }

    private func bindViewModel() {
        viewModel.$users
            .receive(on: DispatchQueue.main)
            .sink { [weak self] _ in
                self?.tableView.reloadData()
            }
            .store(in: &cancellables)

        viewModel.$isLoading
            .receive(on: DispatchQueue.main)
            .sink { [weak self] isLoading in
                if isLoading {
                    self?.activityIndicator.startAnimating()
                } else {
                    self?.activityIndicator.stopAnimating()
                }
            }
            .store(in: &cancellables)
    }
}
```

## 2. Networking & Data Management

Implement robust networking with proper error handling and caching.

```swift
import Foundation
import Combine

// Network Service
protocol UserServiceProtocol {
    func fetchUsers() -> AnyPublisher<[User], NetworkError>
    func fetchUser(id: Int) -> AnyPublisher<User, NetworkError>
}

class UserService: UserServiceProtocol {
    private let session: URLSession
    private let baseURL = URL(string: "https://api.example.com")!

    init(session: URLSession = .shared) {
        self.session = session
    }

    func fetchUsers() -> AnyPublisher<[User], NetworkError> {
        let url = baseURL.appendingPathComponent("users")

        return session.dataTaskPublisher(for: url)
            .map(.data)
            .decode(type: [User].self, decoder: JSONDecoder())
            .mapError { error in
                if error is DecodingError {
                    return NetworkError.decodingError
                } else {
                    return NetworkError.networkError(error)
                }
            }
            .eraseToAnyPublisher()
    }

    func fetchUser(id: Int) -> AnyPublisher<User, NetworkError> {
        let url = baseURL.appendingPathComponent("users/\(id)")

        return session.dataTaskPublisher(for: url)
            .map(.data)
            .decode(type: User.self, decoder: JSONDecoder())
            .mapError { error in
                if error is DecodingError {
                    return NetworkError.decodingError
                } else {
                    return NetworkError.networkError(error)
                }
            }
            .eraseToAnyPublisher()
    }
}

// Error Handling
enum NetworkError: Error, LocalizedError {
    case networkError(Error)
    case decodingError
    case invalidURL
    case noData

    var errorDescription: String? {
        switch self {
        case .networkError(let error):
            return "Network error: \(error.localizedDescription)"
        case .decodingError:
            return "Failed to decode response"
        case .invalidURL:
            return "Invalid URL"
        case .noData:
            return "No data received"
        }
    }
}

// Core Data Manager
import CoreData

class CoreDataManager {
    static let shared = CoreDataManager()

    private init() {}

    lazy var persistentContainer: NSPersistentContainer = {
        let container = NSPersistentContainer(title: "DataModel")
        container.loadPersistentStores { _, error in
            if let error = error {
                fatalError("Core Data error: \(error)")
            }
        }
        return container
    }()

    var context: NSManagedObjectContext {
        return persistentContainer.viewContext
    }

    func save() {
        if context.hasChanges {
            do {
                try context.save()
            } catch {
                print("Save error: \(error)")
            }
        }
    }
}
```

## 3. SwiftUI Modern UI Development

Build declarative user interfaces with SwiftUI.

```swift
import SwiftUI

// SwiftUI View
struct UserListView: View {
    @StateObject private var viewModel = UserViewModel()
    @State private var searchText = ""

    var filteredUsers: [User] {
        if searchText.isEmpty {
            return viewModel.users
        } else {
            return viewModel.users.filter {
                $0.name.localizedCaseInsensitiveContains(searchText)
            }
        }
    }

    var body: some View {
        NavigationView {
            VStack {
                if viewModel.isLoading {
                    ProgressView("Loading users...")
                        .frame(maxWidth: .infinity, maxHeight: .infinity)
                } else {
                    List(filteredUsers, id: \.id) { user in
                        NavigationLink(destination: UserDetailView(user: user)) {
                            UserRowView(user: user)
                        }
                    }
                    .searchable(text: $searchText, prompt: "Search users")
                    .refreshable {
                        await refreshUsers()
                    }
                }
            }
            .navigationTitle("Users")
            .onAppear {
                viewModel.loadUsers()
            }
            .alert("Error", isPresented: .constant(viewModel.errorMessage != nil)) {
                Button("OK") {
                    viewModel.errorMessage = nil
                }
            } message: {
                Text(viewModel.errorMessage ?? "")
            }
        }
    }

    private func refreshUsers() async {
        viewModel.loadUsers()
    }
}

// Custom View Components
struct UserRowView: View {
    let user: User

    var body: some View {
        HStack {
            AsyncImage(url: user.avatarURL) { image in
                image
                    .resizable()
                    .aspectRatio(contentMode: .fill)
            } placeholder: {
                Circle()
                    .fill(Color.gray.opacity(0.3))
            }
            .frame(width: 50, height: 50)
            .clipShape(Circle())

            VStack(alignment: .leading, spacing: 4) {
                Text(user.name)
                    .font(.headline)
                Text(user.email)
                    .font(.subheadline)
                    .foregroundColor(.secondary)
            }

            Spacer()
        }
        .padding(.vertical, 4)
    }
}

// View Modifiers
struct LoadingModifier: ViewModifier {
    let isLoading: Bool

    func body(content: Content) -> some View {
        ZStack {
            content
                .disabled(isLoading)
                .blur(radius: isLoading ? 2 : 0)

            if isLoading {
                ProgressView()
                    .scaleEffect(1.5)
            }
        }
    }
}

extension View {
    func loading(_ isLoading: Bool) -> some View {
        modifier(LoadingModifier(isLoading: isLoading))
    }
}
```

## 4. Memory Management & Performance

Implement proper memory management and performance optimization.

```swift
import UIKit

// Memory Management
class ImageCache {
    static let shared = ImageCache()
    private let cache = NSCache<NSString, UIImage>()

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

    func image(for key: String) -> UIImage? {
        return cache.object(forKey: key as NSString)
    }

    func setImage(_ image: UIImage, for key: String) {
        cache.setObject(image, forKey: key as NSString)
    }
}

// Weak References and Closures
class NetworkManager {
    func fetchData(completion: @escaping (Result<Data, Error>) -> Void) {
        URLSession.shared.dataTask(with: URL(string: "https://api.example.com")!) { data, response, error in
            DispatchQueue.main.async {
                if let error = error {
                    completion(.failure(error))
                } else if let data = data {
                    completion(.success(data))
                }
            }
        }.resume()
    }
}

class ViewController: UIViewController {
    private let networkManager = NetworkManager()

    override func viewDidLoad() {
        super.viewDidLoad()

        // Use weak self to avoid retain cycles
        networkManager.fetchData { [weak self] result in
            switch result {
            case .success(let data):
                self?.handleSuccess(data)
            case .failure(let error):
                self?.handleError(error)
            }
        }
    }

    private func handleSuccess(_ data: Data) {
        // Handle success
    }

    private func handleError(_ error: Error) {
        // Handle error
    }
}

// Lazy Loading
class UserProfileViewController: UIViewController {
    @IBOutlet weak var profileImageView: UIImageView!

    private lazy var imagePickerController: UIImagePickerController = {
        let picker = UIImagePickerController()
        picker.delegate = self
        picker.sourceType = .photoLibrary
        return picker
    }()

    @IBAction func selectImageTapped(_ sender: UIButton) {
        present(imagePickerController, animated: true)
    }
}
```

## 5. Testing Strategies

Implement comprehensive testing with unit tests, UI tests, and snapshot tests.

```swift
import XCTest
@testable import MyApp

// Unit Tests
class UserViewModelTests: XCTestCase {
    var viewModel: UserViewModel!
    var mockUserService: MockUserService!

    override func setUp() {
        super.setUp()
        mockUserService = MockUserService()
        viewModel = UserViewModel(userService: mockUserService)
    }

    override func tearDown() {
        viewModel = nil
        mockUserService = nil
        super.tearDown()
    }

    func testLoadUsersSuccess() {
        // Given
        let expectedUsers = [
            User(id: 1, title: "John Doe", email: "john@example.com", avatarURL: nil)
        ]
        mockUserService.mockUsers = expectedUsers

        let expectation = XCTestExpectation(description: "Users loaded"
	categories: ["development", "best-practices"],)

        // When
        viewModel.$users
            .dropFirst()
            .sink { users in
                XCTAssertEqual(users.count, 1)
                XCTAssertEqual(users.first?.name, "John Doe")
                expectation.fulfill()
            }
            .store(in: &cancellables)

        viewModel.loadUsers()

        // Then
        wait(for: [expectation], timeout: 1.0)
    }

    func testLoadUsersFailure() {
        // Given
        mockUserService.shouldReturnError = true

        let expectation = XCTestExpectation(description: "Error message set"
	categories: ["development", "best-practices"],)

        // When
        viewModel.$errorMessage
            .compactMap { $0 }
            .sink { errorMessage in
                XCTAssertFalse(errorMessage.isEmpty)
                expectation.fulfill()
            }
            .store(in: &cancellables)

        viewModel.loadUsers()

        // Then
        wait(for: [expectation], timeout: 1.0)
    }
}

// Mock Service
class MockUserService: UserServiceProtocol {
    var mockUsers: [User] = []
    var shouldReturnError = false

    func fetchUsers() -> AnyPublisher<[User], NetworkError> {
        if shouldReturnError {
            return Fail(error: NetworkError.networkError(NSError(domain: "Test", code: 0)))
                .eraseToAnyPublisher()
        } else {
            return Just(mockUsers)
                .setFailureType(to: NetworkError.self)
                .eraseToAnyPublisher()
        }
    }

    func fetchUser(id: Int) -> AnyPublisher<User, NetworkError> {
        if let user = mockUsers.first(where: { $0.id == id }) {
            return Just(user)
                .setFailureType(to: NetworkError.self)
                .eraseToAnyPublisher()
        } else {
            return Fail(error: NetworkError.noData)
                .eraseToAnyPublisher()
        }
    }
}

// UI Tests
class MyAppUITests: XCTestCase {
    var app: XCUIApplication!

    override func setUp() {
        super.setUp()
        app = XCUIApplication()
        app.launch()
    }

    func testUserListNavigation() {
        let userListTable = app.tables["UserListTable"]
        XCTAssertTrue(userListTable.waitForExistence(timeout: 5))

        let firstCell = userListTable.cells.firstMatch
        XCTAssertTrue(firstCell.exists)

        firstCell.tap()

        let userDetailView = app.otherElements["UserDetailView"]
        XCTAssertTrue(userDetailView.waitForExistence(timeout: 3))
    }
}
```

## 6. Security & Privacy

Implement security best practices and privacy protection.

```swift
import Security
import LocalAuthentication

// Keychain Helper
class KeychainHelper {
    static let shared = KeychainHelper()

    private init() {}

    func save(key: String, data: Data) -> Bool {
        let query: [String: Any] = [
            kSecClass as String: kSecClassGenericPassword,
            kSecAttrAccount as String: key,
            kSecValueData as String: data
        ]

        SecItemDelete(query as CFDictionary)
        return SecItemAdd(query as CFDictionary, nil) == errSecSuccess
    }

    func load(key: String) -> Data? {
        let query: [String: Any] = [
            kSecClass as String: kSecClassGenericPassword,
            kSecAttrAccount as String: key,
            kSecReturnData as String: true,
            kSecMatchLimit as String: kSecMatchLimitOne
        ]

        var result: AnyObject?
        let status = SecItemCopyMatching(query as CFDictionary, &result)

        return status == errSecSuccess ? result as? Data : nil
    }

    func delete(key: String) -> Bool {
        let query: [String: Any] = [
            kSecClass as String: kSecClassGenericPassword,
            kSecAttrAccount as String: key
        ]

        return SecItemDelete(query as CFDictionary) == errSecSuccess
    }
}

// Biometric Authentication
class BiometricAuthManager {
    static let shared = BiometricAuthManager()

    private init() {}

    func authenticateUser(completion: @escaping (Bool, Error?) -> Void) {
        let context = LAContext()
        var error: NSError?

        if context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) {
            let reason = "Authenticate to access your account"

            context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: reason) { success, authError in
                DispatchQueue.main.async {
                    completion(success, authError)
                }
            }
        } else {
            DispatchQueue.main.async {
                completion(false, error)
            }
        }
    }
}

// Network Security
class SecureNetworkManager {
    private let session: URLSession

    init() {
        let configuration = URLSessionConfiguration.default
        configuration.timeoutIntervalForRequest = 30
        configuration.timeoutIntervalForResource = 60

        self.session = URLSession(
            configuration: configuration,
            delegate: NetworkDelegate(),
            delegateQueue: nil
        )
    }

    func makeSecureRequest(url: URL, completion: @escaping (Result<Data, Error>) -> Void) {
        var request = URLRequest(url: url)
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")

        // Add authentication header
        if let token = getAuthToken() {
            request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
        }

        session.dataTask(with: request) { data, response, error in
            DispatchQueue.main.async {
                if let error = error {
                    completion(.failure(error))
                } else if let data = data {
                    completion(.success(data))
                }
            }
        }.resume()
    }

    private func getAuthToken() -> String? {
        guard let tokenData = KeychainHelper.shared.load(key: "auth_token") else {
            return nil
        }
        return String(data: tokenData, encoding: .utf8)
    }
}

class NetworkDelegate: NSObject, URLSessionDelegate {
    func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {

        // Implement certificate pinning
        guard let serverTrust = challenge.protectionSpace.serverTrust else {
            completionHandler(.cancelAuthenticationChallenge, nil)
            return
        }

        // Add your certificate validation logic here
        completionHandler(.useCredential, URLCredential(trust: serverTrust))
    }
}
```

## 7. Accessibility & Localization

Make your app accessible and support multiple languages.

```swift
import UIKit

// Accessibility
extension UIView {
    func configureAccessibility(label: String?, hint: String? = nil, traits: UIAccessibilityTraits = .none) {
        isAccessibilityElement = true
        accessibilityLabel = label
        accessibilityHint = hint
        accessibilityTraits = traits
    }
}

class AccessibleViewController: UIViewController {
    @IBOutlet weak var titleLabel: UILabel!
    @IBOutlet weak var submitButton: UIButton!

    override func viewDidLoad() {
        super.viewDidLoad()
        setupAccessibility()
    }

    private func setupAccessibility() {
        titleLabel.configureAccessibility(
            label: "Welcome to our app",
            hint: "Main title of the screen"
        )

        submitButton.configureAccessibility(
            label: "Submit form",
            hint: "Double tap to submit the form",
            traits: .button
        )

        // Dynamic Type support
        titleLabel.font = UIFont.preferredFont(forTextStyle: .headline)
        titleLabel.adjustsFontForContentSizeCategory = true
    }
}

// Localization
class LocalizationManager {
    static let shared = LocalizationManager()

    private init() {}

    func localizedString(for key: String, comment: String = "") -> String {
        return NSLocalizedString(key, comment: comment)
    }
}

extension String {
    var localized: String {
        return LocalizationManager.shared.localizedString(for: self)
    }

    func localized(with arguments: CVarArg...) -> String {
        return String(format: localized, arguments: arguments)
    }
}

// Usage
let welcomeMessage = "welcome_message".localized
let userGreeting = "user_greeting".localized(with: userName)
```

## 8. App Store & Distribution

Prepare your app for App Store submission and distribution.

```swift
// App Configuration
import UIKit

@main
class AppDelegate: UIResponder, UIApplicationDelegate {

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

        #if DEBUG
        setupDebugConfiguration()
        #else
        setupReleaseConfiguration()
        #endif

        configureAppearance()
        return true
    }

    private func setupDebugConfiguration() {
        // Debug-only configuration
        print("App launched in DEBUG mode")
    }

    private func setupReleaseConfiguration() {
        // Production configuration
        // Disable logging, enable analytics, etc.
    }

    private func configureAppearance() {
        // Global UI configuration
        UINavigationBar.appearance().tintColor = .systemBlue
        UITabBar.appearance().tintColor = .systemBlue
    }
}

// Version Management
class AppVersionManager {
    static let shared = AppVersionManager()

    private init() {}

    var currentVersion: String {
        return Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "1.0.0"
    }

    var buildNumber: String {
        return Bundle.main.infoDictionary?["CFBundleVersion"] as? String ?? "1"
    }

    func checkForUpdates() {
        // Implement update checking logic
    }
}

// Analytics Integration
class AnalyticsManager {
    static let shared = AnalyticsManager()

    private init() {}

    func track(event: String, parameters: [String: Any]? = nil) {
        #if !DEBUG
        // Track analytics events in production only
        print("Analytics Event: \(event), Parameters: \(parameters ?? [:])")
        #endif
    }

    func setUserProperty(key: String, value: String) {
        #if !DEBUG
        // Set user properties for analytics
        #endif
    }
}
```

## 9. Performance Optimization

Optimize app performance for better user experience.

```swift
import UIKit

// Image Loading and Caching
class ImageLoader {
    static let shared = ImageLoader()

    private let cache = NSCache<NSString, UIImage>()
    private let downloadQueue = DispatchQueue(label: "image.download", qos: .utility, attributes: .concurrent)

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

    func loadImage(from url: URL, completion: @escaping (UIImage?) -> Void) {
        let cacheKey = url.absoluteString as NSString

        // Check cache first
        if let cachedImage = cache.object(forKey: cacheKey) {
            DispatchQueue.main.async {
                completion(cachedImage)
            }
            return
        }

        // Download image
        downloadQueue.async {
            URLSession.shared.dataTask(with: url) { data, response, error in
                guard let data = data, let image = UIImage(data: data) else {
                    DispatchQueue.main.async {
                        completion(nil)
                    }
                    return
                }

                // Cache the image
                self.cache.setObject(image, forKey: cacheKey)

                DispatchQueue.main.async {
                    completion(image)
                }
            }.resume()
        }
    }
}

// Table View Performance
class OptimizedTableViewCell: UITableViewCell {
    static let identifier = "OptimizedTableViewCell"

    override func prepareForReuse() {
        super.prepareForReuse()
        // Reset cell state
        imageView?.image = nil
        textLabel?.text = nil
        detailTextLabel?.text = nil
    }

    func configure(with model: CellModel) {
        textLabel?.text = model.title
        detailTextLabel?.text = model.subtitle

        // Load image asynchronously
        if let imageURL = model.imageURL {
            ImageLoader.shared.loadImage(from: imageURL) { [weak self] image in
                self?.imageView?.image = image
                self?.setNeedsLayout()
            }
        }
    }
}

// Memory Warning Handling
class ViewController: UIViewController {
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()

        // Clear caches
        ImageLoader.shared.clearCache()

        // Release non-essential resources
        releaseNonEssentialResources()
    }

    private func releaseNonEssentialResources() {
        // Implement resource cleanup
    }
}
```

## 10. Debugging & Monitoring

Implement comprehensive debugging and monitoring solutions.

```swift
import os.log

// Logging
class Logger {
    static let shared = Logger()

    private let subsystem = Bundle.main.bundleIdentifier ?? "com.example.app"

    private lazy var networkLogger = os.Logger(subsystem: subsystem, category: "network")
    private lazy var uiLogger = os.Logger(subsystem: subsystem, category: "ui")
    private lazy var dataLogger = os.Logger(subsystem: subsystem, category: "data")

    private init() {}

    func logNetwork(_ message: String, type: OSLogType = .info) {
        networkLogger.log(level: type, "\(message)")
    }

    func logUI(_ message: String, type: OSLogType = .info) {
        uiLogger.log(level: type, "\(message)")
    }

    func logData(_ message: String, type: OSLogType = .info) {
        dataLogger.log(level: type, "\(message)")
    }
}

// Crash Reporting
class CrashReporter {
    static let shared = CrashReporter()

    private init() {}

    func configure() {
        NSSetUncaughtExceptionHandler { exception in
            CrashReporter.shared.handleException(exception)
        }
    }

    private func handleException(_ exception: NSException) {
        let crashInfo = [
            "name": exception.name.rawValue,
            "reason": exception.reason ?? "Unknown",
            "callStack": exception.callStackSymbols.joined(separator: "\n")
        ]

        // Send crash report to your analytics service
        print("Crash detected: \(crashInfo)")
    }
}

// Performance Monitoring
class PerformanceMonitor {
    static let shared = PerformanceMonitor()

    private init() {}

    func measureExecutionTime<T>(
        _ operation: () throws -> T,
        label: String = "Operation"
    ) rethrows -> T {
        let startTime = CFAbsoluteTimeGetCurrent()
        let result = try operation()
        let timeElapsed = CFAbsoluteTimeGetCurrent() - startTime

        Logger.shared.logData("\(label) took \(timeElapsed) seconds", type: .debug)

        return result
    }
}
```

## Checklist

- [ ] Follow MVC or MVVM architecture patterns consistently
- [ ] Implement proper memory management and avoid retain cycles
- [ ] Use SwiftUI or UIKit effectively with modern best practices
- [ ] Handle networking asynchronously with proper error handling
- [ ] Write comprehensive unit tests and UI tests
- [ ] Implement accessibility features and support Dynamic Type
- [ ] Secure sensitive data with Keychain and implement biometric authentication
- [ ] Support localization and internationalization
- [ ] Optimize performance with lazy loading and caching strategies
- [ ] Follow Apple's Human Interface Guidelines
- [ ] Configure proper App Store metadata and screenshots
- [ ] Implement analytics and crash reporting
- [ ] Use Instruments for performance profiling
- [ ] Handle different device sizes and orientations
- [ ] Follow Swift naming conventions and coding standards
Swift iOS Development - Cursor IDE AI Rule