-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #10 from piknotech/work/8-weak-dictionary
Work/8 weak dictionary
- Loading branch information
Showing
9 changed files
with
225 additions
and
27 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
# WeakDictionary | ||
|
||
## Functionality | ||
|
||
A `WeakDictionary` is a dictionary holding weak references onto its values. It may be useful in similar areas as `WeakArray` or `WeakSet`: Everywhere, where only weak references to the dictionary values should be kept, so if the retain count of a value stored in the dictionary becomes zero, the value for the corresponding key gets removed. | ||
|
||
## Implementation | ||
|
||
A `WeakDictionary` is a generic type with Key and Value both conforming to `Hashable`. Itself, it conforms to | ||
- `CustomStringConvertible`, | ||
- `ExpressibleByDictionaryLiteral`, | ||
|
||
Internally, `WeakDictionary` stores a `Dictionary` with the values wrapped as `Weak<Value>`. Its contents can be accessed either via a `subscript` getter or the `contents` property, which returns a `Dictionary` with those key-value-pairs whose value is currently non-nil. This enables use of Swift-implemented methods on the `Dictionary` type. | ||
|
||
Apart from everything implemented by the protocols it conforms to, `WeakDictionary` offers: | ||
- a subscript to `get` and `set` values for a key | ||
- a method to clean out all those `Weak<Element>` instances internally stored that wrap a value already being `nil` | ||
- the `contents` property | ||
|
||
## Use | ||
|
||
You can create a `WeakDictionary` using a dictionary literal: | ||
|
||
```swift | ||
var weakDictionary: WeakDictionary<String: UIView> = ["test1": UIView(), "test2": UIView()] | ||
``` | ||
|
||
To set a value for a key, use a `subscript` setter: | ||
|
||
```swift | ||
let myView = UIView() | ||
weakDictionary["test3"] = myView | ||
``` | ||
|
||
To remove a value for a key, use a `subscript` setter with the value being `nil`: | ||
|
||
```swift | ||
weakDictionary["test3"] = nil | ||
``` | ||
|
||
If you don't call the `subscript` setter, that auto-performs a clean, it may be suitable to call it manually from time to time for a large `WeakDictionary`. | ||
|
||
```swift | ||
weakDictionary.clean() // Cleans out internal Weak<Element> instances whose contained value is nil | ||
``` | ||
|
||
To get the value for a key, you can use a `subscript` getter: | ||
|
||
```swift | ||
weakDictionary["test3"] // Returns nil for this configuration | ||
``` | ||
|
||
To access the `WeakDictionary`'s contents apart from the simple getter, you should use `contents`, which returns a dicionary of those key-value-pairs where the value isn't `nil`. At this point you can use interfaces provided by the `Dictionary` implementation of Swift. | ||
|
||
```swift | ||
print(weakDictionary.keys) | ||
print(weakDictionary.values) | ||
print(weakDictionary.count) | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
// | ||
// WeakDictionary.swift | ||
// SwiftCollections | ||
// | ||
// Created by Frederick Pietschmann on 12.03.18. | ||
// Copyright © 2018 Piknotech. All rights reserved. | ||
// | ||
|
||
import Foundation | ||
|
||
struct WeakDictionary<Key: Hashable, Value: Hashable> where Value: AnyObject { | ||
// MARK: - Properties | ||
var contents: [Key: Value] { | ||
return wrappedContents.filter { $0.value.value != nil }.mapValues { $0.value! } | ||
} | ||
|
||
private var wrappedContents = [Key: Weak<Value>]() | ||
|
||
// MARK: - Initializers | ||
init() { } | ||
|
||
init(_ dictionary: [Key: Value]) { | ||
dictionary.forEach { key, value in | ||
wrappedContents[key] = Weak(value) | ||
} | ||
} | ||
|
||
// MARK: - Subscripts | ||
subscript(key: Key) -> Value? { | ||
get { | ||
return wrappedContents[key]?.value | ||
} | ||
|
||
set(newValue) { | ||
clean() | ||
wrappedContents[key] = newValue.map(Weak.init) | ||
} | ||
} | ||
|
||
mutating func clean() { | ||
wrappedContents = wrappedContents.filter { $0.value.value != nil } | ||
} | ||
} | ||
|
||
// MARK: - CustomStringConvertible | ||
extension WeakDictionary: CustomStringConvertible { | ||
var description: String { | ||
return "WeakDictionary<\(String(describing: contents))>" | ||
} | ||
} | ||
|
||
// MARK: - ExpressibleByDictionaryLiteral | ||
extension WeakDictionary: ExpressibleByDictionaryLiteral { | ||
init(dictionaryLiteral elements: (Key, Value)...) { | ||
self.init() // TODO | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
// | ||
// WeakDictionaryTests.swift | ||
// SwiftCollections | ||
// | ||
// Created by Frederick Pietschmann on 16.04.18. | ||
// Copyright © 2018 Piknotech. All rights reserved. | ||
// | ||
|
||
import Foundation | ||
|
||
@testable import SwiftCollections | ||
import XCTest | ||
|
||
class WeakDictionaryTests: XCTestCase { | ||
// MARK: - Subtypes | ||
class SampleObject: Hashable { | ||
var hashValue: Int | ||
|
||
init(_ hashValue: Int) { | ||
self.hashValue = hashValue | ||
} | ||
|
||
static func == (lhs: SampleObject, rhs: SampleObject) -> Bool { | ||
return lhs.hashValue == rhs.hashValue | ||
} | ||
} | ||
|
||
// MARK: - Properties | ||
private let dictionarySize = 3 | ||
private lazy var dictionary: [Int: SampleObject]? = { | ||
let tuples = (0..<dictionarySize).map { (key: $0, value: SampleObject($0)) } | ||
return Dictionary(tuples) { _, _ in fatalError() } | ||
}() | ||
|
||
// MARK: - Methods | ||
func testCount() { | ||
let weakDictionary = WeakDictionary(dictionary!) | ||
XCTAssertEqual(weakDictionary.contents.count, dictionary!.count) | ||
} | ||
|
||
func testAddingAndRemoving() { | ||
var weakDictionary = WeakDictionary(dictionary!) | ||
|
||
// Test adding | ||
let sampleKey = dictionary!.count | ||
let sampleValue = dictionary!.first!.value | ||
weakDictionary[sampleKey] = sampleValue | ||
XCTAssertEqual(weakDictionary[sampleKey], sampleValue) | ||
XCTAssertEqual(weakDictionary.contents.values.count, dictionary!.count + 1) | ||
|
||
// Test removing | ||
weakDictionary[sampleKey] = nil | ||
XCTAssertEqual(weakDictionary[sampleKey], nil) | ||
XCTAssertEqual(weakDictionary.contents.values.count, dictionary!.count) | ||
} | ||
|
||
func testCleaning() { | ||
let weakDictionary = WeakDictionary(dictionary!) | ||
dictionary = nil | ||
XCTAssertEqual(weakDictionary.contents.isEmpty, true) | ||
} | ||
} |