You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

iOS静态库中使用Skia时的内存异常问题排查

iOS下Skia静态库集成的内存异常问题

Skia编译命令

bin/gn gen out/ios-apple --args='target_os="ios" target_cpu="arm64" ios_use_simulator=false skia_enable_tools=false is_official_build=false is_debug=true is_trivial_abi=false skia_use_metal=true skia_use_expat=false skia_use_system_expat=false skia_use_system_libpng=false skia_use_system_libwebp=false skia_use_system_zlib=false skia_use_system_freetype2=false skia_use_system_harfbuzz=false skia_use_system_icu=false skia_enable_gpu=true skia_enable_skottie=false skia_compile_modules=true extra_cflags=["-F/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/System/Library/Frameworks"] extra_ldflags=["-framework", "CoreFoundation", "-framework", "Metal"]'

ViewController中的Skia使用代码

头文件

#import <MetalKit/MetalKit.h>
#import <Metal/Metal.h>
#import <UIKit/UIKit.h>

#define SK_GANESH
#define SK_METAL

#import <include/gpu/ganesh/GrDirectContext.h>
#import <include/core/SkSurface.h>

@interface SkiaViewController : UIViewController <MTKViewDelegate>

@property (nonatomic, strong) MTKView *mtkView;
@property (nonatomic, strong) id<MTLDevice> metalDevice;
@property (nonatomic, strong) id<MTLCommandQueue> metalQueue;

@property (nonatomic, assign) sk_sp<GrDirectContext> grDirectContext;
@property (nonatomic, assign) sk_sp<SkSurface> surface;
@end

实现文件

#import <MetalKit/MetalKit.h>
#import <Metal/Metal.h>

#define SK_GANESH
#define SK_METAL

#import "include/gpu/ganesh/GrTypes.h"
#import "include/gpu/ganesh/SkSurfaceGanesh.h"
#import "include/gpu/ganesh/mtl/GrMtlTypes.h"

#import <include/gpu/ganesh/GrBackendSurface.h>
#import <include/gpu/ganesh/SkSurfaceGanesh.h>
#import <include/gpu/ganesh/mtl/GrMtlBackendContext.h>
#import <include/gpu/ganesh/mtl/GrMtlBackendSurface.h>
#import <include/gpu/ganesh/mtl/GrMtlDirectContext.h>
#import <include/gpu/ganesh/mtl/SkSurfaceMetal.h>

#import <include/gpu/ganesh/GrDirectContext.h>
#import <include/core/SkSurface.h>
#import <include/core/SkCanvas.h>
#import <include/core/SkColorSpace.h>

#import "SkiaViewController.h"

@implementation SkiaViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
#ifndef SK_GANESH
    NSLog(@"SK_GANESH is undefined");
#endif
    
#ifndef SK_METAL
    NSLog(@"SK_METAL is undefined");
#endif
    
    [self setMetalDevice:MTLCreateSystemDefaultDevice()];
    [self setMetalQueue:[[self metalDevice] newCommandQueue]];
    
    if(![self metalDevice]) {
        NSLog(@"Metal is not supported on this device");
        return;
    }
    
    if(![self metalQueue]) {
        NSLog(@"Failed to create Metal command queue");
        return;
    }
    
    // Initialize MTKView
    self.mtkView = [[MTKView alloc] initWithFrame:self.view.bounds device:self.metalDevice];
    self.mtkView.delegate = self;
    self.mtkView.enableSetNeedsDisplay = YES;
    
    [self.mtkView setDepthStencilPixelFormat:MTLPixelFormatDepth32Float_Stencil8];
    [self.mtkView setColorPixelFormat:MTLPixelFormatBGRA8Unorm];
    [self.mtkView setSampleCount:1];
    
    NSLog(@"self.metalDevice.maxBufferLength=%lu", self.metalDevice.maxBufferLength);
    
    self.mtkView.layer.borderWidth = 2.0; // Set border width
    self.mtkView.layer.borderColor = [UIColor redColor].CGColor; // Set border color
    
    [self.view addSubview:self.mtkView];
    
    // Initialize Skia with Metal
    GrMtlBackendContext backendContext = {};
    backendContext.fDevice.retain((__bridge void*)[self metalDevice]); // also tried __bridge_retained
    backendContext.fQueue.retain((__bridge void*)[self metalQueue]);
    
    GrContextOptions grContextOptions;
    
    self.grDirectContext = GrDirectContexts::MakeMetal(backendContext, grContextOptions);
    
    if (![self grDirectContext]) {
        NSLog(@"Failed to create GrDirectContext");
        return;
    }
    
    NSLog(@"Created GrDirectContext");
}

#pragma mark - MTKViewDelegate

- (void)drawInMTKView:(nonnull MTKView *)view {
}

- (void)mtkView:(nonnull MTKView *)view drawableSizeWillChange:(CGSize)size {
    
}

@end

问题现象

直接在主App项目中使用上述代码时运行正常,但将ViewController移入项目内的静态库,或把Skia初始化逻辑放到独立CMake编译的C++库中时,会出现内存异常:

  • 调用GrDirectContexts::MakeMetal时触发坏内存访问崩溃
  • 刚初始化的backendContext指针被莫名置为nullptr
  • 即使不传递Objective-C对象给Skia,sk_sp智能指针内部的指针也会在释放等操作时失效

可能的原因分析

1. 符号冲突或链接剥离问题

静态库和主App如果都链接了Skia,会导致同一符号(比如sk_sp的引用计数方法)存在多份实现。iOS链接器在处理静态库时,可能错误地剥离掉部分必要符号,或者因为符号重复导致代码执行时调用了错误的实现,进而引发内存访问错误。

2. 编译选项不一致

静态库、主App与Skia的编译选项必须完全匹配,否则会导致内存布局或ABI不兼容:

  • C++标准版本:确保三者都使用相同的C标准(比如C17)
  • Debug/Release模式:Skia编译用了is_debug=true,静态库和主App也必须是Debug模式
  • Trivial ABI设置:Skia设置了is_trivial_abi=false,静态库和主App的C++编译选项中也要关闭Trivial ABI(对应Xcode中的-fno-trivial-abi)
  • 架构一致性:确保所有目标都编译为arm64,没有混合架构

3. 符号可见性配置错误

Skia编译时如果没有正确设置符号可见性,会导致静态库中的Skia内部符号被隐藏,主App访问时出现未定义行为。可以在Skia的gn参数中添加visibility="default",确保必要符号对外可见。

4. 内存分配器不一致

虽然iOS默认使用统一的系统内存分配器,但如果静态库或Skia编译时修改了内存分配选项(比如自定义malloc),会导致sk_sp在释放对象时使用错误的分配器,引发崩溃。

5. 智能指针跨边界的生命周期问题

sk_sp是Skia自定义的智能指针,在静态库和主App之间传递时,如果两者的Skia版本不一致,或者编译时的宏定义不同(比如SK_GANESH、SK_METAL是否统一),会导致智能指针的内部逻辑不一致,进而引发引用计数错误。

内容的提问来源于stack exchange,提问作者artem

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 05:15:01