Vision Framework와 CoreML: 온디바이스 ML iOS 면접 질문
Vision Framework와 CoreML의 핵심 면접 질문으로 iOS 면접을 준비할 수 있습니다. 이미지 인식, 객체 감지, 온디바이스 ML을 다룹니다.

온디바이스 머신러닝은 현대 iOS 애플리케이션에 중요한 경쟁 우위를 제공합니다. Vision Framework와 CoreML을 통해 모델을 디바이스에서 직접 실행할 수 있어 데이터 프라이버시와 실시간 성능이 보장됩니다. 이 면접 질문들은 모든 시니어 iOS 개발자가 숙지해야 할 핵심 개념을 다룹니다.
질문은 주제별로 정리되어 있습니다. CoreML 기초, Vision Framework, 성능 최적화, 실전 사례입니다. 각 답변에는 최신 Swift 코드와 자세한 설명이 포함되어 있습니다.
CoreML 기초
1. CoreML이란 무엇이며 어떤 장점이 있습니까?
CoreML은 iOS, macOS, watchOS, tvOS 애플리케이션에 머신러닝 모델을 통합하기 위한 Apple의 프레임워크입니다. Apple 하드웨어(CPU, GPU, Neural Engine)에 맞춰 모델을 자동으로 최적화하고, 네트워크 연결 없이 온디바이스 실행을 보장합니다.
주요 장점에는 데이터 프라이버시(데이터가 디바이스를 벗어나지 않음), 낮은 지연 시간(네트워크 왕복 없음), Apple Silicon 칩의 Neural Engine을 위한 자동 최적화가 포함됩니다.
import CoreML
// Loading a compiled CoreML model (.mlmodelc)
class ImageClassifier {
// Model is compiled at build time to optimize loading
private let model: VNCoreMLModel
init() throws {
// Configuration to use Neural Engine if available
let config = MLModelConfiguration()
config.computeUnits = .all // CPU + GPU + Neural Engine
// Load model with custom configuration
let mlModel = try MobileNetV2(configuration: config).model
model = try VNCoreMLModel(for: mlModel)
}
// Method to classify an image
func classify(image: CGImage) async throws -> [(String, Float)] {
// Create Vision request with CoreML model
let request = VNCoreMLRequest(model: model)
request.imageCropAndScaleOption = .centerCrop
// Handler to process the image
let handler = VNImageRequestHandler(cgImage: image, options: [:])
try handler.perform([request])
// Extract results
guard let results = request.results as? [VNClassificationObservation] else {
return []
}
// Return top 5 predictions with confidence
return results.prefix(5).map { ($0.identifier, $0.confidence) }
}
}2. TensorFlow나 PyTorch 모델을 CoreML로 어떻게 변환합니까?
변환에는 Apple의 공식 Python 패키지인 coremltools를 사용합니다. TensorFlow, PyTorch, ONNX 및 기타 인기 있는 형식을 지원합니다. 변환에는 모델 크기를 줄이기 위한 양자화 같은 최적화가 포함될 수 있습니다.
# convert_model.py
import coremltools as ct
import torch
# Conversion from PyTorch
class MyClassifier(torch.nn.Module):
def __init__(self):
super().__init__()
self.conv = torch.nn.Conv2d(3, 64, 3)
self.fc = torch.nn.Linear(64, 10)
def forward(self, x):
x = self.conv(x)
x = x.mean([2, 3]) # Global average pooling
return self.fc(x)
# Example input for tracing
example_input = torch.rand(1, 3, 224, 224)
# Trace the PyTorch model
traced_model = torch.jit.trace(MyClassifier(), example_input)
# Convert to CoreML with metadata
mlmodel = ct.convert(
traced_model,
inputs=[ct.ImageType(name="image", shape=(1, 3, 224, 224))],
classifier_config=ct.ClassifierConfig(["cat", "dog", "bird"]),
minimum_deployment_target=ct.target.iOS17
)
# Save model with compression
mlmodel.save("MyClassifier.mlpackage").mlpackage 모델은 Xcode 프로젝트에 직접 추가할 수 있으며, Xcode가 자동으로 타입이 지정된 Swift 클래스를 생성합니다.
3. MLModel과 VNCoreMLModel의 차이점은 무엇입니까?
MLModel은 ML 모델을 로드하고 실행하기 위한 CoreML의 기본 클래스입니다. VNCoreMLModel은 CoreML 모델을 Vision Framework와 함께 사용할 수 있도록 해주는 래퍼이며, 자동 이미지 전처리와 Vision 파이프라인 통합을 제공합니다.
import CoreML
import Vision
// Direct MLModel usage (low level)
func predictWithMLModel(features: MLFeatureProvider) async throws -> String {
let config = MLModelConfiguration()
let model = try MyModel(configuration: config)
// Direct prediction with feature provider
let prediction = try model.prediction(from: features)
// Manual output access
guard let output = prediction.featureValue(for: "classLabel")?.stringValue else {
throw PredictionError.invalidOutput
}
return output
}
// Usage with VNCoreMLModel (high level, recommended for images)
func predictWithVision(image: CGImage) async throws -> [VNClassificationObservation] {
let config = MLModelConfiguration()
let mlModel = try MyModel(configuration: config).model
// Wrapper for use with Vision
let visionModel = try VNCoreMLModel(for: mlModel)
// Vision automatically handles resizing and preprocessing
let request = VNCoreMLRequest(model: visionModel)
request.imageCropAndScaleOption = .scaleFill
let handler = VNImageRequestHandler(cgImage: image)
try handler.perform([request])
return request.results as? [VNClassificationObservation] ?? []
}표 형식 데이터나 이미지가 아닌 입력에는 MLModel을 직접 사용합니다. 이미지와 관련된 모든 작업에는 VNCoreMLModel을 사용합니다. Vision이 형식 변환과 전처리를 자동으로 처리하기 때문입니다.
4. CoreML에서 다양한 iOS 버전을 어떻게 처리합니까?
CoreML은 iOS 버전마다 발전합니다. 변환 시 최소 deployment target을 정의하고, 구버전에서 사용할 수 없는 기능을 처리하는 것이 중요합니다.
import CoreML
class AdaptiveMLManager {
// Check model capabilities based on iOS version
func loadOptimalModel() throws -> MLModel {
let config = MLModelConfiguration()
// iOS 17+: Optimized Neural Engine with compute budget
if #available(iOS 17, *) {
config.computeUnits = .cpuAndNeuralEngine
// New in iOS 17: compute power limit
config.allowLowPrecisionAccumulationOnGPU = true
return try AdvancedModel(configuration: config).model
}
// iOS 16: Enhanced GPU support
else if #available(iOS 16, *) {
config.computeUnits = .all
return try StandardModel(configuration: config).model
}
// iOS 15: CPU only fallback for reliability
else {
config.computeUnits = .cpuOnly
return try LegacyModel(configuration: config).model
}
}
// Check if Neural Engine is available
var hasNeuralEngine: Bool {
if #available(iOS 16, *) {
// Devices with A11+ have Neural Engine
var sysinfo = utsname()
uname(&sysinfo)
let machine = String(bytes: Data(bytes: &sysinfo.machine,
count: Int(_SYS_NAMELEN)), encoding: .ascii)?
.trimmingCharacters(in: .controlCharacters) ?? ""
// iPhone X and later have Neural Engine
return machine.contains("iPhone10") ||
machine.hasPrefix("iPhone1") && machine.count > 7
}
return false
}
}Vision Framework
5. Vision Framework는 어떤 종류의 요청을 지원합니까?
Vision Framework는 이미지 분석을 위한 다양한 요청을 제공합니다. 주요 카테고리로는 얼굴 감지, 텍스트 인식(OCR), 객체 감지, 비디오에서의 객체 추적, 이미지 유사도 분석이 있습니다.
import Vision
class VisionAnalyzer {
// Face detection with landmarks
func detectFaces(in image: CGImage) async throws -> [VNFaceObservation] {
let request = VNDetectFaceLandmarksRequest()
request.revision = VNDetectFaceLandmarksRequestRevision3
let handler = VNImageRequestHandler(cgImage: image)
try handler.perform([request])
return request.results ?? []
}
// Text recognition (OCR)
func recognizeText(in image: CGImage) async throws -> [String] {
let request = VNRecognizeTextRequest()
request.recognitionLevel = .accurate // .fast for real-time
request.recognitionLanguages = ["en-US", "fr-FR"]
request.usesLanguageCorrection = true
let handler = VNImageRequestHandler(cgImage: image)
try handler.perform([request])
return request.results?.compactMap { observation in
observation.topCandidates(1).first?.string
} ?? []
}
// Object detection and classification
func detectObjects(in image: CGImage) async throws -> [VNRecognizedObjectObservation] {
// Use a CoreML model for detection
let config = MLModelConfiguration()
let detector = try YOLOv8(configuration: config)
let visionModel = try VNCoreMLModel(for: detector.model)
let request = VNCoreMLRequest(model: visionModel)
request.imageCropAndScaleOption = .scaleFill
let handler = VNImageRequestHandler(cgImage: image)
try handler.perform([request])
return request.results as? [VNRecognizedObjectObservation] ?? []
}
// Compute similarity between images
func computeSimilarity(image1: CGImage, image2: CGImage) async throws -> Float {
// Generate feature prints for both images
let request = VNGenerateImageFeaturePrintRequest()
let handler1 = VNImageRequestHandler(cgImage: image1)
try handler1.perform([request])
guard let print1 = request.results?.first else { throw VisionError.noResults }
let handler2 = VNImageRequestHandler(cgImage: image2)
try handler2.perform([request])
guard let print2 = request.results?.first else { throw VisionError.noResults }
// Compute distance between embeddings
var distance: Float = 0
try print1.computeDistance(&distance, to: print2)
// Convert distance to similarity score (0-1)
return 1.0 / (1.0 + distance)
}
}6. Vision으로 실시간 객체 추적을 어떻게 구현합니까?
객체 추적은 감지된 객체를 비디오 프레임을 통해 따라가기 위해 VNTrackObjectRequest를 사용합니다. 감지 observation으로 초기화하고, 이후 프레임은 동일한 요청을 사용해 추적합니다.
import Vision
import AVFoundation
class ObjectTracker: NSObject {
private var trackingRequest: VNTrackObjectRequest?
private let sequenceHandler = VNSequenceRequestHandler()
// Callback to notify position updates
var onTrackingUpdate: ((CGRect) -> Void)?
var onTrackingLost: (() -> Void)?
// Initialize tracking with an initial detection
func startTracking(observation: VNDetectedObjectObservation) {
// Create tracking request from observation
trackingRequest = VNTrackObjectRequest(
detectedObjectObservation: observation
) { [weak self] request, error in
self?.handleTrackingResult(request: request, error: error)
}
// Configure tracking
trackingRequest?.trackingLevel = .accurate // .fast for 60fps
}
// Process each new video frame
func processFrame(_ pixelBuffer: CVPixelBuffer) {
guard let request = trackingRequest else { return }
do {
// Sequence handler maintains context between frames
try sequenceHandler.perform([request], on: pixelBuffer)
} catch {
onTrackingLost?()
trackingRequest = nil
}
}
private func handleTrackingResult(request: VNRequest, error: Error?) {
guard let result = request.results?.first as? VNDetectedObjectObservation else {
onTrackingLost?()
return
}
// Check tracking confidence
if result.confidence < 0.3 {
onTrackingLost?()
trackingRequest = nil
return
}
// Update request for next frame
trackingRequest = VNTrackObjectRequest(detectedObjectObservation: result) {
[weak self] request, error in
self?.handleTrackingResult(request: request, error: error)
}
// Notify new position (normalized coordinates)
DispatchQueue.main.async { [weak self] in
self?.onTrackingUpdate?(result.boundingBox)
}
}
}
// Integration with AVCaptureSession
extension ObjectTracker: AVCaptureVideoDataOutputSampleBufferDelegate {
func captureOutput(
_ output: AVCaptureOutput,
didOutput sampleBuffer: CMSampleBuffer,
from connection: AVCaptureConnection
) {
guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else {
return
}
processFrame(pixelBuffer)
}
}iOS 면접 준비가 되셨나요?
인터랙티브 시뮬레이터, flashcards, 기술 테스트로 연습하세요.
7. 실시간 처리를 위해 Vision 성능을 어떻게 최적화합니까?
최적화에는 여러 가지 기법이 포함됩니다. 적절한 인식 수준 사용, 전용 큐에서의 프레임 처리, 동시 요청 수 제한입니다. 정확도와 속도 간의 선택은 사용 사례에 따라 달라집니다.
import Vision
import AVFoundation
class OptimizedVisionPipeline {
// Dedicated queue for Vision processing (avoids main thread)
private let processingQueue = DispatchQueue(
label: "com.app.vision",
qos: .userInteractive,
attributes: .concurrent
)
// Limit number of simultaneously processed frames
private let semaphore = DispatchSemaphore(value: 2)
// Reuse requests to avoid allocations
private lazy var textRequest: VNRecognizeTextRequest = {
let request = VNRecognizeTextRequest()
request.recognitionLevel = .fast // .accurate if precision > speed
request.usesLanguageCorrection = false // Disable for +20% perf
request.minimumTextHeight = 0.05 // Ignore text too small
return request
}()
// Reuse sequence handler for tracking
private let sequenceHandler = VNSequenceRequestHandler()
// Optimized frame processing
func processFrame(_ pixelBuffer: CVPixelBuffer) {
// Skip if pipeline is saturated
guard semaphore.wait(timeout: .now()) == .success else {
return // Drop frame rather than block
}
processingQueue.async { [weak self] in
defer { self?.semaphore.signal() }
guard let self = self else { return }
do {
// Use sequence handler for better performance
try self.sequenceHandler.perform(
[self.textRequest],
on: pixelBuffer,
orientation: .up
)
// Process results
if let results = self.textRequest.results {
self.handleResults(results)
}
} catch {
print("Vision error: \(error)")
}
}
}
// Batch processing for static images
func processImages(_ images: [CGImage]) async throws -> [[VNObservation]] {
// Parallel processing with TaskGroup
try await withThrowingTaskGroup(of: (Int, [VNObservation]).self) { group in
for (index, image) in images.enumerated() {
group.addTask {
let handler = VNImageRequestHandler(cgImage: image)
let request = VNDetectFaceRectanglesRequest()
try handler.perform([request])
return (index, request.results ?? [])
}
}
// Collect results in original order
var results = [[VNObservation]](repeating: [], count: images.count)
for try await (index, observations) in group {
results[index] = observations
}
return results
}
}
private func handleResults(_ results: [VNRecognizedTextObservation]) {
// Async processing of results
}
}8. Vision으로 사람 자세 감지를 어떻게 구현합니까?
Vision Framework iOS 14+는 신체 관절을 감지하기 위해 VNDetectHumanBodyPoseRequest를 제공합니다. 이 기능은 피트니스 앱, AR 게임, 동작 분석에 사용됩니다.
import Vision
struct DetectedPose {
let joints: [VNHumanBodyPoseObservation.JointName: CGPoint]
let confidence: Float
// Calculate angle between three joints
func angleBetween(
_ joint1: VNHumanBodyPoseObservation.JointName,
_ joint2: VNHumanBodyPoseObservation.JointName,
_ joint3: VNHumanBodyPoseObservation.JointName
) -> Double? {
guard let p1 = joints[joint1],
let p2 = joints[joint2],
let p3 = joints[joint3] else { return nil }
let v1 = CGVector(dx: p1.x - p2.x, dy: p1.y - p2.y)
let v2 = CGVector(dx: p3.x - p2.x, dy: p3.y - p2.y)
let dot = v1.dx * v2.dx + v1.dy * v2.dy
let mag1 = sqrt(v1.dx * v1.dx + v1.dy * v1.dy)
let mag2 = sqrt(v2.dx * v2.dx + v2.dy * v2.dy)
return acos(dot / (mag1 * mag2)) * 180 / .pi
}
}
class PoseDetector {
private let request = VNDetectHumanBodyPoseRequest()
func detectPose(in image: CGImage) async throws -> DetectedPose? {
let handler = VNImageRequestHandler(cgImage: image)
try handler.perform([request])
guard let observation = request.results?.first else { return nil }
// Extract all detected joints
var joints: [VNHumanBodyPoseObservation.JointName: CGPoint] = [:]
// List of main joints
let jointNames: [VNHumanBodyPoseObservation.JointName] = [
.nose, .neck,
.leftShoulder, .rightShoulder,
.leftElbow, .rightElbow,
.leftWrist, .rightWrist,
.leftHip, .rightHip,
.leftKnee, .rightKnee,
.leftAnkle, .rightAnkle
]
for jointName in jointNames {
if let point = try? observation.recognizedPoint(jointName),
point.confidence > 0.3 {
// Convert normalized coordinates to points
joints[jointName] = CGPoint(x: point.x, y: point.y)
}
}
return DetectedPose(
joints: joints,
confidence: observation.confidence
)
}
// Detect if person is doing a squat
func isSquatting(pose: DetectedPose) -> Bool {
guard let kneeAngle = pose.angleBetween(
.leftHip, .leftKnee, .leftAnkle
) else { return false }
// A squat typically has knee angle < 100°
return kneeAngle < 100
}
}최적화와 운영
9. CoreML 모델을 양자화하여 크기를 줄이는 방법은?
양자화는 모델 크기를 줄이고 추론 속도를 높이기 위해 가중치의 정밀도를 낮춥니다(Float32에서 Float16 또는 Int8로). 그 대가로 약간의 정확도 손실이 발생합니다.
# quantize_model.py
import coremltools as ct
from coremltools.models.neural_network import quantization_utils
# Load existing model
model = ct.models.MLModel("MyModel.mlpackage")
# Float16 quantization (recommended, good size/precision balance)
model_fp16 = ct.models.neural_network.quantization_utils.quantize_weights(
model,
nbits=16,
quantization_mode="linear"
)
model_fp16.save("MyModel_FP16.mlpackage")
# Int8 quantization (smallest size, possible precision loss)
# Requires calibration dataset for best results
def calibration_data():
import numpy as np
for _ in range(100):
yield {"image": np.random.rand(1, 3, 224, 224).astype(np.float32)}
model_int8 = ct.compression_utils.affine_quantize_weights(
model,
mode="linear_symmetric",
dtype=ct.converters.mil.mil.types.int8
)
model_int8.save("MyModel_INT8.mlpackage")import CoreML
class ModelBenchmark {
// Compare performance of different versions
func benchmark() async throws {
let configs: [(String, URL)] = [
("Full Precision", Bundle.main.url(forResource: "Model", withExtension: "mlmodelc")!),
("Float16", Bundle.main.url(forResource: "Model_FP16", withExtension: "mlmodelc")!),
("Int8", Bundle.main.url(forResource: "Model_INT8", withExtension: "mlmodelc")!)
]
for (name, url) in configs {
let model = try MLModel(contentsOf: url)
// Measure average inference time over 100 iterations
let startTime = CFAbsoluteTimeGetCurrent()
for _ in 0..<100 {
let input = try prepareInput()
_ = try model.prediction(from: input)
}
let elapsed = CFAbsoluteTimeGetCurrent() - startTime
// Model size
let size = try FileManager.default.attributesOfItem(atPath: url.path)[.size] as? Int ?? 0
print("\(name): \(elapsed/100*1000)ms/inference, \(size/1024/1024)MB")
}
}
private func prepareInput() throws -> MLFeatureProvider {
// Prepare test input
fatalError("Implement based on model requirements")
}
}10. 큰 이미지를 처리할 때 메모리를 어떻게 관리합니까?
고해상도 이미지를 처리하면 메모리 사용량이 급증할 수 있습니다. 기법으로는 스마트 다운샘플링, 타일 기반 처리, 능동적인 리소스 해제가 있습니다.
import Vision
import CoreImage
class MemoryEfficientProcessor {
// Reusable CoreImage context to avoid allocations
private let ciContext = CIContext(options: [
.useSoftwareRenderer: false,
.cacheIntermediates: false // Reduces memory usage
])
// Smart downsampling of large images
func downsampleImage(at url: URL, to maxDimension: CGFloat) -> CGImage? {
// Options for downsampling at read time (avoids loading full image)
let options: [CFString: Any] = [
kCGImageSourceCreateThumbnailFromImageAlways: true,
kCGImageSourceThumbnailMaxPixelSize: maxDimension,
kCGImageSourceCreateThumbnailWithTransform: true,
kCGImageSourceShouldCacheImmediately: false
]
guard let source = CGImageSourceCreateWithURL(url as CFURL, nil),
let image = CGImageSourceCreateThumbnailAtIndex(source, 0, options as CFDictionary) else {
return nil
}
return image
}
// Tile processing for very large images
func processByTiles(
image: CGImage,
tileSize: CGSize,
processor: (CGImage) throws -> [VNObservation]
) throws -> [VNObservation] {
var allObservations: [VNObservation] = []
let imageWidth = CGFloat(image.width)
let imageHeight = CGFloat(image.height)
// Iterate through image by tiles
var y: CGFloat = 0
while y < imageHeight {
var x: CGFloat = 0
while x < imageWidth {
// Calculate tile rectangle
let tileRect = CGRect(
x: x, y: y,
width: min(tileSize.width, imageWidth - x),
height: min(tileSize.height, imageHeight - y)
)
// Extract tile
autoreleasepool {
if let tile = image.cropping(to: tileRect) {
do {
let observations = try processor(tile)
// Adjust coordinates relative to full image
let adjusted = observations.compactMap { obs -> VNObservation? in
guard let detected = obs as? VNDetectedObjectObservation else {
return obs
}
// Recalculate bounding box in global coordinates
var box = detected.boundingBox
box.origin.x = (box.origin.x * tileRect.width + x) / imageWidth
box.origin.y = (box.origin.y * tileRect.height + y) / imageHeight
box.size.width = box.size.width * tileRect.width / imageWidth
box.size.height = box.size.height * tileRect.height / imageHeight
return detected
}
allObservations.append(contentsOf: adjusted)
} catch {
print("Tile processing error: \(error)")
}
}
}
x += tileSize.width * 0.9 // 10% overlap to avoid cutting objects
}
y += tileSize.height * 0.9
}
return allObservations
}
}이미지 처리 루프에서는 항상 autoreleasepool을 사용하고, Vision 요청 클로저의 retain cycle을 확인해야 합니다.
11. Create ML Components로 ML 파이프라인을 어떻게 구현합니까?
Create ML Components(iOS 16+)는 사전 정의된 트랜스포머로 모듈식 ML 파이프라인을 구축할 수 있게 해줍니다. 전통적인 단일 모델보다 더 유연합니다.
import CreateMLComponents
import CoreImage
@available(iOS 16.0, *)
class MLPipeline {
// Image classification pipeline with preprocessing
func createImageClassificationPipeline() throws -> some Transformer<CGImage, String> {
// Transformer composition
let pipeline = ImageReader()
.appending(ImageScaler(targetSize: .init(width: 224, height: 224)))
.appending(ImageNormalizer(mean: [0.485, 0.456, 0.406],
std: [0.229, 0.224, 0.225]))
.appending(try ImageFeaturePrint())
.appending(try NearestNeighborClassifier<String>
.load(from: trainingDataURL))
return pipeline
}
// Custom pipeline with custom steps
func createCustomPipeline() -> some Transformer<CIImage, AnalysisResult> {
// Step 1: Preprocessing
let preprocess = CIImageTransformer { image in
// Apply CoreImage filters
let adjusted = image
.applyingFilter("CIColorControls", parameters: [
kCIInputContrastKey: 1.2,
kCIInputSaturationKey: 1.1
])
return adjusted
}
// Step 2: Detection
let detect = VisionTransformer<CIImage, [VNFaceObservation]> { image in
let request = VNDetectFaceRectanglesRequest()
let handler = VNImageRequestHandler(ciImage: image)
try handler.perform([request])
return request.results ?? []
}
// Step 3: Analysis
let analyze = ResultTransformer<[VNFaceObservation], AnalysisResult> { faces in
AnalysisResult(
faceCount: faces.count,
averageConfidence: faces.map(\.confidence).reduce(0, +) / Float(faces.count)
)
}
return preprocess
.appending(detect)
.appending(analyze)
}
}
struct AnalysisResult {
let faceCount: Int
let averageConfidence: Float
}12. CoreML 모델을 어떻게 테스트하고 검증합니까?
테스트에는 정확도 검증, 성능 테스트, 통합 테스트가 포함됩니다. 다양한 디바이스와 조건에서 테스트하는 것이 매우 중요합니다.
import XCTest
import CoreML
import Vision
class CoreMLModelTests: XCTestCase {
var model: VNCoreMLModel!
override func setUpWithError() throws {
let config = MLModelConfiguration()
config.computeUnits = .cpuOnly // Reproducible on CI
let mlModel = try MyClassifier(configuration: config).model
model = try VNCoreMLModel(for: mlModel)
}
// Accuracy test with validation dataset
func testClassificationAccuracy() async throws {
let testCases: [(imageName: String, expectedClass: String)] = [
("cat_001", "cat"),
("dog_001", "dog"),
("bird_001", "bird")
]
var correct = 0
for testCase in testCases {
let image = try loadTestImage(named: testCase.imageName)
let prediction = try await classify(image: image)
if prediction == testCase.expectedClass {
correct += 1
}
}
let accuracy = Double(correct) / Double(testCases.count)
XCTAssertGreaterThan(accuracy, 0.95, "Accuracy should be > 95%")
}
// Performance test (inference time)
func testInferencePerformance() throws {
let image = try loadTestImage(named: "test_image")
measure(metrics: [XCTClockMetric(), XCTMemoryMetric()]) {
let request = VNCoreMLRequest(model: model)
let handler = VNImageRequestHandler(cgImage: image)
try? handler.perform([request])
}
}
// Transformation robustness test
func testRobustness() async throws {
let originalImage = try loadTestImage(named: "cat_001")
let originalPrediction = try await classify(image: originalImage)
// Test with rotation
let rotated = try applyTransform(originalImage, rotation: .pi / 6)
let rotatedPrediction = try await classify(image: rotated)
XCTAssertEqual(originalPrediction, rotatedPrediction)
// Test with noise
let noisy = try addNoise(to: originalImage, intensity: 0.1)
let noisyPrediction = try await classify(image: noisy)
XCTAssertEqual(originalPrediction, noisyPrediction)
}
// Edge case handling test
func testEdgeCases() async throws {
// Very small image
let smallImage = try loadTestImage(named: "tiny_10x10")
let smallResult = try await classify(image: smallImage)
XCTAssertNotNil(smallResult)
// Monochrome image
let monoImage = try loadTestImage(named: "grayscale")
let monoResult = try await classify(image: monoImage)
XCTAssertNotNil(monoResult)
}
// Helpers
private func classify(image: CGImage) async throws -> String {
let request = VNCoreMLRequest(model: model)
let handler = VNImageRequestHandler(cgImage: image)
try handler.perform([request])
guard let results = request.results as? [VNClassificationObservation],
let top = results.first else {
throw TestError.noResults
}
return top.identifier
}
private func loadTestImage(named: String) throws -> CGImage {
guard let url = Bundle(for: type(of: self))
.url(forResource: named, withExtension: "jpg"),
let source = CGImageSourceCreateWithURL(url as CFURL, nil),
let image = CGImageSourceCreateImageAtIndex(source, 0, nil) else {
throw TestError.imageNotFound
}
return image
}
}iOS 면접 준비가 되셨나요?
인터랙티브 시뮬레이터, flashcards, 기술 테스트로 연습하세요.
시스템 디자인 질문
13. 운영 환경 앱을 위한 온디바이스 ML 아키텍처를 어떻게 설계합니까?
견고한 ML 아키텍처는 책임을 분리합니다. 모델, 전처리, 후처리, 캐싱입니다. 모델 업데이트와 점진적 폴백을 관리해야 합니다.
import CoreML
import Vision
// Protocol for model abstraction
protocol MLModelProvider {
associatedtype Input
associatedtype Output
func predict(_ input: Input) async throws -> Output
var modelVersion: String { get }
}
// Model manager with OTA updates
class ModelManager {
static let shared = ModelManager()
private var models: [String: any MLModel] = [:]
private let modelDirectory: URL
private init() {
modelDirectory = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)[0]
.appendingPathComponent("MLModels")
try? FileManager.default.createDirectory(at: modelDirectory, withIntermediateDirectories: true)
}
// Load model with fallback to bundled version
func loadModel<T: MLModel>(
named name: String,
type: T.Type
) async throws -> T {
// Check if downloaded version exists
let downloadedURL = modelDirectory.appendingPathComponent("\(name).mlmodelc")
if FileManager.default.fileExists(atPath: downloadedURL.path) {
// Validate downloaded model integrity
do {
let model = try await loadAndValidate(from: downloadedURL, type: type)
return model
} catch {
// Fallback to bundled version if corrupted
print("Downloaded model corrupted, falling back to bundled version")
try? FileManager.default.removeItem(at: downloadedURL)
}
}
// Load bundled version
guard let bundledURL = Bundle.main.url(forResource: name, withExtension: "mlmodelc") else {
throw ModelError.modelNotFound(name)
}
return try await loadAndValidate(from: bundledURL, type: type)
}
// Download and install new model version
func updateModel(named name: String, from url: URL) async throws {
// Download model
let (tempURL, _) = try await URLSession.shared.download(from: url)
// Compile model if needed
let compiledURL: URL
if tempURL.pathExtension == "mlmodel" {
compiledURL = try MLModel.compileModel(at: tempURL)
} else {
compiledURL = tempURL
}
// Validate before installation
let config = MLModelConfiguration()
_ = try MLModel(contentsOf: compiledURL, configuration: config)
// Install in models directory
let destURL = modelDirectory.appendingPathComponent("\(name).mlmodelc")
try? FileManager.default.removeItem(at: destURL)
try FileManager.default.moveItem(at: compiledURL, to: destURL)
// Notify app of update
NotificationCenter.default.post(name: .modelUpdated, object: name)
}
private func loadAndValidate<T: MLModel>(
from url: URL,
type: T.Type
) async throws -> T {
let config = MLModelConfiguration()
config.computeUnits = .all
let model = try T(contentsOf: url, configuration: config)
// Basic model validation
// Verify inputs/outputs match expectations
return model
}
}
extension Notification.Name {
static let modelUpdated = Notification.Name("MLModelUpdated")
}14. 운영 환경에서 오류와 모니터링을 어떻게 처리합니까?
견고한 모니터링 시스템은 성능 지표와 오류를 수집하고 원격 디버깅을 가능하게 합니다. 분석 도구와의 통합은 필수적입니다.
import OSLog
class MLMonitor {
static let shared = MLMonitor()
private let logger = Logger(subsystem: "com.app.ml", category: "inference")
private var metrics: [InferenceMetric] = []
struct InferenceMetric: Codable {
let modelName: String
let inferenceTime: Double
let inputSize: CGSize?
let confidence: Float?
let timestamp: Date
let success: Bool
let errorDescription: String?
}
// Record an inference
func recordInference(
model: String,
duration: TimeInterval,
inputSize: CGSize? = nil,
confidence: Float? = nil,
error: Error? = nil
) {
let metric = InferenceMetric(
modelName: model,
inferenceTime: duration,
inputSize: inputSize,
confidence: confidence,
timestamp: Date(),
success: error == nil,
errorDescription: error?.localizedDescription
)
metrics.append(metric)
// Log for debugging
if let error = error {
logger.error("ML inference failed: \(model) - \(error.localizedDescription)")
} else {
logger.info("ML inference: \(model) completed in \(duration)s")
}
// Detect anomalies
checkForAnomalies(metric)
}
// Wrapper for automatic measurement
func measure<T>(
model: String,
inputSize: CGSize? = nil,
operation: () async throws -> T
) async rethrows -> T {
let start = CFAbsoluteTimeGetCurrent()
do {
let result = try await operation()
let duration = CFAbsoluteTimeGetCurrent() - start
recordInference(
model: model,
duration: duration,
inputSize: inputSize
)
return result
} catch {
let duration = CFAbsoluteTimeGetCurrent() - start
recordInference(
model: model,
duration: duration,
inputSize: inputSize,
error: error
)
throw error
}
}
// Detect performance issues
private func checkForAnomalies(_ metric: InferenceMetric) {
// Alert if inference time exceeds threshold
if metric.inferenceTime > 1.0 {
logger.warning("Slow inference detected: \(metric.modelName) took \(metric.inferenceTime)s")
// Send alert if available
Task {
await AnalyticsService.shared.reportAnomaly(
type: .slowInference,
details: metric
)
}
}
// Alert if confidence is too low
if let confidence = metric.confidence, confidence < 0.5 {
logger.info("Low confidence prediction: \(confidence) for \(metric.modelName)")
}
}
// Generate performance report
func generateReport() -> PerformanceReport {
let recentMetrics = metrics.filter {
$0.timestamp > Date().addingTimeInterval(-3600) // Last hour
}
let avgInferenceTime = recentMetrics.map(\.inferenceTime).reduce(0, +) / Double(recentMetrics.count)
let successRate = Double(recentMetrics.filter(\.success).count) / Double(recentMetrics.count)
return PerformanceReport(
totalInferences: recentMetrics.count,
averageInferenceTime: avgInferenceTime,
successRate: successRate,
modelBreakdown: Dictionary(grouping: recentMetrics, by: \.modelName)
)
}
}
struct PerformanceReport {
let totalInferences: Int
let averageInferenceTime: Double
let successRate: Double
let modelBreakdown: [String: [MLMonitor.InferenceMetric]]
}결론
Vision Framework와 CoreML은 iOS에서 온디바이스 머신러닝의 토대를 이룹니다. 이러한 기술을 익히는 것은 사용자의 프라이버시를 존중하면서 고급 ML 기능을 제공하는 현대적인 애플리케이션을 개발하는 데 필수적입니다.
점검 체크리스트
- ✅ CoreML과 그 장점(프라이버시, 지연 시간, 오프라인) 이해
- ✅ TensorFlow/PyTorch 모델을 CoreML로 변환할 수 있음
- ✅ Vision 요청(얼굴 감지, OCR, 분류) 숙달
- ✅ 실시간 객체 추적 구현
- ✅ 성능 최적화(양자화, 메모리 관리)
- ✅ 운영 환경을 위한 견고한 ML 아키텍처 설계
- ✅ 모니터링 및 오류 처리 구축
핵심 포인트
온디바이스 성능은 CPU, GPU, Neural Engine 중 어느 것을 선택하느냐에 크게 좌우됩니다. 모델 양자화는 크기와 성능 사이의 훌륭한 절충안을 제공합니다. 운영 환경에서의 모니터링은 회귀를 감지하는 데 필수적입니다.
연습을 시작하세요!
면접 시뮬레이터와 기술 테스트로 지식을 테스트하세요.
태그
공유
관련 기사

2026년 MapKit SwiftUI 면접: 어노테이션, 오버레이, 지오로케이션
iOS 면접을 위한 SwiftUI와 MapKit 마스터하기: 사용자 정의 어노테이션, 오버레이, 지오로케이션, 장소 검색, Maps 통합 패턴.

StoreKit 2 인터뷰: 구독 관리 및 영수증 검증
StoreKit 2, 구독 관리, 영수증 검증, 인앱 구매 구현에 관한 iOS 인터뷰 질문을 실용적인 Swift 코드 예제와 함께 마스터하십시오.

Swift Testing Framework 면접 2026: #expect와 #require 매크로 vs XCTest
iOS 면접을 위한 새로운 Swift Testing Framework를 마스터합니다: #expect와 #require 매크로, XCTest 마이그레이션, 고급 패턴 및 흔한 함정.