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

基于metal-cpp实现NSThread时遇selector为空的崩溃问题求助

问题:metal-cpp自定义NSThread调用崩溃,报错target不实现selector((null))

我在基于metal-cpp封装应用实现自定义Cocoa主循环时,需要调用类似Objective-C的[NSThread detachNewThreadSelector:@selector(doNothing:) toTarget:stubTarget withObject:nil];逻辑,但metal-cpp没有NSThread类,所以自行实现了该类,代码如下:

自定义NSThread实现代码

metal-cpp/Foundation/NSThread.hpp

#pragma once

#include <functional>

#include "NSObject.hpp"

namespace NS
{

class Runnable
{
public:
    virtual ~Runnable() {}
    virtual void run() {}
};

class Thread: public NS::Referencing< Thread >
{
public:
    static void detachNewThread( const Runnable* pRunnable );
};

_NS_INLINE void NS::Thread::detachNewThread( const Runnable* pRunnable )
{
    NS::Value* pWrapper = NS::Value::value( pRunnable );

    typedef void (*DispatchFunction)( NS::Value*, SEL, void* );
    
    DispatchFunction run = []( Value* pSelf, SEL, void* unused )
    {
        auto pDel = reinterpret_cast< NS::Runnable* >( pSelf->pointerValue() );
        pDel->run();
    };

    return Object::sendMessage<void>(_NS_PRIVATE_CLS(NSThread), _NS_PRIVATE_SEL(detachNewThreadSelector_toTarget_withObject_), run, pWrapper, nullptr );
}

}

私有选择器注册(metal-cpp/Foundation/NSPrivate.hpp)

_NS_PRIVATE_DEF_SEL(detachNewThreadSelector_toTarget_withObject_,
    "detachNewThreadSelector:toTarget:withObject:");

同时已将NSThread引入主头文件metal-cpp/Foundation/Foundation.hpp。

应用调用代码

class MainLoopTarget: public NS::Runnable
{
    public:
        void run() override
        {
            std::cout << "run()" << std::endl;
        }
};

auto target = new MainLoopTarget();
NS::AutoreleasePool* autorelease_pool = NS::AutoreleasePool::alloc()->init();

NS::Thread::detachNewThread(target);

autorelease_pool->release();
delete target;

崩溃报错信息

*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[NSThread initWithTarget:selector:object:]: target does not implement selector ((null))'
*** First throw call stack:
(
        0   CoreFoundation                      0x00000001856aeccc __exceptionPreprocess + 176
        1   libobjc.A.dylib                     0x0000000185196788 objc_exception_throw + 60
        2   Foundation                          0x00000001867641d0 -[NSThread setQualityOfService:] + 0
        3   Foundation                          0x00000001867c1118 +[NSThread detachNewThreadSelector:toTarget:withObject:] + 60
        4   libb3engine.dylib                   0x000000010506bb88 _ZN2b38platform6darwin9DarwinApp5startEv + 336
        5   B3                                  0x0000000104b7dd4c main + 252
        6   dyld                                0x00000001851d20e0 start + 2360
)
libc++abi: terminating due to uncaught exception of type NSException

已知问题出在类方法参数传递,但Objective-C基础薄弱,无法定位具体问题,求解决。


解决方案

崩溃的核心原因是对detachNewThreadSelector:toTarget:withObject:的参数传递逻辑完全错误:

  1. 参数顺序与类型不匹配
    Objective-C的+[NSThread detachNewThreadSelector:toTarget:withObject:]三个参数的要求是:

    • 第一个参数:SEL选择器,必须是目标对象能响应的实例方法(签名为- (void)selectorName:(id)object;)
    • 第二个参数:Objective-C对象实例(NSObject子类),要能响应上述选择器
    • 第三个参数:传递给选择器的参数

    你现在传递的第一个参数是run(一个C++函数指针),不是有效的SEL,导致系统认为选择器是null,进而报错目标对象不实现该选择器。

  2. C++对象无法直接作为Objective-C目标
    你封装的NS::Runnable是纯C++类,不是NSObject子类,无法被Objective-C运行时识别为有效的target对象。

修正后的实现方案

1. 调整NSThread.hpp,使用Objective-C兼容的目标对象

#pragma once

#include "NSObject.hpp"

namespace NS
{

class Runnable : public NS::Object
{
public:
    virtual ~Runnable() {}
    virtual void run() = 0;

    // 定义Objective-C兼容的选择器方法
    static void runSelector(NS::Object* pSelf, SEL, void*);
};

class Thread: public NS::Referencing<Thread>
{
public:
    static void detachNewThread(Runnable* pRunnable);
};

_NS_INLINE void NS::Runnable::runSelector(NS::Object* pSelf, SEL, void*)
{
    auto pRunnable = static_cast<Runnable*>(pSelf);
    pRunnable->run();
}

_NS_INLINE void NS::Thread::detachNewThread(Runnable* pRunnable)
{
    // 注册选择器(确保在运行时存在)
    SEL runSel = NS::Selector::registerName("runSelector:");

    // 调用Objective-C的NSThread方法,参数顺序正确:选择器、target、参数
    Object::sendMessage<void>(
        _NS_PRIVATE_CLS(NSThread),
        _NS_PRIVATE_SEL(detachNewThreadSelector_toTarget_withObject_),
        runSel,
        pRunnable,
        nullptr
    );
}

}

2. 应用调用代码调整(遵循metal-cpp内存管理规则)

class MainLoopTarget: public NS::Runnable
{
public:
    void run() override
    {
        std::cout << "run()" << std::endl;
        // 线程结束后释放自身
        release();
    }
};

NS::AutoreleasePool* autorelease_pool = NS::AutoreleasePool::alloc()->init();

// 使用metal-cpp的alloc/init创建对象,避免直接delete
MainLoopTarget* target = MainLoopTarget::alloc()->init();
NS::Thread::detachNewThread(target);

autorelease_pool->release();

关键修改点说明

  • 让Runnable继承自NS::Object,使其成为Objective-C兼容对象
  • 定义runSelector:这个符合Objective-C方法签名的静态方法,作为线程调用的入口
  • 传递正确的SEL选择器作为第一个参数,而非C++函数指针
  • 遵循metal-cpp的内存管理规则,使用alloc/init和release管理对象,避免直接用delete销毁Objective-C兼容对象

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 12:35:00