刺猬的爱情 發表於 2023-3-31 14:16:43

objc方法声明和实现由于参数类型不一致所引发的崩溃

<div id="navCategory"><h5 class="catalogue">目录</h5><ul class="first_class_ul"><li>正文</li><li>分析</li><li>补充</li><li>总结</li></ul></div><p class="maodian"></p><h2>正文</h2>
<p>你有注意过objc方法声明处和方法实现处参数类型不一致的情况吗,就像这样:</p>
<div class="jb51code"><pre class="brush:cpp;">@interface Person : NSObject
- (void)frothTime:(NSInteger)regionTime value1:(BOOL)value;
@end
@implementation Person
- (void)frothTime:(NSInteger)regionTime value1:(NSString *)value;
@end
</pre></div>
<p>这2个方法除了第2个参数的类型不一样,其它都一样,但一旦调用这个方法就会产生一个坏内存访问的崩溃,这是为什么呢?</p>
<p>这是我在真实项目中遇到的1个很有意思的问题,只要调用分类中的某个方法就百分百崩溃,而且控制台没有任何有用的报错信息,被调用的方法里面的代码也都没有执行,非常难调试,我花了一些时间才弄懂了其中的原理,整理后分享出来,希望能帮到你,崩溃如下图所示:</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202303/20230331085353020.jpg" /></p>
<p>以下是我简写后的代码,它是一份完整的代码并且可以直接运行。</p>
<div class="jb51code"><pre class="brush:cpp;">@interface Person : NSObject
- (void)frothTime:(NSInteger)regionTime value1:(BOOL)value;
@end
@interface Person (Category)
- (void)frothTime:(NSInteger)regionTime;
- (void)frothTime:(NSInteger)regionTime value1:(NSString *)value;
@end
@implementation Person
- (void)frothTime:(NSInteger)regionTime value1:(BOOL)value {
    NSLog(@"%s", __func__);
}
@end
@implementation Person (Category)
- (void)frothTime:(NSInteger)regionTime {
    ;
}
- (void)frothTime:(NSInteger)regionTime value1:(NSString *)value {
    NSLog(@"%s", __func__);
}
@end
int main(int argc, const char * argv[]) {
    Person *p = [ init];
    ;
    return 0;
}
</pre></div>
<p class="maodian"></p><h2>分析</h2>
<p>运行代码后,会在 <code>- (void)frothTime:(NSInteger)regionTime value1:(NSString *)value</code> 这行代码处产生一条 <strong>EXC_BAD_ACCESS</strong> 崩溃问题,通过打印和断点,可以看出方法内的代码并没有执行,说明是调用这个方法时发生的崩溃,所以可以排除是方法内的代码问题。</p>
<p>崩溃前的代码位置是 <code>;</code>,这行代码从表面上看没有任何问题,如果你把示例代码粘贴到 xcode 中,编译器可能会在这行代码后面给出1个警告: &quot;Incompatible pointer to integer conversion sending &#39;NSString *&#39; to parameter of type &#39;BOOL&#39; (aka &#39;signed char&#39;)&quot;,意思是说方法接收的是一个 BOOL 类型的参数,而你传了一个 NSString * 类型。</p>
<p>仔细看一下代码,你会发现 Person 类中声明了 <code>- (void)frothTime:(NSInteger)regionTime value1:(BOOL)value;</code>,而且分类中也有一个类似的声明 <code>- (void)frothTime:(NSInteger)regionTime value1:(NSString *)value;</code>,它们除了第2个参数类型不一样,其它都是一样的;熟悉objc的同学应该都知道,objc是没有方法重载的概念,也就是说分类中的方法其实和类中的方法,它们的方法签名都是 <code>frothTime:value1:</code>。</p>
<p>现在有2个同名的方法实现,那么 <code>;</code> 到底调用哪个方法呢?按照 xcode 给出的提示,似乎是调用 <code>- (void)frothTime:(NSInteger)regionTime value1:(BOOL)value;</code> 这个方法,因为编译器提示第2个参数类型不一致。</p>
<p>有些同学在这里或许有一个疑问,明明有2个方法,而且分类中的方法明显更适合调用方,为什么编译器认为我们调用的是类中的方法而不是分类中的方法;有2点原因,第1是因为objc没有方法重载的概念,所以这2个方法对编译器来说其实都是一样的;第2是因为objc的分类是运行时加载的,编译器在编译时并不知道分类以及分类方法的存在。</p>
<p>和其它语言不一样,objc的方法声明和实现可以重复,只是不能在一个作用域中重复,例如在 @interface 和 @end 就不能同时存在 <code>- (void)frothTime:(NSInteger)regionTime value1:(BOOL)value;</code> 和 <code>- (void)frothTime:(NSInteger)regionTime value1:(NSString *)value;</code>,即使它们的参数类型并不是完全一样;但是可以在分类中写出和类中一样的方法声明或实现,即使你在分类中写出 <code>- (void)frothTime:(NSInteger)regionTime value1:(BOOL)value;</code> 这种和类中的方法完全一模一样的方法也不会有任何报错信息,如果你不小心在分类中实现了和类中同名的方法,那么运行时会永远调用分类中的方法实现,不清楚为什么的同学自行上网寻找答案。</p>
<p>现在我们弄明白了为什么编译器会给出警告,也知道了实际调用的其实是分类中的方法实现,但分类中的方法参数类型和我们传递的参数类型明明是一致的,那为什么还会崩溃呢?</p>
<p>原因在于编译器在对代码进行编译时对 <code>@&quot;111&quot;</code> 这个参数是按照 BOOL 类型而不是 NSString 类型处理的,请看下图:</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202303/20230331085353021.jpg" /></p>
<p>使用 <code>xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc 文件路径 -o 输出的文件路径.cpp</code> 将objc代码编译为C++代码。</p>
<p>可以看到编译器把参数强转成了 bool 类型,但是方法实现处却是按照 NSString 类型进行接收的,<strong>按照 NSString 类型去访问一个 bool 类型的内存,这就是崩溃的真正原因</strong>。</p>
<p class="maodian"></p><h2>补充</h2>
<p>如果你尝试将 <code>- (void)frothTime:(NSInteger)regionTime value1:(BOOL)value;</code> 修改为 <code>- (void)frothTime:(NSInteger)regionTime value1:(NSObject *)value;</code>(<code>其实可以把value的参数类型修改为任意objc对象类型,只要不是基础数据类型就行</code>),注意:这里我只修改了方法声明处的参数类型,并没有修改方法实现处的参数类型;然后运行项目;正常运行并输出;编译后的代码截图如下:</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202303/20230331085353022.jpg" /></p>
<p>从截图中可以看到参数虽然还是被强转成了 NSObjet 类型,但是据我观察,只要是objc对象都没关系,你可以把它改为 NSArray 等任何 objc 对象类型,虽然有编译警告,但是并不影响运行。</p>
<p>另外,你也可以将 <code>;</code> 修改为 <code>;</code>,项目也可以正常运行,原因和上面一样,因为 withObject 的参数类型是 id。</p>
<p class="maodian"></p><h2>总结</h2>
<ul><li>从上面的例子可以看出来,objc是一门非常动态的语言,这有很多好处,但也有很多坑,如果你不了解这些细节,那么就很可能会遇到各种奇奇怪怪的问题。</li><li>由于 objc 没有方法重载的概念,所以在分类中写方法时一定一定一定要加前缀(<code>即使方法的参数类型不一样也不行</code>),因为你不知道它会不会覆盖类中的私有方法。</li><li>objc编译器会自动将传递的参数强转为方法声明中的参数类型,如果方法实现处声明处的参数类型不一致,编译器会以方法声明中的参数类型为准,但是运行时会以方法实现处的参数类型进行接收。</li></ul>
<p>以上就是objc方法声明和实现由于参数类型不一致所引发的崩溃的详细内容,更多关于objc方法声明参数类型引发崩溃的资料请关注琼殿技术社区其它相关文章!</p>
                           
                            <div class="art_xg">
                              <b>您可能感兴趣的文章:</b><ul><li>详解如何使用ReactiveObjC</li><li>使用objc runtime实现iOS闭环的懒加载功能</li><li>php调用方法mssql_fetch_row、mssql_fetch_array、mssql_fetch_assoc和mssql_fetch_objcect读取数据的区别</li></ul>
                            </div>

                        </div>
                        <!--endmain-->
頁: [1]
查看完整版本: objc方法声明和实现由于参数类型不一致所引发的崩溃