GPUImage 子线程访问 UI 的输出警告处理

2,986 阅读1分钟

在使用 第三方框架 GPUImage 的时候,项目中控制台会输出一下警告:

Main Thread Checker: UI API called on a background thread: -[UIView bounds] PID: 2791, TID: 521508, Thread name: (none), Queue name: com.sunsetlakesoftware.GPUImage.openGLESContextQueue

原因很明确就是在子线程访问 UI 元素了。

首先找到是哪里的代码在子线程访问 UI 元素,最后发现是在 GPUImageView.m文件的230行- (void)recalculateViewGeometry方法中的这一行代码CGSize currentViewSize = self.bounds.size;

子线程访问 UI 代码

既然找到问题了接下来就是处理问题,处理方法也非常简单,在这里我们做一个判断是否为主线程,然后自己写一个方法,最后调用 GPUImageView的原有实现:

- (void)recalculateViewGeometry
{
    NSLog(@"Is main thread: %@", [NSThread isMainThread] ? @"YES" : @"NO");
    
    if ([NSThread isMainThread] == NO) {
        dispatch_async(dispatch_get_main_queue(), ^{
            [self my_recalculateViewGeometryWithSize:self.bounds.size];
        });
    } else { 
        [self my_recalculateViewGeometryWithSize:self.bounds.size];
    }
}

- (void)my_recalculateViewGeometryWithSize:(CGSize)size
{
    runSynchronouslyOnVideoProcessingQueue(^{
            CGFloat heightScaling, widthScaling;
            
            CGSize currentViewSize = size;
            
            //    CGFloat imageAspectRatio = inputImageSize.width / inputImageSize.height;
            //    CGFloat viewAspectRatio = currentViewSize.width / currentViewSize.height;
            
            CGRect insetRect = AVMakeRectWithAspectRatioInsideRect(inputImageSize, self.bounds);
            
            switch(_fillMode)
            {
                case kGPUImageFillModeStretch:
                {
                    widthScaling = 1.0;
                    heightScaling = 1.0;
                }; break;
                case kGPUImageFillModePreserveAspectRatio:
                {
                    widthScaling = insetRect.size.width / currentViewSize.width;
                    heightScaling = insetRect.size.height / currentViewSize.height;
                }; break;
                case kGPUImageFillModePreserveAspectRatioAndFill:
                {
                    //            CGFloat widthHolder = insetRect.size.width / currentViewSize.width;
                    widthScaling = currentViewSize.height / insetRect.size.height;
                    heightScaling = currentViewSize.width / insetRect.size.width;
                }; break;
            }
            
            imageVertices[0] = -widthScaling;
            imageVertices[1] = -heightScaling;
            imageVertices[2] = widthScaling;
            imageVertices[3] = -heightScaling;
            imageVertices[4] = -widthScaling;
            imageVertices[5] = heightScaling;
            imageVertices[6] = widthScaling;
            imageVertices[7] = heightScaling;
        });
        
    //    static const GLfloat imageVertices[] = {
    //        -1.0f, -1.0f,
    //        1.0f, -1.0f,
    //        -1.0f,  1.0f,
    //        1.0f,  1.0f,
    //    };
}

有的同学可能会担心,如果是主线程,那么后续的操作是不是都在主线程进行了,这和 GPUImage 原来的代码执行流程不一样。这个担心是多余的,我们可以查看runSynchronouslyOnVideoProcessingQueue()这个函数的具体实现,在里面可以发现在这个函数内进行了判断,如果当前队列是 videoProcessingQueue, 那么就直接执行代码块,不是的话就调用 dispatch_sync(videoProcessingQueue, block); 扔到 videoProcessingQueue里面执行这个代码块。下面是简化后的 runSynchronouslyOnVideoProcessingQueue() 函数实现代码。

void runSynchronouslyOnVideoProcessingQueue(void (^block)(void))
{
    dispatch_queue_t videoProcessingQueue = [GPUImageContext sharedContextQueue];

    if (dispatch_get_current_queue() == videoProcessingQueue) {
        block();
    } else {
        dispatch_sync(videoProcessingQueue, block);
    }
}