Skip to content

Commit

Permalink
Merge pull request #10 from piknotech/work/8-weak-dictionary
Browse files Browse the repository at this point in the history
Work/8 weak dictionary
  • Loading branch information
fredpi committed Apr 17, 2018
2 parents 111ce56 + 90707b5 commit 3486f02
Show file tree
Hide file tree
Showing 9 changed files with 225 additions and 27 deletions.
2 changes: 1 addition & 1 deletion Collections/Bag/Bag.swift
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ struct Bag<ContainedElement: Hashable> {
// MARK: - CustomStringConvertible
extension Bag: CustomStringConvertible {
var description: String {
return String(describing: contents)
return "Bag<\(String(describing: contents))>"
}
}

Expand Down
2 changes: 1 addition & 1 deletion Collections/WeakArray/WeakArray.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ struct WeakArray<Element> where Element: AnyObject {
// MARK: - CustomStringConvertible
extension WeakArray: CustomStringConvertible {
var description: String {
return "\(contents.count) Item(s): \(String(describing: contents))"
return "WeakArray<\(String(describing: contents))>"
}
}

Expand Down
23 changes: 12 additions & 11 deletions Collections/WeakArray/WeakArrayDoc.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,20 @@

## Functionality

A `WeakArray` is an array holding weak references onto its elements. It is e. g. useful for delegation patterns, where you would want to add listeners to an array but don't want the array to keep references when nothing else references the items anymore. Its implementatipn is very similar to `WeakSet`.
A `WeakArray` is an array holding weak references onto its elements. It is e. g. useful for delegation patterns, where you would want to add listeners to an array but don't want the array to keep references when nothing else references the items anymore. Its implementation is very similar to `WeakSet`.

## Implementation

A `WeakArray` is a collection with AnyObject as its contained element. It conforms to
- `CustomStringConvertible`,
- `ExpressibleByArrayLiteral`,

It doesn't conform to `Sequence` and `Collection`. Internally `WeakArray` stores an array of `Weak<Element>` objects, but only regards those objects, whose contained value is not nil. The value of the weak array might change all of a sudden (as soon as the reference count of any contained value equals 0), therefore, working with indices (`Collection` protocol) or iterating (`Sequence` protocol) doesn't make sense. However, once the variable `contents` is accessed, an array of the currently non-nil contained item is returned which enables use of `Collection` & `Sequence`.
Internally, `WeakArray` stores an `Array` of `Weak<Element>` objects. Its contents can only be accessed via the `contents` property, which returns an `Array` of the currently non-nil contained items. This enables use of Swift-implemented methods on the `Array` type, like those implemented by conformance to `Collection` & `Sequence`.

Apart from everything implemented by the protocols, `WeakArray` offers:
- methods to add members, remove members and clean out `Weak<Element>` instances internally stored, but with out a non-nil contained value.
- an emptyness check & content property.
Apart from everything implemented by the protocols it conforms to, `WeakArray` offers:
- methods to add & remove members
- a method to clean out all those `Weak<Element>` instances internally stored that wrap a value already being `nil`
- the `contents` property

## Use

Expand All @@ -37,16 +38,16 @@ To remove items that are referentially equal to the one provided as a parameter,
weakArray.removeIdentical(to: myView)
```

If you don't call the add or remove functions, that auto-perform a clean, it may be suitable, to call it manually from time to time for a large `WeakArray`.
If you don't call the add or remove functions, that auto-perform a clean, it may be suitable to call it manually from time to time for a large `WeakArray`.

```swift
weakArray.clean() // Cleans out internal Weak<Element> instances whose contained value is nil
weakArray.clean() // Cleans out internal Weak<Element> instances whose contained value is nil
```

To access the `WeakArray`'s contents, you should use `contents` which returns an array of the contained elements. At this point, you can use interfaces provided by `Collection` or `Sequence`:
To access the `WeakArray`'s contents, you should use `contents`, which returns an `Array` of the contained elements. At this point you can use interfaces provided by the `Array` implementation of Swift.

```swift
print(weakArray.contents.isEmpty) // Use of Collection protocol
print(weakArray.contents.first) // Use of Collection protocol
for element in weakArray.contents { } // Use of Sequence protocol
print(weakArray.contents.isEmpty) // Use of Collection protocol
print(weakArray.contents.first) // Use of Collection protocol
for element in weakArray.contents { } // Use of Sequence protocol
```
59 changes: 59 additions & 0 deletions Collections/WeakDictionary/WeakDictionary.md
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)
```
57 changes: 57 additions & 0 deletions Collections/WeakDictionary/WeakDictionary.swift
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
}
}
4 changes: 2 additions & 2 deletions Collections/WeakSet/WeakSet.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

import Foundation

struct WeakSet<Element> where Element: AnyObject, Element: Hashable {
struct WeakSet<Element: Hashable> where Element: AnyObject {
// MARK: - Properties
var contents: Set<Element> {
return Set(wrappedContents.compactMap { $0.value })
Expand Down Expand Up @@ -44,7 +44,7 @@ struct WeakSet<Element> where Element: AnyObject, Element: Hashable {
// MARK: - CustomStringConvertible
extension WeakSet: CustomStringConvertible {
var description: String {
return "\(contents.count) Item(s): \(String(describing: contents))"
return "WeakSet<\(String(describing: contents))>"
}
}

Expand Down
23 changes: 12 additions & 11 deletions Collections/WeakSet/WeakSetDoc.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,20 @@

## Functionality

A `WeakSet` is a set holding weak references onto its elements. It is e. g. useful for delegation patterns, where you would want to add listeners to a set but don't want the set to keep references when nothing else references the items anymore. Its implementatipn is very similar to `WeakArray`.
A `WeakSet` is a set holding weak references onto its elements. It is e. g. useful for delegation patterns, where you would want to add listeners to a set but don't want the set to keep references when nothing else references the items anymore. Its implementation is very similar to `WeakArray`.

## Implementation

A `WeakSet` is a collection with AnyObject conforming to Hashable as its contained element. It conforms to
- `CustomStringConvertible`,
- `ExpressibleByArrayLiteral`,

It doesn't conform to `Sequence` and `Collection`. Internally `WeakSet` stores a set of `Weak<Element>` objects, but only regards those objects, whose contained value is not nil. The value of the weak set might change all of a sudden (as soon as the reference count of any contained value equals 0), therefore, working with indices (`Collection` protocol) or iterating (`Sequence` protocol) doesn't make sense. However, once the variable `contents` is accessed, a set of the currently non-nil contained item is returned which enables use of `Collection` & `Sequence`.
Internally, `WeakSet` stores a `Set` of `Weak<Element>` objects. Its contents can only be accessed via the `contents` property, which returns a `Set` of the currently non-nil contained items. This enables use of Swift-implemented methods on the `Set` type, like those implemented by conformance to `Collection` & `Sequence`.

Apart from everything implemented by the protocols, `WeakSet` offers:
- methods to add members, remove members and clean out `Weak<Element>` instances internally stored, but with out a non-nil contained value.
- an emptyness check & content property.
Apart from everything implemented by the protocols it conforms to, `WeakSet` offers:
- methods to insert & remove members
- a method to clean out all those `Weak<Element>` instances internally stored that wrap a value already being `nil`
- the `contents` property

## Use

Expand All @@ -37,16 +38,16 @@ To remove items, use `mutating func remove(_ item: Element)`:
weakSet.remove(myView)
```

If you don't call the add or remove functions, that auto-perform a clean, it may be suitable, to call it manually from time to time for a large `WeakSet`.
If you don't call the add or remove functions, that auto-perform a clean, it may be suitable to call it manually from time to time for a large `WeakSet`.

```swift
weakSet.clean() // Cleans out internal Weak<Element> instances whose contained value is nil
weakSet.clean() // Cleans out internal Weak<Element> instances whose contained value is nil
```

To access the `WeakSet`'s contents, you should use `contents` which returns a set of the contained elements. At this point, you can use interfaces provided by `Collection` or `Sequence`:
To access the `WeakSet`'s contents, you should use `contents`, which returns a `Set` of the contained elements. At this point you can use interfaces provided by the `Set` implementation of Swift.

```swift
print(weakSet.contents.isEmpty) // Use of Collection protocol
print(weakSet.contents.first) // Use of Collection protocol
for element in weakSet.contents { } // Use of Sequence protocol
print(weakSet.contents.isEmpty) // Use of Collection protocol
print(weakSet.contents.first) // Use of Collection protocol
for element in weakSet.contents { } // Use of Sequence protocol
```
20 changes: 19 additions & 1 deletion SwiftCollections.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
CC20BB092056DA13001773AA /* BagTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CCAA8C482056C02C000F054D /* BagTests.swift */; };
CC20BB0A2056DA13001773AA /* WeakArrayTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CCAA8C402056BECF000F054D /* WeakArrayTests.swift */; };
CC20BB0B2056DA13001773AA /* WeakSetTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CCAA8C462056C025000F054D /* WeakSetTests.swift */; };
CC850F3F2086820B00E4E890 /* WeakDictionaryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CCC505E720852C8600E2413A /* WeakDictionaryTests.swift */; };
CCC505E420852BFC00E2413A /* WeakDictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = CCC505E320852BFC00E2413A /* WeakDictionary.swift */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
Expand Down Expand Up @@ -59,6 +61,9 @@
CCAA8C422056BECF000F054D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
CCAA8C462056C025000F054D /* WeakSetTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeakSetTests.swift; sourceTree = "<group>"; };
CCAA8C482056C02C000F054D /* BagTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BagTests.swift; sourceTree = "<group>"; };
CCC505E320852BFC00E2413A /* WeakDictionary.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeakDictionary.swift; sourceTree = "<group>"; };
CCC505E520852C0900E2413A /* WeakDictionary.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = WeakDictionary.md; sourceTree = "<group>"; };
CCC505E720852C8600E2413A /* WeakDictionaryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeakDictionaryTests.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -100,6 +105,7 @@
CC6DE94D20405E71009C0BD2 /* Bag */,
CC6DE94720405E71009C0BD2 /* WeakArray */,
CC6DE94A20405E71009C0BD2 /* WeakSet */,
CCC505E620852C0E00E2413A /* WeakDictionary */,
);
path = Collections;
sourceTree = "<group>";
Expand Down Expand Up @@ -165,10 +171,20 @@
CCAA8C482056C02C000F054D /* BagTests.swift */,
CCAA8C402056BECF000F054D /* WeakArrayTests.swift */,
CCAA8C462056C025000F054D /* WeakSetTests.swift */,
CCC505E720852C8600E2413A /* WeakDictionaryTests.swift */,
);
path = Sources;
sourceTree = "<group>";
};
CCC505E620852C0E00E2413A /* WeakDictionary */ = {
isa = PBXGroup;
children = (
CCC505E320852BFC00E2413A /* WeakDictionary.swift */,
CCC505E520852C0900E2413A /* WeakDictionary.md */,
);
path = WeakDictionary;
sourceTree = "<group>";
};
/* End PBXGroup section */

/* Begin PBXNativeTarget section */
Expand Down Expand Up @@ -217,7 +233,7 @@
attributes = {
LastSwiftUpdateCheck = 0920;
LastUpgradeCheck = 0930;
ORGANIZATIONNAME = piknotech;
ORGANIZATIONNAME = Piknotech;
TargetAttributes = {
CC20BAEB2056D8B6001773AA = {
CreatedOnToolsVersion = 9.2;
Expand Down Expand Up @@ -294,6 +310,7 @@
buildActionMask = 2147483647;
files = (
CC20BAF82056D9B8001773AA /* WeakSet.swift in Sources */,
CCC505E420852BFC00E2413A /* WeakDictionary.swift in Sources */,
CC20BAF92056D9B8001773AA /* Weak.swift in Sources */,
CC20BAF62056D9B8001773AA /* Bag.swift in Sources */,
CC20BAF72056D9B8001773AA /* WeakArray.swift in Sources */,
Expand All @@ -305,6 +322,7 @@
buildActionMask = 2147483647;
files = (
CC20BB092056DA13001773AA /* BagTests.swift in Sources */,
CC850F3F2086820B00E4E890 /* WeakDictionaryTests.swift in Sources */,
CC20BB0A2056DA13001773AA /* WeakArrayTests.swift in Sources */,
CC20BB0B2056DA13001773AA /* WeakSetTests.swift in Sources */,
);
Expand Down
62 changes: 62 additions & 0 deletions Tests/Sources/WeakDictionaryTests.swift
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)
}
}

0 comments on commit 3486f02

Please sign in to comment.