{"id":4130,"date":"2014-09-07T21:55:59","date_gmt":"2014-09-07T20:55:59","guid":{"rendered":"http:\/\/www.thetawelle.de\/?p=4130"},"modified":"2015-04-22T00:14:26","modified_gmt":"2015-04-21T23:14:26","slug":"non-blocking-nsstring-search-using-e-g-uitextfield-uisearchviewcontroller","status":"publish","type":"post","link":"https:\/\/www.thetawelle.de\/?p=4130","title":{"rendered":"Non-Blocking NSString-search using e.g. UITextField &#038; UISearchViewController"},"content":{"rendered":"<p><center><img loading=\"lazy\" decoding=\"async\" data-id=\"4148\"  src=\"\/wp-upload\/caution_rant_450.png\" alt=\"caution_rant_450\" width=\"450\" height=\"287\" class=\"aligncenter size-full wp-image-4148\" srcset=\"https:\/\/www.thetawelle.de\/wp-upload\/caution_rant_450.png 450w, https:\/\/www.thetawelle.de\/wp-upload\/caution_rant_450-300x191.png 300w\" sizes=\"auto, (max-width: 450px) 85vw, 450px\" \/><\/center><br \/>\nDoing search is difficult. Usually you start to search for something if the stuff you have has reached an amount you cannot recognize any longer easily. You spend hours searching and finding the right things or do not even try searching for it because it is too much work.<\/p>\n<p><strong>Totally unrelated by <a href=\"https:\/\/twitter.com\/mattetti\/status\/508468654871035904\">@mattetti<\/a>:<\/strong><br \/>\n<img loading=\"lazy\" decoding=\"async\" data-id=\"4179\"  src=\"\/wp-upload\/tweet_mattetti.png\" alt=\"tweet_mattetti\" width=\"446\" height=\"87\" class=\"aligncenter size-full wp-image-4179\" srcset=\"https:\/\/www.thetawelle.de\/wp-upload\/tweet_mattetti.png 446w, https:\/\/www.thetawelle.de\/wp-upload\/tweet_mattetti-300x58.png 300w\" sizes=\"auto, (max-width: 446px) 85vw, 446px\" \/><\/p>\n<p>That is what search was made for&#8230; theoretically. Nowadays &#8222;Search&#8220; often means you have to type into a $textInputField somewhere and as soon as you start typing this damn thing starts searching EVEN ON THE FUCKING FIRST CHARACTER and blocks the UI i.e. the keyboard.<\/p>\n<p>There are thousands of examples of this kind of behaviour but the worst thing at all is that these <strong>INSTANT-SEARCH-AS-YOU-TYPE<\/strong> consumes so much CPU on the MAIN THREAD (which usually is needed to drive the User Interface) that everything literally comes to a halt&#8230; EVEN THE FUCKING KEYBOARD. You simply cannot type the next character until the search comes back and has finished computing. I could kill developers for this kind of behaviour.<\/p>\n<p>But whose fault is this? I would say Apple could definitely improve it&#8217;s own apps in this regard. So to ask Apple to fix this is asking a blind to see the colors. No! You need to fix it yourself! Get to know THREE small things, it is actually really only three (<strong>in numbers &#8222;3&#8220;<\/strong>):<\/p>\n<h3>You need to know 3 things<\/h3>\n<ol style=\"margin-top:20px;\">\n<li><b>MAIN THREAD<\/b><br \/>\n<code>Nearly 99% of your written code runs here. It drives also the UI. Computing intensive stuff like instant-search-on-typing placed here will degrade UXP dramatically.<\/code><\/li>\n<li><b>THREADS YOU CREATED<\/b><br \/>\n<code>The 1% of your code which does very computing intensive search runs here and NOT on the MAIN THREAD and IF YOU DO NOT TAKE CARE it consumes ALL THE CPU it can get. You need to teach it to make pauses.<\/code><\/li>\n<li><b>PAUSING WORK<\/b><br \/>\n<code>The 200 milliseconds you will definitely pause regularly in your computing intensive code to allow others (i.e. the MAIN THREAD) to get stuff done, too.<\/code><\/li>\n<\/ol>\n<p>Things are not that complicated as you might think. But a lot of people post code that does not work and\/or is wrong and has no easily understandable explanation why this is the right code and how it works in the details. If you read one piece of code to learn from, please read <a href=\"https:\/\/stackoverflow.com\/questions\/16685922\/speed-up-search-using-dispatch-async\/16879900#16879900\">this answer on <strong>StackOverflow<\/strong><\/a> explaining how to avoid blocking the keyboard while you do searches.<\/p>\n<p><strong>Step 1:<\/strong> It boils down to YOU first giving MAIN THREAD some CPU-cycles before you even start with your computing intensive stuff. THIS IS IMPORTANT DO NOT EVER LEAVE OUT THIS STEP!!!<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" data-id=\"4146\"  src=\"\/wp-upload\/make_it_3d.png\" alt=\"make_it_3d\" width=\"295\" height=\"350\" class=\"alignright size-full wp-image-4146\" srcset=\"https:\/\/www.thetawelle.de\/wp-upload\/make_it_3d.png 295w, https:\/\/www.thetawelle.de\/wp-upload\/make_it_3d-252x300.png 252w\" sizes=\"auto, (max-width: 295px) 85vw, 295px\" \/><strong>Step 2:<\/strong> Directly after you gave away some cycles to the MAIN THREAD you DISPATCH (other word for starting something, that continues to do stuff on its own afterwards) YOUR CREATED THREAD which will do computing intensive work. <\/p>\n<p>Think about YOUR THREAD as a separate app that has nothing else to to with your app anymore. Since this separate computation starts within the blink of an eye it is so important you give some CPU-cycles to the other threads BEFORE you start your work, because your threads will be started FROM THE MAIN THREAD, and the MAIN THREAD wants to immediately continue with its work after you DISPATCHED YOUR THREAD. In this case (keyboard events) the computing intensive operation gets started over and over again. This is the second reason you need to add PAUSES.<\/p>\n<p><strong>Step 3:<\/strong> Inside YOUR THREAD you will hit a point (hopefully!) where all computation is done and finished. Usually something should happen with the result now. You now need to communicate from YOUR THREAD (&#8230;again, that is best thought of as a separate app) to your real app aka the MAIN THREAD. There is only one recommended way to do this, you have to tell the MAIN THREAD that it should do something now for you. You just give the MAIN THREAD a job ticket from within YOUR THREAD, so it knows what to do with your results.<\/p>\n<h3>Sample Code that works!<\/h3>\n<p>Here come some sample code I actually use successfully on the iDevices. Recognize that the simulator is not really the place you test this. Please <strong>recognize the highlighted lines 52,60,63 and 77<\/strong>, where all the thread-related magic happens.<\/p>\n<pre class=\"brush: cpp; gutter: true; highlight: [52,60,63,77]; title: Source\/Snippet; toolbar: true; notranslate\" title=\"Source\/Snippet\">\r\n\/* YourViewController.h *\/\r\n@interface YourViewController : UIViewController &lt;UITextFieldDelegate&gt; {\r\n\r\n    BOOL isSearchInProgress;\r\n    BOOL wantsAnotherSearch;\r\n    NSString *stringToSearchForWhenCompleted;\r\n}\r\n\r\n@property ( nonatomic, assign ) BOOL isSearchInProgress;\r\n@property ( nonatomic, assign ) BOOL wantsAnotherSearch;\r\n@property ( nonatomic, retain ) NSString *stringToSearchForWhenCompleted;\r\n\r\n@end\r\n\r\n\/* YourViewController.m *\/\r\n@implementation YourViewController\r\n\r\n\/\/ SOME STATUS VARIABLES WE NEED TO SYNTHESIZE\r\n@synthesize isSearchInProgress;\r\n@synthesize wantsAnotherSearch;\r\n@synthesize stringToSearchForWhenCompleted;\r\n\r\n- (void) refreshUiWithItemsFound:(NSArray*)itemsFound {\r\n    \/\/ UPDATE YOUR UI HERE!\r\n}\r\n\r\n- (void) searchForStringAsync:(NSString*)string {\r\n    if( !string || &#x5B;string length] == 0 ) {\r\n        &#x5B;self refreshUiWithItemsFound:&#x5B;NSArray array]];\r\n        return;\r\n    }\r\n    \r\n    if( isSearchInProgress ) {\r\n        \/\/ IF ONE SEARCH-THREAD IS ALREADY IN PROGRESS DO NOT TRIGGER ANOTHER\r\n        \/\/ INSTEAD STORE THE STRING AND REMEMER WE NEED ANOTHER SEARCH\r\n        wantsAnotherSearch = YES;\r\n        self.stringToSearchForWhenCompleted = string;\r\n        return;\r\n    }\r\n    stringToSearchForWhenCompleted = nil;\r\n    \/\/ REMEMBER WE HAVE OFFICIALLY HAVE A COMPUTING INTENSIVE\r\n    \/\/ ACTION RUNNING ALREADY\r\n    isSearchInProgress = YES;\r\n    \r\n    \/\/ USUALLY YOU HAVE SOME DATA SOMEWHERE ALREADY\r\n    \/\/ THIS IS JUST DEMO\/EXAMPLE DATA HERE...\r\n    NSArray *itemsToSearch = @&#x5B;@&quot;The&quot;,@&quot;quick&quot;,@&quot;brown&quot;,@&quot;fox&quot;,@&quot;jumps&quot;,@&quot;over&quot;,@&quot;the&quot;,@&quot;lazy&quot;,@&quot;dog&quot;];\r\n    \r\n    \/\/ STEP 0: CREATE YOUR THREADS QUEUE\r\n    \/\/         (THINK PACKAGING FOR YOUR JOB TICKET)\r\n    dispatch_queue_t searchTextQueue;\r\n    searchTextQueue = dispatch_queue_create(&quot;myFancySearchTextQueueJobId&quot;, DISPATCH_QUEUE_CONCURRENT);\r\n\r\n    \/\/ CPU-CYCLES MEASURE NANOSECONDS SO WE\r\n    \/\/ NEED TO BE VERY PRECISE HERE, D'OH!\r\n    double pauseTimeInMilliseconds = 200.0;\r\n    int64_t pauseTimeInNanoseconds = (int64_t)( pauseTimeInMilliseconds * NSEC_PER_MSEC );\r\n    \r\n    \/\/ STEP 1: GIVE SOME TIME TO MAIN THREAD FIRST (!!) I.E. KEYBOARD UI\r\n    dispatch_time_t pauseTime = dispatch_time( DISPATCH_TIME_NOW, pauseTimeInNanoseconds );\r\n    \r\n    \/\/ STEP 2: SEND\/DISPATCH OUR JOBTICKET TO OUR OWN THREAD QUEUE\r\n    dispatch_after( pauseTime, searchTextQueue, ^(void) {\r\n        NSMutableArray *myItemsFound = &#x5B;NSMutableArray array];\r\n        for( NSString* currentItem in itemsToSearch ) {\r\n            NSString *stringToSearch = ((YourCustomObjectYouIterateOver*)currentItem).someStringProperty;\r\n            if( &#x5B;stringToSearch rangeOfString:string options:(NSCaseInsensitiveSearch|NSDiacriticInsensitiveSearch)].location != NSNotFound ) {\r\n                &#x5B;myItemsFound addObject:currentItem];\r\n            }\r\n            if( wantsAnotherSearch ) {\r\n                \/\/ CANCEL SEARCH-FOR-LOOP BECAUSE WE HAVE NEW REQUEST\r\n                break;\r\n            }\r\n        }\r\n        if( !wantsAnotherSearch ) { \/\/ SKIP UI REFRESH\r\n            \/\/ STEP 3: COMMUNICATE RESULTS TO MAIN THREAD TO UPDATE THE UI\r\n            dispatch_async(dispatch_get_main_queue(), ^{\r\n                &#x5B;self refreshUiWithItemsFound:myItemsFound];\r\n            });\r\n        }\r\n        \/\/ STEP 4: DO SOME CLEANUP AND CHECK IF WE NEED TO\r\n        \/\/         TRIGGER NEXT SEARCH IMMEDIATELY\r\n        isSearchInProgress = NO;\r\n        if( wantsAnotherSearch ) {\r\n            wantsAnotherSearch = NO;\r\n            &#x5B;self searchForStringAsync:stringToSearchForWhenCompleted];\r\n        }\r\n    });\r\n}\r\n<\/pre>\n<p>The sample code shows two methods I implemented in my own code of an app that features an instant-search-as-you-type-inputfield. It searches items for some matching string and returns the matching items, then it updates the UI. It works like a charme.<\/p>\n<p>I have bound my <code>searchForStringAsync:<\/code> to a value-changed-event I got from a UITextField, but you can take any other event you like. Usually each keystroke while typing is ONE SUCH EVENT. So events are coming in quite often while typing. The method <code>refreshUiWithItemsFound:<\/code> is just an example to update the UI after the result is ready, you can call whatever you like. You could also decide to not refresh the UI immediately at all but instead wait until the results have consolidated (i.e. typing events stopped for some time).<\/p>\n<p>Though this is a pretty fine working approach, it is not perfect yet. You may need to adjust the <code>pauseTimeInMilliseconds<\/code>-value to achieve good results depending on the amount of processing going on on the MAIN THREAD.<\/p>\n<p><strong>Known issue:<\/strong> Working with objects coming from MAIN THREAD in a different Thread (i.e. OUR CREATED THREAD) is only allowed\/recommended read-only. As soon as you start writing\/manipulating stuff\/objects across threads you need to find ways the BOTH THREADS do not interfere accessing objects concurrently.<\/p>\n<h3>CREDITS<\/h3>\n<p>I want to thank someone here for the valuable insights I got from his understandable SO-post. Thanks Mathieu!<\/p>\n<div style=\"margin-top:20px;margin-bottom:20px; border-radius:7px; border:1px solid lightgray;padding:20px;background-color:#FCFCFC;font-size:20px;\"><center><img loading=\"lazy\" decoding=\"async\" data-id=\"4164\"  src=\"\/wp-upload\/thank_you_250.png\" alt=\"thank_you_250\" width=\"250\" height=\"190\" class=\"aligncenter size-full wp-image-4164\" \/><\/center><br \/>A huge thank you to <a href=\"http:\/\/about.me\/mathieudamours\"> Matt D&#8217;Amours <\/a> (other <a href=\"http:\/\/matehat.com\/\">website<\/a>, <a href=\"https:\/\/github.com\/matehat\">github<\/a>) from Canada for his <a href=\"https:\/\/stackoverflow.com\/questions\/16685922\/speed-up-search-using-dispatch-async\/16879900#16879900\">excellent explanation<\/a> on StackOverflow, which made me learn the <strong>PAUSE-Feature<\/strong> of threads.<\/div>\n<h3>Have some fun!<\/h3>\n<p><em>And please implement searches this way in the future!<\/em><\/p>\n<h3>Update &#038; Enhancement on SEP, 8th 2014:<\/h3>\n<p>Talking to <a href=\"https:\/\/twitter.com\/arepty\">@arepty (Alexander Repty)<\/a> and <a href=\"https:\/\/twitter.com\/ortwingentz\">@ortwingentz (Ortwin Gentz)<\/a> to reassure myself, that I am not talking total rubbish here, I want to add the following thing.<\/p>\n<ol>\n<li><strong>Ortwin<\/strong> pointed out that an option to cancel stuff would be valuable. So I added a cancel option to the THREAD CREATED because as soon as another search should be triggered, we do not need to wait until the running search is ready. We solve this with the already existing BOOL-flag <code>wantsAnotherSearch<\/code>. You cannot easily cancel a YOUR THREAD from outside, but you can check inside the loop if you better should stop useless work going on. An even better solution propagated by Ortwin is to actually monitor typing on the keyboard more closely and wait several milliseconds until we detect some USER-STOPPED-TYPING situation and only THEN start searching. This is e.g. very important if your search requests are very costly e.g. network-search-queries.<\/li>\n<li><strong>Alex<\/strong> pointed out that even if you give the MAIN THREAD some time before you start, you have to be aware that we did run our iOS-code in a <strong>CPU-Single-Core<\/strong>-Environment for some time, but this time came to an end when the A5 chip was introduced, followed by A6, A7, and soon A8. That means, we only get some kind of &#8222;pseudo&#8220;-threads to dispense CPU-time in slices a bit better between different jobs going &#8222;kinda fake-parallel&#8220; on single cores (read <a href=\"https:\/\/en.wikipedia.org\/wiki\/Computer_multitasking\">WP-entry for Multitasking<\/a> and <a title=\"Threads were born from the idea that the most efficient way for cooperating processes to exchange data would be to share their entire memory space. Thus, threads are basically processes that run in the same memory context. Threads are described as lightweight because switching between threads does not involve changing the memory context.\" href=\"https:\/\/en.wikipedia.org\/wiki\/Multithreading_(computer_architecture)\">WP-entry for Multithreading<\/a>). There was no second CPU in your iDevice some time ago which was able to execute the thread, so even running our own CREATED THREAD did not protect us against UI blocking. If we used too much CPU-cycles stuff got BLOCKED\/DEGRADED even with threads. BUT since we have <strong>Dual-Core-CPUs<\/strong> since the A5 chip, you will need to do a lot of work now to bring current iDevices down. Read more about this in <a href=\"https:\/\/en.wikipedia.org\/wiki\/Apple_system_on_a_chip\">WP-entry about Apple System on a Chip<\/a>.<\/li>\n<li>My conclusion from Alex pointing out the <strong>CPU-Single-Core<\/strong>-issue is to make even more PAUSES. So maybe it is a good idea to let our THREAD make PAUSES using <code>dispatch_time<\/code> in conjunction with <code>dispatch_after<\/code> every 100 iterations in the search-loop. I think this is a viable option, though your code will then look even more fragmented, d`oh!<\/li>\n<\/ol>\n<p>Oh, and <strong>Oliver Jones<\/strong> aka <a href=\"https:\/\/twitter.com\/orj\">@orj<\/a> has also the <a href=\"http:\/\/deeperdesign.wordpress.com\/2011\/05\/30\/cancellable-asynchronous-searching-with-uisearchdisplaycontroller\/\">same thing<\/a> using <strong>NSOperation<\/strong> and <strong>NSOperationQueue<\/strong>, so pls give it a try if you dislike this code, but don&#8217;t do search without threads anymore, okay?<\/p>\n<p><strong>Ray Wenderlich<\/strong> aka <a href=\"https:\/\/twitter.com\/rwenderlich\">@rwenderlich<\/a> has another <a href=\"http:\/\/www.raywenderlich.com\/4295\/multithreading-and-grand-central-dispatch-on-ios-for-beginners-tutorial\">nice piece<\/a> on <strong>Grand Central Dispatch (GCD)<\/strong>, too.<\/p>\n<h3>Update<\/h3>\n<p>Together with Alex I setup a <a href=\"https:\/\/github.com\/HackerspaceBremen\/CocoaheadsHBAsyncSearch\"><strong>small demo app on github<\/strong><\/a> to demonstrate also the use of NSOperation. Have fun with it and make use of it!<\/p>\n<p><small><strong>Why do I blog this?<\/strong> Because I am angry about all those input-fields I have to use which have UI-blocking searches going on. Even Apple&#8217;s own Apps like the AppStore app are annoyingly blocking the UI when doing searches or other things. It&#8217;s an awful UXP! People can do nothing but wait until the blocking computing intensive task ended. In the meantime the keyboard is NOT working anymore. I want a lot of people to just copy this code and use it, so I do not need to use crappy search-\/input-textfields anymore in the future.<\/small><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Doing search is difficult. Usually you start to search for something if the stuff you have has reached an amount you cannot recognize any longer easily. You spend hours searching and finding the right things or do not even try searching for it because it is too much work. Totally unrelated by @mattetti: That is &hellip; <a href=\"https:\/\/www.thetawelle.de\/?p=4130\" class=\"more-link\"><span class=\"screen-reader-text\">\u201eNon-Blocking NSString-search using e.g. UITextField &#038; UISearchViewController\u201c <\/span>weiterlesen<\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[88,67,59,4],"tags":[],"class_list":["post-4130","post","type-post","status-publish","format-standard","hentry","category-coding","category-english","category-iphone","category-learning"],"_links":{"self":[{"href":"https:\/\/www.thetawelle.de\/index.php?rest_route=\/wp\/v2\/posts\/4130","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.thetawelle.de\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.thetawelle.de\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.thetawelle.de\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.thetawelle.de\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=4130"}],"version-history":[{"count":0,"href":"https:\/\/www.thetawelle.de\/index.php?rest_route=\/wp\/v2\/posts\/4130\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.thetawelle.de\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=4130"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.thetawelle.de\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=4130"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.thetawelle.de\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=4130"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}