在运行时公开从框架加载的类的接口

时间:2016-02-10 23:48:16

标签: swift dylib dlopen

我想加载和操作SKUIImageColorAnalyzer和 来自私人StoreKitUI.framework的{​​{3}}个对象。

首先,我尝试在运行时加载框架:

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

然后,我验证可以找到SKUIImageColorAnalyzer类:

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

我想在analyzeImage:上使用SKUIImageColorAnalyzer类方法,该方法接受UIImage进行分析并返回SKUIAnalyzedImageColors个对象。我这样做是通过验证analyzeImage:对象上存在SKUIImageColorAnalyzer选择器,然后重新创建函数:

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)

现在,我可以获得一个UIImage对象并将其作为参数传递给函数:

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

我知道analyzedImageColors的类型为SKUIAnalyzedImageColors,但编译器仍然根据我在上面声明AnyObject的方式认为其类型为Prototype。现在我想访问SKUIAnalyzedImageColors对象的属性。

SKUIAnalyzedImageColors,我可以看到对象上有backgroundColortextPrimaryColortextSecondaryColor等属性。我可以使用valueForKey访问这些属性,但我想在SKUIAnalyzedImageColors上公开一个公共接口,以便我可以访问这些属性。

我的第一次尝试是这样的:

// 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)

这仍然要求我使用valueForKey。有没有办法在运行时加载的框架上公开类的公共接口?

1 个答案:

答案 0 :(得分:2)

执行动态Objective-C的最简单方法是使用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];
}

然后,您可以从Swift或Objective-C代码中轻松使用analyzeImage函数和SKUIAnalyzedImageColors类。

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

如果你真的想在Swift中完成所有工作,首先要声明你想要使用的SKUIAnalyzedImageColors Objective-C接口的部分:

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

然后使用unsafeBitCast将不透明对象实例强制转换为所需的Objective-C接口:

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

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