Увеличете силата на своите приложения за iOS със Swift concurrency

Традиционно, когато искаме да направим мрежова заявка, трябва да използваме базираните на затваряне URLSession API, за да изпълним заявката асинхронно, така че нашите приложения да могат да реагират, докато чакат да завърши. С пускането на Swift 5.5 това вече не е така, сега имаме друга алтернатива, която е да използваме async/await.

В тази статия бих искал да ви покажа как да направите мрежова заявка, като използвате ключовите думи async/await. На всичкото отгоре ще направя и бързо сравнение между async/await и базиран на затваряне API, така че да можете да разберете по-добре ползите от използването на async/await.

Тази статия изисква от вас да имате основно разбиране за async/await. Ето защо, ако не сте запознати със Swift concurrency, горещо ви препоръчвам първо да прочетете публикацията в блога ми, наречена „Първи стъпки с Swift Concurrency.“

С всичко казано дотук, нека се захванем направо!

Предпоставка

В цялата тази статия ще използваме Apple iTunes API, за да извлечем колекция от албуми от Тейлър Суифт. Следва URL адресът на API:

https://itunes.apple.com/search?term=taylor+swift&entity=album

Тази крайна точка на API ще ни даде следния JSON отговор:

За демонстрационни цели ще вземем името и цената на албума и ще ги покажем в списък за преглед на колекция. Ето моделните обекти, от които се нуждаем за JSON декодиране:

Обърнете внимание, че ние съобразяваме структурата Album с протокола Hashable, така че да можем да я използваме като тип идентификатор на източника на данни за изглед на колекцията.

С всичко това, нека преминем към кода на мрежовите заявки.

Традиционният начин

Преди Swift 5.5, за да направим мрежова заявка, трябва да използваме метода dataTask(with:completionHandler:) на URLSession, базиран на затваряне, за да задействаме заявка, която се изпълнява асинхронно във фонов режим. След като мрежовата заявка бъде изпълнена, манипулаторът за завършване ще ни върне резултата от мрежовата заявка.

За по-голяма простота, нека дефинираме структура AlbumsFetcher за това:

Ако преди сте работили върху код, който прави мрежови заявки, горната функция fetchAlbums(completion:) трябва да ви изглежда позната. Първо стартираме задача за данни, за да направим мрежовата заявка. След като заявката бъде изпълнена, ние проверяваме за грешки и анализираме JSON на отговора.

Извикването на функцията fetchAlbums(completion:) също е доста лесно:

Едно нещо, което трябва да вземете под внимание е, че функцията updateCollectionViewSnapshot(_:) е помощна функция, която актуализира нашия списък въз основа на масива albums. Следователно трябва да се върнем към основната нишка, преди да я извикаме.

С традиционния изход от пътя, в следващия раздел, нека да разгледаме как можем да постигнем същото нещо, използвайки новата ключова дума async/await.

Начинът за бърза паралелност

За да преобразуваме нашата базирана на затваряне функция fetchAlbums(completion:) в новия стил async/await, можем да приемем два напълно различни подхода.

Първият подход е да се използва CheckedContinuation (въведен в Swift 5.5) за свързване на функцията fetchAlbums(completion:) с асинхронния контекст, докато вторият подход е да се замени базираното на затваряне URLSession с асинхронния вариант на URLSession.

Засега нека първо се съсредоточим върху подхода CheckedContinuation.

Отметнато продължение

CheckedContinuation е нов механизъм в Swift 5.5, който помага на разработчиците да направят мост между синхронен и асинхронен код. Можем да създадем CheckedContinuation с помощта на метода withCheckedThrowingContinuation(function:_:) или withCheckedContinuation(function:_:).

В нашия случай, тъй като манипулаторът за завършване на функцията fetchAlbums(completion:) ще върне грешка, ние ще използваме варианта „хвърляне“ на метода, за да създадем продължение. Ето как да направите това:

Както можете да видите, методът withCheckedThrowingContinuation(function:_:) приема затваряне, което приема параметър за продължение. Той създава асинхронна задача, която изпълнява функцията fetchAlbums(completion:), за да задейства асинхронно мрежовата заявка.

В горния код има няколко важни аспекта, с които трябва да сте наясно:

  1. Методът withCheckedThrowingContinuation(function:_:) е маркиран като async, следователно трябва да го извикаме с ключовата дума await. На всичкото отгоре, тъй като използваме неговия вариант „хвърляне“, трябва да използваме и ключовата дума try (точно като извикването на нормална функция, която хвърля).
  2. Трябва да извикаме метод за възобновяване точно веднъж на всеки път на изпълнение в цялата асинхронна задача. Възобновяването от продължение повече от веднъж е недефинирано поведение. Докато никога не се възобновява ще остави асинхронната задача в спряно състояние за неопределено време, ние наричаме това изтичане на продължение.
  3. Върнатият тип на метода withCheckedThrowingContinuation(function:_:) трябва да съвпада с типа данни на параметъра на метода resume(returning:), който е [Album].

Сега нека превключим фокуса си върху сайта за обаждания. Ако приемем, че извикваме функцията fetchAlbumWithContinuation() в контролер за изглед, можем да я извикаме така:

Както обикновено, трябва да създадем асинхронна задача, за да можем да изчакаме и изпълним функцията fetchAlbumWithContinuation() в асинхронен контекст. Тъй като вече не използваме манипулатор на завършване, сега можем да обработваме грешките, генерирани от функцията, като използваме оператор docatch.

Също така забележете, че изпращането до главната нишка преди извикването на updateCollectionViewSnapshot() вече не е необходимо, защото извикваме функцията fetchAlbumWithContinuation() в контролер за изглед, който е MainActor.

Асинхронна URL сесия

В Swift 5.5, освен пускането на ключовите думи async и await, Apple също актуализира много от собствените си SDK, за да поддържа и двете от тези ключови думи, а една от тях е URLSession.

Apple добави нов метод data(url:) към URLSession, който е еквивалентен на метода dataTask(with:completionHandler:), който използвахме преди. Това е хвърлящ асинхронен метод, който връща кортеж от Data и URLRespons. Ето как да го използвате, за да направите мрежова заявка:

Кодът по-горе до голяма степен се обяснява сам, но имайте предвид, че методът URLSession.data(from:) е асинхронен метод, следователно кодът може да спре, когато чака да се върне. Ето защо трябва да го извикаме с ключовата дума await.

Следва сайтът за обаждане на fetchAlbumWithAsyncURLSession(), който в общи линии е същият като обаждането на fetchAlbumWithContinuation():

На този етап може да попитате: Какъв е смисълът да използвате CheckedContinuation, ако API на URLSession вече поддържат async/await? Е, вие сте абсолютно прав! Определено трябва да използваме варианта async/await на всички API, ако са налични.

CheckedContinuation е главно за свързване на всички асинхронни API, които тепърва ще поддържат синтаксиса async/await. Ако, да речем, използвате мрежова библиотека на трета страна (като Alamofire), която не поддържа синтаксиса async/await, тогава можете да използвате CheckedContinuation, за да мигрирате постепенно съществуващия си код, за да поддържа async/await, докато чакате третия -партийна библиотека, за да се актуализира.

Асинхронно/изчакване срещу затваряне

Откакто Apple представи async/await в WWDC21, бях попитан от няколко младши разработчици: защо всички са толкова развълнувани за async/await, докато вече можем да направим абсолютно същото, като използваме затваряния и опашки за изпращане?

В този раздел нека се опитаме да отговорим на този въпрос, като прегледаме бързо някои от предимствата на използването на async/await:

  1. Когато използваме затваряния, може да забравим да извикаме манипулатора на завършване и няма начин да предотвратим това да се случи. Когато използваме async/await, ако не сме се върнали от функцията async, ще получим грешка при компилация.
  2. Невъзможно е да се използва оператор docatch за обработка на грешки при използване на затваряния, защото затварянията не поддържат това. От друга страна, можем да обработваме грешки, хвърлени от асинхронна функция, използвайки оператор docatch точно както обработваме грешки, хвърлени от нормална функция.
  3. Като използваме async/await, вече няма нужда да се тревожим, че ще забравим да изпратим обратно към основната нишка, защото тя е била обработена от MainActor.
  4. Async/await осигурява по-голяма безопасност от експлозия на нишка, като същевременно подобрява производителността на нашия код. Можете да разгледате това видео на WWDC, за да научите повече.
  5. Асинхронният код, написан с помощта на синтаксиса async/await, е изцяло праволинеен код. Всички операции, които трябва да бъдат извършени последователно, са изброени една след друга. Това прави нашия код (сайт за внедряване и обаждане) по-кратък, по-чист и по-лесен за разсъждение. Можете да използвате следните изображения, за да направите бързо сравнение:

Обобщавайки

Ето го! Извършването на мрежова заявка с помощта на async/await е доста лесно и ние печелим купища предимства, като просто го използваме. Струва си обаче да се отбележи, че async/await е наличен само в iOS 15 и по-нови версии. Следователно, ако вашият проект все още трябва да поддържа по-стара версия на iOS, може да се наложи да изчакате известно време, преди да актуализирате съществуващия си асинхронен код, за да използвате async/await.

Ето „пълния примерен код“, ако искате да го изпробвате сами.

Ако ви харесва тази статия и искате да получавате известия, когато излязат нови статии, не се колебайте да ме последвате в Twitter.

Благодаря за четенето.