Skip to content

Commit

Permalink
Merge pull request #101 from Shopify/sections-rows-duplicates
Browse files Browse the repository at this point in the history
Supply duplicate keys in exception user info
  • Loading branch information
sherryshao authored Jun 19, 2018
2 parents 35f1be5 + b1aabfa commit 329670b
Show file tree
Hide file tree
Showing 9 changed files with 206 additions and 76 deletions.
10 changes: 9 additions & 1 deletion FunctionalTableData.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
17E57FFA208A415900BFCC3D /* FunctionalTableData.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4C7A26FC1F2FA0F800360E9B /* FunctionalTableData.framework */; };
17E57FFB208A415900BFCC3D /* FunctionalTableData.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 4C7A26FC1F2FA0F800360E9B /* FunctionalTableData.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
17E58000208A425F00BFCC3D /* LabelCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17E57FFF208A425F00BFCC3D /* LabelCell.swift */; };
3624340420D2F40100A75787 /* Array+TableSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3624340320D2F40100A75787 /* Array+TableSection.swift */; };
36C9208D20D3EB7500DA4251 /* TableSectionsValidationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36C9208C20D3EB7500DA4251 /* TableSectionsValidationTests.swift */; };
4C63250B1F8AA89B00B2B74B /* TableCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CCCE8441F8AA7CD00C73258 /* TableCell.swift */; };
4C63250C1F8AA89D00B2B74B /* TableItemConfigType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CCCE8451F8AA7CD00C73258 /* TableItemConfigType.swift */; };
4C63250D1F8AA8A000B2B74B /* UITableView+Reusable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CCCE8461F8AA7CD00C73258 /* UITableView+Reusable.swift */; };
Expand Down Expand Up @@ -86,6 +88,8 @@
17E57FF4208A404800BFCC3D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
17E57FF6208A404800BFCC3D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
17E57FFF208A425F00BFCC3D /* LabelCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LabelCell.swift; sourceTree = "<group>"; };
3624340320D2F40100A75787 /* Array+TableSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Array+TableSection.swift"; sourceTree = "<group>"; };
36C9208C20D3EB7500DA4251 /* TableSectionsValidationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableSectionsValidationTests.swift; sourceTree = "<group>"; };
4C7A26FC1F2FA0F800360E9B /* FunctionalTableData.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = FunctionalTableData.framework; sourceTree = BUILT_PRODUCTS_DIR; };
4C7A26FF1F2FA0F800360E9B /* FunctionalTableData.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FunctionalTableData.h; sourceTree = "<group>"; };
4C7A27001F2FA0F800360E9B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
Expand Down Expand Up @@ -195,6 +199,7 @@
4C924F251F98E7A3005D2F02 /* FunctionalDataTests.swift */,
4CA356731F322D7F0081BE90 /* TableSectionChangeSetTests.swift */,
4CA356741F322D7F0081BE90 /* TableSectionStyleTests.swift */,
36C9208C20D3EB7500DA4251 /* TableSectionsValidationTests.swift */,
);
path = FunctionalTableDataTests;
sourceTree = "<group>";
Expand Down Expand Up @@ -225,8 +230,9 @@
children = (
4C7A27881F2FB69C00360E9B /* UIView+AutoLayout.swift */,
4C7A278A1F2FB89400360E9B /* UIView+Extensions.swift */,
3624340320D2F40100A75787 /* Array+TableSection.swift */,
);
name = Extensions;
path = Extensions;
sourceTree = "<group>";
};
4CCCE83E1F8AA7B200C73258 /* CollectionView */ = {
Expand Down Expand Up @@ -444,6 +450,7 @@
4C63250D1F8AA8A000B2B74B /* UITableView+Reusable.swift in Sources */,
4C7A277B1F2FB55D00360E9B /* CellActions.swift in Sources */,
4C6325131F8AA8A400B2B74B /* FunctionalCollectionData.swift in Sources */,
3624340420D2F40100A75787 /* Array+TableSection.swift in Sources */,
4C7A27861F2FB55D00360E9B /* TableSectionHeaderFooter.swift in Sources */,
4C7A277C1F2FB55D00360E9B /* CellConfigType.swift in Sources */,
4C7A27811F2FB55D00360E9B /* Separator.swift in Sources */,
Expand All @@ -469,6 +476,7 @@
4CA356751F322D7F0081BE90 /* TableSectionChangeSetTests.swift in Sources */,
4C924F261F98E7A3005D2F02 /* FunctionalDataTests.swift in Sources */,
4CD535031F9E3A010041A3F9 /* CellStyleTests.swift in Sources */,
36C9208D20D3EB7500DA4251 /* TableSectionsValidationTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
38 changes: 10 additions & 28 deletions FunctionalTableData/CollectionView/FunctionalCollectionData.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@ public class FunctionalCollectionData: NSObject {
}
}

private func dumpDebugInfoForChanges(_ changes: TableSectionChangeSet, previousSections: [TableSection], visibleIndexPaths: [IndexPath], exceptionReason: String?) {
private func dumpDebugInfoForChanges(_ changes: TableSectionChangeSet, previousSections: [TableSection], visibleIndexPaths: [IndexPath], exceptionReason: String?, exceptionUserInfo: [AnyHashable: Any]?) {
guard let exceptionHandler = FunctionalTableData.exceptionHandler else { return }
let exception = FunctionalTableData.Exception(name: name, newSections: sections, oldSections: previousSections, changes: changes, visible: visibleIndexPaths, viewFrame: collectionView?.frame ?? .zero, reason: exceptionReason)
let exception = FunctionalTableData.Exception(name: name, newSections: sections, oldSections: previousSections, changes: changes, visible: visibleIndexPaths, viewFrame: collectionView?.frame ?? .zero, reason: exceptionReason, userInfo: exceptionUserInfo)
exceptionHandler.handle(exception: exception)
}

Expand Down Expand Up @@ -189,34 +189,16 @@ public class FunctionalCollectionData: NSObject {
return
}

func validateKeyUniqueness() {
let sectionKeys = newSections.map { $0.key }
if Set(sectionKeys).count != newSections.count {
let reason = "\(strongSelf.name) : Duplicate Section keys"
let userInfo: [String: Any] = ["Duplicates": sectionKeys]
NSException(name: NSExceptionName.internalInconsistencyException, reason: reason, userInfo: userInfo).raise()
}

for section in newSections {
let rowKeys = section.rows.map { $0.key }
if Set(rowKeys).count != section.rows.count {
let reason = "\(strongSelf.name) : Section.Row keys must all be unique"
let userInfo: [String: Any] = ["Section": section.key, "Duplicates": rowKeys]
NSException(name: NSExceptionName.internalInconsistencyException, reason: reason, userInfo: userInfo).raise()
}
}
}

if strongSelf.unitTesting {
validateKeyUniqueness()
newSections.validateKeyUniqueness(senderName: strongSelf.name)
} else {
NSException.catchAndRethrow({
validateKeyUniqueness()
newSections.validateKeyUniqueness(senderName: strongSelf.name)
}, failure: {
if $0.name == NSExceptionName.internalInconsistencyException {
guard let exceptionHandler = FunctionalTableData.exceptionHandler else { return }
let changes = TableSectionChangeSet()
let exception = FunctionalTableData.Exception(name: $0.name.rawValue, newSections: newSections, oldSections: strongSelf.sections, changes: changes, visible: [], viewFrame: strongSelf.collectionView?.frame ?? .zero, reason: $0.reason)
let exception = FunctionalTableData.Exception(name: $0.name.rawValue, newSections: newSections, oldSections: strongSelf.sections, changes: changes, visible: [], viewFrame: strongSelf.collectionView?.frame ?? .zero, reason: $0.reason, userInfo: $0.userInfo)
exceptionHandler.handle(exception: exception)
}
})
Expand Down Expand Up @@ -278,7 +260,7 @@ public class FunctionalCollectionData: NSObject {
})
}, failure: { exception in
if exception.name == NSExceptionName.internalInconsistencyException {
strongSelf.dumpDebugInfoForChanges(changes, previousSections: oldSections, visibleIndexPaths: visibleIndexPaths, exceptionReason: exception.reason)
strongSelf.dumpDebugInfoForChanges(changes, previousSections: oldSections, visibleIndexPaths: visibleIndexPaths, exceptionReason: exception.reason, exceptionUserInfo: exception.userInfo)
}
})
}
Expand Down Expand Up @@ -364,7 +346,7 @@ public class FunctionalCollectionData: NSObject {
/// - triggerDelegate: `true` to trigger the `collection:didSelectItemAt:` delegate from `UICollectionView` or `false` to skip it. Skipping it is the default `UICollectionView` behavior.
public func select(keyPath: KeyPath, animated: Bool = true, scrollPosition: UICollectionViewScrollPosition = [], triggerDelegate: Bool = false) {
guard let aCollectionView = collectionView, let indexPath = indexPathFromKeyPath(keyPath) else { return }

aCollectionView.selectItem(at: indexPath, animated: animated, scrollPosition: scrollPosition)
if triggerDelegate {
collectionView(aCollectionView, didSelectItemAt: indexPath)
Expand Down Expand Up @@ -407,7 +389,7 @@ public class FunctionalCollectionData: NSObject {
internal func calculateTableChanges(oldSections: [TableSection], newSections: [TableSection], visibleIndexPaths: [IndexPath]) -> TableSectionChangeSet {
return TableSectionChangeSet(old: oldSections, new: newSections, visibleIndexPaths: visibleIndexPaths)
}

public func previewingContext(_ previewingContext: UIViewControllerPreviewing, viewControllerForLocation location: CGPoint) -> UIViewController? {
guard let indexPath = collectionView?.indexPathForItem(at: location), let cell = collectionView?.cellForItem(at: indexPath) else { return nil }
guard let cellConfig = self[indexPath],
Expand Down Expand Up @@ -491,7 +473,7 @@ extension FunctionalCollectionData: UICollectionViewDelegate {
guard let indexPathsForSelectedItems = collectionView.indexPathsForSelectedItems, !collectionView.allowsMultipleSelection else { return true }
return indexPathsForSelectedItems.contains(indexPath) == false
}

public func collectionView(_ collectionView: UICollectionView, shouldHighlightItemAt indexPath: IndexPath) -> Bool {
return sections[indexPath]?.actions.selectionAction != nil
}
Expand All @@ -507,7 +489,7 @@ extension FunctionalCollectionData: UICollectionViewDelegate {
}
}
}

public func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
guard let cell = collectionView.cellForItem(at: indexPath) else { return }
let cellConfig = sections[indexPath]
Expand Down
56 changes: 56 additions & 0 deletions FunctionalTableData/Extensions/Array+TableSection.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
//
// Array+TableSection.swift
// FunctionalTableData
//
// Created by Sherry Shao on 2018-06-14.
// Copyright © 2018 Shopify. All rights reserved.
//

extension Array where Element: TableSectionType {
func validateKeyUniqueness(senderName: String) {
let sectionKeys = map { $0.key }
if Set(sectionKeys).count != count {
let dupKeys = duplicateKeys()
let reason = "\(senderName) : Duplicate Section keys"
let userInfo: [String: Any] = ["Duplicates": dupKeys]
NSException(name: NSExceptionName.internalInconsistencyException, reason: reason, userInfo: userInfo).raise()
}

for section in self {
let rowKeys = section.rows.map { $0.key }
if Set(rowKeys).count != section.rows.count {
let dupKeys = section.rows.duplicateKeys()
let reason = "\(senderName) : Duplicate Section.Row keys"
let userInfo: [String: Any] = ["Section": section.key, "Duplicates": dupKeys]
NSException(name: NSExceptionName.internalInconsistencyException, reason: reason, userInfo: userInfo).raise()
}
}
}
}

private extension Array where Element: Hashable {
func duplicates() -> Set<Element> {
var dups: Set<Element> = []
var uniques: Set<Element> = []
forEach {
if uniques.contains($0) {
dups.insert($0)
} else {
uniques.insert($0)
}
}
return dups
}
}

private extension Array where Element: TableSectionType {
func duplicateKeys() -> Set<String> {
return map { $0.key }.duplicates()
}
}

private extension Array where Element == CellConfigType {
func duplicateKeys() -> Set<String> {
return map { $0.key }.duplicates()
}
}
File renamed without changes.
File renamed without changes.
9 changes: 9 additions & 0 deletions FunctionalTableData/FunctionalTableData.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,13 @@ static inline void catchAndRethrowException(__attribute__((noescape)) void (^ __
}
}

__attribute__((visibility("hidden")))
static inline void catchException(__attribute__((noescape)) void (^ __nonnull inBlock)(void), __attribute__((noescape)) void (^ __nonnull exceptionHandler)(NSException *)) {
@try {
inBlock();
} @catch (NSException *exception) {
exceptionHandler(exception);
}
}

NS_ASSUME_NONNULL_END
4 changes: 4 additions & 0 deletions FunctionalTableData/ObjCExceptionRethrower.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,8 @@ extension NSException {
public static func catchAndRethrow(_ block: () -> Void, failure: (_ exception: NSException) -> Void) {
catchAndRethrowException(block, failure)
}

public static func catchAndHandle(_ block: () -> Void, failure: (_ exception: NSException) -> Void) {
catchException(block, failure)
}
}
Loading

0 comments on commit 329670b

Please sign in to comment.