Search code examples
swiftmacosspotlight

How to correctly use MDQuerySetSortComparatorBlock in swift?


My code:

//search all of applications
let queryString = "kMDItemContentTypeTree=com.apple.application"

// sort by last metadata change
let sorting = [kMDItemAttributeChangeDate] as CFArray

// create query and assign sorting param
let query = MDQueryCreate(kCFAllocatorDefault, queryString as CFString, nil, sorting)
        
// ISSUE HERE
// Set comparation block that called for sorting
MDQuerySetSortComparatorBlock(query, {
            if let date1 = $0 as? Date,
               let date2 = $1 as? Date {
                  return date1 < date2 ? .compareLessThan : .compareGreaterThan
              }
            
            return CFComparisonResult.compareEqualTo
        })


MDQuerySetDispatchQueue(query, DispatchQueue(label: "background", qos: .background) )
MDQueryExecute(query, CFOptionFlags())
        

issue is in correct way to write MDQuerySetSortComparatorBlock - I'm do not understand how is it must be written

I see warnings:

Cast from 'UnsafePointer<Unmanaged?>?' (aka 'Optional<UnsafePointer<Optional<Unmanaged>>>') to unrelated type 'Date' always fails


Solution

  • I always have to spend time looking up what the deal is with Unmanaged because I need it so infrequently. Apparently my guess at it in comments based on hazy memory of the last time I encountered Unmanaged worked for OP, so I'm putting it here. Basically it comes down to both dereferencing the pointer using its .pointee property then calling .takeRetainedValue() on that, with appropriate optional unwrapping in between.

    MDQuerySetSortComparatorBlock(query)
    {
        guard let date1 = $0?.pointee?.takeRetainedValue() as? Date,
              let date2 = $1?.pointee?.takeRetainedValue() as? Date
        else
        {
            // Do you really want to treat non-dates as equal?
            return .compareEqualTo
        }
        
        if date1 < date2 { return .compareLessThan }
        return date1 == date2 ? .compareEqualTo : .compareGreaterThan
    }
    

    Exactly what to do with the possibility of the casts to Date resulting in nil depends on your app, but one possibility is do the comparison so that all the ones where the cast to Date fails, or where the pointer is genuinely nil, collect at the end.

    MDQuerySetSortComparatorBlock(query)
    {
        guard let date1 = $0?.pointee?.takeRetainedValue() as? Date else {
            return .compareGreaterThan
        }
        guard let date2 = $1?.pointee?.takeRetainedValue() as? Date else {
            return .compareLessThan
        }
        
        if date1 < date2 { return .compareLessThan }
        return date1 == date2 ? .compareEqualTo : .compareGreaterThan
    }
    

    Then when you get the query results, you can make a reverse pass to remove elements with invalid dates without needing to shift elements.