<commit_before>import Foundation

class KVO: NSObject {

	public enum Errors: Error {
		case invalidKVO(String)
	}

	private class Binding {
		let selector: Selector
		init(_ selector: Selector) {
			self.selector = selector
		}
	}
	
	private class Context {
		weak var observable: NSObject?
		var keyPathBindings: [String: Binding] = [:]
		init(_ observable: NSObject) {
			self.observable = observable
		}
	}
	
	
	// MARK: - Properties
	
	private var _contexts: [Context] = []
	private weak var _target: NSObject?
	
	var callOnUIThread = true
	
	
	// MARK: - Inits
	
	init(target: NSObject) {
		_target = target
	}
	
	
	// MARK: - Deinit
	
	deinit {
		removeAllObservers()
	}
	
	
	// MARK: - Functions
	
	func startObserving(_ observable: NSObject,
		forKeyPath keyPath: String,
		options: NSKeyValueObservingOptions = [.new, .initial],
		selector: Selector) throws {
		// Get context structure (or create one).
		var context = _contextForObservable(observable)
		if context == nil {
			// Create new context.
			let newContext = Context(observable)
			context = newContext
			
			// Add context.
			_contexts.append(newContext)
		}
		
		
		if context?.keyPathBindings[keyPath] != nil {
			// Throw an exception if the binding already exists.
			throw Errors.invalidKVO("KVO already observing for keypath: \(keyPath)")
		}
		
		// Create binding.
		let binding = Binding(selector)
		
		// Add binding to array.
		context?.keyPathBindings[keyPath] = binding

		// Start observing.
		observable.addObserver(self,
			forKeyPath: keyPath,
			options: options,
			context: nil)
	}
	
	func stopObserving(_ observable: NSObject, forKeyPath keyPath: String) {
		// Skip if keypath isn't mapped.
		guard let context = _contextForObservable(observable),
			context.keyPathBindings[keyPath] != nil else {
			return
		}
		
		// Remove observer.
		context.observable?.removeObserver(self, forKeyPath: keyPath)
		
		// Remove binding.
		context.keyPathBindings.removeValue(forKey: keyPath)
	}
	
	func removeAllObservers() {
		// Remove all remaining contexts.
		for context in _contexts {
			// Stop observing for all keypaths.
			for keyPath in context.keyPathBindings.keys {
				// Stop observing.
				context.observable?.removeObserver(self, forKeyPath: keyPath)
			}
		}
		
		// Clear the contexts array.
		_contexts.removeAll()
	}
	
	override func observeValue(
		forKeyPath keyPath: String?,
		of object: Any?,
		change: [NSKeyValueChangeKey : Any]?,
		context: UnsafeMutableRawPointer?) {
		// Skip if keypath isn't mapped.
		guard let observable = object as? NSObject,
			let context = _contextForObservable(observable),
			let keyPath = keyPath,
			let binding = context.keyPathBindings[keyPath] else {
			return
		}
		
		// Skip if target isn't set, or is deallocated.
		if _target == nil {
			return
		}
		
		if (callOnUIThread && !Thread.isMainThread)
		{
			// Call back on the UI-thread.
			DispatchQueue.main.async { [weak self] in
				self?._notify(binding, change: change, observable: observable)
			}
		} else {
			// Call back directly.
			_notify(binding, change: change, observable: observable)<commit_msg>Make KVO functions public.<commit_after>import Foundation

public class KVO: NSObject {

	public enum Errors: Error {
		case invalidKVO(String)
	}

	private class Binding {
		let selector: Selector
		init(_ selector: Selector) {
			self.selector = selector
		}
	}
	
	private class Context {
		weak var observable: NSObject?
		var keyPathBindings: [String: Binding] = [:]
		init(_ observable: NSObject) {
			self.observable = observable
		}
	}
	
	
	// MARK: - Properties
	
	private var _contexts: [Context] = []
	private weak var _target: NSObject?
	
	var callOnUIThread = true
	
	
	// MARK: - Inits
	
	public init(target: NSObject) {
		_target = target
	}
	
	
	// MARK: - Deinit
	
	deinit {
		removeAllObservers()
	}
	
	
	// MARK: - Functions
	
	public func startObserving(_ observable: NSObject,
		forKeyPath keyPath: String,
		options: NSKeyValueObservingOptions = [.new, .initial],
		selector: Selector) throws {
		// Get context structure (or create one).
		var context = _contextForObservable(observable)
		if context == nil {
			// Create new context.
			let newContext = Context(observable)
			context = newContext
			
			// Add context.
			_contexts.append(newContext)
		}
		
		
		if context?.keyPathBindings[keyPath] != nil {
			// Throw an exception if the binding already exists.
			throw Errors.invalidKVO("KVO already observing for keypath: \(keyPath)")
		}
		
		// Create binding.
		let binding = Binding(selector)
		
		// Add binding to array.
		context?.keyPathBindings[keyPath] = binding

		// Start observing.
		observable.addObserver(self,
			forKeyPath: keyPath,
			options: options,
			context: nil)
	}
	
	public func stopObserving(_ observable: NSObject, forKeyPath keyPath: String) {
		// Skip if keypath isn't mapped.
		guard let context = _contextForObservable(observable),
			context.keyPathBindings[keyPath] != nil else {
			return
		}
		
		// Remove observer.
		context.observable?.removeObserver(self, forKeyPath: keyPath)
		
		// Remove binding.
		context.keyPathBindings.removeValue(forKey: keyPath)
	}
	
	public func removeAllObservers() {
		// Remove all remaining contexts.
		for context in _contexts {
			// Stop observing for all keypaths.
			for keyPath in context.keyPathBindings.keys {
				// Stop observing.
				context.observable?.removeObserver(self, forKeyPath: keyPath)
			}
		}
		
		// Clear the contexts array.
		_contexts.removeAll()
	}
	
	public override func observeValue(
		forKeyPath keyPath: String?,
		of object: Any?,
		change: [NSKeyValueChangeKey : Any]?,
		context: UnsafeMutableRawPointer?) {
		// Skip if keypath isn't mapped.
		guard let observable = object as? NSObject,
			let context = _contextForObservable(observable),
			let keyPath = keyPath,
			let binding = context.keyPathBindings[keyPath] else {
			return
		}
		
		// Skip if target isn't set, or is deallocated.
		if _target == nil {
			return
		}
		
		if (callOnUIThread && !Thread.isMainThread)
		{
			// Call back on the UI-thread.
			DispatchQueue.main.async { [weak self] in
				self?._notify(binding, change: change, observable: observable)
			}
		} else {
			// Call back directly.
			_notify(binding, change: change, observable: observable)
{'fraction_non_alphanumeric': 0.10039777247414479, 'fraction_numerical': 0.0, 'mean_word_length': 3.215962441314554, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '634955', 'n_tokens_mistral': 2341, 'n_tokens_neox': 1917, 'n_words': 550}