diff --git a/ObserverSet.xcodeproj/project.pbxproj b/ObserverSet.xcodeproj/project.pbxproj index 1c37d17..3672800 100644 --- a/ObserverSet.xcodeproj/project.pbxproj +++ b/ObserverSet.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 3129A56D1F1FD39B006C0C6F /* MemorizingObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3129A56C1F1FD39B006C0C6F /* MemorizingObserver.swift */; }; 8E904FD01C80206600383B20 /* CaptureSemanticsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E904FCF1C80206600383B20 /* CaptureSemanticsTests.swift */; }; C2CC54651A71D8610084E87D /* ObserverSet.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C2CC54591A71D8610084E87D /* ObserverSet.framework */; }; C2CC546C1A71D8610084E87D /* ObserverSetTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2CC546B1A71D8610084E87D /* ObserverSetTests.swift */; }; @@ -24,6 +25,7 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 3129A56C1F1FD39B006C0C6F /* MemorizingObserver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MemorizingObserver.swift; sourceTree = ""; }; 8E904FCF1C80206600383B20 /* CaptureSemanticsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CaptureSemanticsTests.swift; sourceTree = ""; }; C2CC54591A71D8610084E87D /* ObserverSet.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ObserverSet.framework; sourceTree = BUILT_PRODUCTS_DIR; }; C2CC545D1A71D8610084E87D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -74,6 +76,7 @@ isa = PBXGroup; children = ( C2CC54751A71D8FE0084E87D /* ObserverSet.swift */, + 3129A56C1F1FD39B006C0C6F /* MemorizingObserver.swift */, C2CC545C1A71D8610084E87D /* Supporting Files */, ); path = ObserverSet; @@ -162,14 +165,16 @@ attributes = { LastSwiftMigration = 0720; LastSwiftUpdateCheck = 0720; - LastUpgradeCheck = 0720; + LastUpgradeCheck = 0830; ORGANIZATIONNAME = "Mike Ash"; TargetAttributes = { C2CC54581A71D8610084E87D = { CreatedOnToolsVersion = 6.1.1; + LastSwiftMigration = 0830; }; C2CC54631A71D8610084E87D = { CreatedOnToolsVersion = 6.1.1; + LastSwiftMigration = 0830; }; }; }; @@ -213,6 +218,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 3129A56D1F1FD39B006C0C6F /* MemorizingObserver.swift in Sources */, C2CC54761A71D8FE0084E87D /* ObserverSet.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -250,8 +256,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; COPY_PHASE_STRIP = NO; @@ -260,6 +268,7 @@ ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", @@ -295,8 +304,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; COPY_PHASE_STRIP = YES; @@ -305,6 +316,7 @@ ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; @@ -314,6 +326,7 @@ MACOSX_DEPLOYMENT_TARGET = 10.10; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; @@ -336,6 +349,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 3.0; }; name = Debug; }; @@ -355,6 +369,7 @@ PRODUCT_BUNDLE_IDENTIFIER = "com.mikeash.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; + SWIFT_VERSION = 3.0; }; name = Release; }; @@ -374,6 +389,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "com.mikeash.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 3.0; }; name = Debug; }; @@ -389,6 +405,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "com.mikeash.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 3.0; }; name = Release; }; diff --git a/ObserverSet.xcodeproj/xcuserdata/jacobsieradzki.xcuserdatad/xcschemes/ObserverSet.xcscheme b/ObserverSet.xcodeproj/xcuserdata/jacobsieradzki.xcuserdatad/xcschemes/ObserverSet.xcscheme new file mode 100644 index 0000000..eec9e50 --- /dev/null +++ b/ObserverSet.xcodeproj/xcuserdata/jacobsieradzki.xcuserdatad/xcschemes/ObserverSet.xcscheme @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ObserverSet/MemorizingObserver.swift b/ObserverSet/MemorizingObserver.swift new file mode 100644 index 0000000..6576131 --- /dev/null +++ b/ObserverSet/MemorizingObserver.swift @@ -0,0 +1,44 @@ +// +// MemorizingObserver.swift +// ObserverSet +// +// Created by Jake Sieradzki on 19/07/2017. +// Copyright © 2017 Mike Ash. All rights reserved. +// + +import Cocoa + +class MemorizingObserver { + + private var observerStorage = ObserverSet() + private var hasObserver = false + + private var message: T? + + @discardableResult + func set(_ observer: ObserverType, callback: @escaping (ObserverType) -> (T) -> Void) -> ObserverSetEntry { + observerStorage = ObserverSet() + hasObserver = true + let entry = observerStorage.add(observer, callback) + sendBufferMessage() + return entry + } + + func notify(_ message: T) { + if hasObserver { + observerStorage.notify(message) + self.message = nil + } else { + self.message = message + } + } + + private func sendBufferMessage() { + guard let message = message else { + return + } + notify(message) + } + +} + diff --git a/ObserverSet/ObserverSet.swift b/ObserverSet/ObserverSet.swift index 84d5dae..3cc0e0a 100644 --- a/ObserverSet/ObserverSet.swift +++ b/ObserverSet/ObserverSet.swift @@ -8,85 +8,87 @@ import Foundation -public class ObserverSetEntry { - private weak var object: AnyObject? - private let f: AnyObject -> Parameters -> Void - - private init(object: AnyObject, f: AnyObject -> Parameters -> Void) { - self.object = object - self.f = f - } +open class ObserverSetEntry { + fileprivate weak var object: AnyObject? + fileprivate let f: (AnyObject) -> (Parameters) -> Void + + fileprivate init(object: AnyObject, f: @escaping (AnyObject) -> (Parameters) -> Void) { + self.object = object + self.f = f + } } -public class ObserverSet: CustomStringConvertible { - // Locking support - - private var queue = dispatch_queue_create("com.mikeash.ObserverSet", nil) - - private func synchronized(f: Void -> Void) { - dispatch_sync(queue, f) +open class ObserverSet: CustomStringConvertible { + // Locking support + + fileprivate var queue = DispatchQueue(label: "com.mikeash.ObserverSet", attributes: []) + + fileprivate func synchronized(_ f: (Void) -> Void) { + queue.sync(execute: f) + } + + + // Main implementation + + fileprivate var entries: [ObserverSetEntry] = [] + + public init() {} + + @discardableResult + open func add(_ object: T, _ f: @escaping (T) -> (Parameters) -> Void) -> ObserverSetEntry { + let entry = ObserverSetEntry(object: object, f: { f($0 as! T) }) + synchronized { + self.entries.append(entry) } + return entry + } + + @discardableResult + open func add(_ f: @escaping (Parameters) -> Void) -> ObserverSetEntry { + return self.add(self, { ignored in f }) + } + + open func remove(_ entry: ObserverSetEntry) { + synchronized { + self.entries = self.entries.filter{ $0 !== entry } + } + } + + open func notify(_ parameters: Parameters) { + var toCall: [(Parameters) -> Void] = [] - - // Main implementation - - private var entries: [ObserverSetEntry] = [] - - public init() {} - - public func add(object: T, _ f: T -> Parameters -> Void) -> ObserverSetEntry { - let entry = ObserverSetEntry(object: object, f: { f($0 as! T) }) - synchronized { - self.entries.append(entry) + synchronized { + for entry in self.entries { + if let object: AnyObject = entry.object { + toCall.append(entry.f(object)) } - return entry + } + self.entries = self.entries.filter{ $0.object != nil } } - public func add(f: Parameters -> Void) -> ObserverSetEntry { - return self.add(self, { ignored in f }) + for f in toCall { + f(parameters) } - - public func remove(entry: ObserverSetEntry) { - synchronized { - self.entries = self.entries.filter{ $0 !== entry } - } + } + + + // MARK: CustomStringConvertible + + open var description: String { + var entries: [ObserverSetEntry] = [] + synchronized { + entries = self.entries } - public func notify(parameters: Parameters) { - var toCall: [Parameters -> Void] = [] - - synchronized { - for entry in self.entries { - if let object: AnyObject = entry.object { - toCall.append(entry.f(object)) - } - } - self.entries = self.entries.filter{ $0.object != nil } - } - - for f in toCall { - f(parameters) - } + let strings = entries.map{ + entry in + (entry.object === self + ? "\(entry.f)" + : "\(String(describing: entry.object)) \(entry.f)") } + let joined = strings.joined(separator: ", ") - - // MARK: CustomStringConvertible - - public var description: String { - var entries: [ObserverSetEntry] = [] - synchronized { - entries = self.entries - } - - let strings = entries.map{ - entry in - (entry.object === self - ? "\(entry.f)" - : "\(entry.object) \(entry.f)") - } - let joined = strings.joinWithSeparator(", ") - - return "\(Mirror(reflecting: self).description): (\(joined))" - } + return "\(Mirror(reflecting: self).description): (\(joined))" + } } diff --git a/ObserverSetTests/CaptureSemanticsTests.swift b/ObserverSetTests/CaptureSemanticsTests.swift index 74c4066..7a4002d 100644 --- a/ObserverSetTests/CaptureSemanticsTests.swift +++ b/ObserverSetTests/CaptureSemanticsTests.swift @@ -24,7 +24,7 @@ class CaptureSemanticsTests: XCTestCase { }) } - private func foo() { + fileprivate func foo() { } } @@ -32,7 +32,7 @@ class CaptureSemanticsTests: XCTestCase { let observee = TestObservee() init() { - observee.event.add(self, self.dynamicType.voidHandler) + observee.event.add(self, type(of: self).voidHandler) } func voidHandler() { @@ -68,4 +68,4 @@ class CaptureSemanticsTests: XCTestCase { XCTAssertNotNil(obj, "Registering a bound member function may result in a strong reference cycle. Use the overload of add() that takes an observer object and an unbound member function.") } -} \ No newline at end of file +} diff --git a/ObserverSetTests/ObserverSetTests.swift b/ObserverSetTests/ObserverSetTests.swift index 9b87813..6cca85e 100644 --- a/ObserverSetTests/ObserverSetTests.swift +++ b/ObserverSetTests/ObserverSetTests.swift @@ -32,12 +32,12 @@ class ObserverSetTests: XCTestCase { class TestObserver { init(observee: TestObservee) { - observee.voidObservers.add(self, self.dynamicType.voidSent) - observee.stringObservers.add(self, self.dynamicType.stringChanged) - observee.twoStringObservers.add(self, self.dynamicType.twoStringChanged) - observee.intObservers.add(self, self.dynamicType.intChanged) - observee.intAndStringObservers.add(self, self.dynamicType.intAndStringChanged) - observee.namedParameterObservers.add(self, self.dynamicType.namedParameterSent) + observee.voidObservers.add(self, type(of: self).voidSent) + observee.stringObservers.add(self, type(of: self).stringChanged) + observee.twoStringObservers.add(self, type(of: self).twoStringChanged) + observee.intObservers.add(self, type(of: self).intChanged) + observee.intAndStringObservers.add(self, type(of: self).intAndStringChanged) + observee.namedParameterObservers.add(self, type(of: self).namedParameterSent) } deinit { @@ -48,23 +48,23 @@ class ObserverSetTests: XCTestCase { print("void sent") } - func stringChanged(s: String) { + func stringChanged(_ s: String) { print("stringChanged: " + s) } - func twoStringChanged(s1: String, s2: String) { + func twoStringChanged(_ s1: String, s2: String) { print("twoStringChanged: \(s1) \(s2)") } - func intChanged(i: Int, j: Int) { + func intChanged(_ i: Int, j: Int) { print("intChanged: \(i) \(j)") } - func intAndStringChanged(i: Int, s: String) { + func intAndStringChanged(_ i: Int, s: String) { print("intAndStringChanged: \(i) \(s)") } - func namedParameterSent(name: String, count: Int) { + func namedParameterSent(_ name: String, count: Int) { print("Named parameters: \(name) \(count)") } }