Skip to content

Commit

Permalink
Merge pull request #29 from jakubvano/fix_storyboard_injection
Browse files Browse the repository at this point in the history
Fix storyboard injection
  • Loading branch information
yoichitgy authored Nov 2, 2016
2 parents 3c0a184 + 2159604 commit 51f55e5
Show file tree
Hide file tree
Showing 11 changed files with 240 additions and 44 deletions.
11 changes: 11 additions & 0 deletions Sources/InjectionVerifiable.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
//
// InjectionVerifiable.swift
// Swinject
//
// Created by Jakub Vaňo on 28/10/16.
// Copyright © 2016 Swinject Contributors. All rights reserved.
//

internal protocol InjectionVerifiable: AnyObject {
var wasInjected: Bool { get set }
}
13 changes: 0 additions & 13 deletions Sources/RegistrationNameAssociatable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,6 @@
// Copyright © 2015 Swinject Contributors. All rights reserved.
//

import Foundation
import ObjectiveC

internal protocol RegistrationNameAssociatable: AnyObject {
var swinjectRegistrationName: String? { get set }
}

extension RegistrationNameAssociatable {
internal func getAssociatedString(key: UnsafeRawPointer) -> String? {
return objc_getAssociatedObject(self, key) as? String
}

internal func setAssociatedString(_ string: String?, key: UnsafeRawPointer) {
objc_setAssociatedObject(self, key, string, objc_AssociationPolicy.OBJC_ASSOCIATION_COPY)
}
}
18 changes: 12 additions & 6 deletions Sources/SwinjectStoryboard.swift
Original file line number Diff line number Diff line change
Expand Up @@ -82,13 +82,15 @@ public class SwinjectStoryboard: _SwinjectStoryboardBase, SwinjectStoryboardType
let viewController = super.instantiateViewController(withIdentifier: identifier)
SwinjectStoryboard.popInstantiatingStoryboard()

if !SwinjectStoryboard.isCreatingStoryboardReference {
injectDependency(to: viewController)
}
injectDependency(to: viewController)

return viewController
}

private func injectDependency(to viewController: UIViewController) {
guard !viewController.wasInjected else { return }
defer { viewController.wasInjected = true }

let registrationName = viewController.swinjectRegistrationName

// Xcode 7.1 workaround for Issue #10. This workaround is not necessary with Xcode 7.
Expand Down Expand Up @@ -120,13 +122,17 @@ public class SwinjectStoryboard: _SwinjectStoryboardBase, SwinjectStoryboardType
let controller = super.instantiateController(withIdentifier: identifier)
SwinjectStoryboard.popInstantiatingStoryboard()

if !SwinjectStoryboard.isCreatingStoryboardReference {
injectDependency(to: controller)
}
injectDependency(to: controller)

return controller
}

private func injectDependency(to controller: Container.Controller) {
if let controller = controller as? InjectionVerifiable {
guard !controller.wasInjected else { return }
defer { controller.wasInjected = true }
}

let registrationName = (controller as? RegistrationNameAssociatable)?.swinjectRegistrationName

// Xcode 7.1 workaround for Issue #10. This workaround is not necessary with Xcode 7.
Expand Down
74 changes: 49 additions & 25 deletions Sources/ViewController+Swinject.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,48 +6,72 @@
// Copyright © 2015 Swinject Contributors. All rights reserved.
//

import Swinject
import ObjectiveC

#if os(iOS) || os(tvOS)

private var uivcAssociationKey: String = "UIViewController.swinjectRegistrationName"
private var uivcRegistrationNameKey: String = "UIViewController.swinjectRegistrationName"
private var uivcWasInjectedKey: String = "UIViewController.wasInjected"

extension UIViewController: RegistrationNameAssociatable {
extension UIViewController: RegistrationNameAssociatable, InjectionVerifiable {
internal var swinjectRegistrationName: String? {
get {
return getAssociatedString(key: &uivcAssociationKey)
}
set {
setAssociatedString(newValue, key: &uivcAssociationKey)
}
get { return getAssociatedString(key: &uivcRegistrationNameKey) }
set { setAssociatedString(newValue, key: &uivcRegistrationNameKey) }
}

internal var wasInjected: Bool {
get { return getAssociatedBool(key: &uivcWasInjectedKey) ?? false }
set { setAssociatedBool(newValue, key: &uivcWasInjectedKey) }
}
}

#elseif os(OSX)

private var nsvcAssociationKey: String = "NSViewController.swinjectRegistrationName"
private var nswcAssociationKey: String = "NSWindowController.swinjectRegistrationName"
private var nsvcRegistrationNameKey: String = "NSViewController.swinjectRegistrationName"
private var nswcRegistrationNameKey: String = "NSWindowController.swinjectRegistrationName"
private var nsvcWasInjectedKey: String = "NSViewController.wasInjected"
private var nswcWasInjectedKey: String = "NSWindowController.wasInjected"

extension NSViewController: RegistrationNameAssociatable {
extension NSViewController: RegistrationNameAssociatable, InjectionVerifiable {
internal var swinjectRegistrationName: String? {
get {
return getAssociatedString(key: &nsvcAssociationKey)
}
set {
setAssociatedString(newValue, key: &nsvcAssociationKey)
}
get { return getAssociatedString(key: &nsvcRegistrationNameKey) }
set { setAssociatedString(newValue, key: &nsvcRegistrationNameKey) }
}

internal var wasInjected: Bool {
get { return getAssociatedBool(key: &nsvcWasInjectedKey) ?? false }
set { setAssociatedBool(newValue, key: &nsvcWasInjectedKey) }
}
}

extension NSWindowController: RegistrationNameAssociatable {
extension NSWindowController: RegistrationNameAssociatable, InjectionVerifiable {
internal var swinjectRegistrationName: String? {
get {
return getAssociatedString(key: &nswcAssociationKey)
}
set {
setAssociatedString(newValue, key: &nswcAssociationKey)
}
get { return getAssociatedString(key: &nsvcRegistrationNameKey) }
set { setAssociatedString(newValue, key: &nsvcRegistrationNameKey) }
}

internal var wasInjected: Bool {
get { return getAssociatedBool(key: &nswcWasInjectedKey) ?? false }
set { setAssociatedBool(newValue, key: &nswcWasInjectedKey) }
}
}

#endif

extension NSObject {
fileprivate func getAssociatedString(key: UnsafeRawPointer) -> String? {
return objc_getAssociatedObject(self, key) as? String
}

fileprivate func setAssociatedString(_ string: String?, key: UnsafeRawPointer) {
objc_setAssociatedObject(self, key, string, objc_AssociationPolicy.OBJC_ASSOCIATION_COPY)
}

fileprivate func getAssociatedBool(key: UnsafeRawPointer) -> Bool? {
return (objc_getAssociatedObject(self, key) as? NSNumber)?.boolValue
}

fileprivate func setAssociatedBool(_ bool: Bool, key: UnsafeRawPointer) {
objc_setAssociatedObject(self, key, NSNumber(value: bool), objc_AssociationPolicy.OBJC_ASSOCIATION_COPY)
}
}
28 changes: 28 additions & 0 deletions SwinjectStoryboard.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,15 @@
CD2C63AD1D786C1F0075BC14 /* RelationshipReference1.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CD2C63AC1D786C1F0075BC14 /* RelationshipReference1.storyboard */; };
CD2C63AF1D786CD70075BC14 /* RelationshipReference2.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CD2C63AE1D786CD70075BC14 /* RelationshipReference2.storyboard */; };
CD2C63B01D786D6B0075BC14 /* SwinjectStoryboard+StoryboardReference.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD2C63A91D7864840075BC14 /* SwinjectStoryboard+StoryboardReference.swift */; };
CD3AB1991DC3A851001A45FA /* AnimalPagesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD3AB1981DC3A851001A45FA /* AnimalPagesViewController.swift */; };
CD3AB19A1DC3A851001A45FA /* AnimalPagesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD3AB1981DC3A851001A45FA /* AnimalPagesViewController.swift */; };
CD3AB1A91DC3A858001A45FA /* Pages.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CD3AB1A81DC3A858001A45FA /* Pages.storyboard */; };
CD3AB1AA1DC3A858001A45FA /* Pages.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CD3AB1A81DC3A858001A45FA /* Pages.storyboard */; };
CD3AB1AE1DC3A8CD001A45FA /* AnimalPagesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD3AB1AD1DC3A8CD001A45FA /* AnimalPagesViewController.swift */; };
CD3AB1B01DC3A8D6001A45FA /* Pages.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CD3AB1AF1DC3A8D6001A45FA /* Pages.storyboard */; };
CD3AB1B21DC3A94A001A45FA /* InjectionVerifiable.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD3AB1B11DC3A94A001A45FA /* InjectionVerifiable.swift */; };
CD3AB1B31DC3A94A001A45FA /* InjectionVerifiable.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD3AB1B11DC3A94A001A45FA /* InjectionVerifiable.swift */; };
CD3AB1B41DC3A94A001A45FA /* InjectionVerifiable.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD3AB1B11DC3A94A001A45FA /* InjectionVerifiable.swift */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
Expand Down Expand Up @@ -423,6 +432,11 @@
CD2C63A91D7864840075BC14 /* SwinjectStoryboard+StoryboardReference.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SwinjectStoryboard+StoryboardReference.swift"; sourceTree = "<group>"; };
CD2C63AC1D786C1F0075BC14 /* RelationshipReference1.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = RelationshipReference1.storyboard; sourceTree = "<group>"; };
CD2C63AE1D786CD70075BC14 /* RelationshipReference2.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = RelationshipReference2.storyboard; sourceTree = "<group>"; };
CD3AB1981DC3A851001A45FA /* AnimalPagesViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnimalPagesViewController.swift; sourceTree = "<group>"; };
CD3AB1A81DC3A858001A45FA /* Pages.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Pages.storyboard; sourceTree = "<group>"; };
CD3AB1AD1DC3A8CD001A45FA /* AnimalPagesViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnimalPagesViewController.swift; sourceTree = "<group>"; };
CD3AB1AF1DC3A8D6001A45FA /* Pages.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Pages.storyboard; sourceTree = "<group>"; };
CD3AB1B11DC3A94A001A45FA /* InjectionVerifiable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InjectionVerifiable.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -537,8 +551,10 @@
isa = PBXGroup;
children = (
983DFEF01CDB426100D39731 /* AnimalViewController.swift */,
CD3AB1AD1DC3A8CD001A45FA /* AnimalPagesViewController.swift */,
983DFEF11CDB426100D39731 /* AnimalWindowController.swift */,
983DFEEF1CDB426100D39731 /* Animals.storyboard */,
CD3AB1AF1DC3A8D6001A45FA /* Pages.storyboard */,
983DFEF51CDB426100D39731 /* Tabs.storyboard */,
983DFEF61CDB426100D39731 /* ViewController1.swift */,
CD2C63AC1D786C1F0075BC14 /* RelationshipReference1.storyboard */,
Expand All @@ -553,8 +569,10 @@
isa = PBXGroup;
children = (
983DFED91CDB425600D39731 /* AnimalViewController.swift */,
CD3AB1981DC3A851001A45FA /* AnimalPagesViewController.swift */,
983DFED81CDB425600D39731 /* Animals.storyboard */,
983DFEDD1CDB425600D39731 /* Tabs.storyboard */,
CD3AB1A81DC3A858001A45FA /* Pages.storyboard */,
983DFEDA1CDB425600D39731 /* Storyboard1.storyboard */,
983DFEDB1CDB425600D39731 /* Storyboard2.storyboard */,
983DFED71CDB425600D39731 /* AnimalPlayerViewController.swift */,
Expand Down Expand Up @@ -612,6 +630,7 @@
983DFEA01CDB410D00D39731 /* Storyboard+Swizzling.swift */,
983DFEA41CDB410D00D39731 /* ViewController+Swinject.swift */,
983DFF0A1CDB440800D39731 /* Box.swift */,
CD3AB1B11DC3A94A001A45FA /* InjectionVerifiable.swift */,
983DFF0E1CDB444E00D39731 /* RegistrationNameAssociatable.swift */,
CD2C63A91D7864840075BC14 /* SwinjectStoryboard+StoryboardReference.swift */,
985904091CDB0AA700275E4A /* SwinjectStoryboard.h */,
Expand Down Expand Up @@ -1138,6 +1157,7 @@
983DFEEC1CDB425600D39731 /* Tabs.storyboard in Resources */,
983DFEDE1CDB425600D39731 /* AnimalPlayerViewController.storyboard in Resources */,
983DFEE61CDB425600D39731 /* Storyboard1.storyboard in Resources */,
CD3AB1A91DC3A858001A45FA /* Pages.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand All @@ -1158,6 +1178,7 @@
983DFEFA1CDB426100D39731 /* Storyboard1.storyboard in Resources */,
CD2C63AD1D786C1F0075BC14 /* RelationshipReference1.storyboard in Resources */,
CD2C63AF1D786CD70075BC14 /* RelationshipReference2.storyboard in Resources */,
CD3AB1B01DC3A8D6001A45FA /* Pages.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand All @@ -1179,6 +1200,7 @@
983DFEED1CDB425600D39731 /* Tabs.storyboard in Resources */,
983DFEDF1CDB425600D39731 /* AnimalPlayerViewController.storyboard in Resources */,
983DFEE71CDB425600D39731 /* Storyboard1.storyboard in Resources */,
CD3AB1AA1DC3A858001A45FA /* Pages.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand All @@ -1191,6 +1213,7 @@
files = (
983DFEA81CDB410D00D39731 /* Storyboard+Swizzling.swift in Sources */,
983DFEA51CDB410D00D39731 /* Container+SwinjectStoryboard.swift in Sources */,
CD3AB1B21DC3A94A001A45FA /* InjectionVerifiable.swift in Sources */,
983DFEAE1CDB410D00D39731 /* SwinjectStoryboardOption.swift in Sources */,
983DFF0F1CDB444E00D39731 /* RegistrationNameAssociatable.swift in Sources */,
983DFEAB1CDB410D00D39731 /* SwinjectStoryboard.swift in Sources */,
Expand All @@ -1207,6 +1230,7 @@
buildActionMask = 2147483647;
files = (
983DFED21CDB423800D39731 /* ViewController+SwinjectSpec.swift in Sources */,
CD3AB1991DC3A851001A45FA /* AnimalPagesViewController.swift in Sources */,
983DFEE41CDB425600D39731 /* AnimalViewController.swift in Sources */,
983DFEEA1CDB425600D39731 /* SwinjectStoryboardSpec.swift in Sources */,
983DFEC91CDB423800D39731 /* Container+SwinjectStoryboardSpec.swift in Sources */,
Expand All @@ -1225,6 +1249,7 @@
files = (
983DFEA91CDB410D00D39731 /* Storyboard+Swizzling.swift in Sources */,
983DFEA61CDB410D00D39731 /* Container+SwinjectStoryboard.swift in Sources */,
CD3AB1B31DC3A94A001A45FA /* InjectionVerifiable.swift in Sources */,
983DFEAF1CDB410D00D39731 /* SwinjectStoryboardOption.swift in Sources */,
983DFF101CDB444E00D39731 /* RegistrationNameAssociatable.swift in Sources */,
CD2C63B01D786D6B0075BC14 /* SwinjectStoryboard+StoryboardReference.swift in Sources */,
Expand All @@ -1247,6 +1272,7 @@
983DFF081CDB433A00D39731 /* FoodType.swift in Sources */,
983DFEFC1CDB426100D39731 /* SwinjectStoryboardSpec.swift in Sources */,
983DFED01CDB423800D39731 /* Storyboard+SwizzlingSpec.swift in Sources */,
CD3AB1AE1DC3A8CD001A45FA /* AnimalPagesViewController.swift in Sources */,
983DFECD1CDB423800D39731 /* NSWindowController+SwinjectSpec.swift in Sources */,
983DFEF81CDB426100D39731 /* AnimalViewController.swift in Sources */,
983DFEF91CDB426100D39731 /* AnimalWindowController.swift in Sources */,
Expand All @@ -1260,6 +1286,7 @@
files = (
983DFEAA1CDB410D00D39731 /* Storyboard+Swizzling.swift in Sources */,
983DFEA71CDB410D00D39731 /* Container+SwinjectStoryboard.swift in Sources */,
CD3AB1B41DC3A94A001A45FA /* InjectionVerifiable.swift in Sources */,
983DFEB01CDB410D00D39731 /* SwinjectStoryboardOption.swift in Sources */,
983DFF111CDB444E00D39731 /* RegistrationNameAssociatable.swift in Sources */,
983DFEAD1CDB410D00D39731 /* SwinjectStoryboard.swift in Sources */,
Expand All @@ -1276,6 +1303,7 @@
buildActionMask = 2147483647;
files = (
983DFED41CDB423800D39731 /* ViewController+SwinjectSpec.swift in Sources */,
CD3AB19A1DC3A851001A45FA /* AnimalPagesViewController.swift in Sources */,
983DFEE51CDB425600D39731 /* AnimalViewController.swift in Sources */,
983DFEEB1CDB425600D39731 /* SwinjectStoryboardSpec.swift in Sources */,
983DFECB1CDB423800D39731 /* Container+SwinjectStoryboardSpec.swift in Sources */,
Expand Down
23 changes: 23 additions & 0 deletions Tests/OSX/AnimalPagesViewController.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
//
// AnimalPagesViewController.swift
// Swinject
//
// Created by Jakub Vaňo on 27/10/16.
// Copyright © 2016 Swinject Contributors. All rights reserved.
//

import AppKit
import Swinject

internal class AnimalPagesViewController: NSPageController {
let animalPage: AnimalViewController

required init?(coder aDecoder: NSCoder) {
animalPage = NSStoryboard(
name: "Pages",
bundle: Bundle(for: AnimalPagesViewController.self)
).instantiateController(withIdentifier: "AnimalPage") as! AnimalViewController

super.init(coder: aDecoder)
}
}
35 changes: 35 additions & 0 deletions Tests/OSX/Pages.storyboard
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="11201" systemVersion="15G1004" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" initialViewController="DpY-PQ-6TH">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="11201"/>
</dependencies>
<scenes>
<!--Animal Pages View Controller-->
<scene sceneID="ajh-jW-cvc">
<objects>
<pagecontroller id="DpY-PQ-6TH" customClass="AnimalPagesViewController" customModule="SwinjectTests" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" id="O2u-zU-y44">
<rect key="frame" x="0.0" y="0.0" width="450" height="300"/>
<autoresizingMask key="autoresizingMask"/>
</view>
</pagecontroller>
<customObject id="Kah-4j-UwY" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-189" y="-5"/>
</scene>
<!--Animal View Controller-->
<scene sceneID="F7O-wN-Mlm">
<objects>
<viewController storyboardIdentifier="AnimalPage" id="laR-VI-hPN" customClass="AnimalViewController" customModule="SwinjectTests" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" id="kEW-PC-iVW">
<rect key="frame" x="0.0" y="0.0" width="450" height="300"/>
<autoresizingMask key="autoresizingMask"/>
</view>
</viewController>
<customObject id="dRT-QK-Lv6" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="282" y="-5"/>
</scene>
</scenes>
</document>
12 changes: 12 additions & 0 deletions Tests/OSX/SwinjectStoryboardSpec.swift
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,18 @@ class SwinjectStoryboardSpec: QuickSpec {
expect(animalViewController.hasAnimal(named: "Mimi")) == true
}
}
context("with second controller instantiation during instantiation of initial one") {
it("injects second controller.") {
container.storyboardInitCompleted(AnimalViewController.self) { r, c in
c.animal = r.resolve(AnimalType.self)
}
container.register(AnimalType.self) { _ in Cat(name: "Mimi") }

let storyboard = SwinjectStoryboard.create(name: "Pages", bundle: bundle, container: container)
let pagesController = storyboard.instantiateInitialController() as! AnimalPagesViewController
expect(pagesController.animalPage.hasAnimal(named: "Mimi")) == true
}
}
}
describe("Initial controller") {
it("injects dependency definded by initCompleted handler.") {
Expand Down
Loading

0 comments on commit 51f55e5

Please sign in to comment.