Swift + VK.API, or the story of SwiftyVK
Today I want to tell you about how I once got acquainted with the Swift programming language and decided to write on it an application for the VKontakte social network under OSX (which, unfortunately, is still not finished). What pitfalls I had to face while curbing, at that moment, a new language and crossing it with VK.API. Share with the public the result of what exactly all this resulted in and try to justify why you had to come up with the next bike in the form of a library for working with VK.API.
If anyone is interested, then welcome to cat.
Part One: Hello, Swift!
It was the evening of June 2, 2014. As a user of Apple products and a beginner objc developer, I was confined to the screen of my laptop, on which shots of WWDC 2014 presentation flashed one after another. Spending one evening a year watching the broadcast was already a standard rite. This time was no exception. As expected, nothing supernatural was presented, and the new OSX interface even left a bit of frustration. I already fully intended to watch the remaining 15 minutes and go on my worldly affairs. But here, in the guise of the next Xcode update, without the famous “one more thing”, unceremoniously, Craig Federigi introduces a new programming language, whose name is Swift.
That was exactly what I was waiting for. How much I did not try to get used to the objc syntax, but many points were very inconvenient. It was like an unremovable itch that periodically reminded itself in the process of writing code. I remember that a couple of weeks before this very day, I dreamed that objc was more humane and readable. Even came across an article on Habré about thisbut found the solution to be a crutch that would only interfere more than be useful. And now, it is done. I could not believe it. but the rest of the evening was definitely lost. All plans have faded into the background. The first task was to quickly pull together the new Xcode and find out what this outlandish bird is all about. I played with the novelty until the dream kingdom beckoned me to me, and there was no more strength left to resist. But stocking up on The Swift Programming Language, I swore that in the morning I would come close to studying all the intricacies of the newly released swift.
No sooner said than done. I enthusiastically took up the task assigned to myself. I re-read the entire aforementioned book and very eagerly tried to parse the code examples from it in order to understand what was happening. And when it was finished, he successfully owned the basic structures and fell in love with this language. Simplified readable syntax, static typing, optional values, generics and many other Swift things did not leave me indifferent. I absolutely did not want to go back to the old objc anymore (although I probably still have a long time).
Part two: Forward and with the song
So. Language training materials were learned, the basics were disassembled, and I wanted to go deeper into the subtleties so much that a slightly reckless decision was made to write the application in a language that was still in beta status.
It is worth saying that before that I wrote only small programs for automating routine actions. Not to say that they were just some kind of scripts. No, these were small objc applications using OOP, multithreading, a graphical interface, and even some design patterns that still work successfully and make life easier everyday. Only now they solve specific tasks for me, and it is unlikely for this reason that they will be very useful to someone (although there is an idea to bring them to the appropriate state and put them on the side. Suddenly I am wrong).
The motivation for all my brainchildren was one thought - if this thing is not there or it is not implemented as we would like, but I know how I would like to see it, then I need to do it myself. This time I followed the same logic. For some time now, the idea had been in my head to create an application for the VKontakte social network under OSX, since the analogs did not suit me at all (I will not say what kind of application it is, since it is still far from the release. And this is not the essence of the article) . The project was going to be much larger than what I usually did and required more labor and brains than usual. I thought it was a good opportunity to delve deeper into Swift development, and to bring to life a pending idea. Moreover, one friend of mine was already familiar with the work of VK.API and also wanted to learn Swift. It inspired me even more.
And so, I began to try to build in my head a clearer concept of what I would like to see after work. Only one, since my friend with this idea, apparently, caught fire less. But with the API, he still helped me, for which special thanks to him.
The first thing that came to mind was to find a library for working with VK.API for Objective-C (the benefit of Swift allows you to use objc code in your projects). This should greatly simplify the development, would not have to bother with the logic of requests / responses, but only receive data, scatter it into models and just send new data to the servers.
Having delved into the official documentation of VK, the first thing I noticed was the official SDK. It was created for iOS, but can it somehow be screwed to OSX? It turned out that no. In addition to the requests themselves, a lot of them are tied to UIKit, and it was almost impossible to twist nuts under AppKit. A desperate search in Google also did not give anything. All libraries were developed either exclusively for iOS, or as a few years ago the author threw unfinished work on the dusty shelf. Not a single option suited me. This, of course, saddened me, but since I took up the realization of the idea, I did not stop. No library? We can do without her!
Part Three: We are ours, we will build a new house
The first thing to do was to figure out the authorization process. It all turned out simply because it passes through the oAuth protocol. At the very least, logging in and tearing out a token with certain rights from the address bar of the browser in order to start trying to send test requests to the API turned out quickly. Then, which is a bit more complicated, you had to implement this in the application itself. A WebView was created that loaded the authorization page and its WebFrameLoadDelegate looked at the URL of the page and caught the token. At first, there were only three options: If there is a token in the URL, then we pick it up and remove the WebView. If the user declines authorization, then we close the application altogether, and if you suddenly decide to go to some other page, we will not let him do this and return to authorization.
I sent requests using NSURLConnection, and synchronously. Parsil responses using NSJSONSerialization and in the end I got these constructs:
(((jsonObject as? NSDictionary)?["phoneNumbers"] as? NSArray)?[0] as? NSDictionary)?["number"] as? NSString
Agree, not the most pleasant sight. At this point, working with the optional turns into hell. Read more about the problem here .
Somewhere here it began to become clear that everything that happens is not good and convenient. Plus, APIs may return errors, requests for authorization, authentication, captcha input. These things need to be somehow foreseen and processed. After some deliberation, it was decided that it was most logical to abstract all the logic of working with the API into a separate library. Firstly, this will allow not to spread the code for communicating with VK within the application, and secondly, it will be possible to reuse it and fix errors more locally. Since there are a lot of libraries for iOS and objc (although one official SDK is enough), I decided to make my own exclusively for Swift and OSX.
In general, it’s worth saying that if I wrote on iOS, then such a problem would not have arisen, but since I’m a desktop, or rather laptop, user and I love OSX more than iOS, then I do all the things that I do for myself - I naturally do that the platform that I use more actively. Yes, and the possibilities which, in my humble opinion, are greater. But the taste and color ...
Part Four: Development. Or a quick review of SwiftyVK
The first appeared structure under the laconic name VK - the entry point, authorization, request creation and just the conductor SwiftyVK. At first, she had only methods for authorization and deauthorization + a draft of the delegate protocol, which must be implemented in the class using the application.
Next, it was necessary to implement full authorization. The existing solution was taken as a basis and brought to mind. The application, during authorization, must transfer the rights that it wants to obtain. In the official SDK, the list of rights is passed as an array of strings. I decided to use the static typing of Swift and made an enum from the rights, which allows us to pass an array like this during authorization:
[.messages,.offline,.friends,.wall,.photos,.audio,.video,.docs]
Previously, the token received after authorization was stored as a variable until the program was closed. Now it was definitely necessary to take care of its storage better. If you do not go into too much detail, then this is an object of the Token class that stores directly the string with the token, the logic of its return and verification of relevance. If the token is out of date, a new one will be requested. Token supports the NSCoding protocol and can be stored in NSUserDefaults or any other place to choose from. The object itself exists as a singleton, since we naturally do not need two tokens.
Authorization can be started either in any window in the sheet view, or in a separate window. After authorization, we begin to send API requests. It was necessary to implement the logic of sending and receiving a response. This is done by a group of classes / structures Request, Connection, Response and NSURLFabric. Outside the library itself, only the first one is visible - Request and some of its properties / methods that allow you to configure the request and send it to distant sites to the VKontakte servers. Connection - the logic of sending a request, waiting for a response, handling errors and calling callbacks. NSURLFabric is a factory with static methods that collects from NSURLRequest configured and ready to send. Well, Response is a simple structure with request, error, success fields and the logic of parsing the response from the server to an error or a successful response.
It was shown above how JSON was parsed using NSJSONSerialization. To get more convenient and Swift-native methods for obtaining values, we first used the library from the same article describing this problem, but since it still left a lot of places where question marks are used for parsing, later we switched to SwiftyJSON , as in to me, a very convenient library. With its help, the problem was completely resolved.
Errors are implemented in the Error class, which resembles an NSError. Error objects can even be initialized with NSError, but more often this comes from JSON. Some errors are handled directly in SwiftyVK. For example, if the application sent a request that requires authorization, but we are not authorized, then an error message will be returned, an authorization window will be displayed, and after passing it, the request will be redirected. Similarly with captcha. To disable this feature, you need to set the request parameter catchErrors to false. Then, even if a known SwiftyVK error returns, an error processing block will be executed.
For a long time I was tormented with synchronous and asynchronous requests. Not in terms of the complexity of multithreading and asynchrony. Nothing complicated here. The problems were due to sending synchronous requests in the main thread. This situation could turn out - the application sends a synchronous request. The stream from which it is sent is blocked until the response block or error is called. The API returns an error, which indicates the need for authorization / captcha input before calling the error block. The window needs to be shown from the main thread, and it is already blocked waiting for a response. As a result, deadlock and hang tight. As much as I did not try to come up with crutch ways to get around this, somewhere something went wrong. Exit - for synchronous requests with error checking from the main thread, an assert will work with a description that you should not send them. Of course, not the best way, but it’s better,
Since Swift is a strongly typed language, I wanted to strongly typify everything so that there would be as few places for making mistakes in the future. Perhaps the most controversial place for someone is to type query arguments. In the official SDK, they are transmitted as a string. I decided to introduce the intersection Arg: String, in which I listed all currently possible query arguments. In addition to minimizing errors, this allows you to use auto-completion in Xcode when setting arguments.
For even greater convenience, an API structure has been introduced, which contains sub-group groups of methods for quickly creating a request for a specific API method. As a result, the creation of the request is something like this:
let req = VK.API.Friends.get([
.count : "1",
.fields : "city,domain"
])
As for me, it’s very simple, readable and concise.
In addition to sending simple requests, it is possible to send requests with a custom method name (passing it a string), execute, or remote procedures. Plus, the implementation of file loading (which most other libraries do not have) and the LongPool client (this is not found anywhere).
Part Five Last
In principle, this is all that SwiftyVK consists of. For a long time I did not upload it, because I tested and brought it to mind. I think now he is ready to meet with the public. I tried to make the simplest and most understandable library for working with VK.API using the advantages of the new language. I want to say that at the end of development I backtracked on the idea of doing it only under OSX and added peer support for iOS. I also wanted to do it for tvOS, but there is no WebView there yet and it is impossible to log in using oAuth. As soon as possible - so immediately. Under watchOS, I decided not to, because I do not know who might need it. I’m not even sure that this library may be needed by anyone other than me. But if I am mistaken and someone is interested in SwiftyVK, then thank you and good luck with your use.
You can read the documentation in English and the source code here .
PS: If you read to this place, then thank you very much for your time.