【问题标题】:Swizzle the AppDelegate methods调配 AppDelegate 方法
【发布时间】:2018-04-12 06:35:14
【问题描述】:

我正在开发需要获取应用程序生命周期事件的框架。我正在尝试使用 NotificationCenter 它在框架下失败。所以我决定使用 obj c 运行时来实现 Method Swizzling。问题是以下代码在 Emulator 中正常工作。当我运行设备时它失败了。

好消息是扩展方法被调用,当调用原始方法时,这会失败并显示消息

Thread 1: EXC_BAD_ACCESS (code=1, address=0x20)

这是我的代码

IMP originalImplementation;

+ (instancetype)initWith:(NSString *)bundleIdentifier{
    Demo *instance = [[Demo alloc] init];
    [instance swizzlingLifeCycleMethods];
    return instance;
}

- (void)swizzlingLifeCycleMethods{

    //Prepare the injected class name to be injecting
    Class originalClass = NSClassFromString(@"AppDelegate");

    //Prepare the methods to swizzling
    SEL originalWillResignAction = @selector(applicationWillResignActive:);
    SEL extendedWillResignActive = NSSelectorFromString(@"extendedApplicationWillResignActive");

    //Get original method and method encoding
    originalResignMethod = class_getInstanceMethod(originalClass, originalWillResignAction);
    originalImplementation = method_getImplementation(originalResignMethod);
    const char *originalResignMethodEncoding = method_getTypeEncoding(originalResignMethod);

    //Add swizzling method into targetted class
    class_addMethod(originalClass, extendedWillResignActive, (IMP)extendedApplicationWillResignActive, originalResignMethodEncoding);

    //Swizzling the methods
    Method extendedResignMethod = class_getInstanceMethod(originalClass, extendedWillResignActive);
    method_exchangeImplementations(originalResignMethod, extendedResignMethod);
}

//Called at the time of user enters into background
void extendedApplicationWillResignActive(id self, SEL _cmd, va_list args1)
{
    //Implement our logic here

    //Call the original function after our stuff done
    ((void(*)(id, SEL, ...))originalImplementation)(self, _cmd, args1);
}

我在我的示例项目中使用通用框架构建来使用这个框架。请让我知道这有什么问题。

【问题讨论】:

    标签: ios objective-c runtime


    【解决方案1】:

    请告诉我这有什么问题。

    我无法运行您的代码(它不完整),但以下内容可能会以某种组合方式导致您的问题。

    首先applicationWillResignActive的声明是:

    - (void)applicationWillResignActive:(UIApplication *)application;
    

    但是,在您的 extendedApplicationWillResignActive 函数中,您已将 UIApplication * 更改为 va_list,这是错误的。在某些情况下它可能会起作用,在模拟器中它似乎对你有用,但在某些情况下它会爆炸,就像你的设备上发生的那样(请注意,模拟器运行 x86 代码,设备 ARM 代码)。

    这里明显的问题是你为什么要改变参数的类型?

    其次,您绕过编写方法并直接使用(键入错误的)C 函数。虽然没有错本身它可能对你没有帮助。

    第三个你正在尝试添加一个方法:

    - (void)extendedApplicationWillResignActive:
    

    到一个类,而不检查这样的方法是否已经存在。这充其量是不明智的,您的框架如何知道使用它的应用程序没有定义这样的方法?可能性可能很小,但大于零,是不必要的风险。也就是说,在您的情况下,这可能不是问题(因为您编写了正在调配的类,所以知道没有这样的现有方法)。

    最后,您已经硬连线了类的名称,AppDelegate,您正在调配。应用程序委托可以有不同的名称。您的 initWith: 方法接受一个参数 bundleIdentifier,目前未使用,因此我认为这表明您打算解决此问题。


    让我们看看我们是否可以帮助您解决此问题。您在这里尝试做的是跨类混合。为此,您可以:

    • 始终为目标方法/实现函数使用正确的类型;
    • 把你的替换写成一个方法;和
    • 只需替换目标方法的实现,无需向类添加新方法

    这是您进行这些更改的代码。 此代码只是在行中复制和编辑,尚未运行,预计会出现错误,仅作为大纲。

    @implementation Demo
    {
    
    // define a typedef for the target method's implementation function
    typedef void (*OriginalImpType)(id self, SEL selector, UIApplication *application);
    // declare this static as it is private to this class
    static OriginalImpType originalImplementation;
    
    + (instancetype)initWith:(NSString *)bundleIdentifier{
        Demo *instance = [[Demo alloc] init];
        [instance swizzlingLifeCycleMethods:bundleIdentifier];
        return instance;
    }
    
    - (void)swizzlingLifeCycleMethods:(NSString *)bundleIdentifier
    {
    
        // Get the class for the application delegate
        Class originalClass = ... however you plan to do this using bundleIdentifier ...;
        // Get the swizzling class
        Class demoClass = [Demo class];
    
        // Prepare the methods to swizzling
        SEL originalWillResignAction = @selector(applicationWillResignActive:);
        SEL extendedWillResignActive = @selector(extendedApplicationWillResignActive:);
    
        // Get original method and save it
        Method originalResignMethod = class_getInstanceMethod(originalClass, originalWillResignAction);
        originalImplementation = (OriginalImpType)method_getImplementation(originalResignMethod);
    
        // Swizzling the method
        Method extendedResignMethod = class_getInstanceMethod(demoClass, extendedWillResignActive);
        method_setImplementation(originalResignMethod, method_getImplementation(extendedResignMethod));
    }
    
    // Called at the time of user enters into background
    - (void) extendedApplicationWillResignActive:(UIApplication *)application
    {
        //Implement our logic here
    
        //Call the original function after our stuff done
        originalImplementation(self, _cmd, application);
    }
    
    } // end of @implementation Demo
    

    HTH

    【讨论】:

    • 感谢 CRD,我已经更改了我的新方法实现并且它有效。 - (void) quixxiApplicationWillResignActive:(UIApplication )application { ((void()(id, SEL, UIApplication *))originalImplementation)(self, _cmd, application); }
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-06-20
    • 2014-06-29
    • 2021-03-15
    • 2014-11-18
    • 1970-01-01
    相关资源
    最近更新 更多