Visual overview

Use the workflow to follow the task, and the architecture map to separate responsibilities. These are conceptual maps; the guide below defines implementation details and verification limits.

01 / WorkflowFrom intent to a checked result
  1. Load compatible model
  2. Prepare typed inputs
  3. Run prediction
  4. Validate output and latency
02 / ArchitectureResponsibility boundaries
  1. Boundary 1Model asset
  2. Boundary 2Prediction adapter
  3. Boundary 3App decision

Connected responsibilities, not a required class hierarchy or an execution trace.

Overview

CoreML is Apple's framework for running machine learning models on-device with hardware-accelerated inference across CPU, GPU, and the Neural Engine. It supports vision, natural language, audio, and tabular models with a unified API surface. Every code example below compiles and follows production best practices.


1. Loading an MLModel

From the App Bundle (Compiled .mlmodelc)

When you drag a .mlmodel file into Xcode, it auto-generates a Swift class. You can also load manually.

import CoreML

// Auto-generated class usage (Xcode compiles the model at build time)
let imageClassifier = try MobileNetV2(configuration: MLModelConfiguration())

// Manual loading from the bundle
let bundleURL = Bundle.main.url(forResource: "MobileNetV2", withExtension: "mlmodelc")!
let model = try MLModel(contentsOf: bundleURL)

From a Compiled Model URL

import CoreML

func loadModel(from compiledURL: URL) async throws -> MLModel {
    let configuration = MLModelConfiguration()
    configuration.computeUnits = .all
    return try MLModel(contentsOf: compiledURL, configuration: configuration)
}

From a Downloaded .mlmodel (Runtime Compilation)

import CoreML

func compileAndLoad(downloadedModelURL: URL) async throws -> MLModel {
    // Compile the .mlmodel into .mlmodelc at runtime
    let compiledURL = try await MLModel.compileModel(at: downloadedModelURL)

    // Move to a permanent location (compiled models are placed in a temp directory)
    let permanentURL = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)[0]
        .appendingPathComponent("Models")
        .appendingPathComponent(compiledURL.lastPathComponent)

    try FileManager.default.createDirectory(
        at: permanentURL.deletingLastPathComponent(),
        withIntermediateDirectories: true
    )

    if FileManager.default.fileExists(atPath: permanentURL.path) {
        try FileManager.default.removeItem(at: permanentURL)
    }
    try FileManager.default.moveItem(at: compiledURL, to: permanentURL)

    let configuration = MLModelConfiguration()
    configuration.computeUnits = .all
    return try MLModel(contentsOf: permanentURL, configuration: configuration)
}

2. Compute Unit Configuration

Control where inference runs: CPU only, CPU and GPU, CPU and Neural Engine, or all available hardware.

import CoreML

func configuredModel() throws -> MLModel {
    let config = MLModelConfiguration()

    // Options:
    // .cpuOnly        -- safest, always available
    // .cpuAndGPU      -- good for large matrix operations
    // .cpuAndNeuralEngine -- best power efficiency for supported models
    // .all            -- let CoreML decide the optimal hardware (recommended)
    config.computeUnits = .all

    // Optionally allow low-precision accumulation for speed
    config.allowLowPrecisionAccumulationOnGPU = true

    return try MLModel(contentsOf: Bundle.main.url(forResource: "MyModel", withExtension: "mlmodelc")!,
                       configuration: config)
}

3. MLMultiArray for Input and Output

MLMultiArray is the primary numeric tensor type for CoreML model inputs and outputs.

import CoreML

// Create a multi-array with shape [1, 3, 224, 224] (batch, channels, height, width)
let inputArray = try MLMultiArray(shape: [1, 3, 224, 224], dataType: .float32)

// Fill with data
for i in 0..<inputArray.count {
    inputArray[i] = NSNumber(value: Float.random(in: 0...1))
}

// Access specific elements using a flat index
let value = inputArray[0].floatValue

// Access with multi-dimensional subscript
let multiIndex = [0, 1, 112, 112] as [NSNumber]
let pixel = inputArray[multiIndex].floatValue

Using MLTensor (iOS 18+)

MLTensor provides a more ergonomic, Accelerate-backed tensor API.

import CoreML

@available(iOS 18.0, *)
func tensorOperations() {
    // Create from shape and scalar
    let zeros = MLTensor(zeros: [1, 3, 224, 224], scalarType: Float.self)

    // Create from an array
    let data = MLTensor([1.0, 2.0, 3.0, 4.0] as [Float])

    // Reshape
    let reshaped = data.reshaped(to: [2, 2])

    // Arithmetic
    let scaled = data * 2.0
    let summed = data + MLTensor([0.5, 0.5, 0.5, 0.5] as [Float])

    // Convert to MLMultiArray for model input
    let multiArray = zeros.shapedArray(of: Float.self)
}

4. Making Predictions

Synchronous Prediction

import CoreML

func predict(with model: MLModel, input: MLMultiArray) throws -> MLFeatureProvider {
    let inputFeature = try MLDictionaryFeatureProvider(
        dictionary: ["input_tensor": MLFeatureValue(multiArray: input)]
    )

    let options = MLPredictionOptions()
    options.usesCPUOnly = false  // allow GPU and Neural Engine

    let output = try model.prediction(from: inputFeature, options: options)
    return output
}

Async Prediction (iOS 17+)

import CoreML

@available(iOS 17.0, *)
func asyncPredict(with model: MLModel, input: MLMultiArray) async throws -> MLFeatureProvider {
    let inputFeature = try MLDictionaryFeatureProvider(
        dictionary: ["input_tensor": MLFeatureValue(multiArray: input)]
    )

    let options = MLPredictionOptions()
    let output = try await model.prediction(from: inputFeature, options: options)
    return output
}

Batch Prediction

import CoreML

func batchPredict(with model: MLModel, inputs: [MLDictionaryFeatureProvider]) throws -> [MLFeatureProvider] {
    let batchProvider = MLArrayBatchProvider(array: inputs)
    let options = MLPredictionOptions()

    let batchResults = try model.predictions(from: batchProvider, options: options)

    var results: [MLFeatureProvider] = []
    for i in 0..<batchResults.count {
        results.append(batchResults.features(at: i))
    }
    return results
}

5. VNCoreMLRequest -- Vision + CoreML Pipeline

Combine Vision preprocessing (resize, crop, normalize) with a CoreML model in a single pipeline.

import CoreML
import Vision
import UIKit

func classifyImage(_ image: UIImage) async throws -> [(String, Float)] {
    guard let cgImage = image.cgImage else {
        throw NSError(domain: "ImageError", code: -1, userInfo: [NSLocalizedDescriptionKey: "Invalid image"])
    }

    let configuration = MLModelConfiguration()
    configuration.computeUnits = .all
    let mlModel = try MobileNetV2(configuration: configuration).model
    let vnModel = try VNCoreMLModel(for: mlModel)

    return try await withCheckedThrowingContinuation { continuation in
        let request = VNCoreMLRequest(model: vnModel) { request, error in
            if let error {
                continuation.resume(throwing: error)
                return
            }

            guard let results = request.results as? [VNClassificationObservation] else {
                continuation.resume(returning: [])
                return
            }

            let topResults = results.prefix(5).map { ($0.identifier, $0.confidence) }
            continuation.resume(returning: topResults)
        }

        // Configure image preprocessing
        request.imageCropAndScaleOption = .centerCrop

        let handler = VNImageRequestHandler(cgImage: cgImage, options: [:])
        do {
            try handler.perform([request])
        } catch {
            continuation.resume(throwing: error)
        }
    }
}

6. Complete Image Classification Example

A full SwiftUI view that loads an image and classifies it with CoreML.

import SwiftUI
import CoreML
import Vision
import PhotosUI

@Observable
final class ImageClassifierViewModel {
    var classifications: [(label: String, confidence: Float)] = []
    var selectedImage: UIImage?
    var isProcessing = false
    var errorMessage: String?

    @MainActor
    func classify() async {
        guard let image = selectedImage, let cgImage = image.cgImage else { return }

        isProcessing = true
        errorMessage = nil

        do {
            let config = MLModelConfiguration()
            config.computeUnits = .all
            let mlModel = try MobileNetV2(configuration: config).model
            let vnModel = try VNCoreMLModel(for: mlModel)

            let results: [(String, Float)] = try await withCheckedThrowingContinuation { continuation in
                let request = VNCoreMLRequest(model: vnModel) { request, error in
                    if let error {
                        continuation.resume(throwing: error)
                        return
                    }
                    let observations = (request.results as? [VNClassificationObservation]) ?? []
                    let top5 = observations.prefix(5).map { ($0.identifier, $0.confidence) }
                    continuation.resume(returning: top5)
                }
                request.imageCropAndScaleOption = .centerCrop

                let handler = VNImageRequestHandler(cgImage: cgImage, options: [:])
                do {
                    try handler.perform([request])
                } catch {
                    continuation.resume(throwing: error)
                }
            }

            classifications = results.map { (label: $0.0, confidence: $0.1) }
        } catch {
            errorMessage = error.localizedDescription
        }

        isProcessing = false
    }
}

struct ImageClassifierView: View {
    @State private var viewModel = ImageClassifierViewModel()
    @State private var photoItem: PhotosPickerItem?

    var body: some View {
        NavigationStack {
            VStack(spacing: 24) {
                if let image = viewModel.selectedImage {
                    Image(uiImage: image)
                        .resizable()
                        .scaledToFit()
                        .frame(maxHeight: 300)
                        .clipShape(RoundedRectangle(cornerRadius: 16))
                        .shadow(color: .black.opacity(0.2), radius: 12, y: 6)
                } else {
                    ContentUnavailableView("Select a Photo",
                                           systemImage: "photo.on.rectangle",
                                           description: Text("Choose an image to classify"))
                }

                PhotosPicker(selection: $photoItem, matching: .images) {
                    Label("Choose Photo", systemImage: "photo.badge.plus")
                        .font(.headline)
                        .padding()
                        .frame(maxWidth: .infinity)
                        .background(.tint, in: RoundedRectangle(cornerRadius: 12))
                        .foregroundStyle(.white)
                }

                if viewModel.isProcessing {
                    ProgressView("Classifying...")
                }

                if let error = viewModel.errorMessage {
                    Text(error)
                        .foregroundStyle(.red)
                        .font(.caption)
                }

                if !viewModel.classifications.isEmpty {
                    VStack(alignment: .leading, spacing: 8) {
                        Text("Results")
                            .font(.headline)

                        ForEach(viewModel.classifications, id: \.label) { item in
                            HStack {
                                Text(item.label)
                                    .font(.body)
                                Spacer()
                                Text("\(item.confidence * 100, specifier: "%.1f")%")
                                    .font(.body.monospacedDigit())
                                    .foregroundStyle(.secondary)
                            }
                            .padding(.vertical, 4)
                        }
                    }
                    .padding()
                    .background(.regularMaterial, in: RoundedRectangle(cornerRadius: 12))
                }

                Spacer()
            }
            .padding()
            .navigationTitle("Image Classifier")
            .onChange(of: photoItem) { _, newItem in
                Task {
                    if let data = try? await newItem?.loadTransferable(type: Data.self),
                       let uiImage = UIImage(data: data) {
                        viewModel.selectedImage = uiImage
                        await viewModel.classify()
                    }
                }
            }
        }
    }
}

7. Text Prediction Example

Using a CoreML model trained on tabular or text data to make predictions.

import CoreML

struct SentimentPredictor {
    private let model: MLModel

    init() throws {
        let config = MLModelConfiguration()
        config.computeUnits = .cpuOnly  // text models often run best on CPU
        self.model = try SentimentClassifier(configuration: config).model
    }

    func predict(text: String) throws -> (label: String, confidence: Double) {
        let input = try MLDictionaryFeatureProvider(
            dictionary: ["text": MLFeatureValue(string: text)]
        )

        let output = try model.prediction(from: input)

        let label = output.featureValue(for: "label")?.stringValue ?? "unknown"
        let probabilities = output.featureValue(for: "labelProbability")?.dictionaryValue ?? [:]

        let confidence = (probabilities[label as NSObject] as? NSNumber)?.doubleValue ?? 0.0
        return (label: label, confidence: confidence)
    }

    func predictBatch(texts: [String]) throws -> [(label: String, confidence: Double)] {
        let inputs: [MLDictionaryFeatureProvider] = try texts.map { text in
            try MLDictionaryFeatureProvider(dictionary: ["text": MLFeatureValue(string: text)])
        }

        let batchProvider = MLArrayBatchProvider(array: inputs)
        let batchResults = try model.predictions(from: batchProvider)

        var results: [(label: String, confidence: Double)] = []
        for i in 0..<batchResults.count {
            let output = batchResults.features(at: i)
            let label = output.featureValue(for: "label")?.stringValue ?? "unknown"
            let probs = output.featureValue(for: "labelProbability")?.dictionaryValue ?? [:]
            let confidence = (probs[label as NSObject] as? NSNumber)?.doubleValue ?? 0.0
            results.append((label: label, confidence: confidence))
        }
        return results
    }
}

8. Model Caching and Performance Optimization

import CoreML

actor ModelCache {
    static let shared = ModelCache()

    private var cache: [String: MLModel] = [:]

    func model(named name: String, computeUnits: MLComputeUnits = .all) throws -> MLModel {
        if let cached = cache[name] {
            return cached
        }

        guard let url = Bundle.main.url(forResource: name, withExtension: "mlmodelc") else {
            throw ModelCacheError.modelNotFound(name)
        }

        let config = MLModelConfiguration()
        config.computeUnits = computeUnits

        let model = try MLModel(contentsOf: url, configuration: config)
        cache[name] = model
        return model
    }

    func preload(modelNames: [String]) async throws {
        for name in modelNames {
            _ = try model(named: name)
        }
    }

    func evict(named name: String) {
        cache.removeValue(forKey: name)
    }

    func evictAll() {
        cache.removeAll()
    }
}

enum ModelCacheError: LocalizedError {
    case modelNotFound(String)

    var errorDescription: String? {
        switch self {
        case .modelNotFound(let name):
            return "Model '\(name)' not found in the app bundle."
        }
    }
}

9. Converting Models with coremltools (Overview)

Use Python's coremltools to convert models from PyTorch, TensorFlow, or ONNX into .mlmodel format.

# Install: pip install coremltools torch torchvision

import coremltools as ct
import torch
import torchvision

# Load a pretrained PyTorch model
torch_model = torchvision.models.mobilenet_v2(pretrained=True)
torch_model.eval()

# Trace the model with example input
example_input = torch.randn(1, 3, 224, 224)
traced_model = torch.jit.trace(torch_model, example_input)

# Convert to CoreML
coreml_model = ct.convert(
    traced_model,
    inputs=[ct.ImageType(name="image", shape=(1, 3, 224, 224), scale=1/255.0)],
    classifier_config=ct.ClassifierConfig("imagenet_classes.txt"),
    compute_units=ct.ComputeUnit.ALL,
    minimum_deployment_target=ct.target.iOS17,
)

# Save
coreml_model.save("MobileNetV2.mlpackage")

Key conversion options: - compute_precision -- use ct.precision.FLOAT16 for smaller models and faster Neural Engine inference. - minimum_deployment_target -- set to the lowest iOS version you support for maximum compatibility. - ct.ImageType -- declares the input as an image so Vision can preprocess it automatically. - ct.ClassifierConfig -- adds classification metadata so results come as VNClassificationObservation.


10. Performance Tips

Technique Benefit
Use .all compute units Let CoreML pick the fastest hardware
Float16 quantization 2x smaller model, faster on Neural Engine
Batch predictions Amortize model setup overhead
Cache loaded models Avoid repeated disk I/O and compilation
Background thread inference Keep UI responsive
Model warmup Run a dummy prediction at launch to prime the pipeline
Use MLPackage over MLModel Modern format with better optimization support
Profile with Instruments Use the CoreML Instrument to find bottlenecks
import CoreML

// Warmup: run a dummy prediction to prime the model pipeline
func warmup(model: MLModel, inputName: String, shape: [NSNumber]) throws {
    let dummyInput = try MLMultiArray(shape: shape, dataType: .float32)
    let provider = try MLDictionaryFeatureProvider(
        dictionary: [inputName: MLFeatureValue(multiArray: dummyInput)]
    )
    _ = try model.prediction(from: provider)
}

Quick Reference

Class / Protocol Purpose
MLModel Core class for loading and running models
MLModelConfiguration Configure compute units, precision
MLMultiArray N-dimensional numeric array for model I/O
MLTensor Modern tensor API (iOS 18+)
MLFeatureProvider Protocol for model input/output
MLDictionaryFeatureProvider Dictionary-based feature provider
MLArrayBatchProvider Batch multiple inputs for prediction
MLPredictionOptions Options for prediction (CPU-only flag)
VNCoreMLModel Bridge a CoreML model into the Vision pipeline
VNCoreMLRequest Vision request that uses a CoreML model