diff --git a/.gitignore b/.gitignore index bb3409d..629b487 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,7 @@ Pods/ # Add this line if you want to avoid checking in source code from Carthage dependencies. Carthage/Checkouts Carthage/Build + +# MacOS +# +.DS_Store diff --git a/NSObject+Rx.swift b/NSObject+Rx.swift deleted file mode 100644 index 1d0669f..0000000 --- a/NSObject+Rx.swift +++ /dev/null @@ -1,44 +0,0 @@ -import Foundation -import RxSwift -import ObjectiveC - -public extension NSObject { - fileprivate struct AssociatedKeys { - static var DisposeBag = "rx_disposeBag" - } - - fileprivate func doLocked(_ closure: () -> Void) { - objc_sync_enter(self); defer { objc_sync_exit(self) } - closure() - } - - var rx_disposeBag: DisposeBag { - get { - var disposeBag: DisposeBag! - doLocked { - let lookup = objc_getAssociatedObject(self, &AssociatedKeys.DisposeBag) as? DisposeBag - if let lookup = lookup { - disposeBag = lookup - } else { - let newDisposeBag = DisposeBag() - self.rx_disposeBag = newDisposeBag - disposeBag = newDisposeBag - } - } - return disposeBag - } - - set { - doLocked { - objc_setAssociatedObject(self, &AssociatedKeys.DisposeBag, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) - } - } - } -} - -public extension Reactive where Base: NSObject { - var disposeBag: DisposeBag { - get { return base.rx_disposeBag } - set { base.rx_disposeBag = newValue } - } -} diff --git a/NSObject-Rx.xcodeproj/project.pbxproj b/NSObject-Rx.xcodeproj/project.pbxproj index db1eaf8..e621aa0 100644 --- a/NSObject-Rx.xcodeproj/project.pbxproj +++ b/NSObject-Rx.xcodeproj/project.pbxproj @@ -10,14 +10,46 @@ 188C6D941C47B2B20092101A /* NSObject_Rx.h in Headers */ = {isa = PBXBuildFile; fileRef = 188C6D931C47B2B20092101A /* NSObject_Rx.h */; settings = {ATTRIBUTES = (Public, ); }; }; 188C6D9F1C47B2D00092101A /* NSObject+Rx.swift in Sources */ = {isa = PBXBuildFile; fileRef = 188C6D9E1C47B2D00092101A /* NSObject+Rx.swift */; }; 188C6DA31C47B4240092101A /* RxSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 188C6DA21C47B4240092101A /* RxSwift.framework */; }; + 1AE62D711F50AE200011BA4F /* DisposeBagable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AE62D701F50AE200011BA4F /* DisposeBagable.swift */; }; + 1AE62DB51F50C2090011BA4F /* NSObject_RxTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AE62DB41F50C2090011BA4F /* NSObject_RxTests.swift */; }; + 1AE62DB71F50C2090011BA4F /* NSObject_Rx.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 188C6D911C47B2B20092101A /* NSObject_Rx.framework */; }; + 1AE62DC01F50C8230011BA4F /* RxSwift.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 1AE62DBF1F50C8230011BA4F /* RxSwift.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; /* End PBXBuildFile section */ +/* Begin PBXContainerItemProxy section */ + 1AE62DB81F50C2090011BA4F /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 18EE7A101C47B12F00C7256C /* Project object */; + proxyType = 1; + remoteGlobalIDString = 188C6D901C47B2B20092101A; + remoteInfo = NSObject_Rx; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 1AE62DBE1F50C7FB0011BA4F /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + 1AE62DC01F50C8230011BA4F /* RxSwift.framework in CopyFiles */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + /* Begin PBXFileReference section */ 188C6D911C47B2B20092101A /* NSObject_Rx.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = NSObject_Rx.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 188C6D931C47B2B20092101A /* NSObject_Rx.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NSObject_Rx.h; sourceTree = ""; }; 188C6D951C47B2B20092101A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 188C6D9E1C47B2D00092101A /* NSObject+Rx.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSObject+Rx.swift"; sourceTree = SOURCE_ROOT; }; + 188C6D9E1C47B2D00092101A /* NSObject+Rx.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "NSObject+Rx.swift"; path = "NSObject_Rx/NSObject+Rx.swift"; sourceTree = SOURCE_ROOT; }; 188C6DA21C47B4240092101A /* RxSwift.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = RxSwift.framework; path = Carthage/Build/iOS/RxSwift.framework; sourceTree = SOURCE_ROOT; }; + 1AE62D701F50AE200011BA4F /* DisposeBagable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DisposeBagable.swift; sourceTree = ""; }; + 1AE62DB21F50C2090011BA4F /* NSObject_RxTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = NSObject_RxTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 1AE62DB41F50C2090011BA4F /* NSObject_RxTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSObject_RxTests.swift; sourceTree = ""; }; + 1AE62DB61F50C2090011BA4F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 1AE62DBF1F50C8230011BA4F /* RxSwift.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = RxSwift.framework; path = Carthage/Build/iOS/RxSwift.framework; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -29,6 +61,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 1AE62DAF1F50C2090011BA4F /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 1AE62DB71F50C2090011BA4F /* NSObject_Rx.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ @@ -38,6 +78,7 @@ 188C6DA21C47B4240092101A /* RxSwift.framework */, 188C6D9E1C47B2D00092101A /* NSObject+Rx.swift */, 188C6D931C47B2B20092101A /* NSObject_Rx.h */, + 1AE62D701F50AE200011BA4F /* DisposeBagable.swift */, 188C6D951C47B2B20092101A /* Info.plist */, ); path = NSObject_Rx; @@ -46,7 +87,9 @@ 18EE7A0F1C47B12F00C7256C = { isa = PBXGroup; children = ( + 1AE62DBF1F50C8230011BA4F /* RxSwift.framework */, 188C6D921C47B2B20092101A /* NSObject_Rx */, + 1AE62DB31F50C2090011BA4F /* NSObject_RxTests */, 18EE7A191C47B12F00C7256C /* Products */, ); sourceTree = ""; @@ -55,10 +98,20 @@ isa = PBXGroup; children = ( 188C6D911C47B2B20092101A /* NSObject_Rx.framework */, + 1AE62DB21F50C2090011BA4F /* NSObject_RxTests.xctest */, ); name = Products; sourceTree = ""; }; + 1AE62DB31F50C2090011BA4F /* NSObject_RxTests */ = { + isa = PBXGroup; + children = ( + 1AE62DB41F50C2090011BA4F /* NSObject_RxTests.swift */, + 1AE62DB61F50C2090011BA4F /* Info.plist */, + ); + path = NSObject_RxTests; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -91,20 +144,43 @@ productReference = 188C6D911C47B2B20092101A /* NSObject_Rx.framework */; productType = "com.apple.product-type.framework"; }; + 1AE62DB11F50C2090011BA4F /* NSObject_RxTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 1AE62DBA1F50C2090011BA4F /* Build configuration list for PBXNativeTarget "NSObject_RxTests" */; + buildPhases = ( + 1AE62DAE1F50C2090011BA4F /* Sources */, + 1AE62DAF1F50C2090011BA4F /* Frameworks */, + 1AE62DB01F50C2090011BA4F /* Resources */, + 1AE62DBE1F50C7FB0011BA4F /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + 1AE62DB91F50C2090011BA4F /* PBXTargetDependency */, + ); + name = NSObject_RxTests; + productName = NSObject_RxTests; + productReference = 1AE62DB21F50C2090011BA4F /* NSObject_RxTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 18EE7A101C47B12F00C7256C /* Project object */ = { isa = PBXProject; attributes = { - LastSwiftUpdateCheck = 0720; - LastUpgradeCheck = 0720; + LastSwiftUpdateCheck = 0830; + LastUpgradeCheck = 0830; ORGANIZATIONNAME = RxSwiftCommunity; TargetAttributes = { 188C6D901C47B2B20092101A = { CreatedOnToolsVersion = 7.2; LastSwiftMigration = 0800; }; + 1AE62DB11F50C2090011BA4F = { + CreatedOnToolsVersion = 8.3.3; + ProvisioningStyle = Automatic; + }; }; }; buildConfigurationList = 18EE7A131C47B12F00C7256C /* Build configuration list for PBXProject "NSObject-Rx" */; @@ -121,6 +197,7 @@ projectRoot = ""; targets = ( 188C6D901C47B2B20092101A /* NSObject_Rx */, + 1AE62DB11F50C2090011BA4F /* NSObject_RxTests */, ); }; /* End PBXProject section */ @@ -133,6 +210,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 1AE62DB01F50C2090011BA4F /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -140,17 +224,35 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 1AE62D711F50AE200011BA4F /* DisposeBagable.swift in Sources */, 188C6D9F1C47B2D00092101A /* NSObject+Rx.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; + 1AE62DAE1F50C2090011BA4F /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 1AE62DB51F50C2090011BA4F /* NSObject_RxTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXSourcesBuildPhase section */ +/* Begin PBXTargetDependency section */ + 1AE62DB91F50C2090011BA4F /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 188C6D901C47B2B20092101A /* NSObject_Rx */; + targetProxy = 1AE62DB81F50C2090011BA4F /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + /* Begin XCBuildConfiguration section */ 188C6D9B1C47B2B20092101A /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { CLANG_ENABLE_MODULES = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; @@ -179,6 +281,7 @@ isa = XCBuildConfiguration; buildSettings = { CLANG_ENABLE_MODULES = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; @@ -215,8 +318,10 @@ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; @@ -259,8 +364,10 @@ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; @@ -279,10 +386,55 @@ IPHONEOS_DEPLOYMENT_TARGET = 9.2; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; VALIDATE_PRODUCT = YES; }; name = Release; }; + 1AE62DBB1F50C2090011BA4F /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Carthage/Build/iOS", + ); + INFOPLIST_FILE = NSObject_RxTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 10.3; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "RxSwiftCommunity.NSObject-Rx.NSObject-RxTests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_VERSION = 3.0; + }; + name = Debug; + }; + 1AE62DBC1F50C2090011BA4F /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Carthage/Build/iOS", + ); + INFOPLIST_FILE = NSObject_RxTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 10.3; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "RxSwiftCommunity.NSObject-Rx.NSObject-RxTests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_VERSION = 3.0; + }; + name = Release; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -304,6 +456,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + 1AE62DBA1F50C2090011BA4F /* Build configuration list for PBXNativeTarget "NSObject_RxTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 1AE62DBB1F50C2090011BA4F /* Debug */, + 1AE62DBC1F50C2090011BA4F /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; /* End XCConfigurationList section */ }; rootObject = 18EE7A101C47B12F00C7256C /* Project object */; diff --git a/NSObject-Rx.xcodeproj/xcshareddata/xcschemes/NSObject_Rx.xcscheme b/NSObject-Rx.xcodeproj/xcshareddata/xcschemes/NSObject_Rx.xcscheme index 42e5bb7..132b2e4 100644 --- a/NSObject-Rx.xcodeproj/xcshareddata/xcschemes/NSObject_Rx.xcscheme +++ b/NSObject-Rx.xcodeproj/xcshareddata/xcschemes/NSObject_Rx.xcscheme @@ -1,13 +1,13 @@ + + + + + + + + + + + + diff --git a/NSObject_Rx/DisposeBagable.swift b/NSObject_Rx/DisposeBagable.swift new file mode 100644 index 0000000..043bd1e --- /dev/null +++ b/NSObject_Rx/DisposeBagable.swift @@ -0,0 +1,53 @@ +// +// DisposeBagable.swift +// NSObject-Rx +// +// Created by Thibault Wittemberg on 2017-08-25. +// Copyright © 2017 RxSwiftCommunity. All rights reserved. +// + +import Foundation +import RxSwift +import ObjectiveC + +fileprivate struct AssociatedKeys { + static var disposeBag = "disposeBag" +} + +/// each DisposeBagable offers a unique Rx DisposeBag instance +public protocol HasDisposeBag { + + /// a unique Rx DisposeBag instance + var disposeBag: DisposeBag { get set } +} + +extension HasDisposeBag { + + private func doLocked(forClosure closure: () -> Void) { + objc_sync_enter(self); defer { objc_sync_exit(self) } + closure() + } + + public var disposeBag: DisposeBag { + get { + var rxDisposeBag: DisposeBag! + doLocked { + let lookup = objc_getAssociatedObject(self, &AssociatedKeys.disposeBag) as? DisposeBag + if let lookup = lookup { + rxDisposeBag = lookup + } else { + let newDisposeBag = DisposeBag() + objc_setAssociatedObject(self, &AssociatedKeys.disposeBag, newDisposeBag, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) + rxDisposeBag = newDisposeBag + } + } + return rxDisposeBag + } + + set { + doLocked { + objc_setAssociatedObject(self, &AssociatedKeys.disposeBag, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) + } + } + } +} diff --git a/NSObject_Rx/NSObject+Rx.swift b/NSObject_Rx/NSObject+Rx.swift new file mode 100644 index 0000000..ac14a4a --- /dev/null +++ b/NSObject_Rx/NSObject+Rx.swift @@ -0,0 +1,18 @@ +import Foundation +import RxSwift +import ObjectiveC + +extension NSObject: HasDisposeBag { +} + +public extension Reactive where Base: HasDisposeBag { + + /// a unique DisposeBag that is related to the Reactive.Base instance + var disposeBag: DisposeBag { + get { return base.disposeBag } + set { + var mutableBase = base + mutableBase.disposeBag = newValue + } + } +} diff --git a/NSObject_RxTests/Info.plist b/NSObject_RxTests/Info.plist new file mode 100644 index 0000000..6c6c23c --- /dev/null +++ b/NSObject_RxTests/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/NSObject_RxTests/NSObject_RxTests.swift b/NSObject_RxTests/NSObject_RxTests.swift new file mode 100644 index 0000000..9e75944 --- /dev/null +++ b/NSObject_RxTests/NSObject_RxTests.swift @@ -0,0 +1,52 @@ +// +// NSObject_RxTests.swift +// NSObject_RxTests +// +// Created by Thibault Wittemberg on 2017-08-25. +// Copyright © 2017 RxSwiftCommunity. All rights reserved. +// + +import XCTest +import RxSwift +@testable import NSObject_Rx + +class MockNSObject: NSObject { +} + +class DisposableObject: HasDisposeBag { +} + +class NSObject_RxTests: XCTestCase { + + private var mockNSObject: MockNSObject! + private var disposableObject: DisposableObject! + + override func setUp() { + super.setUp() + self.mockNSObject = MockNSObject() + self.disposableObject = DisposableObject() + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + super.tearDown() + } + + func testNSObject() { + XCTAssertNotNil(self.mockNSObject.rx.disposeBag) + let newDisposeBag = DisposeBag() + self.mockNSObject.rx.disposeBag = newDisposeBag + let address1 = Unmanaged.passUnretained(self.mockNSObject.rx.disposeBag as AnyObject).toOpaque() + let address2 = Unmanaged.passUnretained(newDisposeBag as AnyObject).toOpaque() + XCTAssert(address1 == address2) + } + + func testDisposeBagable() { + XCTAssertNotNil(self.disposableObject.disposeBag) + let newDisposeBag = DisposeBag() + self.disposableObject.disposeBag = newDisposeBag + let address1 = Unmanaged.passUnretained(self.disposableObject.disposeBag as AnyObject).toOpaque() + let address2 = Unmanaged.passUnretained(newDisposeBag as AnyObject).toOpaque() + XCTAssert(address1 == address2) + } +} diff --git a/Readme.md b/Readme.md index aad867a..e12b843 100644 --- a/Readme.md +++ b/Readme.md @@ -29,6 +29,8 @@ Sweet. It'll work just like a property: when the instance is deinit'd, the `DisposeBag` gets disposed. It's also a read/write property, so you can use your own, too. +If you want to add a DisposeBag to an Object that does not inherit from NSObject, you can also implement the protocol `HasDisposeBag`, and you're good to go. This protocol provides a default DisposeBag called `disposeBag`. + Installing ----------