Expose an interface of a class loaded from a framework at runtime

余生长醉 提交于 2019-12-23 06:13:16

问题


I want to load and manipulate SKUIImageColorAnalyzer and SKUIAnalyzedImageColors objects from the private StoreKitUI.framework.

First, I attempt to load the framework at runtime:

guard case let libHandle = dlopen("/System/Library/PrivateFrameworks/StoreKitUI.framework/StoreKitUI", RTLD_NOW) where libHandle != nil else {
    fatalError("StoreKitUI not found")
}

Then, I verify that the SKUIImageColorAnalyzer class can be found:

guard let analyzerClass: AnyClass = NSClassFromString("SKUIImageColorAnalyzer")  else {
    fatalError("SKUIImageColorAnalyzer lookup failed")
}

I want to use the analyzeImage: class method on SKUIImageColorAnalyzer, which takes in a UIImage for analysis and returns an SKUIAnalyzedImageColors object. I do this by verifying the analyzeImage: selector exists on the SKUIImageColorAnalyzer object, and recreate the function:

let selector: Selector = "analyzeImage:"
guard case let method = class_getClassMethod(analyzerClass, selector) where method != nil else {
    fatalError("failed to look up \(selector)")
}

// recreate the method's implementation function
typealias Prototype = @convention(c) (AnyClass, Selector, UIImage) -> AnyObject? // returns an SKUIAnalyzedImageColors object
let opaqueIMP = method_getImplementation(method)
let function = unsafeBitCast(opaqueIMP, Prototype.self)

Now, I can get a UIImage object and pass that in as the argument to the function:

let img = UIImage(named: "someImage.jpg")!
let analyzedImageColors = function(analyzerClass, selector, img) // <SKUIAnalyzedImageColors: 0x7f90d3408eb0>

I know that analyzedImageColors is of type SKUIAnalyzedImageColors, but the compiler still thinks its type is AnyObject based on the way I declared Prototype above. Now I want to access the properties of an SKUIAnalyzedImageColors object.

From the header, I can see that there are properties such as backgroundColor, textPrimaryColor, and textSecondaryColor on the object. I can access these properties using valueForKey, but I'd like to expose a public interface on SKUIAnalyzedImageColors so I can access these properties.

My first attempt was something like this:

// Create a "forward declaration" of the class
class SKUIAnalyzedImageColors: NSObject { }


// Create convenience extensions for accessing properties
extension SKUIAnalyzedImageColors {
    func backgroundColor() -> UIColor {
        return self.valueForKey("_backgroundColor") as! UIColor
    }

    func textPrimaryColor() -> UIColor {
        return self.valueForKey("_textPrimaryColor") as! UIColor
    }

    func textSecondaryColor() -> UIColor {
        return self.valueForKey("_textSecondaryColor") as! UIColor
    }
}

// ...

// modify the prototype to return an SKUIAnalyzedImageColors object
typealias Prototype = @convention(c) (AnyClass, Selector, UIImage) -> SKUIAnalyzedImageColors?

// ...

// access the properties from the class extension
analyzedImageColors?.backgroundColor() // Optional(UIDeviceRGBColorSpace 0.262745 0.231373 0.337255 1)

This still requires me to use valueForKey. Is there a way to expose a public interface on a class from a framework loaded at runtime?


回答1:


The easiest way to do dynamic Objective-C stuff is to use Objective-C.

ImageAnalyzer.h:

#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN

@interface SKUIAnalyzedImageColors : NSObject

@property (nonatomic, readonly) UIColor* backgroundColor;
@property (nonatomic, readonly) BOOL isBackgroundLight;
@property (nonatomic, readonly) UIColor* textPrimaryColor;
@property (nonatomic, readonly) UIColor* textSecondaryColor;

@end

SKUIAnalyzedImageColors* _Nullable analyzeImage(UIImage* image);

NS_ASSUME_NONNULL_END

ImageAnalyzer.m:

#import "ImageColorAnalyzer.h"
#include <dlfcn.h>

static Class _SKUIImageColorAnalyzerClass;

@interface SKUIImageColorAnalyzer : NSObject
+ (SKUIAnalyzedImageColors*)analyzeImage:(UIImage*)arg1;
@end

SKUIAnalyzedImageColors* analyzeImage(UIImage* image)
{
    if (!_SKUIImageColorAnalyzerClass)
    {
        if (!dlopen("/System/Library/PrivateFrameworks/StoreKitUI.framework/StoreKitUI", RTLD_NOW))
        {
            NSLog(@"No framework.");
            return nil;
        }
        _SKUIImageColorAnalyzerClass = NSClassFromString(@"SKUIImageColorAnalyzer");
        if (!_SKUIImageColorAnalyzerClass)
        {
            NSLog(@"No Class.");
            return nil;
        }
    }

    return [_SKUIImageColorAnalyzerClass analyzeImage:image];
}

You can then use the analyzeImage function and the SKUIAnalyzedImageColors class easily from either Swift or Objective-C code.

if let image = UIImage(named:"MyImage") {
    if let colors = analyzeImage(image) {
        print("Background Color: \(colors.backgroundColor)")
    }
}

If you really want to do it all in Swift, first declare the parts of the SKUIAnalyzedImageColors Objective-C interface you want to use:

@objc protocol ImageColors {
    var backgroundColor: UIColor { get }
    var isBackgroundLight: Bool { get }
    var textPrimaryColor: UIColor { get }
    var textSecondaryColor: UIColor { get }
}

Then use unsafeBitCast to cast the opaque object instance to your desired Objective-C interface:

let img = UIImage(named: "someImage.jpg")!
let rawAnalyzedImageColors = function(analyzerClass, selector, img) 

let analyzedImageColors = unsafeBitCast(rawAnalyzedImageColors, ImageColors.self)
print("Background color: \(analyzedImageColors.backgroundColor)")


来源:https://stackoverflow.com/questions/35328255/expose-an-interface-of-a-class-loaded-from-a-framework-at-runtime

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!