iOS 5 中的方法欺骗?

14

苹果是否在iOS 5中阻止了方法交换(Method Swizzling)?

我进行了一些尝试,发现一个使用方法交换的应用程序可以在iOS 4上运行但不能在iOS 5上运行。

注意:该应用程序在iOS 5上可以正常运行,但在使用方法交换时无法运行。


4
你的目标是哪个类?方法混淆是很脆弱的,因为其私有实现细节超出了你的控制范围。 - FluffulousChimp
@NSBum,您能详细解释一下为什么方法交换是脆弱的吗?我不知道有任何ABI或其他可能脆弱的情况。 - Nikolai Ruhe
3个回答

15

苹果曾向一些在App Store应用中使用方法交换技术的开发人员发送了一封电子邮件:

你的应用程序xxx目前已发布到App Store,正在使用method_exchangeImplementations来交换Apple提供的API实现和你自己的实现。由于即将到来的更改,你的应用程序中的这种行为可能会导致iPhone OS 4.0上的崩溃或导致用户数据丢失。

xxx使用method_exchangeImplementations来交换dealloc方法的实现和你的ttdealloc方法。它还将popViewControllerAnimated:方法的实现与你的popViewControllerAnimated2:方法进行了交换。

请立即解决此问题并将新二进制文件上传至iTunes Connect。如果我们认为这样做是明智或必要的,我们可能会删除你的应用程序。

看起来他们想要摆脱它,因此我可以说它完全被封锁的可能性非常高。


1
所以没有方法交换,或者对于苹果API类没有方法交换? - Dan Rosenstark
2
@Yar 很好的问题,我不确定答案。(虽然方法交换似乎是非常hacky的代码,如果它不是为了解决API限制 - 你确定没有更好的方式来构建你的代码吗?) 不过自己测试应该不难,如果你在这里发布结果,我会更新答案 :) - Jordan Smith
1
@Yar,您肯定仍然可以使用相关对象和class_addMethod在运行时添加额外的属性。class_addIvarobjc_registerClassPair之后停止工作,所以您无法再使用它了,遗憾。 - Tommy
1
@Yar,严格来说它不是一个ivar,但是由于具有完全功能的getter和setter,因此与任何其他属性无法区分。 - Tommy
2
@Yar 噢,抱歉,关联对象就像 objc_setAssociatedObjectobjc_getAssociatedObjectobjc_removeAssociatedObjects。运行时可以在低级别上将一个对象链接到另一个对象,然后您可以编写 setter 和 getter 来弥补差距。有关前者,请参见 http://developer.apple.com/library/ios/#documentation/cocoa/conceptual/objectivec/Chapters/ocAssociativeReferences.html。 - Tommy
显示剩余5条评论

9

更新:(我的应用程序使用此方法并在App Store中)

截至2012年5月30日,方法交换似乎已经起作用了。这是我的实现。

(这是为那些四处寻找错误代码的维基页面并只想要快速实现的人准备的。)

Swizz.h

#import <Foundation/Foundation.h>

void ActivateAutoSwizz();

void Swizz(Class c, SEL orig, SEL replace);


@interface NSObject (swizz)

// This Method allows the class to Swizzle more methods within itself.
// And allows for an overridable init method in Class Extensions
// ###################### 
// //// To Swizzle a method, call Swizzle once on the class in question.
// //// dispatch_once is a good way to handle that.
//            static dispatch_once_t onceToken;
//            dispatch_once(&onceToken, ^{
//                Swizz([UITableViewCell class], @selector(reuseIdentifier), @selector(classReuseIdentifier));
//            });
- (void) swizzInit;

@end

Swizz.m

#import "Swizz.h"
#import <objc/runtime.h> 
#import <objc/message.h>
//....

void Swizz(Class c, SEL orig, SEL replace)
{
    Method origMethod = class_getInstanceMethod(c, orig);
    Method newMethod = class_getInstanceMethod(c, replace);
    if(class_addMethod(c, orig, method_getImplementation(newMethod), method_getTypeEncoding(newMethod)))
        class_replaceMethod(c, replace, method_getImplementation(origMethod), method_getTypeEncoding(origMethod));
    else
        method_exchangeImplementations(origMethod, newMethod);
}


@implementation NSObject (swizz)

// Load gets called on every object that has it. Off Thread, before application start.
+ (void) load
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Swizz([NSObject class], @selector(init), @selector(initIntercept));
    });
}

- (id) initIntercept{
    self = [self initIntercept]; // Calls the Original init method
    if(self){
        [self swizzInit];
    }
    return self;
}

- (void) swizzInit{
    //Do Nothing.. Gives an extension point for other classes.
}

@end

我创建了一个UITableViewCell扩展,可以拦截UITableViewCell上的reuseIdentifier。以下是示例,文件名为UITableViewCell+ReuseIdentifier.h。
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>

@interface UITableViewCell (ReuseIdentifier)

@end

UITableViewCell+ReuseIdentifier.m

#import "UITableViewCell+ReuseIdentifier.h"
#import "Swizz.h"

@implementation UITableViewCell(ReuseIdentifier)


// Edited to remove Class variable.
// Class variables in Categories are Global.
// And as it turns out, this method did not need the variable anyhow.
- (NSString *)classReuseIdentifier{
    NSString *reuseIdentifierResult = [self classReuseIdentifier]; // Gets the original reuseIdentifier
    if (reuseIdentifierResult == nil){
        reuseIdentifierResult = [[self class] description];
    }
    return reuseIdentifierResult;
}

// Alternatively you can use the +(void)load method on the class to achieve the same thing.
- (void)swizzInit{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Swizz([UITableViewCell class], @selector(reuseIdentifier), @selector(classReuseIdentifier));
    });
}

@end

正如您所看到的,ActivateAutoSwizz()和我的swizzInit方法都使用dispatch_once来执行一次交换。

static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
    Swizz([NSObject class], @selector(init), @selector(initIntercept));
});

如果你执行两次,它会将你的方法转换回原始状态。希望这对一些iOS开发者有所帮助。

注意:我已经确定+(void)load在应用程序启动时只被调用一次,并且是一个非常好的地方来实现方法交换。不幸的是,在某些开发情况下,+(void)load可能不会被调用,你可能需要测试你的应用程序以确保这些方法被调用。


3

好的,我们大约一个月前(2012年5月初)收到了关于一个应用程序的批准,该应用程序大量使用方法交换来自定义iOS4中的标准UI组件(iOS5使用外观)。此外,方法交换是一个完全记录的API,还提供了与苹果本身或私有API无关的非常强大的功能。我很难相信他们会拒绝这样的东西!

无论如何,请在看到更多与此相关的拒绝时保持每个人都知情!谢谢!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接