📜 ⬆️ ⬇️

IOS application development Aviasales.ru. Airport selection screen

When creating the application Aviasales.ru for iOS, we faced many interesting tasks. One of them is the implementation of a convenient mechanism for selecting points of departure and destination. In this post, we would like to briefly describe how we solved this task and what features the iOS SDK used.



You may have seen this post some time ago. We just wrote the wrong time last time - and it was removed from the publication. But I really do not want the material to disappear. So if you have already seen it - just ignore it.

The screen for selecting the airport of departure is divided into three parts: the nearest airports, the list of airports previously selected by the user, and the airport name entry line.
')
The functionality of the nearest airports is implemented in two stages: first, the application, after first asking for permission from the user, finds out the coordinates of the device. By the way, in the request for obtaining geolocation data, you can add a clarification with which, in fact, we are interested in the target. To do this, you can use the purpose property of the CLLocationManager:

locationManager.purpose = @”   ”; 



We do not need the exact coordinates of the user, it’s enough to know which city he is located next to. Therefore, the accuracy of geolocation can be reduced:

 locationManager.desiredAccuracy = kCLLocationAccuracyThreeKilometers; 

The second stage - the received coordinates are sent to our server, which determines which airports may be of interest to the user. For example, residents of St. Petersburg, in addition to Pulkovo, are offered Finnish Lappeenranta.

Search history is the last five selected items that are stored in the database (using SQLite with Core Data, nothing complicated).

We turn to the most interesting - the search for airports and cities on the line entered by the user. The search works in three stages:
  1. we are looking for an exact match in the list of popular airports;
  2. if nothing is found, we look for inaccurate matches in the same list;
  3. If the user has entered more than two characters, he can send a search query to the server to find a less popular airport.

Now in order.

There are a great many airports in the world - about 10 thousand. But the really popular ones (those that users with a certain regularity use in search queries) are only one and a half thousand. We decided to deliver this list of popular destinations initially within the application so that when you first start the user can select the desired city as quickly as possible. To store information about these airports, we also use SQLite with Core Data (on Habré there is an article about how to preload Core Data into an application ). When launched, the application reads the airport data from the database to the array:

 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSManagedObjectContext* context = [[ASCoreDataManager sharedInstance] currentManagedObjectContext]; NSArray *dbAirports = [ASAirport MR_findAllInContext:context]; @synchronized(self){ _airports = dbAirports; } }); 

We use the Grand Central Dispatch mechanism to perform this operation in a background thread. A call via synchronized is necessary to ensure the security of memory access during multi-threaded operation.

“What is the MR_findAllInContext method?” You ask. This is a function of the MagicalRecord library. The fact is that the Core Data mechanism itself is not thread-safe. In practice, this may cause the application to crash if fetch read requests are sent from different threads. This is solved by using separate NSManagedObjectContext for each stream, and they will all have a common persistentStoreCoordinator . The MagicalRecord library helps coordinate all these contexts.

First, it implements the [NSManagedObjectContext MR_contextForCurrentThread] method (used in the currentManagedObjectContext method from the code above), which returns the context for the thread where it was called. Secondly, MagicalRecord has a lot of life-saving wrappers over the standard blocks of code: for example, creating a variety of NSManagedObjectModel , NSPersistentStoreCoordinator , NSManagedObject and their heirs can be done in one line just by setting the necessary parameters.

Sometimes data on popular airports are updated - some destinations become popular, others, on the contrary, become less relevant, some change their names. Therefore, periodically, the application downloads a JSON file compressed with gzip from the server, the data from which, also in the background thread, updates the database. First, clear the database of records:

 [ASAirport MR_truncateAllInContext:context]; 

Then we write new data:

 NSManagedObjectContext* context = [[ASCoreDataManager sharedInstance] currentManagedObjectContext]; for (APIAirport *airport in airportsArray) { ASAirport *initialAirport = [ASAirport MR_createInContext:context]; //       } [context MR_save]; 


Returning to the search: so, the application has read the airport data into memory and is waiting for the user to start typing characters into the search string. When entering each new character, the program bypasses the data array and finds those airports whose names begin exactly from the line that the user entered. In case nothing is found, re-traversing the array is started, and those names that have a small Levenshtein distance from the search string are selected. This helps if the user made a typo or simply does not know how exactly the name of the city is spelled.



The search process can take considerable time, especially if we are looking for a fuzzy match. Therefore, it is implemented in the heir class NSOperation . This gives an important advantage over simple asynchronous execution of the block: we can interrupt the operation if the search process is not yet completed, and the user has already changed the search string. In practice, it looks like this:

When implementing the main function, after each iteration of the loop, we check if the operation is not canceled:

 for (ASAirport *currentAirport in initialAirports) { if (self.isCancelled) { return; } //    } 

We cancel the operation when it is no longer relevant:

 [_searchOperation cancel]; 

and it will immediately stop doing so without consuming precious resources.

If the user could not find the desired airport in the list of popular, he can always send a request to the server. Example: looking for Nazran - we find Kazan (Nazran is an unpopular airport, it is not in the local database, and Kazan is closest to Levenshteyn).



A second waiting for a response from the server, and the happy Nazran warrior can now find a cheap ticket and fly away!

Thanks for attention!

PS
Aviasales app for iOS .
And we also have an application for Android .

Source: https://habr.com/ru/post/159297/


All Articles