Objective-C 中方法混合的危险是什么?

发布于 2024-10-23 21:26:03 字数 401 浏览 1 评论 0原文

我听人们说方法调配是一种危险的做法。就连名字的混淆也表明它有点作弊。

Method Swizzling 正在修改映射,以便调用选择器 A 实际上会调用实现 B。其用途之一是扩展闭源类的行为。

我们能否将风险形式化,以便任何决定是否使用 swizzling 的人都可以做出明智的决定,对于他们正在尝试做的事情是否值得。

例如

  • 命名冲突:如果该类稍后扩展其功能以包含您添加的方法名称,则会导致大量问题。通过明智地命名混合方法来降低风险。

I have heard people state that method swizzling is a dangerous practice. Even the name swizzling suggests that it is a bit of a cheat.

Method Swizzling is modifying the mapping so that calling selector A will actually invoke implementation B. One use of this is to extend behavior of closed source classes.

Can we formalise the risks so that anyone who is deciding whether to use swizzling can make an informed decision whether it is worth it for what they are trying to do.

E.g.

  • Naming Collisions: If the class later extends its functionality to include the method name that you have added, it will cause a huge manner of problems. Reduce the risk by sensibly naming swizzled methods.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

心奴独伤 2024-10-30 21:26:03

我认为这是一个非常好的问题,遗憾的是大多数答案都回避了这个问题,只是说不要使用 swizzling,而不是解决真正的问题。

使用铁板的方法就像在厨房里使用锋利的刀一样。有些人害怕锋利的刀子,因为他们认为这样会严重割伤自己,但事实是锋利的刀更安全

方法调配可用于编写更好、更高效、更易于维护的代码。它也可能被滥用并导致可怕的错误。

背景

与所有设计模式一样,如果我们充分了解该模式的后果,我们就能够就是否使用它做出更明智的决定。单例是一个很有争议的例子,而且有充分的理由——它们确实很难正确实现。尽管如此,许多人仍然选择使用单例。对于调配也可以这样说。一旦你充分了解了好与坏,你就应该形成自己的看法。

讨论

以下是方法调配的一些陷阱:

  • 方法调配不是原子的
  • 更改无主代码的行为
  • 可能的命名冲突
  • 调配更改方法的参数
  • 调配的顺序很
  • 重要 难以理解(看起来是递归的)
  • 难以调试

这些点都是有效,并且在解决这些问题时,我们可以提高对方法调配以及用于实现结果的方法的理解。我会一次拿一个。

方法调配不是原子的

我还没有看到可以安全地同时使用的方法调配的实现1。实际上,在您想要使用方法调配的 95% 的情况下,这并不是问题。通常,您只想替换方法的实现,并且希望该实现在程序的整个生命周期中使用。这意味着您应该在 +(void)load 中进行方法调配。 load 类方法在应用程序启动时串行执行。如果您在这里进行调整,就不会有任何并发​​问题。但是,如果您要在 +(void)initialize 中进行 swizzle,则可能会在 swizzling 实现中遇到竞争条件,并且运行时可能会处于奇怪的状态。

更改非拥有代码的行为

这是 swizzling 的一个问题,但它是重点。目标是能够更改该代码。人们之所以指出这是一件大事,是因为您不仅要更改要更改内容的 NSButton 的一个实例,而是要更改所有 NSButton您的应用程序中的 实例。因此,在 swizzle 时应该小心谨慎,但不需要完全避免。

这样想...如果您重写类中的方法并且不调用超类方法,则可能会导致出现问题。在大多数情况下,超类期望调用该方法(除非另有说明)。如果您将同样的想法应用于 swizzling,您就已经解决了大多数问题。始终调用原始实现。如果你不这样做,你可能会改变太多而不安全。

可能的命名冲突

命名冲突是整个 Cocoa 中的一个问题。我们经常在类别中为类名和方法名添加前缀。不幸的是,命名冲突是我们语言中的一大瘟疫。但在调配的情况下,它们不一定是这样。我们只需要稍微改变一下我们对方法混合的看法。大多数调配都是这样完成的:

@interface NSView : NSObject
- (void)setFrame:(NSRect)frame;
@end

@implementation NSView (MyViewAdditions)

- (void)my_setFrame:(NSRect)frame {
    // do custom work
    [self my_setFrame:frame];
}

+ (void)load {
    [self swizzle:@selector(setFrame:) with:@selector(my_setFrame:)];
}

@end

这工作得很好,但是如果在其他地方定义 my_setFrame: 会发生什么?这个问题并不是 swizzling 独有的,但无论如何我们都可以解决它。该解决方法还有一个额外的好处,即可以解决其他陷阱。这就是我们所做的:

@implementation NSView (MyViewAdditions)

static void MySetFrame(id self, SEL _cmd, NSRect frame);
static void (*SetFrameIMP)(id self, SEL _cmd, NSRect frame);

static void MySetFrame(id self, SEL _cmd, NSRect frame) {
    // do custom work
    SetFrameIMP(self, _cmd, frame);
}

+ (void)load {
    [self swizzle:@selector(setFrame:) with:(IMP)MySetFrame store:(IMP *)&SetFrameIMP];
}

@end

虽然这看起来有点不像 Objective-C(因为它使用函数指针),但它避免了任何命名冲突。原则上,它所做的事情与标准调整完全相同。对于那些一直在使用 swizzling 的人来说,这可能是一个小小的改变,因为它的定义已经有一段时间了,但最终,我认为它更好。 swizzling 方法是这样定义的:

typedef IMP *IMPPointer;

BOOL class_swizzleMethodAndStore(Class class, SEL original, IMP replacement, IMPPointer store) {
    IMP imp = NULL;
    Method method = class_getInstanceMethod(class, original);
    if (method) {
        const char *type = method_getTypeEncoding(method);
        imp = class_replaceMethod(class, original, replacement, type);
        if (!imp) {
            imp = method_getImplementation(method);
        }
    }
    if (imp && store) { *store = imp; }
    return (imp != NULL);
}

@implementation NSObject (FRRuntimeAdditions)
+ (BOOL)swizzle:(SEL)original with:(IMP)replacement store:(IMPPointer)store {
    return class_swizzleMethodAndStore(self, original, replacement, store);
}
@end

通过重命名方法进行 Swizzling 会更改方法的参数

这是我心中最重要的一个。这就是不应该进行标准方法调配的原因。您正在更改传递给原始方法的实现的参数。这就是它发生的地方:

[self my_setFrame:frame];

这一行的作用是:

objc_msgSend(self, @selector(my_setFrame:), frame);

它将使用运行时来查找my_setFrame:的实现。一旦找到实现,它就会使用给定的相同参数调用该实现。它找到的实现是 setFrame: 的原始实现,因此它会继续调用它,但 _cmd 参数不是 setFrame:就像它应该的那样。现在是 my_setFrame:。原始实现被调用时带有一个它从未预料到会收到的参数。这可不行。

有一个简单的解决方案 - 使用上面定义的替代混合技术。论据将保持不变!

调配的顺序很重要

方法调配的顺序很重要。假设 setFrame: 仅在 NSView 上定义,想象一下这样的顺序:

[NSButton swizzle:@selector(setFrame:) with:@selector(my_buttonSetFrame:)];
[NSControl swizzle:@selector(setFrame:) with:@selector(my_controlSetFrame:)];
[NSView swizzle:@selector(setFrame:) with:@selector(my_viewSetFrame:)];

NSButton 上的方法被调配时会发生什么?大多数调配将确保它不会替换所有视图的 setFrame: 实现,因此它将拉起实例方法。这将使用现有的实现在 NSButton 类中重新定义 setFrame: ,以便交换实现不会影响所有视图。现有的实现是在 NSView 上定义的。当在 NSControl 上进行 swizzling(再次使用 NSView 实现)时,也会发生同样的事情。

当您在按钮上调用 setFrame: 时,它会调用您的 swizzled 方法,然后直接跳转到最初在 NSViewsetFrame: 方法>。 NSControlNSView swizzled 实现将不会被调用。

但是,如果顺序是:

[NSView swizzle:@selector(setFrame:) with:@selector(my_viewSetFrame:)];
[NSControl swizzle:@selector(setFrame:) with:@selector(my_controlSetFrame:)];
[NSButton swizzle:@selector(setFrame:) with:@selector(my_buttonSetFrame:)];

由于视图调配首先发生,因此控件调配将能够拉出正确的方法。同样,由于控件调配发生在按钮调配之前,因此按钮将拉起控件的 setFrame: 调配实现。这有点令人困惑,但这是正确的顺序。我们怎样才能确保事情的顺序呢?

再次强调,只需使用 load 来混合内容即可。如果您在 load 中进行调配并且仅对正在加载的类进行更改,那么您将是安全的。 load 方法保证超类加载方法将在任何子类之前被调用。我们会得到完全正确的订单!

很难理解(看起来是递归的)

看看传统定义的 swizzled 方法,我认为很难判断发生了什么。但看看我们上面完成 swizzling 的另一种方式,它很容易理解。这个问题已经解决了!

难以调试

调试过程中的困惑之一是看到一个奇怪的回溯,其中混淆了名称,所有内容都在你的脑海中变得混乱。同样,替代实现解决了这个问题。您将在回溯中看到明确命名的函数。不过,调配可能很难调试,因为很难记住调配产生的影响。妥善记录您的代码(即使您认为您是唯一会看到它的人)。遵循良好的做法,你就会没事的。调试并不比多线程代码更难。

结论

如果使用得当,方法混合是安全的。您可以采取的一个简单的安全措施是仅在 load 中进行 swizzle。就像编程中的许多事情一样,它可能很危险,但了解后果将使您能够正确使用它。


1 使用上面定义的 swizzling 方法,如果您要使用蹦床,则可以使事情变得线程安全。你需要两个蹦床。在方法开始时,您必须将函数指针 store 分配给一个自旋的函数,直到 store 指向的地址发生变化。这将避免在您能够设置 store 函数指针之前调用 swizzled 方法的任何竞争条件。然后,在类中尚未定义实现的情况下,您需要使用蹦床,并进行蹦床查找并正确调用超类方法。定义方法以便动态查找超级实现将确保调配调用的顺序无关紧要。

I think this is a really great question, and it's a shame that rather than tackling the real question, most answers have skirted the issue and simply said not to use swizzling.

Using method sizzling is like using sharp knives in the kitchen. Some people are scared of sharp knives because they think they'll cut themselves badly, but the truth is that sharp knives are safer.

Method swizzling can be used to write better, more efficient, more maintainable code. It can also be abused and lead to horrible bugs.

Background

As with all design patterns, if we are fully aware of the consequences of the pattern, we are able to make more informed decisions about whether or not to use it. Singletons are a good example of something that's pretty controversial, and for good reason — they're really hard to implement properly. Many people still choose to use singletons, though. The same can be said about swizzling. You should form your own opinion once you fully understand both the good and the bad.

Discussion

Here are some of the pitfalls of method swizzling:

  • Method swizzling is not atomic
  • Changes behavior of un-owned code
  • Possible naming conflicts
  • Swizzling changes the method's arguments
  • The order of swizzles matters
  • Difficult to understand (looks recursive)
  • Difficult to debug

These points are all valid, and in addressing them we can improve both our understanding of method swizzling as well as the methodology used to achieve the result. I'll take each one at a time.

Method swizzling is not atomic

I have yet to see an implementation of method swizzling that is safe to use concurrently1. This is actually not a problem in 95% of cases that you'd want to use method swizzling. Usually, you simply want to replace the implementation of a method, and you want that implementation to be used for the entire lifetime of your program. This means that you should do your method swizzling in +(void)load. The load class method is executed serially at the start of your application. You won't have any issues with concurrency if you do your swizzling here. If you were to swizzle in +(void)initialize, however, you could end up with a race condition in your swizzling implementation and the runtime could end up in a weird state.

Changes behavior of un-owned code

This is an issue with swizzling, but it's kind of the whole point. The goal is to be able to change that code. The reason that people point this out as being a big deal is because you're not just changing things for the one instance of NSButton that you want to change things for, but instead for all NSButton instances in your application. For this reason, you should be cautious when you swizzle, but you don't need to avoid it altogether.

Think of it this way... if you override a method in a class and you don't call the super class method, you may cause problems to arise. In most cases, the super class is expecting that method to be called (unless documented otherwise). If you apply this same thought to swizzling, you've covered most issues. Always call the original implementation. If you don't, you're probably changing too much to be safe.

Possible naming conflicts

Naming conflicts are an issue all throughout Cocoa. We frequently prefix class names and method names in categories. Unfortunately, naming conflicts are a plague in our language. In the case of swizzling, though, they don't have to be. We just need to change the way that we think about method swizzling slightly. Most swizzling is done like this:

@interface NSView : NSObject
- (void)setFrame:(NSRect)frame;
@end

@implementation NSView (MyViewAdditions)

- (void)my_setFrame:(NSRect)frame {
    // do custom work
    [self my_setFrame:frame];
}

+ (void)load {
    [self swizzle:@selector(setFrame:) with:@selector(my_setFrame:)];
}

@end

This works just fine, but what would happen if my_setFrame: was defined somewhere else? This problem isn't unique to swizzling, but we can work around it anyway. The workaround has an added benefit of addressing other pitfalls as well. Here's what we do instead:

@implementation NSView (MyViewAdditions)

static void MySetFrame(id self, SEL _cmd, NSRect frame);
static void (*SetFrameIMP)(id self, SEL _cmd, NSRect frame);

static void MySetFrame(id self, SEL _cmd, NSRect frame) {
    // do custom work
    SetFrameIMP(self, _cmd, frame);
}

+ (void)load {
    [self swizzle:@selector(setFrame:) with:(IMP)MySetFrame store:(IMP *)&SetFrameIMP];
}

@end

While this looks a little less like Objective-C (since it's using function pointers), it avoids any naming conflicts. In principle, it's doing the exact same thing as standard swizzling. This may be a bit of a change for people who have been using swizzling as it has been defined for a while, but in the end, I think that it's better. The swizzling method is defined thusly:

typedef IMP *IMPPointer;

BOOL class_swizzleMethodAndStore(Class class, SEL original, IMP replacement, IMPPointer store) {
    IMP imp = NULL;
    Method method = class_getInstanceMethod(class, original);
    if (method) {
        const char *type = method_getTypeEncoding(method);
        imp = class_replaceMethod(class, original, replacement, type);
        if (!imp) {
            imp = method_getImplementation(method);
        }
    }
    if (imp && store) { *store = imp; }
    return (imp != NULL);
}

@implementation NSObject (FRRuntimeAdditions)
+ (BOOL)swizzle:(SEL)original with:(IMP)replacement store:(IMPPointer)store {
    return class_swizzleMethodAndStore(self, original, replacement, store);
}
@end

Swizzling by renaming methods changes the method's arguments

This is the big one in my mind. This is the reason that standard method swizzling should not be done. You are changing the arguments passed to the original method's implementation. This is where it happens:

[self my_setFrame:frame];

What this line does is:

objc_msgSend(self, @selector(my_setFrame:), frame);

Which will use the runtime to look up the implementation of my_setFrame:. Once the implementation is found, it invokes the implementation with the same arguments that were given. The implementation it finds is the original implementation of setFrame:, so it goes ahead and calls that, but the _cmd argument isn't setFrame: like it should be. It's now my_setFrame:. The original implementation is being called with an argument it never expected it would receive. This is no good.

There's a simple solution — use the alternative swizzling technique defined above. The arguments will remain unchanged!

The order of swizzles matters

The order in which methods get swizzled matters. Assuming setFrame: is only defined on NSView, imagine this order of things:

[NSButton swizzle:@selector(setFrame:) with:@selector(my_buttonSetFrame:)];
[NSControl swizzle:@selector(setFrame:) with:@selector(my_controlSetFrame:)];
[NSView swizzle:@selector(setFrame:) with:@selector(my_viewSetFrame:)];

What happens when the method on NSButton is swizzled? Well most swizzling will ensure that it's not replacing the implementation of setFrame: for all views, so it will pull up the instance method. This will use the existing implementation to re-define setFrame: in the NSButton class so that exchanging implementations doesn't affect all views. The existing implementation is the one defined on NSView. The same thing will happen when swizzling on NSControl (again using the NSView implementation).

When you call setFrame: on a button, it will therefore call your swizzled method, and then jump straight to the setFrame: method originally defined on NSView. The NSControl and NSView swizzled implementations will not be called.

But what if the order were:

[NSView swizzle:@selector(setFrame:) with:@selector(my_viewSetFrame:)];
[NSControl swizzle:@selector(setFrame:) with:@selector(my_controlSetFrame:)];
[NSButton swizzle:@selector(setFrame:) with:@selector(my_buttonSetFrame:)];

Since the view swizzling takes place first, the control swizzling will be able to pull up the right method. Likewise, since the control swizzling was before the button swizzling, the button will pull up the control's swizzled implementation of setFrame:. This is a bit confusing, but this is the correct order. How can we ensure this order of things?

Again, just use load to swizzle things. If you swizzle in load and you only make changes to the class being loaded, you'll be safe. The load method guarantees that the super class load method will be called before any subclasses. We'll get the exact right order!

Difficult to understand (looks recursive)

Looking at a traditionally defined swizzled method, I think it's really hard to tell what's going on. But looking at the alternative way we've done swizzling above, it's pretty easy to understand. This one's already been solved!

Difficult to debug

One of the confusions during debugging is seeing a strange backtrace where the swizzled names are mixed up and everything gets jumbled in your head. Again, the alternative implementation addresses this. You'll see clearly named functions in backtraces. Still, swizzling can be difficult to debug because it's hard to remember what impact the swizzling is having. Document your code well (even if you think you're the only one who will ever see it). Follow good practices, and you'll be alright. It's not harder to debug than multi-threaded code.

Conclusion

Method swizzling is safe if used properly. A simple safety measure you can take is to only swizzle in load. Like many things in programming, it can be dangerous, but understanding the consequences will allow you use it properly.


1 Using the above defined swizzling method, you could make things thread safe if you were to use trampolines. You would need two trampolines. At the start of the method, you would have to assign the function pointer, store, to a function that spun until the address to which store pointed to changed. This would avoid any race condition in which the swizzled method was called before you were able to set the store function pointer. You would then need to use a trampoline in the case where the implementation isn't already defined in the class and have the trampoline lookup and call the super class method properly. Defining the method so it dynamically looks up the super implementation will ensure that the order of swizzling calls does not matter.

夜光 2024-10-30 21:26:03

首先,我将准确定义方法调配的含义:

  • 将最初发送到方法(称为 A)的所有调用重新路由到新方法(称为 B)。
  • 我们拥有方法 B
  • 我们不拥有方法 A
  • 方法 B 执行一些工作,然后调用方法 A。

方法调配比这更通用,但这是我感兴趣的情况。

危险:

  • 原始类中的更改。我们不拥有我们正在调配的类。如果班级发生变化,我们的混合可能会停止工作。

  • 难以维护。您不仅需要编写和维护 swizzled 方法。您必须编写并维护执行 swizzle 的代码

  • 难以调试。很难跟踪 swizzle 的流程,有些人甚至可能没有意识到 swizzle 已经进行了。如果存在从 swizzle 引入的错误(可能是由于原始类的更改),它们将很难解决。

总之,您应该将 swizzling 保持在最低限度,并考虑原始类中的更改可能如何影响您的 swizzle。此外,您还应该清楚地评论并记录您正在做的事情(或者完全避免这样做)。

First I will define exactly what I mean by method swizzling:

  • Re-routing all calls that were originally sent to a method (called A) to a new method (called B).
  • We own Method B
  • We dont own method A
  • Method B does some work then calls method A.

Method swizzling is more general than this, but this is the case I am interested in.

Dangers:

  • Changes in the original class. We dont own the class that we are swizzling. If the class changes our swizzle may stop working.

  • Hard to maintain. Not only have you got to write and maintain the swizzled method. you have to write and maintain the code that preforms the swizzle

  • Hard to debug. It is hard to follow the flow of a swizzle, some people may not even realise the swizzle has been preformed. If there are bugs introduced from the swizzle (perhaps dues to changes in the original class) they will be hard to resolve.

In summary, you should keep swizzling to a minimum and consider how changes in the original class might effect your swizzle. Also you should clearly comment and document what you are doing (or just avoid it entirely).

只是偏爱你 2024-10-30 21:26:03

真正危险的并不是搅拌本身。正如您所说,问题在于它经常用于修改框架类的行为。假设您了解这些私人课程的运作方式,这是“危险的”。即使您的修改今天有效,Apple 也总有可能在将来更改类并导致您的修改失败。此外,如果许多不同的应用程序都这样做,那么苹果在不破坏大量现有软件的情况下改变框架就会变得更加困难。

It's not the swizzling itself that's really dangerous. The problem is, as you say, that it's often used to modify the behavior of framework classes. It's assuming that you know something about how those private classes work that's "dangerous." Even if your modifications work today, there's always a chance that Apple will change the class in the future and cause your modification to break. Also, if many different apps do it, it makes it that much harder for Apple to change the framework without breaking a lot of existing software.

若沐 2024-10-30 21:26:03

仔细而明智地使用,它可以产生优雅的代码,但通常,它只会导致令人困惑的代码。

我说它应该被禁止,除非你碰巧知道它为特定的设计任务提供了一个非常优雅的机会,但你需要清楚地知道为什么它适用于这种情况,以及为什么替代方案不能很好地适应这种情况。

例如,方法调配的一个很好的应用是 isa 调配,这就是 ObjC 实现键值观察的方式。

一个不好的例子可能是依赖方法调配作为扩展类的方法,这会导致极高的耦合。

Used carefully and wisely, it can lead to elegant code, but usually, it just leads to confusing code.

I say that it should be banned, unless you happen to know that it presents a very elegant opportunity for a particular design task, but you need to clearly know why it applies well to the situation, and why alternatives do not work elegantly for the situation.

Eg, one good application of method swizzling is isa swizzling, which is how ObjC implements Key Value Observing.

A bad example might be relying on method swizzling as a means of extending your classes, which leads to extremely high coupling.

请帮我爱他 2024-10-30 21:26:03

尽管我已经使用了这种技术,但我想指出:

  • 它会混淆您的代码,因为它可能会导致未记录的(尽管是期望的)副作用。当一个人阅读代码时,他/她可能不知道所需的副作用行为,除非他/她记得搜索代码库以查看它是否已被混合。我不确定如何缓解这个问题,因为并不总是能够记录代码依赖于副作用混合行为的每个地方。
  • 它可以使您的代码的可重用性降低,因为如果有人发现一段代码取决于他们想要在其他地方使用的 swizzled 行为,则不能简单地将其剪切并粘贴到其他代码库中,而无需查找和复制 swizzled 方法。

Although I have used this technique, I would like to point out that:

  • It obfuscates your code because it can cause un-documented, though desired, side effects. When one reads the code he/she may be unaware of the side effect behavior that is required unless he/she remembers to search the code base to see if it has been swizzled. I'm not sure how to alleviate this problem because it is not always possible to document every place where the code is dependent upon the side effect swizzled behavior.
  • It can make your code less reusable because someone who finds a segment of code which depends upon the swizzled behavior that they would like to use elsewhere cannot simply cut and paste it into some other code base without also finding and copying the swizzled method.
痴情换悲伤 2024-10-30 21:26:03

我觉得最大的危险是完全意外地产生许多不需要的副作用。这些副作用可能会以“错误”的形式出现,从而导致您走上错误的道路来寻找解决方案。根据我的经验,危险在于代码难以辨认、令人困惑和令人沮丧。有点像有人在 C++ 中过度使用函数指针。

I feel that the biggest danger is in creating many unwanted side effects, completely by accident. These side effects may present themselves as 'bugs' which in turn lead you down the wrong path to find the solution. In my experience, the danger is illegible, confusing, and frustrating code. Kind of like when someone overuses function pointers in C++.

握住你手 2024-10-30 21:26:03

您最终可能会得到看起来很奇怪的代码,例如

- (void)addSubview:(UIView *)view atIndex:(NSInteger)index {
    //this looks like an infinite loop but we're swizzling so default will get called
    [self addSubview:view atIndex:index];

与某些 UI 魔法相关的实际生产代码。

You may end up with odd looking code like

- (void)addSubview:(UIView *)view atIndex:(NSInteger)index {
    //this looks like an infinite loop but we're swizzling so default will get called
    [self addSubview:view atIndex:index];

from actual production code related to some UI magic.

黯然#的苍凉 2024-10-30 21:26:03

方法调配在单元测试中非常有用。

它允许您编写模拟对象并使用该模拟对象而不是真实对象。您的代码保持干净,并且单元测试具有可预测的行为。假设您想要测试一些使用 CLLocationManager 的代码。您的单元测试可以调整 startUpdatingLocation,以便它将一组预定的位置提供给您的委托,并且您的代码不必更改。

Method swizzling can be very helpful is in unit testing.

It allows you to write a mock object and have that mock object used instead of the real object. Your code to remain clean and your unit test has predictable behavior. Let's say you want to test some code that uses CLLocationManager. Your unit test could swizzle startUpdatingLocation so that it would feed a predetermined set of locations to your delegate and your code would not have to change.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文