如何在 Objective-C 中调试单例
我的应用程序包含多个单例(以下自本教程) 。然而,我注意到,当应用程序因单例而崩溃时,几乎不可能弄清楚它来自哪里。即使问题出在 Singleton 对象之一,主函数处的应用程序断点也会给出 EXEC_BAD_ACCESS。如果单例对象有问题,是否有指导如何调试它们?
My app contains several singletons (following from this tutorial). I've noticed however, when the app crashes because of a singleton, it becomes nearly impossible to figure out where it came from. The app breakpoints at the main function giving an EXEC_BAD_ACCESS even though the problem lies in one of the Singleton objects. Is there a guide to how would I debug my singleton objects if they were problematic?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
如果你不想改变你的设计(正如我在另一篇文章中所建议的),那么考虑常用的调试工具:断言、单元测试、僵尸测试、内存测试(GuardMalloc、乱写)等。这应该可以识别绝大多数人们会遇到的问题。
当然,您将受到一些关于您可以做什么和不能做什么的限制 - 特别是关于不能使用单元测试独立测试的内容。
同样,在某些情况下,当/如果您正在处理复杂的全局状态时,再现性可能会更加困难,因为您创建了多个强制单例。当全局状态相当大且复杂时 - 独立测试这些类型可能不会在所有情况下都富有成效,因为错误可能只出现在应用程序中发现的复杂全局状态中(当 4 个单例以特定方式交互时)。如果您已将问题隔离到多个单例实例(例如 MONAudioFileCache 和 MONVideoCache)的交互,则将这些对象放置在容器类中将允许您引入耦合,这将有助于诊断此问题。尽管增加耦合通常被认为是一件坏事;这并没有真正增加耦合(它已经作为全局状态的组件存在),而只是集中了现有的全局状态依赖关系——当这些单例的状态影响其他组件时,您实际上并没有像集中它那样增加它可变的全局状态。
如果您仍然坚持使用单例,这些可能会有所帮助:
要么使它们线程安全,要么添加一些断言来验证突变仅发生在主线程上(例如)。太多人认为具有原子属性的对象意味着该对象是线程安全的。这是错误的。
更好地封装您的数据,尤其是那些发生变化的数据。例如:让单例类将对象添加到它所保存的数组中,而不是传递您的类所保存的数组以供客户端进行变异。如果您确实必须将数组公开给客户端,则返回一个副本。这只是基本的 ood,但是许多 objc 开发者暴露了他们的大部分 ivars,无视封装的重要性。
如果它不是线程安全的并且该类在多线程上下文中使用,请使该类(而不是客户端)实现适当的线程安全。
将单例的错误检查设计得特别稳健。如果程序员传递了无效的参数或误用了接口 - 只需断言(带有有关问题/解决方案的良好消息)。
编写单元测试。
分离状态(例如,如果您可以轻松删除 ivar,就这样做)
降低状态的复杂性。
如果在使用彻底的断言、单元测试、僵尸测试、内存测试(GuardMalloc、乱写)等进行编写/测试后仍然无法调试某些内容,那么您正在编写过于复杂的程序(例如,将复杂性划分为多个)类),或者要求与实际使用不符。如果你到了那个时候,你绝对应该参考我的另一篇文章。全局变量状态越复杂,调试所需的时间就越长,当出现问题时,您可以重用和测试程序的时间就越少。
祝你好运
if you don't want to change your design (as recommended in my other post), then consider the usual debugging facilities: assertions, unit tests, zombie tests, memory tests (GuardMalloc, scribbling), etc. this should identify the vast majority of issues one would encounter.
of course, you will have some restrictions regarding what you can and cannot do - notably regarding what cannot be tested independently using unit tests.
as well, reproducibility may be more difficult in some contexts when/if you are dealing with a complex global state because you have created several enforced singletons. when the global state is quite large and complex - testing these types independently may not be fruitful in all cases since the bug may appear only in a complex global state found in your app (when 4 singletons interact in a specific manner). if you have isolated the issue to interactions of multiple singleton instances (e.g. MONAudioFileCache and MONVideoCache), placing these objects in a container class will allow you to introduce coupling, which will help diagnose this. although increasing coupling is normally considered a bad thing; this does't really increase coupling (it already exists as components of the global state) but simply concentrates existing global state dependencies -- you're really not increasing it as much as you are concentrating it when the state of these singletons affect other components of the mutable global state.
if you still insist on using singletons, these may help:
either make them thread safe or add some assertions to verify mutations happen only on the main thread (for example). too many people assume an object with atomic properties implies the object is thread safe. that is false.
encapsulate your data better, particularly that which mutates. for example: rather than passing out an array your class holds for the client to mutate, have the singleton class add the object to the array it holds. if you truly must expose the array to the client, then return a copy. ths is just basic ood, but many objc devs expose the majority of their ivars disregarding the importance of encapsualtion.
if it's not thread safe and the class is used in a mutithreaded context, make the class (not the client) implement proper thread safety.
design singletons' error checking to be particularly robust. if the programmer passes an invalid argument or misuses the interface - just assert (with a nice message about the problem/resolution).
do write unit tests.
detach state (e.g. if you can remove an ivar easily, do it)
reduce complexity of state.
if something is still impossible to debug after writing/testing with thorough assertions, unit tests, zombie tests, memory tests (GuardMalloc, scribbling), etc,, you are writing programs which are too complex (e.g. divide the complexity among multiple classes), or the requirements do not match the actual usage. if you're at that point, you should definitely refer to my other post. the more complex the global variable state, the more time it will take to debug, and the less you can reuse and test your programs when things do go wrong.
good luck
我浏览了这篇文章,虽然它有一些好的想法,但也有一些不好的建议,它不应该被视为福音。
而且,正如其他人所建议的,如果您有很多单例对象,则可能意味着您只是保留了太多的全局/持久状态。通常只需要您自己的一两个(除了其他一种或另一种“包”可能实现的包之外)。
至于调试单例,我不明白为什么你说它很难——在大多数情况下,并不比其他事情更糟糕。如果您收到 EXEC_BAD_ACCESS ,那是因为您遇到了某种寻址错误,这与单例方案无关(除非您使用的是非常糟糕的方案)。
宏使调试变得困难,因为它们包含的代码行不能在其中放置断点。如果没有别的的话,深六个宏。特别是,本文中的 SYNTHESIZE_SINGLETON_FOR_CLASS 宏会干扰调试。将此宏函数的调用替换为它为您的单例类生成的代码。
I scanned the article, and while it had some good ideas it also had some bad advice, and it should not be taken as gospel.
And, as others have suggested, if you have a lot of singleton objects it may mean that you're simply keeping too much state global/persistent. Normally only one or two of your own should be needed (in addition to those that other "packages" of one sort or another may implement).
As to debugging singletons, I don't understand why you say it's hard -- no worse than anything else, for the most part. If you're getting EXEC_BAD_ACCESS it's because you've got some sort of addressing bug, and that's nothing specific to singleton schemes (unless you're using a very bad one).
Macros make debugging difficult because the lines of code they incorporate can't have breakpoints put in them. Deep six macros, if nothing else. In particular, the
SYNTHESIZE_SINGLETON_FOR_CLASS
macro from the article is interfering with debugging. Replace the call to this macro function with the code it generates for your singleton class.呃 - 不要强制执行单例。只需创建普通类即可。如果您的应用需要仅一个实例,请将它们添加到创建一次的对象中,例如您的应用委托。
我见过的大多数可可单例实现不应该是单例。
然后您将能够像往常一样调试、测试、创建、变异和销毁这些对象。
当然,好的一点是,当您将这些类实现为普通对象时,大多数全局变量的麻烦都会消失。
ugh - don't enforce singletons. just create normal classes. if your app needs just one instance, add them to something which is created once, such as your app delegate.
most cocoa singleton implementations i've seen should not have been singletons.
then you will be able to debug, test, create, mutate and destroy these objects as usual.
the good part is course that the majority of your global variable pains will disappear when you implement these classes as normal objects.