Список установленных приложений на El Capitan с помощью Spotlight в Swift 2.2

В настоящее время я создаю приложение для Mac, которое в будущем должно иметь возможность убивать и запускать приложения в OS X.

Чтобы это стало возможным, мне нужно найти способ получить список всех установленных приложений на машине.

Я уже провел довольно много исследований и решил использовать Spotlight с NSMetadataQuery, чтобы иметь возможность получить список.

Мне удалось найти это пост на указанную тему и начал реализовывать функциональность в Swift 2.2 (оружие выбора для проекта). С небольшим переводом я смог заставить его работать, и теперь код успешно строится и работает. Однако во время выполнения у меня, похоже, возникла проблема с самим запросом:

<NSMetadataQuery: 0x6080000e3880> is being deallocated without first calling -stopQuery. To avoid race conditions, you should first invoke -stopQuery on the run loop on which -startQuery was called

Это код, который я сейчас использую.

    public func doSpotlightQuery() {
    query = NSMetadataQuery()
    let predicate = NSPredicate(format: "kMDItemKind ==[c] %@", "Application")
    let defaultNotificationCenter = NSNotificationCenter()
    defaultNotificationCenter.addObserver(self, selector: #selector(queryDidFinish(_:)), name: NSMetadataQueryDidFinishGatheringNotification, object: nil)
    query.predicate = predicate
    query.startQuery()
}

public func queryDidFinish(notification: NSNotification) {
    for i in 0 ... query.resultCount {
        print(query.resultAtIndex(i).valueForAttribute(kMDItemDisplayName as String))
    }
}

Тестирование

mdfind "kMDItemKind == 'Application'"

Команда (со всевозможными вариациями) в терминале моего Mac также не дала мне никаких результатов, что подводит меня к моему вопросу:

Я неправильно настроил запрос или эта команда не работает в «Эль-Капитане»?

Может кто-нибудь, пожалуйста, помогите мне найти мою ошибку? Я бы очень хотел, наконец, сделать эту работу!


person bob_mosh    schedule 27.06.2016    source источник


Ответы (2)


Сообщение Dealloc похоже на то, что в запросе отсутствует сильная ссылка.

var query: NSMetadataQuery? {
    willSet {
        if let query = self.query {
            query.stopQuery()
        }
    }
}

public func doSpotlightQuery() {
    query = NSMetadataQuery()
    let predicate = NSPredicate(format: "kMDItemKind ==[c] %@", "Application")
    let defaultNotificationCenter = NSNotificationCenter()
    defaultNotificationCenter.addObserver(self, selector: #selector(queryDidFinish(_:)), name: NSMetadataQueryDidFinishGatheringNotification, object: nil)
    query?.predicate = predicate
    query?.startQuery()
}

public func queryDidFinish(notification: NSNotification) {
    guard let query = notification.object as? NSMetadataQuery else {
        return
    }

    for i in 0 ... query.resultCount {
        print(query.resultAtIndex(i).valueForAttribute(kMDItemDisplayName as String))
    }
}

Я бы предложил использовать другой предикат, поскольку kMDItemKind является локализованным ключом в соответствии с комментарием Джона здесь

так что let predicate = NSPredicate(format: "kMDItemContentType == 'com.apple.application-bundle'") будет работать на то, что мы делаем.

В Swift 3 это может выглядеть так:

var query: NSMetadataQuery? {
    willSet {
        if let query = self.query {
            query.stop()
        }
    }
}

public func doSpotlightQuery() {
    query = NSMetadataQuery()
    let predicate = NSPredicate(format: "kMDItemContentType == 'com.apple.application-bundle'")
    NotificationCenter.default.addObserver(self, selector: #selector(queryDidFinish(_:)), name: NSNotification.Name.NSMetadataQueryDidFinishGathering, object: nil)
    query?.predicate = predicate
    query?.start()
}

public func queryDidFinish(_ notification: NSNotification) {
    guard let query = notification.object as? NSMetadataQuery else {
        return
    }

    for result in query.results {
        guard let item = result as? NSMetadataItem else {
            print("Result was not an NSMetadataItem, \(result)")
            continue
        }
        print(item.value(forAttribute: kMDItemDisplayName as String))
    }
}
person Maximillian Rose    schedule 09.02.2017

Вот решение, которое получает содержимое /applications и /applications/utilities и преобразует содержимое в NSMetaDataItems.

public func getAllApplications() -> [NSMetadataItem] {
    let fileManager = FileManager()

    guard let applicationsFolderUrl = try? FileManager.default.url(for: .applicationDirectory, in: .localDomainMask, appropriateFor: nil, create: false) else { return [] }

    let applicationUrls = try! fileManager.contentsOfDirectory(at: applicationsFolderUrl , includingPropertiesForKeys: [], options: [FileManager.DirectoryEnumerationOptions.skipsPackageDescendants, FileManager.DirectoryEnumerationOptions.skipsSubdirectoryDescendants])

    guard let systemApplicationsFolderUrl = try? FileManager.default.url(for: .applicationDirectory, in: .systemDomainMask, appropriateFor: nil, create: false) else { return [] }

    let utilitiesFolderUrl = NSURL.init(string: "\(systemApplicationsFolderUrl.path)/Utilities") as! URL

    guard let utilitiesUrls = try? fileManager.contentsOfDirectory(at: utilitiesFolderUrl, includingPropertiesForKeys: [], options: [FileManager.DirectoryEnumerationOptions.skipsPackageDescendants, FileManager.DirectoryEnumerationOptions.skipsSubdirectoryDescendants]) else { return [] }

    let urls = applicationUrls + utilitiesUrls

    var applications = [NSMetadataItem]()

    for url in urls {
        print(url.path, fileManager.isExecutableFile(atPath: url.path))
        if fileManager.isExecutableFile(atPath: url.path) {
            guard let mdi = NSMetadataItem(url: url) else { continue }
            applications.append(mdi)
        }
    }

    for app in applications {
        print(app.value(forAttribute: kMDItemDisplayName as String))
    }
    return applications
}

(Есть некоторые поправки, которые можно сделать, но я написал это в спешке)

person rohaldb    schedule 14.04.2020