Производительность NSURLSession - Возможные условия гонки или заблокированные потоки?
Я столкнулся с небольшой проблемой производительности моего приложения для iOS, я впервые работаю с NSURLSession и NSURLRequest, и хотя я старался изо всех сил информировать себя, я врезался в стену, пытаясь отладить проблему производительности, с которой я сталкиваюсь.
Итак, вот что я получил: у меня есть приложение для iOS 9, написанное на Swift 2, я общаюсь с сервером NodeJS/Express через запросы Get, Post и Put Http, используя NSURLRequest и NSURLMutableRequest. Я отправляю запросы на выборку группы объектов (вместе не более 12000 байт), однако запросы занимают значительное количество времени (иногда до минуты). Я добавил протоколирование на сервер nodeJs и вижу, что обработка запросов занимает не более 30 миллисекунд.
Примечание: я не уверен, что это уместно, но я использую одноэлементный "вспомогательный" класс для выполнения всех моих запросов API и анализа результатов (сохранение токенов аутентификации, анализ объектов JSON и сохранение их в Core Data, сохранение пользовательских настроек к NSUserDefaults и т. д.), я использую синглтон, чтобы я мог получить к нему статический доступ, и я анализирую все данные, не сохраняя ничего в свойствах синглтона, кроме URL-адреса сервера и NSURLSession.
Вот как выглядит мой код.
//On initialization of the helper class
private let session = NSURLSession.sharedSession()
func getAllObjects() {
let route = "api/someRoute"
let request = getRequest(route)
request.timeoutInterval = httpTimeout
session.dataTaskWithRequest(request, completionHandler: ResultingObjects).resume()
}
Метод getRequest возвращает отформатированный NSMutableURLRequest, показанный здесь:
func getRequest(route: String) -> NSMutableURLRequest {
let request = NSMutableURLRequest()
request.URL = NSURL(string: "\(serverUrl)/\(route)")!
request.HTTPMethod = "GET"
request.addValue("Bearer \(self.AuthenticationToken()!)", forHTTPHeaderField: "Authorization")
return request
}
Обработчик завершения проанализирует возвращенные объекты и уведомит основной поток с полученными проанализированными объектами следующим образом:
private func ResultingObjects(data: NSData?, response: NSURLResponse?, error: NSError?) {
if let d = data {
if !isAuthorized(d){
return
}
do {
if let JSON = try NSJSONSerialization.JSONObjectWithData(d, options: []) as? NSDictionary {
if let message = JSON["message"] as? String {
if message == "Empty result" {
//- Return notification to be handled in main thread
notifyMainThread(NoObjectsFetched, payload: nil)
return
}
}
if let objcts = JSON["SomeObjects"] as? NSArray {
if let SomeObjects = parseResultingObjects(objcts) {
//- Return notification to be handled in main thread
notifyMainThread(ObjectsFetched, payload: ["payload": SomeObjects])
}
return
}
}
}
catch {
print("Error getting resulting objects")
}
}
else if let e = error {
print("\(e), could not process GET request")
}
}
Я также попытался проанализировать полученные объекты в главном потоке, но, похоже, это не имеет значения.
Если вам интересно, вот как я отправляю данные в основной поток:
private func notifyMainThread(notification: String, payload: AnyObject?) {
dispatch_async(dispatch_get_main_queue(), {
if let p = payload {
NSNotificationCenter.defaultCenter().postNotificationName(notification, object: nil,
userInfo: p as! [String: [MYMODEL]])
}
else {
NSNotificationCenter.defaultCenter().postNotificationName(notification, object: nil)
}
});
}
Что я узнал: ничего не имеет смысла! Я попытался отладить это, но я не могу точно определить, в чем проблема. Когда отладчик нажимает на мой метод "getAllObjects", может пройти несколько хороших секунд (до 45 секунд), прежде чем сервер зарегистрирует, что он получил и обработал запрос (который обычно занимает около 30 миллисекунд). Насколько я могу судить, это происходит для всех типов запросов. Кроме того, после того как приложение вернет данные (очень быстро), потребуется много времени (около 4 секунд) для его анализа и только около 11 КБ.
Я также пытался изменить политику кеширования запросов, если приложение проверяло правильность кэшированных записей на сервере, я использовал ReloadIgnoringLocalAndRemoteCachedData, который тоже не работал.
Теперь это звучит как утечка памяти. Если я приостановлю приложение в любой момент (после нескольких минут его использования), я могу увидеть соответствующее количество потоков. Я, честно говоря, не слишком знаком с IOS, поэтому я не уверен, принадлежат ли эти потоки симулятору или все они принадлежат приложению, приложение передает потоковое видео (без проблем с задержкой) с использованием класса AVPlayer, и я считаю, что многие из этих тем связаны с этим, однако я не уверен, что это нормально, вот скриншот того, что я имею в виду (обратите внимание на полосу прокрутки T_T) Снимок экрана
Может ли быть так, что у меня утечка памяти или некоторые потоки зомби, значительно снижающие производительность моего приложения? Единственная действительно заметная задержка происходит только для HTTP-запросов, что довольно странно, никакая другая часть моего пользовательского интерфейса не задерживается, и ни одна другая функция в моем приложении не страдает от проблем с производительностью (даже потокового видео с URL).
Как лучше всего описать проблемы с производительностью, чтобы точно определить источник проблемы?
ОБНОВЛЕНИЕ 1: Благодаря предложениям Scriptable мне удалось решить проблему с многопоточностью (вызванную тем, что несколько AVPlayers делают свое дело). Выполнение запросов, однако, не решается.
Стоит отметить, что сервер физически находится в той же стране, откуда я делаю запросы, когда при выполнении запросов из браузера или из командной строки запросы почти мгновенные.
Кроме того, когда я случайным образом приостанавливаю приложение (ожидая, пока произойдет запрос), я вижу "mach_msg_trap" в некоторых потоках, я не знаком с этим, но считаю, что это может быть условием гонки? или тупик?