菜鸟教程小白 发表于 2022-12-13 05:25:13

ios - 在新的运行时使用 objc_msgSend 而不进行强制转换的替代方法


                                            <p><p>我正在编写一个 objc 桥,我发现了一种使用 objc_msgSend 调用 objc 方法的非常有效的方法。</p>

<p>基本上,该代码能够生成一个宏,该宏将传递给 objc_msg 从 NSArray 发送正确数量的参数(需要 metamacros.h)。</p>

<pre><code>#import &#34;metamacros.h&#34;

#define CFIEXTRACTARGS(COUNT, ARR) \
, ARR \

#define objc_call(RECIEVER, SELECTOR, COUNT, ARR) \
objc_msgSend(RECIEVER, SELECTOR \
metamacro_for_cxt(COUNT, CFIEXTRACTARGS,, ARR) \
) \
</code></pre>

<p>一切都按预期工作并且非常好,但不幸的是我刚刚发现由于 Apple 在最近的运行时更改,如果没有显式转换为正确的函数指针,就无法直接调用 objc_msgSend。</p>

<p>发件人:<a href="https://developer.apple.com/library/ios/documentation/General/Conceptual/CocoaTouch64BitGuide/ConvertingYourAppto64-Bit/ConvertingYourAppto64-Bit.html" rel="noreferrer noopener nofollow">https://developer.apple.com/library/ios/documentation/General/Conceptual/CocoaTouch64BitGuide/ConvertingYourAppto64-Bit/ConvertingYourAppto64-Bit.html</a>
:“虽然消息函数的原型(prototype)是可变参数形式,但Objective-C运行时调用的方法函数并不共享同一个原型(prototype)。Objective-C运行时直接调度到实现该方法的函数,所以调用约定不匹配,如前所述。因此,您必须将 objc_msgSend 函数转换为与被调用方法函数匹配的原型(prototype)。"</p>

<p>例子:</p>

<pre><code>- (int) doSomething:(int) x { ... }
- (void) doSomethingElse {
    int (*action)(id, SEL, int) = (int (*)(id, SEL, int)) objc_msgSend;
    action(self, @selector(doSomething:), 0);
}
</code></pre>

<p>显然我不能为每个函数组合创建一个强制转换,所以我想知道什么是有效的替代方案。 NSInvocation 真的太慢了​​。</p></p>
                                    <br><hr><h1><strong>Best Answer-推荐答案</ strong></h1><br>
                                            <p><p>使用 <code>NSArray</code> 作为参数有点困惑,这肯定意味着它们都必须是对象类型?</p>

<p>但是,除了这个问题,没有必要停止使用基于宏的方法,只需添加强制转换作为参数:</p>

<pre><code>#define objc_call(RECIEVER, TYPE, SELECTOR, COUNT, ARR) \
(TYPE objc_msgSend)(RECIEVER, SELECTOR \
metamacro_for_cxt(COUNT, CFIEXTRACTARGS,, ARR) \
)
</code></pre>

<p>并按如下方式使用:</p>

<pre><code>- (NSString *) doSomething:(NSString *) x { ... }
</code></pre>

<p>(注意使用 <code>NSString</code> - 一种对象类型,与问题中的 <code>int</code> 不同)</p>

<pre><code>- (void) doSomethingElseToo
{
   NSArray *args = @[ @&#34;too&#34; ];
   NSString *res = objc_call(self, (NSString * (*)(id, SEL, NSString *)), @selector(doSomething:), 1, args);
   NSLog(@&#34;res = %@&#34;, res);
}
</code></pre>

<p>HTH</p>

<p><strong>附录</strong></p>

<p>@RichardJ.RossIII 在评论中建议他认为您希望完全避免提供任何类型转换;并不是说您希望避免针对不同类型使用不同的宏,我认为是这种情况并在上面提供了解决方案。</p>

<p>鉴于您希望避免 <code>NSInvocation</code> 让我们再次查看您的宏。如前所述,它使用 <code>NSArray</code> 从中提取参数,因此每个参数都是一个对象,即 <code>id</code> 类型。如果所有参数都属于同一类型,则可能的强制转换数量会大大减少。</p>

<p>我们已经考虑了参数,您希望支持多少种返回类型?让我们暂时假设它是一个,<code>id</code>。以下两个宏将完成最多 19 个参数的工作:</p>

<pre><code>#define metamacro_cast(COUNT) \
metamacro_at(COUNT, \
(id (*)(id, SEL)), \
(id (*)(id, SEL, id)), \
(id (*)(id, SEL, id, id)), \
(id (*)(id, SEL, id, id, id)), \
... // repeat until there are 19 id&#39;s
)

#define objc_call2(RECIEVER, SELECTOR, COUNT, ARR) \
(metamacro_cast(COUNT) objc_msgSend)(RECIEVER, SELECTOR \
metamacro_for_cxt(COUNT, CFIEXTRACTARGS,, ARR) \
)
</code></pre>

<p>这个宏的用法和你的完全一样,没有多余的参数:</p>

<pre><code>- (void) doSomethingElseThree
{
   NSArray *args = @[ @&#34;three&#34; ];
   NSString *res = objc_call2(self, @selector(doSomething:), 1, args);
   NSLog(@&#34;res = %@&#34;, res);
}
</code></pre>

<p>如果您希望支持多种返回类型,则需要多个 <code>objc_call_returning_type</code> 宏;或者写一个只接受返回类型的宏,不像我第一个接受整个 Actor 的宏,然后用 <code>metamacros.h</code> 尝试更多的宏体操。</p></p>
                                   
                                                <p style="font-size: 20px;">关于ios - 在新的运行时使用 objc_msgSend 而不进行强制转换的替代方法,我们在Stack Overflow上找到一个类似的问题:
                                                        <a href="https://stackoverflow.com/questions/28612101/" rel="noreferrer noopener nofollow" style="color: red;">
                                                                https://stackoverflow.com/questions/28612101/
                                                        </a>
                                                </p>
                                       
页: [1]
查看完整版本: ios - 在新的运行时使用 objc_msgSend 而不进行强制转换的替代方法