Last active
July 16, 2018 16:59
-
-
Save evgeniyd/d974121b94b17931480c to your computer and use it in GitHub Desktop.
NSSortDescriptor subclass to sort objects w/ nullable key. These objects appears (unsorted) at the end of the sorted list when ascending == true. Written in Swift, this class merely extends the idea from this SO answer: http://stackoverflow.com/a/11188999/1492173
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
import Foundation | |
class UBINullableSortDescriptor: NSSortDescriptor { | |
override init(key: String, ascending: Bool) { | |
super.init(key: key, ascending: ascending) | |
} | |
required override init(key: String, ascending: Bool, selector: Selector) { | |
super.init(key: key, ascending: ascending, selector: selector) | |
} | |
required init(coder aDecoder: NSCoder) { | |
super.init(coder: aDecoder) | |
} | |
override func copyWithZone(zone: NSZone) -> AnyObject { | |
let thisClassType = self.dynamicType | |
return thisClassType(key: self.key!, ascending:self.ascending, selector:self.selector) | |
} | |
override func compareObject(object1: AnyObject, toObject object2: AnyObject) -> NSComparisonResult { | |
if self.isNull(object: object1.valueForKey(self.key!)/*object1[self.key!]*/) { | |
if self.isNull(object: object2.valueForKey(self.key!)/*object2[self.key!]*/) { | |
return .OrderedSame | |
} | |
return .OrderedDescending | |
} | |
if self.isNull(object: object2.valueForKey(self.key!)/*object2[self.key!]*/) { | |
return .OrderedAscending | |
} | |
return super.compareObject(object1, toObject: object2) | |
} | |
override var reversedSortDescriptor: AnyObject! { | |
get { | |
let thisClassType = self.dynamicType | |
let asc: Bool = self.ascending | |
let desc = !self.ascending | |
return thisClassType(key: self.key!, ascending: desc, selector:self.selector) | |
} | |
} | |
// Note: there is no reason to this being w/ 'internal' access level, | |
// but it is so we are able to test the class's internals w/ XCTest | |
internal func isNull(#object: AnyObject?) -> Bool { | |
// (a) == nil | |
if let notNil: AnyObject = object { | |
// [(a) isEqual:[NSNull null]] | |
let nsNullObject = NSNull() | |
if let derivesFromNSObject = notNil as? NSObject { | |
if derivesFromNSObject == nsNullObject { | |
return true | |
} | |
} | |
return false | |
} | |
else { | |
return true | |
} | |
} | |
} |
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
import XCTest | |
// objects compared by NSSortDescriptor must be key-value compliant (e.g. NSObject subclasses) | |
// https://developer.apple.com/library/ios/documentation/Cocoa/Reference/Foundation/Classes/NSSortDescriptor_Class/index.html | |
class TestDataItemAnyObject: NSObject { | |
var sortIndex: AnyObject? | |
var expectedIndex: Int | |
init(sortIndex: AnyObject?, expectedIndex: Int) { | |
self.sortIndex = sortIndex | |
self.expectedIndex = expectedIndex | |
} | |
} | |
class TestDataItemNSNumber: NSObject { | |
var sortIndex: NSNumber? | |
var expectedIndex: Int | |
init(sortIndex: NSNumber?, expectedIndex: Int) { | |
self.sortIndex = sortIndex | |
self.expectedIndex = expectedIndex | |
} | |
} | |
class UBINullableSortDescriptorTests: XCTestCase { | |
override func setUp() { | |
super.setUp() | |
// Put setup code here. This method is called before the invocation of each test method in the class. | |
} | |
override func tearDown() { | |
// Put teardown code here. This method is called after the invocation of each test method in the class. | |
super.tearDown() | |
} | |
// MARK: Init/Copy Test | |
func testInit() { | |
let testSortDescriptor = UBINullableSortDescriptor(key: "testKey", ascending: true, selector: "testSelector") | |
XCTAssertTrue(testSortDescriptor.key == "testKey") | |
XCTAssertTrue(testSortDescriptor.selector==Selector("testSelector") ) | |
XCTAssertEqual(testSortDescriptor.ascending, true) | |
} | |
func testCopy() { | |
let testSortDescriptor = UBINullableSortDescriptor(key: "testKey", ascending: true, selector: "testSelector") | |
let copyTestSortDescriptor: AnyObject = testSortDescriptor.copy() | |
let sameTestSortDescriptor = testSortDescriptor | |
XCTAssertFalse(copyTestSortDescriptor === testSortDescriptor) | |
XCTAssertTrue(sameTestSortDescriptor === testSortDescriptor) | |
} | |
// MARK: Archieve Test | |
func testArchieve() { | |
let thisClassType = self.dynamicType | |
let filePathURL = NSBundle(forClass: thisClassType).resourceURL?.URLByAppendingPathComponent("UBINullableSortDescriptor.archieve") | |
XCTAssertNotNil(filePathURL) | |
// encode | |
let expectedSortDescriptor = UBINullableSortDescriptor(key: "testKey", ascending: true, selector: "testSelector") | |
XCTAssertTrue(NSKeyedArchiver.archiveRootObject(expectedSortDescriptor, toFile: filePathURL!.path!)) | |
// decode | |
//var testSortDescriptor:UBINullableSortDescriptor = NSKeyedUnarchiver.unarchiveObjectWithFile(filePath!) as UBINullableSortDescriptor; // <== this will crash, don't know why | |
let testSortDescriptor:NSSortDescriptor = NSKeyedUnarchiver.unarchiveObjectWithFile(filePathURL!.path!) as! NSSortDescriptor; | |
XCTAssertTrue(testSortDescriptor.key == "testKey") | |
XCTAssertTrue(testSortDescriptor.selector==Selector("testSelector") ) | |
XCTAssertEqual(testSortDescriptor.ascending, true) | |
} | |
// MARK: Internals Tests | |
func testIsNull() { | |
let testSortDescriptor = UBINullableSortDescriptor(key: "", ascending: true, selector: "") | |
XCTAssertNotNil(testSortDescriptor) | |
// test NSNull equality | |
// Note: I hope that 'NSNull()' in Swift is equal to '[NSNull null]' in Objective-C | |
let testObject01 = TestDataItemAnyObject(sortIndex: NSNull(), expectedIndex: 0) | |
XCTAssertTrue(testSortDescriptor.isNull(object: testObject01.sortIndex)) | |
// test nil equality | |
let testObject02 = TestDataItemAnyObject(sortIndex: nil, expectedIndex: 0) | |
XCTAssertTrue(testSortDescriptor.isNull(object: testObject02.sortIndex)) | |
// test not nil not-NSObject-descender | |
let testObject03 = TestDataItemAnyObject(sortIndex: "1", expectedIndex: 0) | |
XCTAssertFalse(testSortDescriptor.isNull(object: testObject03.sortIndex)) | |
// test not nil objects | |
let testObject04 = TestDataItemAnyObject(sortIndex: NSNumber(int: 2), expectedIndex: 0) | |
XCTAssertFalse(testSortDescriptor.isNull(object: testObject04.sortIndex)) | |
let testObject05 = TestDataItemNSNumber(sortIndex: NSNumber(int: 3), expectedIndex: 0) | |
XCTAssertFalse(testSortDescriptor.isNull(object: testObject05.sortIndex)) | |
} | |
// MARK: Comparizon tests | |
func testReversedSortDescriptorPrimitive() { | |
let testSortDescriptor = UBINullableSortDescriptor(key: "testKey", ascending: true, selector: "testSelector") | |
XCTAssertNotNil(testSortDescriptor) | |
let testReversedSortDescriptor: UBINullableSortDescriptor = testSortDescriptor.reversedSortDescriptor as UBINullableSortDescriptor | |
XCTAssertFalse(testReversedSortDescriptor.ascending) | |
} | |
func testComparizonNSNumber() { | |
// 1. create test data set | |
let testArray: [TestDataItemNSNumber] = | |
[ | |
TestDataItemNSNumber(sortIndex: nil, expectedIndex:2), | |
TestDataItemNSNumber(sortIndex: NSNumber(integer: 2), expectedIndex:1), | |
TestDataItemNSNumber(sortIndex: NSNumber(integer: 1), expectedIndex:0), | |
TestDataItemNSNumber(sortIndex: nil, expectedIndex:3) | |
] | |
// 2. create sort descriptor | |
var descriptor = UBINullableSortDescriptor(key: "sortIndex", ascending: true) | |
// 3. sort | |
let sortedArray = (testArray as NSArray).sortedArrayUsingDescriptors([descriptor]) | |
// 4. test | |
for index: Int in 0...3 { | |
let expIndex: Int = (sortedArray[index] as TestDataItemNSNumber).expectedIndex as Int | |
XCTAssertTrue(expIndex == index, "object w/ sortIndex == \((sortedArray[index] as TestDataItemNSNumber).sortIndex) is expected to be at \(index) position") | |
} | |
} | |
func testComparizonNSNumberAllNil() { | |
let testArray: [TestDataItemNSNumber] = | |
[ | |
TestDataItemNSNumber(sortIndex: nil, expectedIndex:0), | |
TestDataItemNSNumber(sortIndex: nil, expectedIndex:1), | |
TestDataItemNSNumber(sortIndex: nil, expectedIndex:2), | |
TestDataItemNSNumber(sortIndex: nil, expectedIndex:3) | |
] | |
var descriptor = UBINullableSortDescriptor(key: "sortIndex", ascending: true) | |
let sortedArray = (testArray as NSArray).sortedArrayUsingDescriptors([descriptor]) | |
for index: Int in 0...3 { | |
let expIndex: Int = (sortedArray[index] as TestDataItemNSNumber).expectedIndex as Int | |
XCTAssertTrue(expIndex == index, "object w/ sortIndex == \((sortedArray[index] as TestDataItemNSNumber).sortIndex) is expected to be at \(index) position") | |
} | |
} | |
func testComparizonNSNumberOposite() { | |
let testArray: [TestDataItemNSNumber] = | |
[ | |
TestDataItemNSNumber(sortIndex: nil, expectedIndex:2), | |
TestDataItemNSNumber(sortIndex: nil, expectedIndex:3), | |
TestDataItemNSNumber(sortIndex: NSNumber(integer: 15), expectedIndex:1), | |
TestDataItemNSNumber(sortIndex: NSNumber(integer: 12), expectedIndex:0) | |
] | |
var descriptor = UBINullableSortDescriptor(key: "sortIndex", ascending: true) | |
let sortedArray = (testArray as NSArray).sortedArrayUsingDescriptors([descriptor]) | |
for index: Int in 0...3 { | |
let expIndex: Int = (sortedArray[index] as TestDataItemNSNumber).expectedIndex as Int | |
XCTAssertTrue(expIndex == index, "object w/ sortIndex == \((sortedArray[index] as TestDataItemNSNumber).sortIndex) is expected to be at \(index) position") | |
} | |
} | |
func testComparizonAnyObject() { | |
let testArray: [TestDataItemAnyObject] = | |
[ | |
TestDataItemAnyObject(sortIndex: nil, expectedIndex:2), | |
TestDataItemAnyObject(sortIndex: nil, expectedIndex:3), | |
TestDataItemAnyObject(sortIndex: 1, expectedIndex:0), | |
TestDataItemAnyObject(sortIndex: 2, expectedIndex:1) | |
] | |
var descriptor = UBINullableSortDescriptor(key: "sortIndex", ascending: true) | |
let sortedArray = (testArray as NSArray).sortedArrayUsingDescriptors([descriptor]) | |
for index: Int in 0...3 { | |
let expIndex: Int = (sortedArray[index] as TestDataItemAnyObject).expectedIndex as Int | |
XCTAssertTrue(expIndex == index, "object w/ sortIndex == \((sortedArray[index] as TestDataItemAnyObject).sortIndex) is expected to be at \(index) position") | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This is awesome! Thanks for the link :)
I also suggest you add another one for having the null objects at the top when ascending is true.