I am putting together a simple video editor app for iOS. The videos are exported with a "watermark" in the form of a text overlay (e.g. "This video was made with XYZ").
The app (still a prototype) was working until around February this year. Then I got busy, moved to other projects and stopped working on it for a while.
About a months ago, I resumed work on the app but suddenly noticed that it was crashing whenever I attempted to export any video.
The crash looks like this:
libxpc.dylib`_xpc_api_misuse: 0x7fff51c53154 <+0>: pushq %rbp 0x7fff51c53155 <+1>: movq %rsp, %rbp 0x7fff51c53158 <+4>: pushq %rbx 0x7fff51c53159 <+5>: subq $0xa8, %rsp 0x7fff51c53160 <+12>: movq %rdi, %r9 0x7fff51c53163 <+15>: movaps 0xdba6(%rip), %xmm0 ; __xpcVersionNumber + 160 0x7fff51c5316a <+22>: leaq -0xb0(%rbp), %rbx 0x7fff51c53171 <+29>: movaps %xmm0, 0x90(%rbx) 0x7fff51c53178 <+36>: movaps %xmm0, 0x80(%rbx) 0x7fff51c5317f <+43>: movaps %xmm0, 0x70(%rbx) 0x7fff51c53183 <+47>: movaps %xmm0, 0x60(%rbx) 0x7fff51c53187 <+51>: movaps %xmm0, 0x50(%rbx) 0x7fff51c5318b <+55>: movaps %xmm0, 0x40(%rbx) 0x7fff51c5318f <+59>: movaps %xmm0, 0x30(%rbx) 0x7fff51c53193 <+63>: movaps %xmm0, 0x20(%rbx) 0x7fff51c53197 <+67>: movaps %xmm0, 0x10(%rbx) 0x7fff51c5319b <+71>: movaps %xmm0, (%rbx) 0x7fff51c5319e <+74>: leaq 0x1150d(%rip), %r8 ; "XPC API Misuse: %s" 0x7fff51c531a5 <+81>: movl $0xa0, %esi 0x7fff51c531aa <+86>: movl $0xa0, %ecx 0x7fff51c531af <+91>: movq %rbx, %rdi 0x7fff51c531b2 <+94>: movl $0x0, %edx 0x7fff51c531b7 <+99>: xorl %eax, %eax 0x7fff51c531b9 <+101>: callq 0x7fff51c5fe18 ; symbol stub for: __snprintf_chk 0x7fff51c531be <+106>: movq %rbx, 0x380787c3(%rip) ; gCRAnnotations + 8 0x7fff51c531c5 <+113>: leaq 0x114f9(%rip), %rax ; "API Misuse" 0x7fff51c531cc <+120>: movq %rax, 0x380787bd(%rip) ; gCRAnnotations + 16 -> 0x7fff51c531d3 <+127>: ud2 < Thread 55: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)
After experimenting a bit by eliminating features, I found out that the app crashes only if the exported video compositions contain the text overlay: if I comment out the code responsible for overlaying the text layer, the issue resolves.
This is the code I am using to export the video composition:
func export(completion: @escaping (() -> Void), failure: @escaping ((Error) -> Void)) { let exportQuality = AVAssetExportPresetHighestQuality guard let exporter = AVAssetExportSession(asset: composition, presetName: exportQuality) else { return failure(ProjectError.failedToCreateExportSession) } guard let documents = try? FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true) else { return failure(ProjectError.temporaryOutputDirectoryNotFound) } let dateFormatter = DateFormatter() dateFormatter.dateFormat = "yyyy-MM-dd_HHmmss" let fileName = dateFormatter.string(from: Date()) let fileExtension = "mov" let fileURL = documents.appendingPathComponent(fileName).appendingPathExtension(fileExtension) exporter.outputURL = fileURL exporter.outputFileType = AVFileType.mov exporter.shouldOptimizeForNetworkUse = true if shouldAddWatermark { // Watermark overlay: let frame = CGRect(origin: .zero, size: videoComposition.renderSize) let watermark = WatermarkLayer(frame: frame) let parentLayer = CALayer() let videoLayer = CALayer() parentLayer.frame = frame videoLayer.frame = frame parentLayer.addSublayer(videoLayer) parentLayer.addSublayer(watermark) videoComposition.animationTool = AVVideoCompositionCoreAnimationTool(postProcessingAsVideoLayer: videoLayer, in: parentLayer) } exporter.videoComposition = videoComposition exporter.exportAsynchronously { if exporter.status == .completed { /* Composition was successfully saved to the documents folder. Now create a new asset in the device's camera roll (i.e. photo library): */ AssetLibrary.saveVideo(at: fileURL, completion: { completion() }, failure: {(error) in failure(ProjectError.failedToExportToPhotoLibrary(detail: error?.localizedDescription ?? "Unknown")) }) } else { DispatchQueue.main.async { failure(ProjectError.failedToExportComposition(detail: exporter.error?.localizedDescription ?? "Unknown")) } } } }
The WatermarkLayer class used above is defined as follows:
class WatermarkLayer: CATextLayer { // MARK: - Constants private let defaultFontSize: CGFloat = 48 private let rightMargin: CGFloat = 10 private let bottomMargin: CGFloat = 10 // MARK: - Initialization init(frame: CGRect) { super.init() guard let appName = Bundle.main.infoDictionary?["CFBundleName"] as? String else { fatalError("!!!") } self.foregroundColor = CGColor.srgb(r: 255, g: 255, b: 255, a: 0.5) self.backgroundColor = CGColor.clear self.string = String(format: String.watermarkFormat, appName) self.font = CTFontCreateWithName(String.watermarkFontName as CFString, defaultFontSize, nil) self.fontSize = defaultFontSize self.shadowOpacity = 0.75 self.alignmentMode = .right self.frame = frame } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented. Use init(frame:) instead.") } // MARK: - CALayer override func draw(in ctx: CGContext) { let height = self.bounds.size.height let fontSize = self.fontSize //let yDiff = (height-fontSize)/2 - fontSize/10 // Center let yDiff = (height-fontSize) - fontSize/10 - bottomMargin // Bottom (minus margin) ctx.saveGState() ctx.translateBy(x: -rightMargin, y: yDiff) super.draw(in: ctx) ctx.restoreGState() } }
What's going on? Which API is being "misused"?