Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Swiftで相互依存のオブジェクト生成に失敗する #3

Closed
mono0926 opened this issue Jan 14, 2015 · 7 comments
Closed

Swiftで相互依存のオブジェクト生成に失敗する #3

mono0926 opened this issue Jan 14, 2015 · 7 comments

Comments

@mono0926
Copy link

Swiftで使おうとしたら、以下の現象が起こってしまいました。

// 自分で書いたテストクラス
class BTKInjectorProviderTests: XCTestCase {    
    func testCircularReferenceWithInstance() {
        let injector = BTKGlobalInjector.injectorWithBlock { mInjector in
            mInjector.bindProtocol(BTKTestProtocol1.self) { i in
                let o = BTKTestProtocol1Impl()
                o.protocol2 = i.proxyFor(BTKTestProtocol2.self) as BTKTestProtocol2
                return o
            }
            mInjector.bindProtocol(BTKTestProtocol2.self) { i in
                let o = BTKTestProtocol2Impl()
                o.protocol3 = i.proxyFor(BTKTestProtocol3.self) as BTKTestProtocol3
                return o
            }
            mInjector.bindProtocol(BTKTestProtocol3.self) { i in
                let o = BTKTestProtocol3Impl()
                o.protocol1 = i.proxyFor(BTKTestProtocol1.self) as BTKTestProtocol1
                return o
            }
        }
        let o = injector.proxyFor(BTKTestProtocol1.self) as BTKTestProtocol1
        XCTAssertTrue(o.conformsToProtocol(BTKTestProtocol1.self))
    }
}
// BTKInjectorProviderBase.m
- (id)get
{
    // 1st check without synchronized
    if(_obj == nil){
        @synchronized(self){
            // 2nd check with synchronized
            if(_obj == nil){
                if(_injecting){
                    // BTKTestProtocol1のインスタンス時にこのパスに陥る
                    [NSException raise:NSInternalInconsistencyException
                                format:@"Circular reference detected for %@", self.description];
                    return nil;
                }

あと、別途Issue立てるか迷ったのですが、現在Factoryクラスが無いようですが、常に新しいインスタンスを返してほしい時はどういう使い方を想定していますか?

Objective-C - BTKInjector : DI Containerを作ってみた。 - Qiita

Providerは常に一つのインスタンスを返し、Factoryは常に新しいインスタンスを返します。

相互依存の問題はありましたが、Swiftからも使い易い感じで良いですね!

@tomohisaota
Copy link
Owner

BTKInjectorとしての支援は特にないので、単純にFactory用のプロトコルを定義して、Providerとして注入します。最初はFactoryとProviderを分けて考えていたのですが、どのみちFactory用のプロトコルは使用者が書かないといけないので、Providerだけのサポートとしました。Simple is the best :-)

あと、Swiftの件、ありがとうございます。
Proxyをつかってインスタンス生成を遅延しているのですが、Swiftだとちょっと挙動が違うのかも。
遅かれ早かれSwiftからも使いたいので、ちょっと見てみます。

@mono0926
Copy link
Author

ありがとうございます 👍

ちなみに、Swiftは以下のテストコードは動きました。
しかし、Swiftでは、XCTAssertThrowsSpecificNamed相当のassertionや、try-catchが無いのでtestWithoutDuckTypingが記述出来なかったです…。
例外は発生したので、動作はばっちりでしたが。

Objective-Cのテストも、 try-catchじゃなくてXCTAssertThrowsSpecificNamed([obj test1], NSException, NSInternalInconsistencyException); でも書けると思いました。(XCTAssertThrowsSpecificNamedだとエラー内容チェックが出来ないかもしれませんが)

class BTKInjectorDuckTypingTests: XCTestCase {

    func testWithDuckTyping() {
        let injector = BTKGlobalInjector.injectorWithBlock { mInjector in
            mInjector.bindProtocol(BTKTestProtocol4.self, forceConform: true) { injector in
                return BTKTestProtocol1Impl()
            }
        }
        let proxy = injector.proxyFor(BTKTestProtocol4.self) as BTKTestProtocol1
        XCTAssertTrue(proxy.isKindOfClass(BTKTestProtocol1Impl.self))
        let obj = proxy as BTKTestProtocol1Impl
        XCTAssertEqual(obj.test1(), "test1")
    }
}

@tomohisaota
Copy link
Owner

やはり、想定より早いタイミングでプロトコルチェックが走るね。
swiftランタイムの厳しい型チェックのせいかなぁ。swiftはまだ全然やってないので確証なし。

BTKInjectorProxyLazyで下記のようにprotocolチェックを飛ばすのがいいかな。
あくまでもbindしたプロトコルのチェックだから問題ないはずだし、BTKInjectorProviderBaseでインスタンス割り当て時にもチェックしていたはず。

- (BOOL)conformsToProtocol:(Protocol *)aProtocol
{
    if(_provider.targetProtocol == aProtocol){
        return YES;
    }
    return [_provider.get conformsToProtocol:aProtocol];
}

いかがでしょう?

@mono0926
Copy link
Author

早速ありがとうございます。
以下でも良いくらいでは?と思いましたが、SwiftのBTKInjectorDuckTypingTestsが失敗しちゃうので、 @tomohisaota さんのコードが良いと思いました。

- (BOOL)conformsToProtocol:(Protocol *)aProtocol
{
    return _provider.targetProtocol == aProtocol;
}

@tomohisaota
Copy link
Owner

あと、逆に一つ質問があるのですが、ライブラリの方にブリッジヘッダを入れておいたほうがSwiftな人には便利なんでしょうか?
#上記の変更も含めて、Swiftな人的にいい感じの状態でpull reqいただけるととてもとても嬉しい。

@mono0926
Copy link
Author

ブリッジヘッダには自前で入れる習慣なので、無くて良いかもです。

プルリクは明日やってみますヽ(・ω・`)

mono0926 pushed a commit to mono0926/BTKInjector that referenced this issue Jan 14, 2015
@mono0926
Copy link
Author

#4 でプルリクエスト投げました。(プルリクエストのお作法通りか少し自信ないです。

「ライブラリの方にブリッジヘッダを入れる」など、Swiftならではの工夫などは特に無かったです。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants