台风加载情节提要以编程方式似乎可以执行异步实例化而不会阻塞 [英] Typhoon loading storyboard programmatically appears to perform asynchronous instantiation without blocking

查看:90
本文介绍了台风加载情节提要以编程方式似乎可以执行异步实例化而不会阻塞的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在开发iOS应用程序,并试图将Typhoon集成到测试中.我目前正在尝试模拟来自情节提要的视图控制器中的依存关系,因此在我的程序集中使用依存关系:

I am developing an iOS application and am trying to integrate Typhoon into the testing. I am currently trying to mock out a dependency in a view controller that comes from the storyboard, so with in my assembly:

public dynamic var systemComponents: SystemComponents!
public dynamic func storyboard() -> AnyObject {
    return TyphoonDefinition.withClass(TyphoonStoryboard.self) {
        (definition) in
        definition.useInitializer("storyboardWithName:factory:bundle:") {
            (initializer) in
            initializer.injectParameterWith("Main")
            initializer.injectParameterWith(self)
            initializer.injectParameterWith(NSBundle.mainBundle())
        }
    }
}

我想创建一个CameraModeViewController(我正在对其进行单元测试的类),它依赖于模拟出的提供系统相机功能的协议.依赖性为dynamic var cameraProvider: CameraAPIProvider?.我想我已经正确创建了一个替换协作程序集来替换systemComponentsMockSystemComponentsSystemComponents的子类,它覆盖了函数.这是我注入模拟的地方:

I want to create a CameraModeViewController (the class I am unit testing) with its dependency upon a system-camera-functions-providing protocol mocked out. The dependency is dynamic var cameraProvider: CameraAPIProvider?. I think I correctly created a replacement collaborating assembly to replace systemComponents; MockSystemComponents is a subclass of SystemComponents that overrides functions. This is where I inject the mock:

let assembly = ApplicationAssembly().activateWithCollaboratingAssemblies([
                            MockSystemComponents(camera: true)
                        ])
let storyboard = assembly.storyboard()
subject = storyboard.instantiateViewControllerWithIdentifier("Camera-Mode") as! CameraModeViewController

测试中的下一行代码是let _ = subject.view,据我了解,这是调用viewDidLoad并获取所有与故事板链接的IBOutlet的技巧,该测试是其中之一.

The next line of code in the tests is let _ = subject.view, which I learned is a trick to call viewDidLoad and get all the storyboard-linked IBOutlets, one of which is required for this test.

但是,我得到的结果非常神秘:有时但并非总是如此,所有测试都会失败,因为在viewDidLoad中,我调用了依赖项(cameraProvider),并且收到了无法识别的消息发送给班级"错误.该错误似乎表明在发送消息时(这是协议CameraAPIProvider中的正确实例方法),该字段当前是CLASS而不是实例:如错误报告中所述,它将消息解释为+[MockSystemCamera cameraStreamLayer].消息.

However, I am getting very mysterious result: sometimes but not always, all the tests fail because in the viewDidLoad I make a call to the dependency (cameraProvider), and I get an "unrecognized message sent to class" error. The error seems to indicate that at the time the message is sent (which is a correct instance method in protocol CameraAPIProvider) the field is currently a CLASS and not an instance: it interprets the message as +[MockSystemCamera cameraStreamLayer] as reported in the error message.

~~~ BUT ~~~

这是关键:如果我在对assembly.storyboard()subject.view的调用之间添加断点,则测试始终会通过.一切都已正确设置,并且消息已正确发送到实例,而无需使用这种类方法"伪造的解释.因此,我想知道Typhoon是否在我必须等待的注入过程中执行某种异步过程?可能仅在处理情节提要提供的视图控制器时?如果是这样,有什么方法可以确保它阻止吗?

Here's the kicker: if I add a breakpoint between the calls to assembly.storyboard() and subject.view, the tests always pass. Everything is set up correctly, and the message is correctly sent to an instance without this "class method" bogus interpretation. Therefore, I have to wonder if Typhoon does some kind of asynchronous procedure in the injection that I have to wait for? Possibly only when dealing with storyboard-delivered view controllers? And if so, is there any way to make sure it blocks?

在Typhoon的源代码中浏览了一段时间之后,我得到的印象是,在TyphoonDefinition(Instance Builder) initializeInstanceWithArgs:factory:方法中,有一个__block id instance临时为Class类型,然后被该类型的实例替换;并且可能可以不阻塞地异步调用它,因此注入的成员保留为Class类型?

After digging around in Typhoon's source for a while, I get the impression that in the TyphoonDefinition(Instance Builder) initializeInstanceWithArgs:factory: method there is an __block id instance that is temporarily a Class type, and then is replaced with an instance of that type; and possibly this can be called asynchronously without blocking, so the injected member is left as a Class type?

更新:添加MockSystemComponents(camera:)的代码.请注意,SystemComponents继承自TyphoonAssembly.

UPDATE: Adding the code for MockSystemComponents(camera:). Note that SystemComponents inherits from TyphoonAssembly.

@objc
public class MockSystemComponents: SystemComponents {
    var cameraAvailable: NSNumber

    init(camera: NSNumber) {
        self.cameraAvailable = camera
        super.init()
    }

    public override func systemCameraProvider() -> AnyObject {
        return TyphoonDefinition.withClass(MockSystemCamera.self) {
            (definition) in
            definition.useInitializer("initWithAvailable:") {
                (initializer) in
                initializer.injectParameterWith(self.cameraAvailable)
            }
        }
    }
}


更新#2:我尝试用属性注入替换MockSystemComponents.systemCameraProvider()中的构造函数注入.不同的问题,但是我怀疑它的原因是相同的:现在,注入的属性(声明为可选)在我去包装它的某些时候仍然是nil(但并非总是如此-可能大约是测试的4/5)运行失败,与之前大致相同.


UPDATE #2: I tried replacing the constructor injection in the MockSystemComponents.systemCameraProvider() with a property injection. Different issue, but I suspect it's equivalent in cause: now, the property that is injected (declared optional) is still nil some of the time when I go to unwrap it (but not always -- probably about 4/5 of test runs fail, about the same as before).

更新#3:已尝试使用以下代码块,并根据

UPDATE #3: have tried using the following code block, using factory construction according to this answer (note that setting factory directly didn't work as that OP did, but I think I correctly used the feature added in response to Jasper's issue). The results are the same as when using property injection like Update #2 above), so no dice there.

推荐答案

实际上,甚至在调用实例化之前,就已经出现了此问题.实际上,问题在于程序集通常不是打算有状态的.有几种方法可以解决此问题,但是不建议使用我使用的一种方法-具有成员变量和初始化方法.这样做的问题在于,在activateWithCollaboratingAssemblies方法中,将枚举程序集的所有实例方法以进行定义,并且实际上将在协作程序集上调用初始化程序.因此,即使您使用初始化程序创建程序集,也可能会使用虚假值再次调用该程序集.

This issue was in fact arising even before the call to the instantiation. In fact, the problem was assemblies aren't generally intended to be stateful. There are a few ways to get around this, but the one I used -- having a member variable and an initializer method -- is NOT recommended. The problem with doing this is that in the activateWithCollaboratingAssemblies method, all the instance methods of the assembly are enumerated for definitions, and initializers will actually get called on the collaborating assembly. Consequently, even if you create your assembly with an initializer, it may get called again with a bogus value.

请注意,出现异步行为的原因实际上是定义的组装顺序不确定(将其存储在NSDictionary中的属性).这意味着,如果activateWithCollaboratingAssemblies碰巧先枚举了依赖状态的方法,则它们会很好地工作;但是,如果首先枚举了初始化程序,并且破坏了状态,那么之后创建的定义将被取消.

Note that the reason there appeared to be async behavior is actually that there is nondeterministic order in which definitions are assembled (property of storing them in an NSDictionary). This means that if activateWithCollaboratingAssemblies happens to enumerate methods which depend on state first, they'll work fine; but if the initializer is enumerated first, and the state is destroyed, definitions that are created after will be borked.

这篇关于台风加载情节提要以编程方式似乎可以执行异步实例化而不会阻塞的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆