Programming on the iOS platform (the one that was recently called the iPhone OS) is a strange combination of joy from fruitful work and the agony of swimming against the tide. Each developer has his own opinion as to which of these components prevails. Personally, I like this activity, so it seemed to me appropriate to share my impressions of the
process of working on another project.
At the end of March I was offered to write a mobile version of
Bookmate for iPhone. The design of the most part of the application was already ready in the form of a thick PSD, on the server side the work was in full swing, I had, as they say, “only” to write the client part in Objective-C.
In this article we will discuss the first container with a rake, attacking us. If you play Starcraft, the analogy with the zerg will be more suitable, which suddenly climbed out of all the cracks in typically incredible quantities.
Architecture
In a nutshell, Bookmate is a server that stores books consisting of a set of HTML files. The main task of the Bookmate client (in common, "reading rooms") is to show and process this HTML. Why html? Unlike, for example, PDF, it is easy enough to reformat it when changing the font size, which is absolutely necessary in a mobile application on a tiny screen. On the other hand, since HTML does not have the usual concept of a page, it is, strictly speaking, not very suitable for displaying books in the traditional page form. To correctly transfer a line that does not fit on the screen to the next page, you need to know its vertical indent on the page. To do this, the HTML engine must first process the entire document and calculate the size and coordinates of all the blocks in the document according to the style sheet. After that, you can do what any typesetting program does like Adobe InDesign or QuarkXPress.
')
Colleagues from Bookmate by that time had already written a library in JavaScript, which does all this nicely and quickly.
Hello iPhone
As a base test platform, we chose the iPhone 3G, because this is both very popular in Russia, and the oldest iPhone model I still have. As one of the test books, the publication “Models for assembly” by Julio Cortazar was made in one file of 890 KB in size.
The first prototype was UIWebView, a JavaScript library and some (albeit substantial) amount of code in the interlayer between them in Objective-C.
iPhone 3G is a very, very smart phone.
Mostly . Honestly, I hardly ever tried to open such heavy sites in Mobile Safari, and I’m not sure that these are found in nature. Poor iPhone! For the more than 40 seconds that he spent on the opening of the “Model for assembly”, he managed to close all other applications due to lack of memory, and shout at me that now the memory will end altogether, and moan about hard fate, and warm not able to change anything.
But at that moment we had no time for joking. The first vanguard of the avant-garde clutched at the foot, and the earth shook under the horde, still invisible, but already terrible.
To optimize, in fact, there was nothing. For a good half of these 40 seconds, WebKit was engaged in parsing, building a DOM, recalculating geometry, and devouring (for lack of other terms) memory. The latter is in itself a serious problem in iOS due to the inability to use a disk for virtual memory, and in our case, this also led to the need to urgently free up memory due to background programs (such as Mail and Phone), which also takes time. JavaScript, iterating over the entire DOM, just finished off WebKit.
Such performance, obviously, did not suit anyone. It was also obvious that JavaScript on the iPhone is not fast enough for our purposes, and that we cannot afford 20 MB of memory.
And what to do with it?
To better understand the scale of the disaster, you need a little interruption to the description of the context in which it occurs. WebKit is
an open source library . But in iOS, it refers to a
private API , the use of which is prohibited by Apple. Even if it were possible to build WebKit for iOS on its own and include it in the program as a static library, UIKit is already linked with WebKit, which inevitably leads to character collisions; even if you rename the entire WebKit and avoid collisions, UIWebView does not provide any access to the WebKit components on which it is built: see above for using the private API. It turns out that we cannot get to the DOM from Objective-C directly, without JavaScript. And that means, as the one-eyed blind tortoise said recently, “Well, that's it, they came.”
All that remains is to forget WebKit: parse the HTML using libxml and draw the DOM manually. That is, you need to write your own HTML engine. I could agree on this only for the sake of experiment, in order to understand whether it makes sense in terms of performance. In the end, I saw the source of WebKit and am aware of the futility of trying to rewrite it alone, while making it much faster. On the other hand, we do not use the features of WebKit even by 10%. If you write your own highly specialized engine, it will be ten times easier than any modern browser. Oh, where our not disappeared!
libXML
At annually held in hell for programmers competition for the worst API libxml again the main prize. Perhaps I find fault, but the source is much clearer than the
documentation . Typical example:
Function: htmlCtxtReset
void htmlCtxtReset(htmlParserCtxtPtr ctxt)
Reset a parser context
ctxt: an HTML parser context
In all seriousness, is that all ?! Everything that is written here is clear from the names of the function and the argument. What exactly does this feature do? Why is it needed? When to use it? What, I'm sorry,
I wrote this
dunce ? Sit down, libxml, deuce.
It is fair to say that the library itself works well enough for almost everyone to use it. And in life it is much easier. And there is on every iPhone.
First results
In order to lose as little time as possible on a dead-end development, I started from the slowest section. This is the calculation of the size of blocks of text, which is reduced to the summation of the sizes of
glyphs . The results of performance measurements were not without surprises.
- UIWebView + JavaScript - 38 seconds, 20 MB of memory. This is our first option after all optimizations.
- NSString, -sizeWithFont: - 14 seconds, 8 MB of memory. Since it makes no sense to repeatedly calculate the size of the same glyphs in the same font, the results are cached. 35% of the time it takes to call the method - [NSString sizeWithFont:].
- Core Text under iPhone OS 3.1.3 - more than 70 seconds. Core Text is a private API in iPhone OS up to version 3.2, I just wanted to compare its speed, because It is amazingly easy to use and very fast on a Mac.
Armed with numbers, I wrote a letter to those. support for developers (so-called
DTS , Developer Technical Support, which is generally a paid service). At the same time he asked why the underlying function CGFontGetGlyphsForUnichars () - [NSString sizeWithFont:] belongs to a private API.
That's why I respect Apple, because it is related to developers, when the latter do not whine on the whole Internet, but ask questions "in the prescribed form." Two days later I received a detailed answer to my questions, from which it followed:
- access to WebKit is not provided by party policy;
- Core Text under iPhone OS prior to version 3.2 is a private API due to being too slow; since version 3.2, it should be significantly faster than UIWebView + JavaScript;
- CGFontGetGlyphsForUnichars () is a private API due to its not quite adequate design; On the Internet, you can find its analogues and make sure that this is the case;
- In general, I am on the right path, because they themselves do not know the other;
- you'll have to choose between convenience - [NSString sizeWithFont:] and Core Graphics speed.
For the cause!
Algorithm text layout sounds pretty simple, if not go into details. First you need to break the text into fragments with the same style, for example, if in the middle of a block of text there is a phrase in italics, such a block is divided into three style fragments. Then you need to understand where the text
can be transferred to a new line - these are, as a rule, punctuation between words or hyphenation within words. Let's call them candidates for the transfer. Then we take fragments of the text between candidates for transfer, one by one, count their length, and put them on a line until their total length exceeds the length of the line. Similar to pages: we add lines until the next line comes out beyond the height of the page.
It turns out an impressive set of objects: each block of text (for example, a paragraph) consists of an array of strings containing so-called.
glyph runs - text fragments of the same style. These same glyph runs we draw as they are.
At this point, there are interesting opportunities for optimization, and the code quickly turns into spaghetti. As we descend to the lowest-level API, it becomes clear that there is not enough linguistic education. On the horizon, there are all new writing systems with which this code does not work. For example, because of the direction of the letter, as in Arabic or Hebrew, or because there are no spaces between words, as in Thai.
As a result, realizing that it was impossible to grasp the immense from the first attempt, I had to postpone the support of languages ​​with the direction of writing from right to left and languages ​​like Thai, which I know too little. For the same reason, the implementation of text alignment at both edges has been postponed - it requires an efficient hyphenation system, which, in turn, needs to know in what language each word is.
Nevertheless, in terms of productivity, significant progress was achieved compared to what we started with. Now the masterpiece of Cortazar appears on the screen in less than 6 (!) Seconds. It was a really tough month. I had to temporarily reflash my brain, otherwise he refused to work with HTML and CSS. My wife all this time listened from my corner only grunting and swearing. The client was also not sweet: firstly, no one counted on an extra month, secondly, the start of work on the project was discouraged (“a pretty start, what will happen next?”), Thirdly, there were no guarantees that this pile of plywood will be able to take off at all. However, who does not risk, that goes on foot. And we so wanted to heaven!
The Bookmate application is already available in the
App Store (the link opens in iTunes; if for some reason it does not work, there is
an alternative one , it opens in a browser).
Epilogue
About Steve Jobs tell the
story . In 1983, he urged engineers to optimize the loading time of a mac with these words: “How many people will use a mac? Million? No more. I bet a few years later, five million people will turn on their macs at least once a day. Suppose you can reduce the boot time by 10 seconds. Multiply it by five million users, it will turn out 50 million seconds, every single day. For the year it's probably dozens of lives. If you load it 10 seconds faster, you saved a dozen lives. Well, is it worth it? ” I think yes.