Jekyll2020-10-15T14:28:57+02:00https://blog.cocoafrog.de//Joachim Complains About Things đ¤A blog about bad software and how not to write bad software. Occasionally, about good software.
Joachim KurzMy 10 Year Journey to Apple2020-10-14T14:00:00+02:002020-10-14T14:00:00+02:00https://blog.cocoafrog.de/how-to/2020/10/14/Joining-Apple<p>On October 19th, Iâll join Appleâs Developer Tools organization as an engineer. Looking back, I feel like this journey started 10 years ago, although at the time I neither knew that Iâd actually end up at Apple, nor how long it would take. This is a summary of my âjourneyâ until now. Maybe it helps someone or is helpful for someone just starting out as an idea how things can go. And if you are looking to work at Apple it might be an inspiration to not be discouraged, just because you didnât get a response to your application or got a rejection and to just try again.</p>
<p><strong>Disclaimer</strong>: Of course, everything in this post is my personal view and experience, your experience may differ (a lot). Iâm also writing this before Iâve actually started at Apple, so I have no inside knowlegde of Appleâs hiring process or policy and will only describe my experience and impression as a candidate.</p>
<h3 id="2010-starting-out-with-objective-c-and-xcode-development">2010: Starting Out With Objective-C and Xcode Development</h3>
<p>About 10 years ago, I had just returned from studying abroad in the UK<sup id="fnref:germany"><a href="#fn:germany" class="footnote">1</a></sup> and was looking for what I would do for my bachelorâs thesis. I was looking for an advisor for my thesis and a job as a student assistant and found both at the <a href="https://hci.rwth-aachen.de">chair for Media Computing and Human-Computer Interaction</a> at my university. I had dabbled with Objective-C a bit before, but my student assistant job was the first time I actually wrote an application for macOS<sup id="fnref:macOS"><a href="#fn:macOS" class="footnote">2</a></sup>.</p>
<h3 id="2011-an-xcode-plugin-for-call-graph-navigation">2011: An Xcode Plugin for Call-Graph Navigation</h3>
<p>After having worked as a student assistant at the chair, I found a topic for my bachelorâs thesis: A <a href="https://khd2.de/BA/">code navigation plugin for Xcode 3</a>. At the time, Xcode had an unofficial and undocumented (<abbr title="if I recall correctly">iirc</abbr>) plugin API that was working pretty well. Another student had used it to develop a call-graph navigation tool and I took their project and developed it further and created a new UI representation to see whether it would work better.</p>
<p><img src="https://blog.cocoafrog.de/assets/posts/joining-apple/finalSoftwarePrototype.png" alt="A screenshot of Xcode 3 running on Snow Leopard with a split view showing the editor on the left and a custom plugin on the right. The plugin-view contains a stack of methods with the currently displayed method in the editor highlighted in the plugin view with a darker metallic background. All methods preceding and succeeding the current method in the stack have arrows on the left and right indicating that they can be exchanged for other callee/caller methods and each stack-level has a page indicator showing how many methods there are on this level. The different "frames" of the stack are connected by little buttons with an icon showing showing on arrow branching of into three. In addition, a popover HUD-like view is shown pointing to one of the buttons connecting two frames. The popover shows a list of all methods called by the originating method and thus allowing a quick-selection of a method without stepping through them via the arrow buttons." /></p>
<h3 id="2011-my-first-application-at-apple">2011: My First Application at Apple</h3>
<p>I figured doing an internship in the Developer tools group would align very well with my interests and would be a great continuation of my bachelorâs thesis. Of course, this is much easier said than done. Luckily, another student, who was working at the same chair I was writing my bachelor thesis at, had just completed their own internship at Apple and introduced me via email to Matthew Firlik, who was already heading the Developer Tools Group at the time<sup id="fnref:firlik"><a href="#fn:firlik" class="footnote">3</a></sup>. So I sent an application to Matthew directly, who then referred me to one of the recruiters. I donât remember what exactly happened afterwards and wasnât able to find all the emails anymore to piece it together, but long story short: It fizzled out. I believe I had contact to a recruiter, but it didnât lead anywhere, but I also didnât get a clear rejection either.</p>
<h3 id="2012---2016-more-applications">2012 - 2016: More Applications</h3>
<p>Sadly, this is representative of my general experience with applying at Apple: If you manage to get hold<sup id="fnref:spam"><a href="#fn:spam" class="footnote">4</a></sup> of one of the actual engineers/managers in the team or organization you want to work at, they are usually very nice and helpful. Theyâll answer questions and offer to forward your resume to the hiring manager or submit it to the internal referal system. But then you are basically stuck at the top of the funnel with all applicants again and there is only a slim chance youâll make it to any of the interesting steps like a phone interview and most of the time youâll simply hear nothing about your application, even if you sent it to an actual human via email. Once theyâve entered your resume into the pipeline, they also donât know what the status is and wonât be able to give you feedback about it, until the resume comes across their desk again for setting up a phone interview.</p>
<p>The only time this is different is if you manage to contact the hiring manager for the position directly and you somehow convince them that you are such an interesting candidate that they should set up a call with you directly without going through the recruiter and basically take the recruiting process into their own hands<sup id="fnref:bias"><a href="#fn:bias" class="footnote">5</a></sup>. But of course, this will rarely happen. After all, each position at Apple likely gets hundreds of applicants and there is a reason the hiring managers work with a recruiter: Itâs a lot of work to manage all the applicants and their state in the process and the hiring managers still have other work to do.</p>
<p>For one application, I had actually talked to the hiring manager via Twitter DM, sent them my resume via mail and they then sent it on to the recruiter to be considered. After not getting any feedback regarding the status of my application, I checked in with the hiring manager a month later. They told me I was still in the âwaiting to be reviewedâ state, but they didnât have more details. Three months later, I saw them post the same position again and checked in again. They apologized that they didnât know what happened to my original application, as they are âat the very end of the firehoseâ of applications and as such donât have good visibility of the state of every single application. As I said above: Individually, most people are nice and try to help, but even the hiring managers donât necessarily have a good overview of all applications for a position as thatâs the recruiters job.</p>
<p>And while itâs basically the job of the recruiters to keep track of all the applicants and make sure they are all considered, they usually manage more than one position at a time in parallel, which means theyâll likley deal with thousands of applications at any given point in time. And from what Iâve heard Appleâs internal HR/recruiting software is just as good as everybody elseâs, which is: not very good. If you work at a large-ish company, think of the last time you had to plan your vacation days and the software you had to use to do so: This is what candidate management systems are like and recruiters have to deal with them all day.</p>
<p>Itâs very frustrating to get âlost in the pileâ, but itâs likely to happen, even if you had a direct contact to someone from the team or the hiring manager or got a referral. And if you applied just via uploading your resume via jobs.apple.com for a specific position there is a good chance no one apart from a recruiter will ever look at your resume.</p>
<p>After my first application for an internship, I applied again for a set of positions after I finished university in 2013, this time full-time and applying by uploading my resume via <a href="https://jobs.apple.com">jobs.apple.com</a>. For those, I donât think I ever got a reply apart from the automated system email âThanks for your interest in Appleâ confirming my submission. After that I got into a habit of checking jobs.apple.com every one or two years for interesting positions and applying to them. But nothing ever came of it until 2017.</p>
<h3 id="2013-joining-a-small-agency">2013: Joining a Small Agency</h3>
<p>As the Apple applications didnât work out, I joined <a href="https://www.mobile2b.de">a small agency</a> after getting my masterâs degree in Computer Science. We worked on mobile apps (mainly iOS in my case) for businesses, with a focus on internal use-cases. I was able to work with clients directly and enjoyed using the newest technologies due to basically starting a new project every couple of months.</p>
<h3 id="2015-joining-xing">2015: Joining XING</h3>
<p>While I enjoyed my time at my first job, my main issue there was that I didnât have any other iOS developers as colleagues, so I felt limited in learning more about iOS development. This is why I started to look for a job in 2015 and ended up at <a href="https://www.xing.com/">XING</a>. For those of you who havenât lived in Germany: XING is basically Germanyâs equivalent of LinkedIn and has one of the largest iOS teams in Germany. I worked on the team that was responsible for some of the âpremiumâ features of the app and maintained the âUpsellâ flow, which was used to sell subscriptions to users<sup id="fnref:notacv"><a href="#fn:notacv" class="footnote">6</a></sup>.</p>
<h3 id="2017-my-first-actual-apple-interview">2017: My First Actual Apple Interview</h3>
<p>In 2017, I once again applied for a set of positions. I found some old confirmation emails from back then and here is the (potentially incomplete) list of positions I applied for<sup id="fnref:interesting"><a href="#fn:interesting" class="footnote">7</a></sup>:</p>
<ul>
<li>Authoring Tools Engineer (Swift)</li>
<li>Contacts Frameworks Software Engineer</li>
<li>Design Tools Engineer</li>
<li>iOS Application Engineer</li>
<li>iOS Developer for Maps Transit</li>
<li>Mail UI Engineer</li>
<li>Privacy Engineer</li>
<li>Swift iOS App Developer</li>
<li>User Experience Prototype Engineer</li>
<li>User Experience Application Engineer</li>
<li>Documentation Frameworks Engineer</li>
<li>Design Tools Engineer</li>
<li>Playgrounds Engineer</li>
</ul>
<p>A couple of those I found via Apple engineers/managers posting them on Twitter and contacted them this way. For a couple others friends at Apple referred me. For some I simply uploaded my resume. And this time I actually got an invitation for an initial phone interview for a single one out of those 13 applications, specifically for the âDocumentation Frameworks Engineerâ.</p>
<p>It was mostly a âgetting to know each otherâ phone interview with the hiring manager. I felt it went reasonably well, but after the call I got feedback from the hiring manager that I wouldnât be a good fit for that position, but that they had a good impression of me and would like to recommend me for another position within the Developer Tools group: Interface Builder Engineer<sup id="fnref:name"><a href="#fn:name" class="footnote">8</a></sup>. I excitedly agreed, as Interface Builder was and is <a href="https://blog.cocoafrog.de/talks/#ib-considered-awesome">one of my favorite parts of Xcode</a> and I also felt like this was a better fit. And this is how I ended up with my second initial phone interview.</p>
<p>The phone interview for the Interface Builder position also went well, according to my impression. I had a nice chat with one of the engineers from the team, as the manager couldnât make it. After the call, I got the usual âthe recruiter will be in touch with next stepsâ feedback. But I never heard of the recruiter. I reached out to the recruiter one or two weeks later, but didnât get a response. After reaching out to them a couple more times over the following weeks but never getting any response, I gave up. To this day, I donât know whether I didnât pass the initial interview and just didnât get a response, whether the recruiter maybe left Apple, or whether my mails somehow simply got lost in the candidate management system. So again, this application fizzled out.</p>
<p>This was even more frustrating than my first application, as I had made it at least to a phone call and talked to an actual human. But having done a couple more interviews with Apple since then, I can at least say: This not representative. Normally, when you made it to a phone call, youâll get some feedback. I did so for the previous interview and again for all interviews I had with Apple since then.</p>
<h3 id="2018-joining-yelp">2018: Joining Yelp</h3>
<p>As Apple didnât work out I looked for my other options and ended up joining <a href="https://www.xing.com/home">Yelp</a>. At both XING and Yelp I felt like I learned a lot less about iOS development than I expected and a lot more about all the everyday challenges of software development in large teams, like estimations, communication, figuring out what your predecessor did, not building the same feature twice in different teams, etc.</p>
<p>At Yelp, I was working in a lot of different teams. I started out working on a team that was generally responsible for the Yelp Business Owner app. Later, I joined an infrastructure team, responsible for the networking side of both the mobile apps and the backend. We maintained the networking libraries on both sides (app + server), discussed how to transition our infrastructure to GraphQL, etc. For internal reasons, this specific team got dissolved and I ended up briefly working on a team responsible for the login and business claim flow in the Yelp Business Owner app. Before I left, I worked as a Python backend developer on a team responsible for some of the internal admin tools Yelpâs customer support is using.</p>
<h3 id="2020-a-surprisingly-good-year-for-me-">2020: A Surprisingly Good Year (for me đŹ)</h3>
<p>While I was reasonably happy at Yelp, I continued applying for jobs at Apple from time to time, as I still wanted to get back to work on Developer Tools, ideally at Apple. Again, I found a couple of positions to apply for and this time I actually got a response for two of them:</p>
<ul>
<li>Software Engineer, iWork Charts</li>
<li>Performance Tools Engineer</li>
</ul>
<p>For the iWork Charts position, I had an initial phone interview, which went well, followed by a technical phone interview. For this one, I was asked to solve a relatively easy array-based programming question<sup id="fnref:leetcode"><a href="#fn:leetcode" class="footnote">9</a></sup> that I struggled to find an efficient solution for. I had not practiced any of these types of âcoding challengesâ as I figured Iâd either get a real-world like task or it would be more of a technical conversation instead of Leetcode-style challenges.</p>
<p>Maybe due to that, maybe due to other reasons I got a rejection for the iWork Charts position after the technical phone interview. But even though the result obviously wasnât what I wanted, this felt like a nice process. I enjoyed both the initial chat with the hiring manager and the technical phone interview and it only took two business days from the technical phone interview to the final response.</p>
<p>Luckily, at the time I got the rejection for the iWork Charts position, I had already started a conversation with the hiring manager of the Performance Tools Engineer position. We had a similar initial phone call as the one for the previous positions and afterwards agreed to schedule a technical phone interview. The phone interview was again about a coding challenge using CoderPad, but this time it felt less like an artifical Leetcode-style question and closer to reality. It was still rather artificial, as I was asked to implement some string transformation using the raw bytes of a string without using the already existing Swift standard library function for the same transformation, but it felt like it was more about getting to a correct solution and less about finding the optimal one. Also, it was simply easier for me, personally, as the hard part was understanding the unicode structure of a string and I was already familiar with that.</p>
<p>After passing the the technical phone interview, we scheduled a âvirtual onsiteâ. This is the part where youâd usually fly over to the US, but due to COVID-19 we simply did all of it via video calls. In my case, it was basically a full day of interviews with 4 technical interviews (each 50 minutes) followed by 3 âmanagerâ interviews (each 30 minutes) with the hiring manager, the Sr. Engineering Manager and the Sr. Director of Engineering. After my experience with the technical phone screens, I expected the 4 technical interviews to be basically 4 different coding challenges, but that wasnât the case at all. Only one of them had a Leetcode-style task to solve (this time about traversing binary trees), but the other three were really just technical conversations, that I thoroughly enjoyed. Among other things, we talked about implementation details of the prototype I built during my master thesis, how debuggers and breakpoints work under the hood, the UI design of instruments, how to find retain cycles in memory and display them and how to heuristically determine what the âstartâ of the retain cycle is, etc. I want to emphasize that I didnât actually know all of this in detail, e.g. I didnât know how one would implement a debugger. But I had enough of a general idea of how things work to ask the right questions or come up with a reasonable theory to discuss.</p>
<p>From what I can tell, every organization or maybe even every team at Apple does hiring in their own way. Some teams seem to use Leetcode-style questions. Some do coding challenges. Some simply have technical conversations or ask some technical questions to test you. Some use brain-teasers. While both technical phone interviews used CoderPad, one used the integrated CoderPad video chat functionality while the other used FaceTime for Audio + Video. Most initial calls where done via FaceTime, but some managers called with audio-only, others used FaceTime video. Recruiters tended to go with audio-only. I had 4 technical interviews and 3 manager interviews as part of the virtual onsite, while a friend of mine who applied to a different org had only 3 technical interviews + 1 manager interview. My technical interviews were all independent of each other while my friendâs technical interviews where connected and all about the same example app they had to develop beforehand. Overall, there seems to be a lot of variety, which is yet another reason not to give up because one specific team rejected you one time. Maybe you fail the interview-style of one team, like I did, but succeed with another.</p>
<p>After the virtual onsite it took another 2 business days until I got feedback. At that point it was simply a âwe want to hire youâ without any more details. At that point in time, we hadnât yet discussed when I would start or talked about salary<sup id="fnref:salary"><a href="#fn:salary" class="footnote">10</a></sup> at all. This all happened afterwards. From the initial contact via mail to âwe want to hire youâ it took ~2 months. Then it took another month to iron out the details regarding salary, get formalities like visa status, background check etc. out of the way until I got a first contract and then <em>another</em> month to agree on a final contract because there was some back and forth about some parts of the contract that I wanted to get changed.</p>
<h2 id="becoming-a-performance-tools-engineer">Becoming a Performance Tools Engineer</h2>
<p>Long story short: On October 19th Iâll join Apple as a Performance Tools Engineer. Afaik, my future team works on Instruments, Xcodeâs Memory Graph Debugger and a couple of command line tools like <code class="highlighter-rouge">atos</code>, <code class="highlighter-rouge">heap</code>, <code class="highlighter-rouge">leaks</code>, etc. And probably a couple of things that I soon wonât be able to talk about anymore. Iâm excited to help drive all of these tools forward and if you have suggestions or a wishlist, feel free to reach out, but I obviously wonât be able to make any promises and I donât even know whether Iâll be able to reply. Even if I wonât be able to speak as freely about Appleâs tools, Iâm hoping Iâll still be able to keep an open ear for the Apple developer community.</p>
<h2 id="a-couple-of-stats">A Couple of Stats</h2>
<ul>
<li>Time from first application to actual job offer: ~9 years</li>
<li>Time from first application to first phone interview: ~6 years</li>
<li>Time from first contact for a position to getting a signed contract: ~4 month</li>
<li>Positions at Apple I applied for over the years: >50</li>
<li>People I got to know over the years at Apple who referred me via their internal system: ~5</li>
<li>Times a recruiter/sourcer reached out to me based on the profile I had saved on jobs.apple.com: 1
<ul>
<li>And my profile actually fit what they were looking for: 0</li>
</ul>
</li>
<li>Positions I had a least an initial phone interview for: 4</li>
<li>Positions I got a final feedback (accepted/rejected) for after the application online/via email/referral: 3</li>
<li>Times after I was rejected that I got a referral by the rejecting hiring manager/recruiter to another/better-fitting position: 1</li>
<li>Offers: 1</li>
</ul>
<h2 id="so-long">So Long</h2>
<p>This will probably be my last blogpost for a while. Both because I historically didnât find a lot of time writing here and because writing about Apple-related software development as an Apple engineer is rather difficult. But Iâll see how it goes. đ</p>
<div class="footnotes">
<ol>
<li id="fn:germany">
<p>I grew up and still live in Germany. <a href="#fnref:germany" class="reversefootnote">↩</a></p>
</li>
<li id="fn:macOS">
<p>At the time it was still called Mac OS X. <a href="#fnref:macOS" class="reversefootnote">↩</a></p>
</li>
<li id="fn:firlik">
<p>If youâve watched some of the more recent WWDCs youâll have seen him present a section of the Keynote or the Platform State of the Union. <a href="#fnref:firlik" class="reversefootnote">↩</a></p>
</li>
<li id="fn:spam">
<p>Please be considerate of everyoneâs time here. If you actually have a friend at Apple: Great, feel free to ask them about positions, refer you, or whatever, you hopefully know best what level of request your friendship tolerates. If an engineer or hiring manager posted a position on their team on Twitter and offered to answer questions or forward resumes for that position: Great contact them on the channel they asked for, but donât expect them to do more than forwarding your resume as support for your application, they wonât be able to vouch for you if they donât know you and theyâll probably get hundreds of requests. And if you find an Apple engineer on Twitter or elsewhere that happens to work on the team your are interested in, but hasnât made it clear they want to help people interested in joining Apple, respect that. At most, ask them politely whether they could help by forwarding your resume and if they say no <em>or donât respond</em> accept that. Definitely donât hunt for peopleâs work email addresses to ask them to do something for you (e.g. refer you).<sup id="fnref:myview"><a href="#fn:myview" class="footnote">11</a></sup> <a href="#fnref:spam" class="reversefootnote">↩</a></p>
</li>
<li id="fn:bias">
<p>And there are good reasons not to do that, even if they are convinced you are a good fit, among others that this will probably introduce a lot of biases. <a href="#fnref:bias" class="reversefootnote">↩</a></p>
</li>
<li id="fn:notacv">
<p>This is not a resume, so Iâll refrain from making claims like âI increased x number by y percentâ. I donât need to convince you to hire me, Iâve got a job already, this is mainly to give you an idea of what I worked on. If you want to know more, feel free to ask. <a href="#fnref:notacv" class="reversefootnote">↩</a></p>
</li>
<li id="fn:interesting">
<p>I chose them based on what sounded interesting to me. In my case that meant: Developer tools, productivity software, frameworks, and UX- and privacy-related stuff. No games; no computer vision, AR or otherwise âgraphics-heavyâ stuff; no machine learning. <a href="#fnref:interesting" class="reversefootnote">↩</a></p>
</li>
<li id="fn:name">
<p>I donât remember whether this was the actual name of the position, but it was similar. <a href="#fnref:name" class="reversefootnote">↩</a></p>
</li>
<li id="fn:leetcode">
<p>I will not reveal the detailed questions used here. You will probably find similar questions on Leetcode or whatever other interview training platform or book you use. <a href="#fnref:leetcode" class="reversefootnote">↩</a></p>
</li>
<li id="fn:salary">
<p>Iâm not willing to post my compensation details online, as I feel like I donât know what potential negative effects that might have. However, if we know each other or have met, feel free to ask, I think compensation details should be shared more freely to combat the information asymmetry between employees and employers. <a href="#fnref:salary" class="reversefootnote">↩</a></p>
</li>
<li id="fn:myview">
<p>Of course, all of this is my opinion what you should and shouldnât do, there isnât a clear ruleset or anything like that. General courtesy applies, use your best judgement. <a href="#fnref:myview" class="reversefootnote">↩</a></p>
</li>
</ol>
</div>Joachim KurzOn October 19th, Iâll join Appleâs Developer Tools organization as an engineer. Looking back, I feel like this journey started 10 years ago, although at the time I neither knew that Iâd actually end up at Apple, nor how long it would take. This is a summary of my âjourneyâ until now. Maybe it helps someone or is helpful for someone just starting out as an idea how things can go. And if you are looking to work at Apple it might be an inspiration to not be discouraged, just because you didnât get a response to your application or got a rejection and to just try again.You Should All Build This Custom Instrument for Your App2020-06-20T14:00:00+02:002020-06-20T14:00:00+02:00https://blog.cocoafrog.de/how-to/2020/06/20/You-should-all-build-this-custom-instrument<p><em>Note: This was written shortly before WWDC 2020, but you might only read it afterwards. So anything in here is <strong>already available</strong>, you donât need to download the beta for it or wait until autumn to use it. Iâll update this if anything relevant to this article comes out from WWDC.</em></p>
<p>Most of you have probably worked with an app or are currently working with an app for which a significant part of the functionality relies on talking to a server via HTTP. When something doesnât work as expected or you simply want to understand an area of the code you are unfamiliar with, itâs often useful to look at the HTTP calls going back and forth between the server. What kind of calls are made? What exactly is the server sending? To do so, you are likely use a tool like <a href="https://www.charlesproxy.com">Charles Proxy</a> or <a href="https://www.wireshark.org">Wireshark</a>.</p>
<p>However, these tools are often rather complex to use and especially to set up. They may require you to set up a custom SSL certificate and jump through multiple hoops to make the device trust it. They also show a lot of information you might not actually need to understand your application. At the same time, itâs hard to cross-reference them with whatever else is going on in your application. So what if I told you that instruments could actually do a lot of that work as well, with a little help from you and could display it in a manner thatâs <em>much</em> better suited to what your application is actually doing?</p>
<p>As preparation for WWDC next week<sup id="fnref:other-reasons"><a href="#fn:other-reasons" class="footnote">1</a></sup>, Iâve been (re)watching a couple of talks from previous WWDCs. Somehow, I had completely missed that the <a href="https://developer.apple.com/videos/play/wwdc2018/410/">core of Instruments had been rewritten to unify the instruments and make it much easier to build custom instruments</a> for Xcode 10. Also, WWDC 2019 had <a href="https://developer.apple.com/videos/play/wwdc2019/411/">a great introduction to instruments</a>, something Iâve been missing for years.</p>
<p>Ok, cool, so you can now write custom instruments to measure things Instruments might not ordinarily measure. But what can you measure and how easy is it? âPretty much anythingâ and âmedium difficulty, little time effortâ, Iâd say. Generally, you can do most things you need by writing an XML file that specifies how to translate signposts from your code into data to display in instruments and the XML you write is not particularly complex. The main hurdle is that the âcodeâ you write will likely be quite different from what you are used to, there are only very few examples to follow, the documentation only gives a high-level overview of how to do it and while Xcode actually validates the XML file pretty strictly there is little help of how to fix an issue and no autocompletion. But with a bit of time you can find the pieces you need, and if you have an example to adapt you can actually make progress rather quickly. So Iâm going to give you such an example and try to list all the useful links as well.</p>
<p>But letâs start at the beginning: I want anyone of you who has used Charles or Wireshark before to debug your app, or who has an app that makes a lot of HTTP requests, to build an HTTP tracing instrument custom for your app or at least framework. It will look like this:</p>
<p><img src="https://blog.cocoafrog.de/assets/posts/custom-instruments/wordpress-alamofire-instruments-trace.png" alt="A screenshot of Xcode's Instruments app, showing a new custom instrument that displays the duration of HTTP requests grouped by host and path. Successful calls are green, HTTP Status errors are orange, network failures are red. The detail view at the bottom shows a hierarchy of the HTTP requests, again grouped by host, then by path. Each row in the tree view shows aggregate data like the average and max duration and the average and max number of bytes downloaded." /></p>
<p>It took me about a day to get this prototype up and running (after watching the relevant WWDC videos). If you donât care about the rest and just want to see the code, <a href="https://github.com/JoachimK/AlamofireInstrument">itâs here</a>.</p>
<h3 id="overview">Overview</h3>
<p>What we are doing here, and whatâs the easiest way to get a custom instrument, is to use <a href="https://developer.apple.com/documentation/os/3019242-os_signpost"><code class="highlighter-rouge">os_signpost</code></a>. You use it to log <a href="https://developer.apple.com/documentation/os/ossignposttype/3006910-event"><code class="highlighter-rouge">.event</code></a>s or <a href="https://developer.apple.com/documentation/os/ossignposttype/3006908-begin"><code class="highlighter-rouge">.begin</code></a> and <a href="https://developer.apple.com/documentation/os/ossignposttype/3006909-end"><code class="highlighter-rouge">.end</code></a> signposts. You then configure a custom instrument to parse these os_signpost intervalls and extract the extra values you logged to it to configure how to display them in a graph, how to group them, which ones to filter out and how to configure lists and tree/outline views in the detailed pane of Instruments.</p>
<p>We want to build an instrument that displays all HTTP requests that go through our networking library as intervalls (start + end), so we can see how long they take and cross-reference them with other things going on in our app. In this blog post, Iâm using Alamofire as the Networking library to instrument and Wordpress as the app to profile, simply because both are open source. But you should be easily able to adapt all the code to your networking library.</p>
<h3 id="step-0-familiarize-yourself-with-the-instruments-app">Step 0: Familiarize Yourself With the Instruments App</h3>
<ol>
<li><a href="https://developer.apple.com/videos/play/wwdc2019/411/">Getting Started with Instruments (Session 411 from WWDC 2019)</a> is a really good overview of instruments, at least watch the âOrientationâ part to get familiar with the terminology, like instruments, tracks, lanes, traces, templates, detail view, etc.</li>
<li>Watch <a href="https://developer.apple.com/videos/play/wwdc2018/410/">Creating Custom Instruments (Session 410 from WWDC 2018)</a> to get an idea of what we are doing. If you are impatient, itâs enough to watch the âArchitectureâ (for additional details how Instruments works and what you are actually configuring) and the âIntermediateâ part. Donât expect to understand every detail while watching it, itâs a lot of stuff in one session and they donât explain every detail due to time limitations. I had to watch it multiple times and find additional documentation before I actually managed to get my instrument working in the way I wanted. However, Iâm trying to fill in the gaps below.</li>
</ol>
<h3 id="step-1-log-the-data-you-need-to-signposts">Step 1: Log the Data You Need to Signposts</h3>
<p>We want to build a signposts-based instrument, so we need to log our data via signposts. Alamofire sends <code class="highlighter-rouge">Notification</code>s whenever a request starts or completes, so all we need is something like this<sup id="fnref:Alamofire-code"><a href="#fn:Alamofire-code" class="footnote">2</a></sup>:</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kt">NotificationCenter</span><span class="o">.</span><span class="k">default</span><span class="o">.</span><span class="nf">addObserver</span><span class="p">(</span><span class="nv">forName</span><span class="p">:</span> <span class="kt">Notification</span><span class="o">.</span><span class="kt">Name</span><span class="o">.</span><span class="kt">Task</span><span class="o">.</span><span class="kt">DidResume</span><span class="p">,</span> <span class="nv">object</span><span class="p">:</span> <span class="kc">nil</span><span class="p">,</span> <span class="nv">queue</span><span class="p">:</span> <span class="kc">nil</span><span class="p">)</span> <span class="p">{</span> <span class="p">(</span><span class="n">notification</span><span class="p">)</span> <span class="k">in</span>
<span class="k">guard</span> <span class="k">let</span> <span class="nv">task</span> <span class="o">=</span> <span class="n">notification</span><span class="o">.</span><span class="n">userInfo</span><span class="p">?[</span><span class="kt">Notification</span><span class="o">.</span><span class="kt">Key</span><span class="o">.</span><span class="kt">Task</span><span class="p">]</span> <span class="k">as?</span> <span class="kt">URLSessionTask</span><span class="p">,</span>
<span class="k">let</span> <span class="nv">request</span> <span class="o">=</span> <span class="n">task</span><span class="o">.</span><span class="n">originalRequest</span><span class="p">,</span>
<span class="k">let</span> <span class="nv">url</span> <span class="o">=</span> <span class="n">request</span><span class="o">.</span><span class="n">url</span> <span class="k">else</span> <span class="p">{</span>
<span class="k">return</span>
<span class="p">}</span>
<span class="k">let</span> <span class="nv">signpostId</span> <span class="o">=</span> <span class="kt">OSSignpostID</span><span class="p">(</span><span class="nv">log</span><span class="p">:</span> <span class="n">networking</span><span class="p">,</span> <span class="nv">object</span><span class="p">:</span> <span class="n">task</span><span class="p">)</span>
<span class="nf">os_signpost</span><span class="p">(</span><span class="o">.</span><span class="n">begin</span><span class="p">,</span> <span class="nv">log</span><span class="p">:</span> <span class="kt">SignpostLog</span><span class="o">.</span><span class="n">networking</span><span class="p">,</span> <span class="nv">name</span><span class="p">:</span> <span class="s">"Request"</span><span class="p">,</span> <span class="nv">signpostID</span><span class="p">:</span> <span class="n">signpostId</span><span class="p">,</span> <span class="s">"Request Method %{public}@ to host: %{public}@, path: %@, parameters: %@"</span><span class="p">,</span> <span class="n">request</span><span class="o">.</span><span class="n">httpMethod</span> <span class="p">??</span> <span class="s">""</span><span class="p">,</span> <span class="n">url</span><span class="o">.</span><span class="n">host</span> <span class="p">??</span> <span class="s">"Unknown"</span><span class="p">,</span> <span class="n">url</span><span class="o">.</span><span class="n">path</span><span class="p">,</span> <span class="n">url</span><span class="o">.</span><span class="n">query</span> <span class="p">??</span> <span class="s">""</span><span class="p">)</span>
<span class="p">}</span></code></pre></figure>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kt">NotificationCenter</span><span class="o">.</span><span class="k">default</span><span class="o">.</span><span class="nf">addObserver</span><span class="p">(</span><span class="nv">forName</span><span class="p">:</span> <span class="kt">Notification</span><span class="o">.</span><span class="kt">Name</span><span class="o">.</span><span class="kt">Task</span><span class="o">.</span><span class="kt">DidComplete</span><span class="p">,</span> <span class="nv">object</span><span class="p">:</span> <span class="kc">nil</span><span class="p">,</span> <span class="nv">queue</span><span class="p">:</span> <span class="kc">nil</span><span class="p">)</span> <span class="p">{</span> <span class="p">(</span><span class="n">notification</span><span class="p">)</span> <span class="k">in</span>
<span class="k">guard</span> <span class="k">let</span> <span class="nv">task</span> <span class="o">=</span> <span class="n">notification</span><span class="o">.</span><span class="n">userInfo</span><span class="p">?[</span><span class="kt">Notification</span><span class="o">.</span><span class="kt">Key</span><span class="o">.</span><span class="kt">Task</span><span class="p">]</span> <span class="k">as?</span> <span class="kt">URLSessionTask</span> <span class="k">else</span> <span class="p">{</span> <span class="k">return</span> <span class="p">}</span>
<span class="k">let</span> <span class="nv">signpostId</span> <span class="o">=</span> <span class="kt">OSSignpostID</span><span class="p">(</span><span class="nv">log</span><span class="p">:</span> <span class="n">networking</span><span class="p">,</span> <span class="nv">object</span><span class="p">:</span> <span class="n">task</span><span class="p">)</span>
<span class="k">let</span> <span class="nv">statusCode</span> <span class="o">=</span> <span class="p">(</span><span class="n">task</span><span class="o">.</span><span class="n">response</span> <span class="k">as?</span> <span class="kt">HTTPURLResponse</span><span class="p">)?</span><span class="o">.</span><span class="n">statusCode</span> <span class="p">??</span> <span class="mi">0</span>
<span class="nf">os_signpost</span><span class="p">(</span><span class="o">.</span><span class="n">end</span><span class="p">,</span> <span class="nv">log</span><span class="p">:</span> <span class="kt">SignpostLog</span><span class="o">.</span><span class="n">networking</span><span class="p">,</span> <span class="nv">name</span><span class="p">:</span> <span class="s">"Request"</span><span class="p">,</span> <span class="nv">signpostID</span><span class="p">:</span> <span class="n">signpostId</span><span class="p">,</span> <span class="s">"Status: %@, Bytes Received: %llu, error: %d, statusCode: %d"</span><span class="p">,</span> <span class="s">"Completed"</span><span class="p">,</span> <span class="n">task</span><span class="o">.</span><span class="n">countOfBytesReceived</span><span class="p">,</span> <span class="n">task</span><span class="o">.</span><span class="n">error</span> <span class="o">==</span> <span class="kc">nil</span> <span class="p">?</span> <span class="mi">0</span> <span class="p">:</span> <span class="mi">1</span><span class="p">,</span> <span class="n">statusCode</span><span class="p">)</span>
<span class="p">}</span></code></pre></figure>
<p>When the requests starts, we log a <code class="highlighter-rouge">.begin</code> signpost, when it completes, we add an <code class="highlighter-rouge">.end</code> signpost. The <code class="highlighter-rouge">signpostId</code> is used to match an end call with a corresponding begin call, to make sure we close the correct interval, if multiple requests are happening in parallel. Ideally, we would store the <code class="highlighter-rouge">signpostId</code> on the request object, to make sure we use the same one for <code class="highlighter-rouge">.begin</code> and <code class="highlighter-rouge">.end</code>. However, I didnât want to modify the <code class="highlighter-rouge">Request</code> type in Alamofire, so a workaround is to use <code class="highlighter-rouge">OSSignpostID(log:, object:)</code> and pass an identifier object to it. We use the underlying <code class="highlighter-rouge">URLSessionTask</code> object here as this will hopefully be the same in both cases. And with the object being the same, <code class="highlighter-rouge">OSSignpostID(log:, object:)</code> will return us the same id when calling it multiple times.</p>
<p>We log data via a format string. You probably want to make sure, to always separate two arguments with some fixed string in-between to make it easier to parse on the instrument-side and also make the parsing easier to understand. Note that you donât need to log data in the <code class="highlighter-rouge">.end</code> call if youâve already logged it in the <code class="highlighter-rouge">.begin</code> call. Both will be combined to one interval and youâll have acces to all of them.</p>
<h3 id="step-2-create-a-new-custom-instruments-xcode-project">Step 2: Create a new custom instruments Xcode project.</h3>
<p>Follow the steps in <a href="https://developer.apple.com/videos/play/wwdc2018/410/">Creating Custom Instruments (Session 410 from WWDC 2018)</a> or in the <a href="https://help.apple.com/instruments/developer/mac/current/#/devfd0adc59c">Instruments app help - Create an Instruments Package project</a> to create a new Instruments Package project in Xcode. This will give you a basic Xcode project with a <code class="highlighter-rouge">.instrpkg</code> file. Youâll specify all the details there.</p>
<h3 id="step-3-do-all-the-rest-">Step 3. Do all the rest đ</h3>
<p>Basically, youâll follow the steps outlined in the <a href="https://help.apple.com/instruments/developer/mac/current/#/devbe5c72066">Instruments app help - Create an instrument based on signpost data</a>. While all the steps in there are correct, they are lacking a lot of detail, so itâs good to have an example of an actual custom instrument. Take a look at <a href="https://github.com/JoachimK/AlamofireInstrument/blob/main/AlamofireInstrument/AlamofireInstrument.instrpkg">mine here</a>. Youâll basically need these parts:</p>
<h4 id="a-schema">A Schema</h4>
<p>This tells instruments how to parse the data from your signposts into variables you can use. You define a pattern that extracts variables from your logged messages and assign those to columns.</p>
<figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="nt"><os-signpost-interval-schema></span>
<span class="nt"><id></span>org-alamofire-networking-schema<span class="nt"></id></span>
<span class="nt"><title></span>Alamofire Networking Schema<span class="nt"></title></span>
<span class="nt"><subsystem></span>"org.alamofire"<span class="nt"></subsystem></span>
<span class="nt"><category></span>"networking"<span class="nt"></category></span>
<span class="nt"><name></span>"Request"<span class="nt"></name></span>
<span class="nt"><start-pattern></span>
<span class="nt"><message></span>"Request Method " ?http-method " to host: " ?host ", path: " ?url-path ", parameters: " ?query-parameters<span class="nt"></message></span>
<span class="nt"></start-pattern></span>
<span class="nt"><end-pattern></span>
<span class="nt"><message></span>"Status: " ?completion-status ", Bytes Received: " ?bytes-received ", error: " ?errored ", statusCode: " ?http-status-code<span class="nt"></message></span>
<span class="nt"></end-pattern></span>
<span class="nt"><column></span>
<span class="nt"><mnemonic></span>column-http-method<span class="nt"></mnemonic></span>
<span class="nt"><title></span>HTTP Method<span class="nt"></title></span>
<span class="nt"><type></span>string<span class="nt"></type></span>
<span class="nt"><expression></span>?http-method<span class="nt"></expression></span>
<span class="nt"></column></span>
<span class="c"><!-- and lots more columns like that --></span>
<span class="nt"></os-signpost-interval-schema></span></code></pre></figure>
<p>The <code class="highlighter-rouge">mnemonic</code> is the identifier of the column that you use to refer to it later on. I somehow felt weird naming the columns the same as the variables, so I prefixed them with <code class="highlighter-rouge">column</code>. But from what I can tell there is no need to do that.</p>
<h4 id="an-instrument">An Instrument</h4>
<p>An Instrument consists of the basic definition:</p>
<figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="nt"><instrument></span>
<span class="nt"><id></span>org.alamofire.networking.instrument<span class="nt"></id></span>
<span class="nt"><title></span>Alamofire<span class="nt"></title></span>
<span class="nt"><category></span>Behavior<span class="nt"></category></span>
<span class="nt"><purpose></span>Trace HTTP calls made via Alamofire, grouped by method, host, path, etc.<span class="nt"></purpose></span>
<span class="nt"><icon></span>Network<span class="nt"></icon></span>
<span class="nt"><create-table></span>
<span class="nt"><id></span>alamofire-requests<span class="nt"></id></span>
<span class="nt"><schema-ref></span>org-alamofire-networking-schema<span class="nt"></schema-ref></span>
<span class="nt"></create-table></span>
<span class="c"><!-- rest of the instrument definition --></span>
<span class="nt"></instrument></span></code></pre></figure>
<p>This is fairly basic stuff. Most of these fields are free-form text or refer to stuff you defined previously (<code class="highlighter-rouge">schema-ref</code>). But <code class="highlighter-rouge">category</code> and <code class="highlighter-rouge">icon</code> can only have a small set of values defined <a href="https://help.apple.com/instruments/developer/mac/current/#/dev289276251">here</a> and <a href="https://help.apple.com/instruments/developer/mac/current/#/dev208470310">here</a>.</p>
<h4 id="a-graph-inside-an-instrument">A Graph Inside an Instrument</h4>
<p>A graph defines the, well, graphing part of the instruments UI, the visual representation you see in the track area.
It looks roughly like this:</p>
<figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="nt"><instrument></span>
<span class="c"><!-- Basic instrument definition --></span>
<span class="nt"><graph></span>
<span class="nt"><title></span>HTTP Requests<span class="nt"></title></span>
<span class="nt"><lane></span>
<span class="nt"><title></span>the Requests<span class="nt"></title></span>
<span class="nt"><table-ref></span>alamofire-requests<span class="nt"></table-ref></span>
<span class="nt"><plot-template></span>
<span class="nt"><instance-by></span>column-host<span class="nt"></instance-by></span>
<span class="nt"><label-format></span>%s<span class="nt"></label-format></span>
<span class="nt"><value-from></span>column-url-path<span class="nt"></value-from></span>
<span class="nt"><color-from></span>column-response<span class="nt"></color-from></span>
<span class="nt"><label-from></span>column-url-path<span class="nt"></label-from></span>
<span class="nt"></plot-template></span>
<span class="nt"></lane></span>
<span class="nt"></graph></span>
<span class="c"><!-- other parts of the instrument --></span>
<span class="nt"></instrument></span></code></pre></figure>
<p>You can have different lanes and you can use a plot-template to have a dynamic number of plots in a lane. My example also contains <a href="https://github.com/JoachimK/AlamofireInstrument/blob/main/AlamofireInstrument/AlamofireInstrument.instrpkg#L131-L135">an example for a simple plot</a>. Iâm not really sure why both <code class="highlighter-rouge">graph</code> and <code class="highlighter-rouge">lane</code> have a title. In addition to that, each plot in a <code class="highlighter-rouge">plot-template</code> also gets a label from <code class="highlighter-rouge">label-format</code> đ¤ˇ.</p>
<h4 id="a-list-aggregation-or-something-else-for-the-detail-view">A List, Aggregation, Or Something Else for the Detail View</h4>
<p>With just a graph, Instruments would look somewhat empty. You want to display something in the detail view as well. You do so by using a <code class="highlighter-rouge">list</code>, <code class="highlighter-rouge">aggregation</code>, or <code class="highlighter-rouge">narrative</code>. Maybe more, that I havenât figured out, yet.
An aggregation looks something like this:</p>
<figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="nt"><instrument></span>
<span class="c"><!-- Basic instrument definition --></span>
<span class="nt"><aggregation></span>
<span class="nt"><title></span>Summary: Completed Requests<span class="nt"></title></span>
<span class="nt"><table-ref></span>alamofire-requests<span class="nt"></table-ref></span>
<span class="nt"><slice></span>
<span class="nt"><column></span>column-completion-status<span class="nt"></column></span>
<span class="nt"><equals><string></span>Completed<span class="nt"></string></equals></span>
<span class="nt"></slice></span>
<span class="nt"><hierarchy></span>
<span class="nt"><level></span>
<span class="nt"><column></span>column-host<span class="nt"></column></span>
<span class="nt"></level></span>
<span class="nt"><level></span>
<span class="nt"><column></span>column-url-path<span class="nt"></column></span>
<span class="nt"></level></span>
<span class="nt"></hierarchy></span>
<span class="nt"><column><count/></column></span>
<span class="nt"><column><average></span>duration<span class="nt"></average></column></span>
<span class="nt"><column><max></span>duration<span class="nt"></max></column></span>
<span class="nt"><column><sum></span>column-size<span class="nt"></sum></column></span>
<span class="nt"><column><average></span>column-size<span class="nt"></average></column></span>
<span class="nt"></aggregation></span>
<span class="c"><!-- other parts of the instrument --></span>
<span class="nt"></instrument></span></code></pre></figure>
<p>a list looks like this:</p>
<figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="nt"><instrument></span>
<span class="c"><!-- Basic instrument definition --></span>
<span class="nt"><list></span>
<span class="nt"><title></span>List: Requests<span class="nt"></title></span>
<span class="nt"><table-ref></span>alamofire-requests<span class="nt"></table-ref></span>
<span class="nt"><column></span>start<span class="nt"></column></span>
<span class="nt"><column></span>duration<span class="nt"></column></span>
<span class="nt"><column></span>column-host<span class="nt"></column></span>
<span class="c"><!-- Lots more columns --></span>
<span class="nt"></list></span>
<span class="c"><!-- other parts of the instrument --></span>
<span class="nt"></instrument></span></code></pre></figure>
<h3 id="bonus-material">Bonus Material</h3>
<p>With this, you are basically done đ¤ˇ. However, you also havenât done much more than described in the WWDC video and I promised to fill in some gaps.</p>
<p>My example instrument constains a couple more nice things.</p>
<ul>
<li>A small <a href="https://en.wikipedia.org/wiki/CLIPS">CLIPS</a> expression so the <a href="https://github.com/JoachimK/AlamofireInstrument/blob/main/AlamofireInstrument/AlamofireInstrument.instrpkg#L93">interval is colored based on whether the request was successful or not</a>. You can find valid color values in the <a href="https://help.apple.com/instruments/developer/mac/current/#/dev66257045">Instruments Engineering Type Reference</a>.</li>
<li>With the plot template, you can draw multiple plots in one lane, and e.g. have one plot per host as in my example. However, you can have more than one level of hierarchy and can allow the user to expand or collapse the details. To do so, youâll need to use the <code class="highlighter-rouge"><engineering-type-track></code> element <a href="https://github.com/JoachimK/AlamofireInstrument/blob/main/AlamofireInstrument/AlamofireInstrument.instrpkg#L315-L325">to specify your hierarchy</a> and then add <a href="https://github.com/JoachimK/AlamofireInstrument/blob/main/AlamofireInstrument/AlamofireInstrument.instrpkg#L105">augmentations</a> for the <a href="https://github.com/JoachimK/AlamofireInstrument/blob/main/AlamofireInstrument/AlamofireInstrument.instrpkg#L110-L114">different</a> hierarchy <a href="https://github.com/JoachimK/AlamofireInstrument/blob/main/AlamofireInstrument/AlamofireInstrument.instrpkg#L165-L172">levels</a> to <a href="https://github.com/JoachimK/AlamofireInstrument/blob/main/AlamofireInstrument/AlamofireInstrument.instrpkg#L179-L196">add graphs</a> and <a href="https://github.com/JoachimK/AlamofireInstrument/blob/main/AlamofireInstrument/AlamofireInstrument.instrpkg#L198-L216">detail views</a>. Also, donât forget to <a href="https://github.com/JoachimK/AlamofireInstrument/blob/main/AlamofireInstrument/AlamofireInstrument.instrpkg#L232-L233">activate your augmentation</a> inside the relevant instrument.</li>
</ul>
<h2 id="how-to-go-further">How to Go Further</h2>
<p>If you havenât seen it from the links before, there is actually <a href="https://help.apple.com/instruments/developer/mac/current/#/devcd5016d31">a complete reference</a>, for all the stuff you can put into the <code class="highlighter-rouge">.instrpkg</code> file. E.g., it will tell you, which elements are <a href="https://help.apple.com/instruments/developer/mac/current/#/dev119734497">valid inside an <code class="highlighter-rouge"><instrument></code>-element</a> or from which <a href="https://help.apple.com/instruments/developer/mac/current/#/dev208470310"><code class="highlighter-rouge">icon</code>s you can choose</a> for your instrument. One gotcha: Order actually matters. So, e.g. in an <code class="highlighter-rouge"><instrument></code>, <code class="highlighter-rouge"><title></code> must appear first, then <code class="highlighter-rouge"><category></code>, the other way round is invalid.</p>
<p>Watch <a href="https://developer.apple.com/videos/play/wwdc2018/410/">Creating Custom Instruments (Session 410 from WWDC 2018)</a> again and look out for the parts that you need. There is also <a href="https://developer.apple.com/documentation/xcode/improving_your_app_s_performance/creating_custom_modelers_for_intelligent_instruments">example code from a WWDC 2019 session</a>, which is where I found the usage example for <code class="highlighter-rouge"><engineering-type-track></code>.</p>
<p>CLIPS is the language used to write custom modelers (not covered here), but can also be used for short expressions during the column declaration. The <a href="http://www.clipsrules.net/Documentation.html">documentation about the language</a> has much more than you need. The main thing you probably need to know to write expressions for yourself: CLIPS uses prefix notation, so instead of <code class="highlighter-rouge">?a + ?b</code> youâd have to write <code class="highlighter-rouge">(+ ?a ?b)</code>.</p>
<h3 id="other-posts-about-custom-instruments">Other Posts About Custom Instruments</h3>
<ul>
<li>Igor about <a href="https://medium.com/appspector/building-custom-instruments-package-9d84fd9339b6">Building custom Xcode Instruments Package</a></li>
</ul>
<h3 id="debugging">Debugging</h3>
<p>Itâs a good idea to always add the <code class="highlighter-rouge">os_signpost</code> instrument to your trace document as well. This way, if something doesnât work as expected, you can also check whether your data wasnât logged correctly, or your instrument didnât interpret it correctly.</p>
<h3 id="what-i-havent-figured-out-yet">What I Havenât Figured Out Yet</h3>
<ul>
<li>How to use values that Instruments gives you out of the box and displays in the UI (e.g. the duration) in expressions for column definitions (e.g. to make a transfer rate column by dividing the bytes-received by the duration).</li>
<li>How to display anything in the extra detail area. It feels like itâs only for call stacks. Iâd love to display e.g. a JSON body of a selected request there, but havenât found any example that actually populates it.</li>
</ul>
<h2 id="what-this-instrument-can-do">What This Instrument Can Do</h2>
<p><em>Work in Progress</em></p>
<p>For now, download it and just try it out.</p>
<div class="footnotes">
<ol>
<li id="fn:other-reasons">
<p>Ok, and some other reasons. đ <a href="#fnref:other-reasons" class="reversefootnote">↩</a></p>
</li>
<li id="fn:Alamofire-code">
<p>The full code for logging in my example is in the <a href="https://github.com/JoachimK/AlamofireInstrument/blob/main/Logger.swift"><code class="highlighter-rouge">Logger.swift</code></a> file. Itâs targeted at Alamofire 4.8 because thatâs what the current version of the Wordpress iOS app is using, even though at the time of writing, Alamofire 5 is already out. Due to the notifications, itâs easy to add this logging code without modifying Alamofire itself, however if you have a custom networking library it might be easier to add the logging to the library itself to get access to more details. <a href="#fnref:Alamofire-code" class="reversefootnote">↩</a></p>
</li>
</ol>
</div>Joachim KurzNote: This was written shortly before WWDC 2020, but you might only read it afterwards. So anything in here is already available, you donât need to download the beta for it or wait until autumn to use it. Iâll update this if anything relevant to this article comes out from WWDC.Why you should not name your @IBActions didTapButton2018-04-12T21:30:00+02:002018-04-12T21:30:00+02:00https://blog.cocoafrog.de/how-to/2018/04/12/How-to-name-IBActions<p>Often I see code like this:</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">class</span> <span class="kt">MyViewController</span> <span class="p">:</span> <span class="kt">UIViewController</span> <span class="p">{</span>
<span class="k">override</span> <span class="kd">func</span> <span class="nf">viewDidLoad</span><span class="p">()</span> <span class="p">{</span>
<span class="c1">// [...]</span>
<span class="k">let</span> <span class="nv">button</span> <span class="o">=</span> <span class="kt">UIBarButtonItem</span><span class="p">(</span><span class="nv">barButtonSystemItem</span><span class="p">:</span> <span class="o">.</span><span class="n">cancel</span><span class="p">,</span>
<span class="nv">target</span><span class="p">:</span> <span class="k">self</span><span class="p">,</span>
<span class="nv">action</span><span class="p">:</span> <span class="kd">#selector(</span><span class="nf">didTapCancelButton(sender:)</span><span class="kd">)</span><span class="p">)</span>
<span class="p">}</span>
<span class="kd">@IBAction</span> <span class="kd">func</span> <span class="nf">didTapCancelButton</span><span class="p">(</span><span class="nv">sender</span><span class="p">:</span><span class="kt">Any</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// cancel </span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p>And I <em>really</em> donât like it. The part that I donât like is the name of the action-method. More specifically, the <code class="highlighter-rouge">didTap</code>-part. It might as well be <code class="highlighter-rouge">didTapSaveButton</code> or <code class="highlighter-rouge">didHitSend</code>. Or <code class="highlighter-rouge">cancelButtonTapped</code>. All of those feel wrong⢠to me. There are two main reasons for that:</p>
<h2 id="target-action-is-a-command-pattern-not-a-delegate-pattern">Target-Action is a Command Pattern, not a Delegate Pattern</h2>
<p>Deep in Appleâs Developer Documentation, hidden in the âGuides and Sample Codeâ, you can find an article <a href="https://developer.apple.com/library/content/documentation/General/Conceptual/CocoaEncyclopedia/Model-View-Controller/Model-View-Controller.html">explaining the Model-View-Controller (MVC) pattern</a>. <sup id="fnref:recommended"><a href="#fn:recommended" class="footnote">1</a></sup> They explain that the original MVC-pattern is made up of the <a href="https://en.wikipedia.org/wiki/Composite_pattern">Composite</a>, <a href="https://en.wikipedia.org/wiki/Strategy_pattern">Strategy</a> and <a href="https://en.wikipedia.org/wiki/Observer_pattern">Observer</a> patterns and go on to explain that their version of MVC uses two additional patterns: the <a href="https://en.wikipedia.org/wiki/Mediator_pattern">Mediator</a> and the <a href="https://en.wikipedia.org/wiki/Command_pattern">Command</a> pattern. <a href="https://developer.apple.com/library/content/documentation/General/Conceptual/CocoaEncyclopedia/Target-Action/Target-Action.html">Target-Action</a> is the Command Pattern part of Appleâs MVC.</p>
<p>In the Target-Action version of the Command pattern, the UI element (e.g. a button) is the <em>invoker</em>, the <code class="highlighter-rouge">action</code>/message passed to/method called on the object is the command and the <code class="highlighter-rouge">target</code> is the receiver. Now, a command should be something like <a href="https://developer.apple.com/documentation/foundation/undomanager/1412189-undo"><code class="highlighter-rouge">undo</code></a>, <a href="https://developer.apple.com/documentation/appkit/nsresponder/1533734-deletebackward"><code class="highlighter-rouge">deleteBackward</code></a>, <a href="https://developer.apple.com/documentation/appkit/nsresponder/1525494-lowercaseword"><code class="highlighter-rouge">lowercaseWord</code></a>, or <a href="https://developer.apple.com/documentation/appkit/nsresponder/1532690-scrolltoendofdocument"><code class="highlighter-rouge">scrollToEndOfDocument</code></a>. Those are all actions an <code class="highlighter-rouge">NSResponder</code> understands and might (depending on the specific instance and other circumstances) react to. <code class="highlighter-rouge">WKWebView</code> has <a href="https://developer.apple.com/documentation/webkit/wkwebview/1414987-reload"><code class="highlighter-rouge">reload</code></a> among others. <code class="highlighter-rouge">UIResponder</code> is a little smaller but still has the classics: <a href="https://developer.apple.com/documentation/uikit/uiresponderstandardeditactions"><code class="highlighter-rouge">cut</code>, <code class="highlighter-rouge">copy</code>, <code class="highlighter-rouge">paste</code>, <code class="highlighter-rouge">selectAll()</code> and of course <code class="highlighter-rouge">toggleBoldface</code>, <code class="highlighter-rouge">toggleItalics</code>, and <code class="highlighter-rouge">toggleUnderline</code></a>. What did you think how the overlay menu you get for a text-selection works?</p>
<p>So Apple definitely calls their actions like commands, which seems like a good example to follow. In contrast, calling your action <code class="highlighter-rouge">didTapXButton</code> sounds like a delegate method. But we are not using a delegate pattern here. We donât expect a call from some very specific source asking as for specific information like in a delegate pattern. Instead we get a command to interpret and execute. And this command may or may not be invoked. Or may be invoked multiple times. By different senders, we donât even know who will be triggering the action.</p>
<h2 id="we-dont-know-the-sender-at-compile-time-and-the-sender-shouldnt-be-relevant">We donât know the sender at compile-time and the sender shouldnât be relevant</h2>
<p>An IBAction can be invoked from any kind of control<sup id="fnref:limit"><a href="#fn:limit" class="footnote">2</a></sup>. Your <code class="highlighter-rouge">didTapSendButton</code> could be triggered when the user hits the return key on the keyboard after entering text in the message field. In a macOS app, I might trigger the sending of the mail from a menu item. Or maybe I tapped on a button in the TouchBar and not the one in your window. Or maybe by a <a href="https://developer.apple.com/documentation/uikit/uigesturerecognizer/1624230-addtarget">triple tap gesture recognizer</a>. It doesnât matter, the intention of the user is clear, they want to âsendâ whatever they were writing.</p>
<p>By adding the name of the UI element to the method name, you couple the controller to the view more closely than necessary. And should you ever need to know <em>how</em> the user invoked the action, e.g. because you want to track whether anyone really uses the TouchBar, you can still look at the <code class="highlighter-rouge">sender</code> property and do some customization<sup id="fnref:fyi"><a href="#fn:fyi" class="footnote">3</a></sup>. Thatâs what itâs for. But adding some customization for special cases and otherwise keeping your action-method generic makes it much more flexible to be triggered from UI elements you might not even have heard of when you initially wrote the code.</p>
<p>Except for some very specific commands, it shouldnât matter from where you action is invoked. <code class="highlighter-rouge">lowercaseWord</code> means âmake the currently selected word lowercaseâ. <code class="highlighter-rouge">copy</code> means store the currently selected data (text, image, whatever) in the clipboard. <code class="highlighter-rouge">selectAll</code> means âselect everything I can see hereâ, whatever that everything is. And <code class="highlighter-rouge">undo</code> means âI changed my mind, please roll back.â</p>
<h2 id="name-your-actions-like-commands">Name Your Actions Like Commands</h2>
<p>So Apple names their methods in this way, it fits the pattern and it decouples our controllers from our views. Whatâs not to like?
Thus, we should name our actions like commands. Make it <code class="highlighter-rouge">cancel(sender:Any)</code>, <code class="highlighter-rouge">save(sender:Any)</code> and <code class="highlighter-rouge">sendMessage(sender:Any)</code> instead of <code class="highlighter-rouge">didTapCancelButton(sender:Any)</code>, <code class="highlighter-rouge">saveButtonTapped(sender:Any)</code> and <code class="highlighter-rouge">didHitSend(sender:Any)</code><sup id="fnref:macOS"><a href="#fn:macOS" class="footnote">4</a></sup>.</p>
<p>And by the way, it doesnât matter whether you actually use Interface Builder or not. As long as you assign targets and actions to UI elements your action-methods should follow this naming pattern. I would even recommend to add <code class="highlighter-rouge">@IBAction</code> in front of them, as itâs a nice marker to indicate to other programmers that this will be invoked from UI elements and they might not have full control over when and how and should thus not assume anything more than is provided by the action name.</p>
<div class="footnotes">
<ol>
<li id="fn:recommended">
<p>I highly recommend reading it, independent of this blog post. <a href="#fnref:recommended" class="reversefootnote">↩</a></p>
</li>
<li id="fn:limit">
<p>And should you ever want to make your action only be invokeable by buttons, still name it in a generic way but make the type of sender e.g. <code class="highlighter-rouge">UIButton</code>. It doesnât help when you specify the action programatically, but Interface Builder will respect your choice and only offer it to be triggered by elements that fit that type. Yet another great reason to use Interface Builder. ;-) <a href="#fnref:limit" class="reversefootnote">↩</a></p>
</li>
<li id="fn:fyi">
<p>Did you know that you can add more than one action to a <code class="highlighter-rouge">UIControl</code> (sadly not to <code class="highlighter-rouge">NSControl</code>, thanks to <a href="https://twitter.com/mark_sabbatical">Mark Aufflick</a> for pointing that out)? So instead of customizing the <code class="highlighter-rouge">send</code> action and adding your tracking there, just make the TouchBar element invoke the normal <code class="highlighter-rouge">send</code> action and add another action <code class="highlighter-rouge">trackTouchBarUse</code> with your <code class="highlighter-rouge">TrackingController</code> as target. They will both be invoked when the user taps the TouchBar item. This way your view controller neither needs to know about tracking nor about how many different ways there are to invoke <code class="highlighter-rouge">send</code> and you can simply configure the actions in Interface Builder. 𤯠<a href="#fnref:fyi" class="reversefootnote">↩</a></p>
</li>
<li id="fn:macOS">
<p>macOS makes this much clearer. <code class="highlighter-rouge">NSWindow</code>, <code class="highlighter-rouge">NSDocument</code> and friends all have lots of great action-methods. This is why you can so often build pretty advanced macOS applications by just combining some instances of Cocoa classes in Interface Builder and configuring them. Sadly, UIDocument and itâs friends have forgotten most of that, but I think following this naming pattern still fits well with Cocoa Touch and has all of the advantages described above. <a href="#fnref:macOS" class="reversefootnote">↩</a></p>
</li>
</ol>
</div>Joachim KurzOften I see code like this:My dream for NSURLSession Authentication2017-11-18T17:00:00+01:002017-11-18T17:00:00+01:00https://blog.cocoafrog.de/2017/11/18/how-nsurlsession-authentication-should-work<p>Recently, I worked on implementing <a href="https://tools.ietf.org/html/rfc6749">OAuth 2</a> authentication using just plain <code class="highlighter-rouge">NSURLSession</code> APIs. While I came to really like Appleâs authentication API for HTTP Basic authentication in the past, I was really disappointed with it when trying to use it for OAuth 2.</p>
<h2 id="the-problem">The Problem</h2>
<p>OAuth 2 generally works like this:</p>
<ol>
<li>You use some initial authentication method (e.g. web login or username & password) to obtain a <em>refresh token</em> and an <em>access token</em>.</li>
<li>You add the access token to the <code class="highlighter-rouge">Authorization</code> header of every request that needs authentication. The value of this header field looks <a href="https://tools.ietf.org/html/rfc6749#section-7.1">roughly like this</a>: <code class="highlighter-rouge">Bearer my_access_token</code>.</li>
<li>The access token has a very short life-time, usually between a few minutes and a few hours. When it expires, the request fails with a HTTP 401 status code and the header field <code class="highlighter-rouge">WWW-Authenticate</code> will be set to something like <code class="highlighter-rouge">Bearer realm:additional_info</code>. The <code class="highlighter-rouge">Bearer</code> part is basically the identifier for OAuth 2<sup id="fnref:moreComplicated"><a href="#fn:moreComplicated" class="footnote">1</a></sup>.</li>
<li>The client then needs to use the <em>refresh token</em> from step 1 to obtain a new <em>access token</em> from the authorization server.</li>
<li>It then retries the original request with the new access token.</li>
</ol>
<p>More generally, most authentication flows work as describe in this graphic:</p>
<p><img src="https://blog.cocoafrog.de/assets/posts/nsurlsession-authorization/AuthenticationDiagram.jpg" alt="A diagram showing a generalized authentication flow. 1. Client talks to server. 2. Server responds with authentication failure. 3. Client talks to authentication details provider. 4. Authentication details provider respondes. 5. Client retries original request to server." /></p>
<ol>
<li>The client makes a request to the server.</li>
<li>The request fails because the client did not provide authentication details or the ones provided were invalid.</li>
<li>The client talks to some kind of authentication details provider to get new authentication details. In the case of OAuth 2 this is the authentication server that provides new access tokens. But it could just as well simply be the user that the application asks for their username and password in the case of HTTP Basic authentication or a lookup in the local keychain.</li>
<li>The authentication provider provides new details. Be that the response from a server, or the input from a dialog shown to the user.</li>
<li>The client uses these new authentication details, adapts the original request to include these details and retries the request.</li>
</ol>
<h4 id="how-to">How To</h4>
<p>To implement authentication flows like this, the <code class="highlighter-rouge">NSURLSessionDelegate</code> has a really useful method called: <a href="https://developer.apple.com/documentation/foundation/nsurlsessiontaskdelegate/1411595-urlsession?language=objc"><code class="highlighter-rouge">URLSession:task:didReceiveChallenge:completionHandler:</code></a> where the <code class="highlighter-rouge">completionHandler</code> has the following type: <code class="highlighter-rouge">void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential)</code>.</p>
<p>This is basically exactly what you want.
According to the <a href="https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/URLLoadingSystem/Articles/AuthenticationChallenges.html#//apple_ref/doc/uid/TP40009507-SW1">documentation</a>:</p>
<blockquote>
<p>The URL loading system classes do not call their delegates to handle request challenges unless the server response contains a WWW-Authenticate header.</p>
</blockquote>
<p>Calling the delegate method when the <code class="highlighter-rouge">WWW-Authenticate</code> header is set is exactly what we want.</p>
<p>You can then fetch the required credential and call the completion block when you are done to hand the new credentials to the URL loading system and it will retry the request automatically, without you having to do anything.
This is especially valuable, as there is no other way to retry an NSURLSessionTask, all you can do if you are not relying on this method is create a new task once the original one failed.</p>
<p>Except there are two things missing:</p>
<ol>
<li>
<p>The delegate method is not actually called whenever the <code class="highlighter-rouge">WWW-Authenticate</code> is set. Itâs <em>only</em> called if the <code class="highlighter-rouge">WWW-Authenticate</code> specifies HTTP-Basic<sup id="fnref:Basic"><a href="#fn:Basic" class="footnote">2</a></sup> Authorization, so only when its value is something like <code class="highlighter-rouge">Basic Realm=...</code>.
This means it will not be called for OAuth (2).</p>
</li>
<li>
<p>The completion block also only supports HTTP Basic authorization.
The only thing you can hand to the completion block is a <code class="highlighter-rouge">URLCredential</code> object.
But a <code class="highlighter-rouge">URLCredential</code> always consists of a <code class="highlighter-rouge">username</code> and <code class="highlighter-rouge">password</code>, there is no <code class="highlighter-rouge">token</code> property or similar.
And the username and password will always be encoded as HTTP Basic Authorization header, there is no way to influence how whatever you put in <code class="highlighter-rouge">username</code> and <code class="highlighter-rouge">password</code> gets encoded into the header of the retried request.
In fact, there is no way to modify the request to retry <em>at all</em>.
There is just no way to access it<sup id="fnref:tellme"><a href="#fn:tellme" class="footnote">3</a></sup>.</p>
</li>
</ol>
<p>In summary, almost all the pieces are there to have a nice and simple way to implement OAuth following all the patterns in the URL Loading system, except that the conditions when the delegate method is called and the <code class="highlighter-rouge">completionBlock</code> parameters are too limited. đ¤Śââď¸</p>
<h2 id="my-wish">My Wish</h2>
<p>All we would need is a slightly more flexible way to configure the retried response. It would even be enough to only be able to provide a value for the authorization header. Similar to the problems above we only need two things:</p>
<ol>
<li>Call <code class="highlighter-rouge">URLSession:task:didReceiveChallenge:completionHandler:</code> on every response that contains a <code class="highlighter-rouge">WWW-Authenticate</code> header, not just for HTTP Basic Auhorization.</li>
<li>Make the <code class="highlighter-rouge">completionBlock</code> accept a protocol instead of the <code class="highlighter-rouge">URLCredential</code> directly, like so:</li>
</ol>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">protocol</span> <span class="kt">AuthorizationValueProviding</span> <span class="p">{</span>
<span class="kd">func</span> <span class="nf">authorizationHeaderValue</span><span class="p">()</span> <span class="o">-></span> <span class="kt">String</span>
<span class="p">}</span>
<span class="kd">protocol</span> <span class="kt">URLSessionTaskDelegate</span> <span class="p">{</span>
<span class="c1">// adapted version of existing method, note the changed completionBlock</span>
<span class="kd">optional</span> <span class="kd">func</span> <span class="nf">urlSession</span><span class="p">(</span><span class="n">_</span> <span class="nv">session</span><span class="p">:</span> <span class="kt">URLSession</span><span class="p">,</span> <span class="nv">task</span><span class="p">:</span> <span class="kt">URLSessionTask</span><span class="p">,</span> <span class="n">didReceive</span> <span class="nv">challenge</span><span class="p">:</span> <span class="kt">URLAuthenticationChallenge</span><span class="p">,</span> <span class="nv">completionHandler</span><span class="p">:</span> <span class="kd">@escaping</span> <span class="p">(</span><span class="kt">URLSession</span><span class="o">.</span><span class="kt">AuthChallengeDisposition</span><span class="p">,</span> <span class="kt">AuthorizationValueProviding</span><span class="p">?)</span> <span class="o">-></span> <span class="kt">Void</span><span class="p">);</span>
<span class="p">}</span></code></pre></figure>
<p>And <code class="highlighter-rouge">URLCredential</code> could just implement that protocol with a default HTTP Basic handling</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">extension</span> <span class="kt">URLCredential</span> <span class="p">:</span> <span class="kt">AuthorizationValueProviding</span> <span class="p">{</span>
<span class="kd">func</span> <span class="nf">authorizationHeaderValue</span><span class="p">()</span> <span class="o">-></span> <span class="kt">String</span> <span class="p">{</span>
<span class="c1">// HTTP Basic encoding of username and password. See https://tools.ietf.org/html/rfc7617#section-2</span>
<span class="k">let</span> <span class="nv">authorizationData</span> <span class="o">=</span> <span class="p">(</span><span class="n">user</span> <span class="o">+</span> <span class="s">":"</span> <span class="o">+</span> <span class="n">password</span><span class="p">)</span><span class="o">.</span><span class="nf">data</span><span class="p">(</span><span class="nv">using</span><span class="p">:</span> <span class="o">.</span><span class="n">utf8</span><span class="p">)?</span><span class="o">.</span><span class="nf">base64EncodedString</span><span class="p">()</span>
<span class="k">return</span> <span class="s">"Basic </span><span class="se">\(</span><span class="n">authorizationData</span><span class="se">)</span><span class="s">"</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p>This way you could still pass a simple <code class="highlighter-rouge">URLCredential</code> object to the same <code class="highlighter-rouge">completionBlock</code> and keep backwards compatibility<sup id="fnref:digest"><a href="#fn:digest" class="footnote">4</a></sup>.</p>
<p>And you could even make String implement it by default if you liked:</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">extension</span> <span class="kt">String</span> <span class="p">:</span> <span class="kt">AuthorizationValueProviding</span> <span class="p">{</span>
<span class="kd">func</span> <span class="nf">authorizationHeaderValue</span><span class="p">()</span> <span class="o">-></span> <span class="kt">String</span> <span class="p">{</span>
<span class="k">return</span> <span class="k">self</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p>and then use that to implement an OAuth callback like so:</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">optional</span> <span class="kd">func</span> <span class="nf">urlSession</span><span class="p">(</span><span class="n">_</span> <span class="nv">session</span><span class="p">:</span> <span class="kt">URLSession</span><span class="p">,</span> <span class="nv">task</span><span class="p">:</span> <span class="kt">URLSessionTask</span><span class="p">,</span> <span class="n">didReceive</span> <span class="nv">challenge</span><span class="p">:</span> <span class="kt">URLAuthenticationChallenge</span><span class="p">,</span> <span class="nv">completionHandler</span><span class="p">:</span> <span class="kd">@escaping</span> <span class="p">(</span><span class="kt">URLSession</span><span class="o">.</span><span class="kt">AuthChallengeDisposition</span><span class="p">,</span> <span class="kt">AuthorizationValueProviding</span><span class="p">?)</span> <span class="o">-></span> <span class="kt">Void</span><span class="p">)</span> <span class="p">{</span>
<span class="nf">refreshAccessToken</span><span class="p">()</span> <span class="p">{</span> <span class="nv">newAccessToken</span><span class="p">:</span><span class="kt">String</span> <span class="k">in</span>
<span class="nf">completionHandler</span><span class="p">(</span><span class="o">.</span><span class="n">useCredential</span><span class="p">,</span> <span class="s">"Bearer </span><span class="se">\(</span><span class="n">newAccessToken</span><span class="se">)</span><span class="s">"</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p>Can I please have that, Apple? Itâs Christmas soon, right?</p>
<p>I filed a <a href="http://www.openradar.me/radar?id=5044454306086912">bugreport with Apple</a>, if you agree, please duplicate it. If there are other ideas how to do this, please let me know.</p>
<div class="footnotes">
<ol>
<li id="fn:moreComplicated">
<p>Of course, itâs more complicated than that. There are different token types and different authentication schemes within OAuth. But itâs a good enough approximation, and at least in the case I tried I couldnât find a better way to identify whether a server is actually asking for OAuth. In reality, in most cases you donât need to guess from the response, you already <em>know</em> that server requires OAuth when it needs authentication and you would only sanity-check the response to confirm it contains what you expect before you start the OAuth flow. <a href="#fnref:moreComplicated" class="reversefootnote">↩</a></p>
</li>
<li id="fn:Basic">
<p>It might also support HTTP Digest Authorization but I didnât try it and could not find any documentation. <a href="#fnref:Basic" class="reversefootnote">↩</a></p>
</li>
<li id="fn:tellme">
<p>If you know of one or find one, please tell me! As far as I can tell, the request on the <code class="highlighter-rouge">URLSessionTask</code> is immutable, the task doesnât have any way to retry it and the <code class="highlighter-rouge">completionBlock</code> of the delegate call doesnât have a way to specify a new request. <a href="#fnref:tellme" class="reversefootnote">↩</a></p>
</li>
<li id="fn:digest">
<p>If we need to support both Basic and Digest authorization the credential probably would have to be wrapped in another object that specifies whether it should be encoded as Basic or Digest. Alternatively, at least in Swift, the delegate method could be overloaded with two different completion blocks, one using the new <code class="highlighter-rouge">AuthorizationValueProviding</code> the other using the old <code class="highlighter-rouge">URLCredential</code> and mapping it accordingly. <a href="#fnref:digest" class="reversefootnote">↩</a></p>
</li>
</ol>
</div>Joachim KurzRecently, I worked on implementing OAuth 2 authentication using just plain NSURLSession APIs. While I came to really like Appleâs authentication API for HTTP Basic authentication in the past, I was really disappointed with it when trying to use it for OAuth 2.How Bad can You Make a Door?2017-08-07T20:00:00+02:002017-08-07T20:00:00+02:00https://blog.cocoafrog.de/2017/08/07/how-bad-can-you-make-a-door<p>In <a href="https://www.jnd.org">Don Norman</a>âs <a href="https://www.jnd.org/books/design-of-everyday-things-revised.html">The Design of Everyday Things</a>, one of the prime examples of an âinterfaceâ (as in âuser interfaceâ) are doors and their handles (or the absence of).
He explains the concept of <a href="https://en.wikipedia.org/wiki/Affordance">affordance</a> using doors.
Wikipedia describes âaffordanceâ as âthe possibility of actionâ. The affordance of an object describes what the object tells you about how it can be used, what it âaffords youâ to do with it.
Don Norman explains that e.g. a vertical bar at a door âaffordsâ pulling.
A horizontal bar, or even better, no bar at all and instead just a plate at the door, âaffordsâ pushing.
This way, you can make it obvious whether a door needs to be pulled or pushed without putting stupid labels for âpushâ and âpullâ on it.
Most people will just naturally use doors with such an âinterfaceâ correctly, without thinking about it.</p>
<p>Now imagine someone has read Don Normanâs book, studied it all intensively, learned all the rules of how to design good interfaces and what to avoid.
They then decide to apply all this knowledge to create the worst door âinterfaceâ they can think of.
Of course, to create a really bad interface you need some kind of touch interface.
But just creating a normal door would be boring, I mean people would quickly forget about it and be at most mildly annoyed if they are made to feel stupid by a simple door they want to go through.
To create a truly bad experience, you need to up the ante, make people really rely on that door make them really <em>want</em> to understand how that door works and trust it.
If <em>that</em> door fails, it must be truly memorable.</p>
<p>What kind of door could you choose for that?
Maybe the front-door of a house?
No, whoever lives in that house could learn it and not many other people use it.
Maybe the door of a safe, if that fails it would be bad right?
But how many safes are there? Almost no one uses a safe.
So whatâs a door everybody uses <em>daily</em> and really, <em>really</em> relies on being able to control when it opens, when it closes and when it doesnât open?
Right: A toilet door.</p>
<p>Enter âDeutsche Bahnâ, the (ex state-operated) German train company<sup id="fnref:DB"><a href="#fn:DB" class="footnote">1</a></sup>, and their disability-friendly toilets in the double-deck regional trains.
I am sure their thought-process must have been something like above, there is no reasonable other explanation.
This is what they look like from the outside:</p>
<p><img src="https://blog.cocoafrog.de/assets/posts/bad-doors/door_locked.jpg" alt="The "controls" for a the disability-friendly toilets in german trains. Visible is a small vertical bar, which looks a bit like a handle, attached to a metal sliding door. At the top end of the bar there is a green rectangle, at the bottom there is a shining red circle with a black border." /></p>
<p>Letâs ignore the obvious mechanical affordances (e.g. vertical bars indicate pulling), as we already know that this is a sliding door. It also has this nice label at the top that says something about âautomaticâ so we probably canât just âpullâ or âpushâ it. Instead, we know already from experience with these kinds of door that we need to press some kind of button to open it. But what should we press on?</p>
<p>If you now said âof course itâs the big red circle button at the bottom, I mean it even shines bright red, so you know itâs there!â, you are wrong.
But not alone.
Iâve sat in front of that toilet door on many train rides<sup id="fnref:Mehrzweckabteil"><a href="#fn:Mehrzweckabteil" class="footnote">2</a></sup>, and many people tried to press that âbuttonâ.
Now, after you have seen this door in use you will learn that this is actually the âbusyâ indicator.
It shines when the toilet is occupied.
This is what this door âhandleâ looks like when the toilet is free:</p>
<p><img src="https://blog.cocoafrog.de/assets/posts/bad-doors/door_unlocked.jpg" alt="Same door handle as above. This time the red circle no longer glows, but is just there. Instead in the green rectangle at the top, two arrows pointing to the left and right glow." /></p>
<p>So now the red circle at the bottom does not glow anymore.
Instead, there are two green arrows pointing to the sides, inside the green rectangle at the top.
If you saw only this picture, you would probably still have a good chance to press the red thing at the bottom.
It <em>does</em> look very button-y.
But seeing the two states after another, you have a chance of figuring out that the glowing arrows at the top indicate that there is an interface element active now that was disabled before and that it probably means that you should press the green thing at the top.
Maybe.
Of course, it doesnât give any haptic feedback when you press it (remember I said you need touch interfaces for a truly bad interface?), but a short moment after you press it you hear a short hissing sound and the door moves to the side.
You have passed the first test, you opened the door!<sup id="fnref:doorOpen"><a href="#fn:doorOpen" class="footnote">3</a></sup></p>
<p>But opening the door is only the first challenge.
What you really want, is to close the door behind you.
And this is the real challenge.</p>
<p>So you walk through the door, turn around and see this:
<img src="https://blog.cocoafrog.de/assets/posts/bad-doors/door_inside.jpg" alt="The inside of the toilet door, a similar door handle as before is visible. But this time the red circle is at the top, and there is no green area for opening or closing. There is only a weird mechanical dial above the handle. And above that dial is a white note on red ground that says "Door unlocking emergency", next to an arrow indicating that dial must be turned counter-clockwise." /></p>
<p>We have already learned that the red button is not actually a button and does not open or close the door.
But the green touch area that <em>did</em> open or close the door is gone and there does not seem to be anything else to replace it.
So we try the button anyway, for a lack of alternatives.
Of course, itâs still no button and does nothing.</p>
<p>After spending several seconds trying to figure out how to close the door, it closes automatically.
Great, so now itâs closed and we are on the inside.
We donât know yet how to get out, but maybe that problem can be solved later.
But we do not feel comfortable doing our business without being assured that we will not be disturbed.
How do we actually lock this door and prevent someone else from opening it while we are in the middle of things?</p>
<p>Nope, the big red non-button still does nothing.
How about this weird mechanical dial above it?
The note above says something about emergency unlocking, but everything about this door is so confusing maybe the note is not actually about that dial?
So we try to turn it and of course, the door opens.</p>
<p>Turns out it actually was the emergency unlock/opening of the door.</p>
<p>The passengers in front of the toilet are happy to see us again.
And we wait, standing inside the toilet room because we still donât know how to close that door.<br />
Then we reach around and press the green touch area on the <em>outside</em> to make the door close while we are still on the inside.<br />
At least itâs closed now.<br />
But still not locked.</p>
<p>After calming down a bit, we look around.
And there, on the right wall, about a meter away from the actual door handle, are some more buttons:</p>
<p><img src="https://blog.cocoafrog.de/assets/posts/bad-doors/open_button.jpg" alt="A button with two arrows pointing to the sides. A label above it says "Open"." /></p>
<p>That label says âOpenâ, so at least we found out how to open the door again without the emergency thing.</p>
<p>Right below, there is another button:</p>
<p><img src="https://blog.cocoafrog.de/assets/posts/bad-doors/open_and_close_buttons.jpg" alt="The "open" button from above and a button with two arrows pointing inside (but hard to see). A label above it says "Close"." /></p>
<p>That buttonâs label says âSchlieĂenâ, which means âcloseâ.
But âAbschlieĂenâ (very similar) in German means âlockâ.
So when I press this button, does it just close the door, or will it lock it?
Maybe if I press it while the door is closed already, it will be locked?
Like 2 * close = lock?</p>
<p>But pressing the close-button while the door is closed doesnât seem to have any effect.
And pressing the âopenâ button to check whether the door is locked (as there is no handle you can try), opens the door again.
Hello, dear other passengers, again.
Yes, Iâll be done soon.
Yes, I know the toilet doesnât smell all that great.</p>
<p>Ah, but there is one more button below the two others:</p>
<p><img src="https://blog.cocoafrog.de/assets/posts/bad-doors/all_door_buttons.jpg" alt="The two buttons from above and a third button below which has a barely visible key icon engraved in it. A label above it says "When the light blinks, please lock"." /></p>
<p>The label roughly translates as âwhen the light blinks, please lockâ, which is a bad label in and of itself.
At the moment the button doesnât do anything.
But after closing the door again, it starts blinking.
And pressing it stops the blinking and makes the red circle at the door shine, so apparently, we finally locked the door!</p>
<p>Now if you trust the system enough after all this, you can now go and do your business.
Of course, those three buttons are on the other side of the room from the actual toilet bowl and the room is easily two meters wide to fit a wheelchair. So, <em>if</em> you misunderstood how the locking of the door works and someone were to come along and open the door, while you are inside you have <em>no chance at all</em> to reach those buttons and quickly press the close button again.
And you know how long it will take the other person to figure out how to close the door again, once they realized they should not have opened it.</p>
<p>So just relax and trust the technology<sup id="fnref:PS"><a href="#fn:PS" class="footnote">4</a></sup>.</p>
<div class="footnotes">
<ol>
<li id="fn:DB">
<p>Of course, they only bought the train, another company built it. But first, I donât know which one. And second, <em>they</em> bought that train and <em>they</em> accepted it. Heck, they probably even made a checklist with lots of requirements, none of which included that it should be âunderstandableâ, âusableâ or âpeople should be able to lock the door with less than three triesâ. <a href="#fnref:DB" class="reversefootnote">↩</a></p>
</li>
<li id="fn:Mehrzweckabteil">
<p>Itâs where you can put your bike and itâs usually where there is enough space if the rest of the train is full <em>and</em> there is plenty of leg-room which is not the case in many other parts of the train. This actually ads to the humiliation when using this door. There are about 10-20 seats in front of that door. If you fail (and you most likely will), a dozen people see you fail. <a href="#fnref:Mehrzweckabteil" class="reversefootnote">↩</a></p>
</li>
<li id="fn:doorOpen">
<p>Of course, if you press on the green rectangle in the locked state, nothing happens and there is no indication whatâs wrong. There is also no other sign, except for the red light, that this toilet is occupied. There <em>is</em> another glowing toilet sign somewhat near the door, but too far away to really notice it if you are standing in front of it. <a href="#fnref:doorOpen" class="reversefootnote">↩</a></p>
</li>
<li id="fn:PS">
<p>If you are one of those people that is now saying âYes, itâs not optimal, but you just need to think a bit about it and pay attention to figure it outâ, let me tell you two things: First, go an read Don Normanâs book because he explains very well that we should not have to think about everyday things like doors and itâs almost always the thing thatâs badly designed, not the person being stupid. And second, 80% of the people I have seen using this toilet have made at least one of the mistakes I described, be it trying to unlock a locked door, accidentally opening the door again, not figuring out how to lock it, etc. If such a big majority fails to use this door, itâs not the people that are wrong. And the ones who did not fail probably just learned how to use that door in a previous encounter. <a href="#fnref:PS" class="reversefootnote">↩</a></p>
</li>
</ol>
</div>Joachim KurzIn Don Normanâs The Design of Everyday Things, one of the prime examples of an âinterfaceâ (as in âuser interfaceâ) are doors and their handles (or the absence of).
He explains the concept of affordance using doors.
Wikipedia describes âaffordanceâ as âthe possibility of actionâ. The affordance of an object describes what the object tells you about how it can be used, what it âaffords youâ to do with it.
Don Norman explains that e.g. a vertical bar at a door âaffordsâ pulling.
A horizontal bar, or even better, no bar at all and instead just a plate at the door, âaffordsâ pushing.
This way, you can make it obvious whether a door needs to be pulled or pushed without putting stupid labels for âpushâ and âpullâ on it.
Most people will just naturally use doors with such an âinterfaceâ correctly, without thinking about it.How not to do Localization2017-07-28T16:00:00+02:002017-07-28T16:00:00+02:00https://blog.cocoafrog.de/localization/programming/how-not-to/2017/07/28/how-not-to-do-localization<p><em>The content of this blog post applies to all Apple platforms and to most other user-facing operating systems and frameworks. For brevity, Iâll use iOS as an example but almost everything in here can be done similarly on other platforms.</em></p>
<p>Some time ago I stumbled upon the following code in a project I was working on:</p>
<figure class="highlight"><pre><code class="language-objective_c" data-lang="objective_c"><span class="c1">// de.lproj/Localizable.strings
// [...]
</span><span class="s">"ID_REAL_CURRENT_LOCALE"</span> <span class="o">=</span> <span class="s">"de_DE"</span><span class="p">;</span></code></pre></figure>
<figure class="highlight"><pre><code class="language-objective_c" data-lang="objective_c"><span class="c1">// en.lproj/Localizable.strings
// [...]
</span><span class="s">"ID_REAL_CURRENT_LOCALE"</span> <span class="o">=</span> <span class="s">"en_US"</span><span class="p">;</span></code></pre></figure>
<figure class="highlight"><pre><code class="language-objective_c" data-lang="objective_c"><span class="c1">// Usage
</span><span class="n">longDateFormatter</span><span class="p">.</span><span class="n">locale</span> <span class="o">=</span> <span class="p">[[</span><span class="n">NSLocale</span> <span class="nf">alloc</span><span class="p">]</span> <span class="nf">initWithLocaleIdentifier</span><span class="p">:</span><span class="n">NSLocalizedString</span><span class="p">(</span><span class="s">@"ID_REAL_CURRENT_LOCALE"</span><span class="p">,</span> <span class="s">@"the locale ID of this language"</span><span class="p">)];</span></code></pre></figure>
<p>Whatâs wrong about this you ask? Well, almost everything.</p>
<h2>What it does</h2>
<p>The <code class="highlighter-rouge">Localizable.strings</code> files are used to provide translations for strings used within an app on Appleâs platforms. The first one above is the translation-file for German, the second one is for English. E.g. to translate the title of a button we could do the following:</p>
<figure class="highlight"><pre><code class="language-objective_c" data-lang="objective_c"><span class="c1">// en.lproj/Localizable.strings
</span><span class="s">"DONE_BUTTON_TITLE"</span> <span class="o">=</span> <span class="s">"Done"</span><span class="p">;</span></code></pre></figure>
<figure class="highlight"><pre><code class="language-objective_c" data-lang="objective_c"><span class="c1">// de.lproj/Localizable.strings
</span><span class="s">"DONE_BUTTON_TITLE"</span> <span class="o">=</span> <span class="s">"Fertig"</span><span class="p">;</span></code></pre></figure>
<figure class="highlight"><pre><code class="language-objective_c" data-lang="objective_c"><span class="c1">// Usage
</span><span class="n">barButton</span><span class="p">.</span><span class="n">title</span> <span class="o">=</span> <span class="n">NSLocalizedString</span><span class="p">(</span><span class="s">@"DONE_BUTTON_TITLE"</span><span class="p">,</span> <span class="s">@"Title of a Done button"</span><span class="p">);</span></code></pre></figure>
<p><code class="highlighter-rouge">NSLocalizedString</code> checks the deviceâs current language and looks at the appropriate file to fetch the string. So if you have set your device to English the buttonâs title will be âDoneâ, if you have set it to German, the buttonâs title will be âFertigâ.</p>
<p>Except <code class="highlighter-rouge">NSLocalizedString</code>âand the whole localization system of iOS/macOSâis much more intelligent. E.g. it will find the best matching language. This is what my âLanguage and Regionâ settings screen looks like:</p>
<p><img src="https://blog.cocoafrog.de/assets/posts/how-not-to-do-localization/iphone-language-settings.jpeg" alt="Two iPhone Screenshots. The first one shows the Language & Region Screen with "iPhone-Language" set to "Deutsch" and two preferred languages: Deutsch and English (U.K.). The second screen is the one to change the iPhone's language. It shows lots of languages and Regions with "Deutsch (Deutschland)" at the top, followed by English (U.K.) and others. There are several other entries for "Detusch", e.g. "Deutsch (Ăsterreich)", or "Deutsch (Schweiz)"" /></p>
<p>On the first screen, you can see that my iPhone is currently set to German (=Deutsch).
But below you see my list of âpreferred languagesâ.
This is used to determine what language should be used.
If Iâm going to use an app that was developed in French, uses French as the default language and is only localized to English but not to German you might assume that the app would be displayed in French to me (as itâs the default language and German isnât available).
But thatâs not what happens.
Because I have set English as my second choice, the system chooses the English language localization of the app for me.
Thatâs great because my French is really rusty.</p>
<p>But there is one more thing. If you look closely, on the second screen you can see that my language is actually set to âDeutsch (Deutschland)â so itâs actually âGerman (Germany)â or, to use itâs locale-identifier: <code class="highlighter-rouge">de_DE</code>.
This specifies the variant of German spoken in Germany in contrast to the one spoken in Austria (<code class="highlighter-rouge">de_AT</code>).
You are probably more familiar with variants of English like <code class="highlighter-rouge">en_UK</code> and <code class="highlighter-rouge">en_US</code>.
These variants allow you to fine-tune your translations to the specific dialect of a language.</p>
<p>But now letâs look at the example above again. We had translations for <code class="highlighter-rouge">en</code> and <code class="highlighter-rouge">de</code>, but not for <code class="highlighter-rouge">de_DE</code>. Now, if the appâs default language is English, does that mean I would get the app in English because <code class="highlighter-rouge">de_DE</code> is not available and neither is my second choice <code class="highlighter-rouge">en_UK</code>? So would I have to set my preferred languages to âGerman (Germany), German (Austria), German (Switzerland), English (U.K), English, English (Australia), English (India)â just to be sure I get a language I like instead of an appâs default language? Again, the system is more intelligent than that. It knows that <code class="highlighter-rouge">de_DE</code> is a variant of <code class="highlighter-rouge">de</code> and by supplying only a <code class="highlighter-rouge">de</code> translation the app developer assures us that this translation works for all variants of German. So even though I never set <code class="highlighter-rouge">de</code> as an acceptable language, this is what I will get (which will most likely be what I expect).</p>
<p>Great, so we can have base translations for a language family and we can have specific dialects.
So whatâs the problem of putting an <code class="highlighter-rouge">en_US</code> âCURRENT_LOCALEâ key into the <code class="highlighter-rouge">en</code> file and using it for the date formatter?
Okay, it would use US-American names for the month even for someone from the UK.
But last I checked, they spelt at least the month and weekday names the same, so what does it matter?</p>
<h2>Language â Locale</h2>
<p>The problem is that we are not just setting the language of the date formatter by setting its locale but much more.
The locale encompasses not only the language (and the dialect) used, but also which currency is used, how numbers are formatted, which calendar to use and, surprise, how dates are formatted.
You can specify all that in a locale-identifier, so e.g. <code class="highlighter-rouge">en_US@calendar=japanese</code> specifies I want the US-English language variants, all the defaults from the US-region, but a Japanese calendar.</p>
<p>But even if I donât specify all properties of a Locale in the identifier, the region sets some defaults. So for example, my locale identifier <code class="highlighter-rouge">de_DE</code> means the following:</p>
<ul>
<li>Language: Germanyâs German</li>
<li>Decimal Separator: <code class="highlighter-rouge">,</code> (yes we use a comma, so 2,5 means two-and-a-half here)</li>
<li>Calendar: Gregorian</li>
<li>Currency Code: âŹ</li>
<li>Date Formatting: <code class="highlighter-rouge">28.07.17, 16:05</code><sup id="fnref:dateFormatFootnote"><a href="#fn:dateFormatFootnote" class="footnote">1</a></sup> (We specify the day of the month before the month and we use a dot to separate the components. We also like a 24-hour time style.)</li>
</ul>
<p>and many other things. If you want to see all the things specified by a Locale, take a look at the <a href="https://developer.apple.com/documentation/foundation/locale">documentation by Apple</a> or of your favourite framework.</p>
<p>So an <code class="highlighter-rouge">en_US</code> Locale without any other specifiers means the following:</p>
<ul>
<li>Language: US English</li>
<li>Decimal Separator: <code class="highlighter-rouge">.</code></li>
<li>Calendar: Gregorian</li>
<li>Currency Code: $</li>
<li>Date Formatting: <code class="highlighter-rouge">7/28/17 4:05 PM</code></li>
</ul>
<p>But what does an <code class="highlighter-rouge">en_UK</code> Locale default look like? This:</p>
<ul>
<li>Language: British English</li>
<li>Decimal Separator: <code class="highlighter-rouge">.</code></li>
<li>Calendar: Gregorian</li>
<li><span style="color:red">Currency Code: ¥</span></li>
<li><span style="color:red">Date Formatting: <code class="highlighter-rouge">28/7/2017, 16:05</code></span></li>
</ul>
<p>So people<sup id="fnref:peopleFromFootnote"><a href="#fn:peopleFromFootnote" class="footnote">2</a></sup> from the UK apparently agree with the Germans that it should be day-month-year and one should use a 24-hour time format.
But they use slashes as separators, instead of the dots used by Germans.
Oh, and did you notice that people from the US and Germany are usually happy with the the year specified as <code class="highlighter-rouge">17</code>, whereas people from the UK would also like to know the millenium we are talking about?</p>
<h2>The Problem</h2>
<p>So by using the <em>language</em>-file to determine the <em>locale</em>-code, doing that incorrectly (because e.g. <code class="highlighter-rouge">en</code> does not autoamtically mean <code class="highlighter-rouge">en_US</code> and <code class="highlighter-rouge">de</code> does not automatically mean <code class="highlighter-rouge">de_DE</code>) and using the resulting code, e.g. <code class="highlighter-rouge">en_US</code>, to instantiate and set a Locale on the date formatter, we override the defaults the user has specified in the system preferences.
This leads to a user from the UK, or anyone who prefers their apps in a variant of english, getting their dates formatted as <code class="highlighter-rouge">7/28/17</code> and being forced to use AM/PM again.</p>
<p>It also means if we use the same way to set the locale of a <code class="highlighter-rouge">NumberFormatter</code>, people from the UK would get <code class="highlighter-rouge">$</code> as their currency symbol. And while people from Germany and Austria format a number like ten-thousand-and-a-half like this <code class="highlighter-rouge">10.000,5</code> and the UK and US format it like this <code class="highlighter-rouge">10,000.5</code>, German-speaking people from Switzerland format it like this: <code class="highlighter-rouge">10'000.5</code>.
But the Swiss canât seem to agree as people speaking french in Switzerland do it like this: <code class="highlighter-rouge">10Â 000.5</code>.</p>
<p>And while those are nice defaults for the separate regions, maybe some people would like to change it. Maybe I want my device-language to be German but I kinda like those slashes the people from the UK have and because Iâm programming a lot Iâm actually more used to having <code class="highlighter-rouge">.</code> as a decimal separator. So I just go to the system preferences and change my region to <code class="highlighter-rouge">U.K.</code> but keep the language as <code class="highlighter-rouge">Deutsch</code>.</p>
<p>In macOS you can even configure your own date and number formats to use, if you like. While this is not yet possible on iOS, who says it wonât be introduced with the next iOS version?</p>
<p>And thatâs not all, I can also change the calendar. Maybe I want to try out an islamic calendar. There are several choices, I will go with the <code class="highlighter-rouge">islamicTabular</code> one and get <code class="highlighter-rouge">05.11.1438 AH</code> for the date above. And while we are at it, if I change my language to arabic the number symbols also change, so it looks like this: <code class="highlighter-rouge">ŮĽ Ř°Ů. ŮŘ ŮĄŮ¤ŮŁŮ¨ ŮŮ</code>. I hope you all have browsers with proper unicode-support.</p>
<h2>How to do it correctly</h2>
<p>So, how can we support all the fancy configurations a user has made? If I canât get the Locale-code from the language-file how am I supposed to figure out what the user wants? What Locale am I supposed to set on that date formatter?</p>
<p>Well, thatâs actually really easy: Apple provides <code class="highlighter-rouge">[NSLocale currentLocale]</code> which contains all the settings the user made. So you can just use that. But you know what? If you create a new <code class="highlighter-rouge">DateFormatter</code> (or any other Formatter for that matter), thatâs actually the default. So all you need to do to support all of this awesomeness: <strong>Donât break it!</strong></p>
<p>Do not set the <code class="highlighter-rouge">locale</code> property if you donât know what you are doing and how locales work.</p>
<h2>Other DON'Ts </h2>
<ul>
<li>Do not set the <a href="https://developer.apple.com/documentation/foundation/dateformatter/1413514-dateformat"><code class="highlighter-rouge">dateFormat</code></a> property on DateFormatters that are formatting user-visible dates. Only use it for formatting dates sent to other computers that need a fixed format. And in that case you probably want <a href="https://developer.apple.com/documentation/foundation/iso8601dateformatter"><code class="highlighter-rouge">ISO8601DateFormatter</code></a> anyway.</li>
<li>Do not rely on strings produced by formatters having a specific length in your UI. As you can see above there are lots of different versions.</li>
<li>In general, do not assume any region, calendar, currency, decimal delimiter, number symbol, date format etc. for your users.</li>
<li>Donât use <a href="https://developer.apple.com/documentation/foundation/locale/2293155-preferredlanguages"><code class="highlighter-rouge">preferredLanguages</code></a> to figure out which language to request from a server that supplies data to your app.
<code class="highlighter-rouge">preferredLanguages</code> contains all the userâs languages including the ones your app doesnât support.
So if the userâs device is set to French, but your app only supports English, but your server supports French, your app will request French data from the server and suddenly mix English and French.
<ul>
<li>To figure out which of your supported languages the user prefers use <a href="https://developer.apple.com/documentation/foundation/bundle/1413220-preferredlocalizations"><code class="highlighter-rouge">[[NSBundle mainBundle] preferredLocalizations]</code></a></li>
</ul>
</li>
</ul>
<h2>DOs</h2>
<ul>
<li>Do use Formatters. Donât event think about trying to format <a href="https://developer.apple.com/documentation/foundation/numberformatter">numbers</a>, <a href="https://developer.apple.com/documentation/foundation/dateformatter">dates</a>, <a href="https://developer.apple.com/documentation/foundation/dateintervalformatter">date intervals</a>, <a href="https://developer.apple.com/documentation/foundation/numberformatter">currencies</a>, <a href="https://developer.apple.com/documentation/foundation/personnamecomponentsformatter">person names</a> or <a href="https://developer.apple.com/documentation/foundation/measurementformatter">any kind of measurements</a>.
No, not even if your app should only be released in one single country that only speaks a single language (how many of those are there?).
And <code class="highlighter-rouge">[NSString stringWithFormat:"Your number: %d", theNumber]</code> (or string interpolation in Swift) counts as ârolling your own formatterâ<sup id="fnref:localizedStringWithFormat"><a href="#fn:localizedStringWithFormat" class="footnote">3</a></sup>.
Apple did a really good job taking care of all those variants and edge-cases.
Take advantage of it!
<ul>
<li>The <a href="https://developer.apple.com/documentation/foundation/datecomponentsformatter"><code class="highlighter-rouge">DateComponentsFormatter</code></a> is good for formatting durations and things like âx minutes remainingâ. However, it has some limitations, e.g. it cannot create a string like âx minutes agoâ.</li>
</ul>
</li>
<li>If you need more fine-grained control about the date-format (do you really?), use the <a href="https://developer.apple.com/documentation/foundation/dateformatter/1417087-setlocalizeddateformatfromtempla"><code class="highlighter-rouge">setLocalizedDateFormatFromTemplate</code></a>-method to specify the components you need but let the <code class="highlighter-rouge">DateFormatter</code> take care of the ordering.</li>
</ul>
<h2>Further Reading</h2>
<ul>
<li>Download <a href="https://blog.cocoafrog.de/assets/posts/how-not-to-do-localization/Formatters.playground.zip">this playground</a> and play around with a few formatters to get a better feel for them.</li>
<li>Take a look at Appleâs <a href="https://developer.apple.com/library/content/documentation/MacOSX/Conceptual/BPInternational/Introduction/Introduction.html#//apple_ref/doc/uid/10000171i">Internationalization and Localization</a> Guide.
<ul>
<li>The <a href="https://developer.apple.com/library/content/documentation/MacOSX/Conceptual/BPInternational/SpecifyingPreferences/SpecifyingPreferences.html#//apple_ref/doc/uid/10000171i-CH12-SW2">Reviewing Language and Region Seetings</a> chapter gives a nice overview how the calendar app behaves for different languages, regions and calendars.</li>
</ul>
</li>
<li><a href="https://en.wikipedia.org/wiki/ISO_639">ISO 639 for language codes</a></li>
<li><a href="https://en.wikipedia.org/wiki/ISO_3166">ISO 3166 for country codes</a></li>
<li><a href="https://en.wikipedia.org/wiki/ISO_15924">ISO 15924 for script codes</a></li>
<li><a href="https://en.wikipedia.org/wiki/ISO_4217">ISO 4217 for currency codes</a></li>
<li><a href="https://developer.apple.com/videos/play/wwdc2016/201/">Internationalization Best Practices</a> from WWDC 2016</li>
</ul>
<div class="footnotes">
<ol>
<li id="fn:dateFormatFootnote">
<p>This uses the short <code class="highlighter-rouge">dateStyle</code> and <code class="highlighter-rouge">timeStyle</code> of <a href="https://developer.apple.com/documentation/foundation/dateformatter"><code class="highlighter-rouge">DateFormatter</code></a>. The longer styles already contain month names in some cases and I wanted to focus on the order of the components and the delimiters, not translations of names. <a href="#fnref:dateFormatFootnote" class="reversefootnote">↩</a></p>
</li>
<li id="fn:peopleFromFootnote">
<p>Whenever Iâm referring to âpeople fromâ in this post what I actually mean is âmany people fromâ or âthe default in this country isâ. And the âdefaultâ Iâm describing is the result of an iOS <code class="highlighter-rouge">DateFormatter</code> or <code class="highlighter-rouge">NumberFormatter</code> set to the specific language and region. It might be that Apple got their defaults wrong for one of my examples, in that case, please let me know. <a href="#fnref:peopleFromFootnote" class="reversefootnote">↩</a></p>
</li>
<li id="fn:localizedStringWithFormat">
<p>If you really, <em>really</em> need to use <code class="highlighter-rouge">stringWithFormat:</code> for a string with numbers displayed to a user <em>at least</em> use <a href="https://developer.apple.com/documentation/foundation/nsstring/1497301-localizedstringwithformat"><code class="highlighter-rouge">localizedStringWithFormat:</code></a> <a href="#fnref:localizedStringWithFormat" class="reversefootnote">↩</a></p>
</li>
</ol>
</div>Joachim KurzThe content of this blog post applies to all Apple platforms and to most other user-facing operating systems and frameworks. For brevity, Iâll use iOS as an example but almost everything in here can be done similarly on other platforms.