├── Closures in API design ├── xcode.gif ├── wikipedia.png ├── good-quick-doc.png ├── quick-documentation.png └── Closures in API design.md ├── Building Swift frameworks ├── build-phases.png ├── import-paths.png ├── new-project.png └── Building Swift frameworks.md ├── LICENSE └── Empathy coding └── Empathy coding.md /Closures in API design/xcode.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hpique/Talks/HEAD/Closures in API design/xcode.gif -------------------------------------------------------------------------------- /Closures in API design/wikipedia.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hpique/Talks/HEAD/Closures in API design/wikipedia.png -------------------------------------------------------------------------------- /Building Swift frameworks/build-phases.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hpique/Talks/HEAD/Building Swift frameworks/build-phases.png -------------------------------------------------------------------------------- /Building Swift frameworks/import-paths.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hpique/Talks/HEAD/Building Swift frameworks/import-paths.png -------------------------------------------------------------------------------- /Building Swift frameworks/new-project.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hpique/Talks/HEAD/Building Swift frameworks/new-project.png -------------------------------------------------------------------------------- /Closures in API design/good-quick-doc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hpique/Talks/HEAD/Closures in API design/good-quick-doc.png -------------------------------------------------------------------------------- /Closures in API design/quick-documentation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hpique/Talks/HEAD/Closures in API design/quick-documentation.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /Empathy coding/Empathy coding.md: -------------------------------------------------------------------------------- 1 | ## Intro 2 | 3 | Hi! My name is Hermes Pique and I work at Facebook. Facebook is a great company that allows me to do many things, and one of them is help with accessibility. 4 | 5 | Today I'm going to try something different. There's going to be no slides in this presentation. Nothing to see. And we're going to do a couple of activities. So I kindly ask you to close your laptops, and listen closely. 6 | 7 | Let's talk about something that is deeply related with accessibility but that is not often associated with software: empathy. 8 | 9 | By “empathy” I mean the textbook definition of the word. That is, empathy as the capacity to understand another persons' perspective. 10 | 11 | To illustrate, I'd like us to do a quick experiment. 12 | 13 | ## Activity: Credit Card Numbers 14 | 15 | Take out any credit or debit card and hold it with your hands. 16 | 17 | So there's a plastic card in front of you. Now close your eyes, and try to read the embossed numbers only using your fingers. 18 | 19 | One by one. Picture the numbers in your head as you discover them. 4, 9, 4, 0. No need to say them out loud, just doing it to illustrate. 20 | 21 | I'll give you a minute. 22 | 23 | Got it? Easy? Hard? Probably a bit harder than you might have expected. 24 | 25 | Congratulations! You put yourself in the position of someone who can't see. 26 | 27 | Now, the embossing in credit cards is not for blind people. It's there for imprinting. But embossing is the basis for braille, and your capacity to understand braille readers -your empathy for them-, has probably increased a little bit. 28 | 29 | ## The Disconnect 30 | 31 | Why is empathy important? As developers, there's always a disconnect between the way we experience our products and the way our users do. 32 | 33 | In our world, in the iOS development world, this disconnect starts with the Simulator. Which is where we spend most of our time testing apps. 34 | 35 | The Simulator has access to all of the host computer resources. It never runs out of space, doesn't have other apps running in background. It's fast, allows us to iterate quickly. And we love it for it! Ask any Android developer what they think of their emulators. 36 | 37 | But it doesn't stop with the Simulator. We tend to have the latest and greatest devices and OS. Who here has an iPhone 6 or 6 Plus? Raise your hand. iPhone 5? More than 50% of iPhone users still have an iPhone 5S or lower. That doesn't match what we see in this room. We might have even an iPhone 6S already! 38 | 39 | It's not just about the device. We're tech savvy and work in a booming industry. We will most likely have a very good data plan and the best Wifi available. 40 | 41 | And it's not even about technology. Most of us live in a first-world country and statistically motor or visual impairments are rare among us. We're a very specific and fortunate subset of the world. 42 | 43 | ## A Vicious Circle 44 | 45 | The disconnect between us and the people who use our apps is dangerous. 46 | 47 | We are the first user of our creations, and it's natural for us to think of -well- us when designing or coding apps. Yet, we represent a very small subset of people. This is specifically relevant for developers who work in isolation, such as independent app developers. 48 | 49 | Still, most of the time we make conscious decisions to reach audiences different than us. Development time and resources are limited, so we pick our battles. Do we support iOS 8? iOS 7? That's 10% of iOS users to date. What about languages? English? Español? Right-to-left languages? If we have a test plan, shall we be include accessibility support in it? 50 | 51 | Those are all conscious decisions, and there are very good reasons to restrict your audience, specially in startups where focus is key. 52 | 53 | It's the unconscious decisions that are most problematic. Did we notice when our app binary size grew above 100MB? Or when the sever started sending high quality images instead of thumbnails? 54 | 55 | If we're not careful, we might lose whole countries whose citizens don't have easy access to Wifi or who have bad connections in average, like Bolivia, to name one. 56 | 57 | Eventually this becomes a vicious circle. We target a limited audience, which excludes the rest of the world. Since the rest of the world doesn't use our app, we don't hear about them, then we don't pay attention to their needs, and we never encounter their problems ourselves, so we keep building for the limited audience we first picked. 58 | 59 | We're leaving people out, and we might not even notice. 60 | 61 | The only way to break this vicious circle is with empathy. 62 | 63 | ## The Visually Impaired 64 | 65 | I want to focus on one specific kind of user as an example of applying empathy to our work: visually impaired people. This is because I had the opportunity of doing a lot of work with them in mind at my current job. 66 | 67 | The WHO -the World Health Organization- estimates 285 million people with visual impairment, of which 39 millions are blind and the rest have low vision. In Spain alone we have 1 million. Not all of these are iPhone users of course, but the more accessible apps we make, the more reasons they will have to get an iPhone. 68 | 69 | You might think this number is not big enough. Thinking of impaired users as numbers misses one very important thing. The effort you put into them makes a much bigger impact in their life. 70 | 71 | Making your app delightful with animations can make the difference between a 4 star and a 5 star rating. 72 | 73 | Making you app accessible makes the difference between people with impairments being able to use your app or not at all. An accessibility user is worth much more than the number one, and it doesn't require much effort, just empathy. 74 | 75 | So how do we gain empathy for the visually impaired? If you have the opportunity, I wholeheartedly recommend witnessing a blind person using their smartphone. I had the opportunity a handful of times and learned two core things. 76 | 77 | That performing very simple operations in apps is often a struggle. But more importantly, I learned that making this better for them is within my reach. 78 | 79 | If you don't know anyone blind with a smartphone, there's a more immediate way to increase your empathy for low vision or blind users. Simply, use your app as they would do. 80 | 81 | ## Screen Readers 82 | 83 | Visually impaired people use screen readers. A screen reader is one form of assistive technology that reads back what's on the screen. Typically, ensuring compatibility with a screen reader will also ensure compatibility with other kinds of assistive technologies. 84 | 85 | The iOS screen reader is called VoiceOver and it's used by both the visually and motor impaired. 86 | 87 | VoiceOver is top of class when it comes to screen readers, and it's one of the reasons why many visually impaired people opt for iPhone devices. 88 | 89 | Not only VoiceOver describes elements on the screen, it also redefines touch gestures to interact without requiring sight or precision. 90 | 91 | The 3 most basic gestures are: tap to describe an element, double tap to select it -this is the equivalent of a tap without VoiceOver-, and swipe to focus on the next or previous element. There's more gestures, but those are the most used. 92 | 93 | To control VoiceOver there's a framework called UIAccessibility. I'm not going to cover the APIs, there's great WWDC talks that do that. Instead I'm going to do something much more useful. I'm going to teach you the most convenient way to turn VoiceOver on and off. 94 | 95 | ## Activity: Accessibility Shortcut 96 | 97 | Please take out your iPhone this time and unlock it. 98 | 99 | Go to Settings and then General. 100 | 101 | Within General look for the Accessibility option. Select it. 102 | 103 | The Accessibility page has a lot of interesting options. If you haven't already, I recommend you to explore them. Right now we're going to ignore them and scroll all the way to the bottom to Accessibility Shortcut. Select it. 104 | 105 | The Accessibility Shortcut defines the action of a home button triple tap. Please select VoiceOver. 106 | 107 | Close Settings and triple tap the home button. Tap, tap, tap. 108 | 109 | This will turn VoiceOver on. It might take a few seconds. Play on the screen with the 3 gestures I mentioned before. Tap to describe the current element, double tap to select it, swipe to change focus. I'll give you a minute. 110 | 111 | Triple tap the home button again to deactivate VoiceOver. 112 | 113 | ## Labeling 114 | 115 | Now you're 3 taps away from testing your app with VoiceOver at any time. No need to do it now. But once you do, try closing your eyes. 116 | 117 | The first thing you might notice once you test your app with VoiceOver is that a lot of it just works. UIKit components support accessibility by default and will often give VoiceOver the best possible description for themselves. 118 | 119 | Still, VoiceOver might need a little guidance. For example, custom icons need to be labeled. This is because VoiceOver can't process images. It doesn't know that a gear is a settings button, or that a plus mean add a post. It might know they're buttons, but not what they do. 120 | 121 | So after the conference you go back to your place and try your app with VoiceOver. More or less everything you expect is there. You might be tempted to think that labelling everything in your app is enough. And yes, it might make your app accessible, functional. 122 | 123 | Labelling is perfunctory work. It's something you do tick out accessibility from a checklist. That's not what empathy is about. I'll give you 2 examples. 124 | 125 | ## Example: Wiggling Password Field 126 | 127 | First example. Consider a login form with a password field. When the user enters an invalid password the password field will wiggle, and perhaps change color. This is an example of using animations as visual cues, and giving something as mundane as a password field a little bit of personality. 128 | 129 | For blind users, neither the animation nor the change in color will be perceptible. If we don't do anything VoiceOver users might be able to figure out that something went wrong and retry anyway. 130 | 131 | Or maybe not, and they might stop using the app. This is not a great experience, and defeats the intention we had when adding the wiggle animation. 132 | 133 | How do we convey the same information in VoiceOver mode? How can we give VoiceOver users a similar experience? Fortunately the solution is very simple. We can play a nice error sound and make VoiceOver announce what just happened: "Invalid user or password." The sound takes replaces the animation. 134 | 135 | This small work goes beyond labelling. We're actually designing behaviour specifically for VoiceOver users. It's still much easier than coding a wiggling animation, though. 136 | 137 | It's easy to break things for some when we have only ourselves in mind when we test. Would we have detected that the wiggling password field was not accessible if we don't use the app with VoiceOver ourselves? Probably not. 138 | 139 | ## Example: Gmail 140 | 141 | Second example. We're going to look at a real app you might know. Let me turn on VoiceOver on my device. 142 | 143 | Action: Toggle VoiceOver. 144 | 145 | VoiceOver: VoiceOver On, Gmail, double tap to open. 146 | 147 | Action: Double tap Gmail. 148 | 149 | VoiceOver: Gmail, Inbox. 150 | 151 | I'm talking about Gmail. 152 | 153 | Here is a search of my recent flights confirmations with easyJet, rendered in a nice table view. I'm going to go over each email using the swipe gesture to change focus. Listen carefully. 154 | 155 | Action: Select first email, then multiple Swipes. 156 | 157 | VoiceOver: “easyJet: Your booking...”, Select, checkbox, unchecked, double-tap to toggle setting, Star, checkbox, unchecked, double tap to toggle setting , “easyJet: Your booking...”, Select, checkbox, unchecked, double-tap to toggle setting, Star, checkbox, unchecked, double tap to toggle setting , “easyJet: Your booking...”, Select, checkbox, unchecked, double-tap to toggle setting, Star, checkbox, unchecked, double tap to toggle setting. 158 | 159 | You might have noticed that after each email, we focus two checkboxes. This email list is certainly accessible, but it's a bit cumbersome to navigate, as we have to do 3 swipes per message. Also, when swiping quickly it's easy to forget the email that corresponds to a given checkbox. 160 | 161 | For non VoiceOver users the cost of showing the Select and Favorite options for each email is relatively small in terms of UI space. It's just a checkbox at each side of the email. 162 | 163 | For VoiceOver users, it's as if each email required 3 table view cells. 164 | An alternative would be to hide these checkboxes from VoiceOver and use double tap and hold -the VoiceOver equivalent for long press- to make the corresponding actions show up as a dialog. This is what Twitter does for tweet actions, for example. 165 | 166 | So with this change a VoiceOver user would go from email to email with swiping. If they want to select or favorite an email, they can double tap and hold to access these options. To make this discoverable, we can add a hint for each message: “Double tap and hold for to select or favorite”. 167 | 168 | Would we have thought of these improvements if we don't seriously use the app with a screen reader? Probably not. 169 | 170 | ## Conclusions 171 | 172 | The best way to discover how to improve our app for VoiceOver is to use it with VoiceOver. There's no magic. Without training, it's practically impossible to detect problems like the wiggling password field or the cumbersome email list from code alone. 173 | 174 | By using apps with VoiceOver we put ourselves in the shoes of impaired users. This is what empathy is all about, and it's a principle that applies to all types of users. 175 | 176 | Are you doing right-to-left support properly? Change your language to Hebrew and open your app. See what brakes. 177 | 178 | Want to see how it really feels to use your app on the go? Try it with a 3G connection on the device, not Wifi in the Simulator. Turn off Wifi for a week. 179 | 180 | If we limit ourselves to our own perspective, to our code and our tools, we will either ignore the needs of others, or simply do perfunctory work. 181 | 182 | There is a natural disconnect between the way we experience our apps and the way our users do. So we need to make an extra effort to be empathetic. 183 | 184 | The first step to empathy is awareness. Awareness that others have different perspectives. I hope that if nothing else, this talk prompts you to explore how others really perceive your app, be it an average user, or someone whose life will be significantly better thanks to your efforts. 185 | 186 | And by doing so, it might change your life as well. I know it did for me. 187 | Thank you. And if you’re interested in making your app accessible, I’ll be around if you want to talk about it or check it out together. -------------------------------------------------------------------------------- /Building Swift frameworks/Building Swift frameworks.md: -------------------------------------------------------------------------------- 1 | # Building *Swift* frameworks 2 | 3 | #### @hpique 4 | 5 | --- 6 | 7 | > iOS developers can now create dynamic frameworks. Frameworks are a collection of code and resources to encapsulate functionality that is valuable across multiple projects. 8 | -- [New Features in Xcode 6](https://developer.apple.com/library/ios/documentation/DeveloperTools/Conceptual/WhatsNewXcode/Articles/xcode_6_0.html) 9 | 10 | --- 11 | 12 | ## No more header tinkering! 13 | 14 | ![](http://media.giphy.com/media/ITnn2IenMv7SE/giphy.gif) 15 | 16 | --- 17 | 18 | ## Creating a new framework 19 | 20 | ![inline fit](new-project.png) 21 | 22 | --- 23 | 24 | # Swift frameworks 25 | 26 | * *CAN* mix-and-match Objective-C code 27 | * *CAN* import all Objective-C system frameworks (e.g., UIKit) 28 | * *CAN* import C libraries defined as modules (e.g., Dispatch) 29 | * *CAN'T* use other libraries directly (e.g., CommonCrypto) 30 | 31 | See: *[Swift and Objective-C in the Same Project](https://developer.apple.com/library/ios/documentation/swift/conceptual/buildingcocoaapps/MixandMatch.html#//apple_ref/doc/uid/TP40014216-CH10-XID_77)* 32 | 33 | --- 34 | 35 | ## What about *static libraries*? 36 | 37 | --- 38 | 39 | > Xcode does not support building static libraries that include Swift code. 40 | -- [Xcode 6.0 Beta 5 release notes](http://mazur.me/SwiftInFlux/docs/beta5.pdf) 41 | 42 | --- 43 | 44 | # Let's try anyway! 45 | 46 | 47 | Add a Swift file to a static library and build: 48 | 49 | ``` 50 | error: /Applications/Xcode.app/Contents/Developer/Toolchains/ 51 | XcodeDefault.xctoolchain/usr/bin/libtool: 52 | unknown option character `X' in: -Xlinker 53 | ``` 54 |
55 | 56 | 57 | Follow: *[http://stackoverflow.com/q/24020986/143378](http://stackoverflow.com/q/24020986/143378)* 58 | 59 | --- 60 | 61 | # Using frameworks 62 | 63 | --- 64 | 65 | # No *CocoaPods* 66 | 67 | ![](https://farm5.staticflickr.com/4083/4966109357_eb24f7b6a1_b.jpg) 68 | 69 | --- 70 | 71 | # *Step 1*: Add the framework as a subproject 72 | 73 | Drag the framework project into your app project. 74 | 75 | --- 76 | 77 | # *Step 2*: Set the framework as build dependency 78 | 79 | 1. Select your project and then your app target 80 | 2. Open the *Build Phases* panel 81 | 3. Expand *Target Dependencies* 82 | 4. Click *+* and select the framework 83 | 84 | --- 85 | 86 | # *Step 3*: Copy the framework during build 87 | 88 | From *Build Phases*: 89 | 90 | 1. Click on the *+* button at the top left of the panel and select *New Copy Files Phase* 91 | 2. Set *Destination* to *Frameworks* 92 | 3. Click *+* and select the framework 93 | 94 | --- 95 | 96 | ![](build-phases.png) 97 | 98 | --- 99 | # Finally 100 | 101 | 102 | ```Swift 103 | import MyFramework 104 | ``` 105 | 106 | 107 | --- 108 | 109 | # Access control 110 | 111 | --- 112 | 113 | > [I]f you are writing a single-target app, you may not need to specify explicit access control levels at all. 114 | -- [The Swift Programming Language](https://developer.apple.com/library/mac/documentation/Swift/Conceptual/Swift_Programming_Language/AccessControl.html) 115 | 116 | --- 117 | 118 | ## But you're writing a framework 119 | 120 | ![](http://i.ytimg.com/vi/8PKR6lx79r4/maxresdefault.jpg) 121 | 122 | --- 123 | 124 | ## Access levels 125 | 126 | * *Public*: accessible everywhere 127 | * *Internal* (default): accessible within any source file from the defining module, but not outside of that module 128 | * *Private*: accessible only within its own defining source file 129 | 130 | --- 131 | 132 | ## We kind* of had that already 133 | 134 | In Objective-C land: 135 | 136 | * *Public*: declared in public headers 137 | * *Internal*: declared in project headers 138 | * *Private*: declared in implementation files 139 | 140 | \* (without enforcing access control) 141 | 142 | --- 143 | 144 | ### *Guiding principle* 145 | 146 | 147 | ### No entity can be defined in terms of another entity that has a lower (more restrictive) access level 148 | 149 | --- 150 | 151 | # BAD 152 | 153 | ```Swift 154 | private struct PrivateThing {} 155 | 156 | struct InternalThing {} 157 | 158 | public struct PublicThing { 159 | 160 | let t1 : PrivateThing 161 | public let t2 : InternalThing 162 | func doSomething(t : PrivateThing) {} 163 | public func doSomethingElse(t : InternalThing) {} 164 | 165 | } 166 | ``` 167 | 168 | --- 169 | 170 | # Example 171 | 172 | Consider a framework with a public function that fetches a string asynchronously. 173 | 174 | The function returns a *Fetch* object that can have a callback for success. 175 | 176 | ```Swift 177 | let fetch = fetchSomething().onSuccess { println($0) } 178 | ``` 179 | 180 | Let's define the access levels of the *Fetch* class. 181 | 182 | --- 183 | 184 | ```Swift 185 | class Fetch { 186 | 187 | var onSuccess : ((String) -> ())? 188 | 189 | var state : FetchState = FetchState.Pending 190 | 191 | func onSuccess(onSuccess : ((String) -> ())) -> Self { 192 | self.onSuccess = onSuccess 193 | switch self.state { 194 | case FetchState.Success(let value): 195 | onSuccess(value) 196 | default: break 197 | } 198 | return self 199 | } 200 | 201 | func succeed(value : String) { 202 | self.state = FetchState.Success(value) 203 | self.onSuccess?(value) 204 | } 205 | } 206 | ``` 207 | 208 | 209 | --- 210 | 211 | ```Swift 212 | public class Fetch { 213 | 214 | private var onSuccess : ((String) -> ())? 215 | 216 | private var state : FetchState = FetchState.Pending 217 | 218 | public func onSuccess(onSuccess : ((String) -> ())) -> Self { 219 | self.onSuccess = onSuccess 220 | switch self.state { 221 | case FetchState.Success(let value): 222 | onSuccess(value) 223 | default: break 224 | } 225 | return self 226 | } 227 | 228 | func succeed(value : String) { 229 | self.state = FetchState.Success(value) 230 | self.onSuccess?(value) 231 | } 232 | } 233 | ``` 234 | 235 | --- 236 | 237 | # Namespacing 238 | 239 | --- 240 | 241 | > Namespacing is implicit in swift, all classes (etc) are implicitly scoped by the module (Xcode target) they are in. no class prefixes needed 242 | -- [Chris Lattner](https://twitter.com/clattner_llvm/status/474730716941385729) 243 | 244 | --- 245 | 246 | ![](http://media.giphy.com/media/w6MNHOoLEzBVm/giphy.gif) 247 | 248 | --- 249 | 250 | # Framework namespace 251 | 252 | The framework name *is* the namespace. 253 | 254 | When importing a framework types can be accessed *implicitly* or *explicitly*. 255 | 256 | --- 257 | 258 | # Fun with namespaces 259 | 260 | Consider: 261 | 262 | A framework *FrameworkA* that defines a public type *Thing*. 263 | 264 | An app *MyApp* that imports *FrameworkA*. 265 | 266 | --- 267 | 268 | ```Swift 269 | import FrameworkA 270 | 271 | var a : FrameworkA.Thing? 272 | var t : Thing? 273 | ``` 274 | 275 |
276 | 277 | The two variables have the same type. 278 | 279 | --- 280 | 281 | ```Swift 282 | import FrameworkA 283 | 284 | public class Thing { } 285 | 286 | var a : FrameworkA.Thing? 287 | var t : Thing? 288 | var t2 : MyApp.Thing? 289 | ``` 290 | 291 |
292 | 293 | What is the type of *t*? 294 | 295 | --- 296 | 297 | # Namespace conflict 298 | 299 | 300 | What if *MyApp* also imports a *FrameworkB* that also defines a public type *Thing*? 301 | 302 | ```Swift 303 | import FrameworkA 304 | import FrameworkB 305 | 306 | var a : Thing? 307 | ``` 308 | 309 | We get a nice error. 310 | 311 | --- 312 | 313 | ``` 314 | 'Thing' is ambiguous for type lookup in this context 315 | let t1 : Thing? = nil 316 | ^~~~~ 317 | FrameworkA.Thing:1:7: note: found this candidate 318 | class Thing 319 | ^ 320 | FrameworkB.Thing:1:7: note: found this candidate 321 | class Thing 322 | ^ 323 | ``` 324 | 325 | --- 326 | 327 | Solution: use fully qualified names. 328 | 329 | ```Swift 330 | import FrameworkA 331 | import FrameworkB 332 | 333 | public class Thing { } 334 | 335 | var a : FrameworkA.Thing? 336 | var t : Thing? 337 | var b : FrameworkB.Thing? 338 | ``` 339 | 340 |
341 | 342 | *t* is of type *MyApp.Thing*. 343 | 344 | --- 345 | 346 | # Namespace shenanigans 347 | 348 | 349 | What if *FrameworkB* also defines a public type named... 350 |
351 | *FrameworkA* 352 |
353 | ? 354 | 355 | ![right](http://media.giphy.com/media/NbgeJftsErO5q/giphy.gif) 356 | 357 | --- 358 | 359 | ```Swift 360 | import FrameworkA 361 | import FrameworkB 362 | 363 | var a : FrameworkA.Thing? 364 | var b : FrameworkB.Thing? 365 | ``` 366 | 367 | We get a misleading error: `'Thing' is not a member type of 'FrameworkA'` 368 | 369 | --- 370 | 371 | Workaround: define a *typealias* somewhere else in *MyApp*. 372 | 373 | ```Swift 374 | import FrameworkA 375 | 376 | typealias ThingA = Thing 377 | ``` 378 | 379 | Then: 380 | 381 | ```Swift 382 | import FrameworkA 383 | import FrameworkB 384 | 385 | var a : ThingA? 386 | var b : FrameworkB.Thing? 387 | ``` 388 | 389 | --- 390 | 391 | # Best practices 392 | 393 | *Limit the scope** of types whenever possible. 394 | 395 | Even more so for *public* types. 396 | 397 | Don't define a type named like the framework (Haneke oops!). 398 | 399 | 400 |
401 | 402 | #### *(this is always a good practice) 403 | 404 | 405 | --- 406 | 407 | # Limiting the scope 408 | 409 | --- 410 | 411 | # BAD 412 | 413 | ```Swift 414 | public static let ErrorDomain = "io.haneke" 415 | ``` 416 | 417 | # *GOOD* 418 | 419 | ```Swift 420 | public struct HanekeGlobals { 421 | public static let ErrorDomain = "io.haneke" 422 | } 423 | 424 | let s = HanekeGlobals.ErrorDomain 425 | ``` 426 | --- 427 | 428 | # BAD 429 | 430 | ```Swift 431 | public enum ScaleMode { 432 | case Fill 433 | case AspectFit 434 | case AspectFill 435 | } 436 | 437 | public struct ImageResizer { 438 | 439 | public var scaleMode: ScaleMode 440 | 441 | } 442 | ``` 443 | 444 | --- 445 | 446 | # *GOOD* 447 | 448 | ```Swift 449 | 450 | public struct ImageResizer { 451 | 452 | public enum ScaleMode { 453 | case Fill 454 | case AspectFit 455 | case AspectFill 456 | } 457 | 458 | public let scaleMode: ScaleMode 459 | 460 | } 461 | ``` 462 | 463 | --- 464 | 465 | # What about generics? 466 | 467 | Generic types can't define static types. This fails to compile: 468 | 469 | ```Swift 470 | public class NetworkFetcher { 471 | 472 | public enum ErrorCode : Int { 473 | case InvalidData = -400 474 | case MissingData = -401 475 | case InvalidStatusCode = -402 476 | } 477 | 478 | } 479 | ``` 480 | 481 | --- 482 | 483 | Workaround: abuse the globals struct from before. 484 | 485 | ```Swift 486 | extend HanekeGlobals { 487 | public struct NetworkFetcher { 488 | public enum ErrorCode : Int { 489 | case InvalidData = -400 490 | case MissingData = -401 491 | case InvalidStatusCode = -402 492 | } 493 | } 494 | } 495 | 496 | public class NetworkFetcher {} 497 | ``` 498 | 499 | --- 500 | 501 | ## Do extensions need prefixes? 502 | 503 | --- 504 | 505 | # Prefixing extensions 506 | 507 | Extensions of Objective-C types (NSObject subclasses) are categories and as such need prefixes. 508 | 509 | ```Swift 510 | extension UIImage { 511 | 512 | public func hnk_hasAlpha() -> Bool { ... } 513 | 514 | } 515 | ``` 516 | 517 | See: *[http://stackoverflow.com/q/25567743/143378](http://stackoverflow.com/q/25567743/143378)* 518 | 519 | --- 520 | 521 | # Using C libraries 522 | 523 | --- 524 | 525 | # In an app project 526 | 527 | 528 | Easy. Simply import the library in the bridging header: 529 | 530 |
531 | 532 | ```ObjectiveC 533 | #import 534 | ``` 535 | 536 |
537 | 538 | More info: *[Swift and Objective-C in the Same Project](https://developer.apple.com/library/ios/documentation/swift/conceptual/buildingcocoaapps/MixandMatch.html#//apple_ref/doc/uid/TP40014216-CH10-XID_77)* 539 | 540 | --- 541 | 542 | ## But... Swift frameworks don't have a bridging header! 543 | 544 | ![](http://newsbusters7.s3.amazonaws.com/images/2014/July/bardem.jpg) 545 | 546 | 547 | --- 548 | 549 | ## Workaround A 550 | ## *Module map* 551 | 552 | --- 553 | 554 | *import* works with modules. So let's define a module for the C library. 555 | 556 | Using CommonCrypto as an example... 557 | 558 | --- 559 | 560 | # *STEP 1*: Define the module 561 | 562 | 1. Create a CommonCrypto directory inside the framework directory 563 | 2. Within, create a *module.map* file 564 | 565 | ```ruby 566 | module CommonCrypto [system] { 567 | header "/Applications/Xcode.app/Contents/Developer/Platforms/ 568 | iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator8.0.sdk/us 569 | r/include/CommonCrypto/CommonCrypto.h" 570 | link "CommonCrypto" 571 | export * 572 | } 573 | ``` 574 | 575 | --- 576 | 577 | # *STEP 2*: Set a import path 578 | 579 | 1. Go to the framework *Build Settings* 580 | 2. Find *Swift Compiler - Search Paths* 581 | 3. Add the CommonCrypto directory to *Import Paths* 582 | 583 | ![inline](import-paths.png) 584 | 585 | --- 586 | # Finally 587 | 588 | 589 | ```Swift 590 | import CommonCrypto 591 | 592 | func MD5(aString : String) -> String { 593 | if let data = aString.dataUsingEncoding(NSUTF8StringEncoding) { 594 | let result = NSMutableData(length: Int(CC_MD5_DIGEST_LENGTH)) 595 | let resultBytes = UnsafeMutablePointer(result.mutableBytes) 596 | CC_MD5(data.bytes, CC_LONG(data.length), resultBytes) 597 | let e = UnsafeBufferPointer(start: resultBytes, length: result.length) 598 | let MD5 = NSMutableString() 599 | for c in e { MD5.appendFormat("%02x", c) } 600 | return MD5 601 | } 602 | return "" 603 | } 604 | ``` 605 | 606 | --- 607 | # Limitations 608 | 609 | Projects that use the framework must repeat step 2. 610 | 611 | The given module map is not platform independent. Is it possible to make the header path relative to the current platform? 612 | 613 |
614 | 615 | Follow: *[http://stackoverflow.com/q/25248598/143378](http://stackoverflow.com/q/25248598/143378)* 616 | 617 | --- 618 | 619 | ## Workaround B 620 | ## *Bridging framework* 621 | 622 | --- 623 | 624 | Objective-C frameworks can use C libraries without any issues. So let's create one that wraps the C library. 625 | 626 | Using CommonCrypto as an example... 627 | 628 | --- 629 | 630 | # *STEP 1*: Create the bridging framework 631 | 632 | 1. Add a new target to the project. 633 | 2. Select Cocoa Touch Framework. 634 | 3. Name it *MyFrameworkBridge* and select Objective-C as a language. 635 | 636 | --- 637 | 638 | # *STEP 2*: Wrap the C library 639 | 640 | 1. Add a public wrapper class or category. 641 | 2. Import and use the library in the implementation file. 642 | 3. Import the wrapper header (e.g., *NSString+CommonCrypto.h*) in the framework header (*MyFrameworkBridge.h*). 643 | 644 | --- 645 | 646 | ```ObjectiveC 647 | #import "NSString+CommonCrypto.h" 648 | #import 649 | 650 | @implementation NSString(Haneke) 651 | 652 | - (NSString*)hnk_MD5String { 653 | NSData *data = [self dataUsingEncoding:NSUTF8StringEncoding]; 654 | unsigned char result[CC_MD5_DIGEST_LENGTH]; 655 | CC_MD5(data.bytes, (CC_LONG)data.length, result); 656 | NSMutableString *MD5String = [NSMutableString 657 | stringWithCapacity:CC_MD5_DIGEST_LENGTH * 2]; 658 | for (int i = 0; i < CC_MD5_DIGEST_LENGTH; i++) { 659 | [MD5String appendFormat:@"%02x",result[i]]; 660 | } 661 | return MD5String; 662 | } 663 | ``` 664 | 665 | --- 666 | 667 | # No cheating 668 | 669 | Importing the C library in a public header of the bridging framework will cause a compile error: 670 | 671 | `include of non-modular header inside framework module 'MyFrameworkBridge'` 672 | 673 | --- 674 | 675 | # Finally 676 | 677 | 678 | ```Swift 679 | import MyFrameworkBridge 680 | 681 | var s : NSString 682 | ... 683 | let MD5 = s.hnk_MD5String 684 | ``` 685 | 686 | --- 687 | # Limitations 688 | 689 | Projects that use the framework must also include the bridging framework. 690 | 691 | The wrapper methods of the bridging framework will be visible to projects who use the original framework. 692 | 693 |
694 | 695 | --- 696 | 697 | #BTW 698 | 699 | If you need to use simple cryptography such as MD5 in a framework, check out *[CryptoSwift](https://github.com/krzyzanowskim/CryptoSwift)*. 700 | 701 | --- 702 | 703 | # Unit testing 704 | 705 | --- 706 | 707 |
708 | ## "A limitation of the access control system is that unit tests cannot interact with the classes and methods in an application unless they are marked public." 709 | [Xcode 6.0 beta 4 release notes](http://ksm.github.io/SwiftInFlux/docs/beta4.pdf) 710 | 711 | ![right](http://media.giphy.com/media/uTLKbzPlxhF0Q/giphy.gif) 712 | 713 | --- 714 | 715 | > We're aware that our access control design isn't great for unit testing (and this was in the release notes), we're evaluating the situation to see what we can do. 716 | -- [Chris Lattner](https://devforums.apple.com/message/1010766#1010766) 717 | 718 | --- 719 | 720 | # No excuses 721 | ![](http://divas.ucoz.com/_nw/1/18244733.jpg) 722 | 723 | --- 724 | 725 | # Why test *internals*? 726 | 727 | Internals are not public API but they're "public" API within the scope of the framework. Do you always work alone? 728 | 729 | Helper functions/extensions/types will most likely internal and are great candidates for unit testing. 730 | 731 | ```Swift 732 | extension CGSize { 733 | func hnk_aspectFillSize(size: CGSize) -> CGSize { ... } 734 | } 735 | ``` 736 | 737 | --- 738 | 739 | # We shall unit test anyway 740 | 741 | 1. Add all the framework files to the test target. 742 | 2. Remove the framework from *Target Dependencies* and *Link Binary With Libraries* build phases of the test target (to prevent naming collisions). 743 | 744 | You can now test public and *internal* elements. 745 | 746 | --- 747 | 748 | # Thanks! 749 | 750 | #### @hpique -------------------------------------------------------------------------------- /Closures in API design/Closures in API design.md: -------------------------------------------------------------------------------- 1 | # Closures 2 | ## *In API design* 3 | #### { @hpique } 4 | 5 | ^ Hi. My name is Hermes Pique and I love using closures in my APIs. 6 | 7 | ^ I use them in my open source libraries RMStore and Haneke, and most recently at Facebook. 8 | 9 | ^ Today I'd like to share some of the things I learned about closures in Swift APIs. 10 | 11 | --- 12 | 13 | ![fit](http://what-when-how.com/wp-content/uploads/2011/06/tmpE62_thumb_thumb.jpg) 14 | 15 | ^ First let's go back in time. 16 | 17 | ^ This is James Clerk. A physicist and mathematician who worked on closure theory. 18 | 19 | --- 20 | 21 | ![fit](https://weightingfor50.files.wordpress.com/2013/06/brackets.jpg) 22 | 23 | ^ Clerk was also an ornithologist; he studied birds. 24 | 25 | ^ In 1925 he had to pick a character to denotate closures. 26 | 27 | ^ He choose the curly bracket because of this photograph. 28 | 29 | ^ So what do birds and closures have in common? 30 | 31 | --- 32 | 33 | # Agenda 34 | 35 | * Closures 36 | * @autoclosure 37 | * @noescape 38 | * Default values 39 | 40 | ![right, fit](http://media.giphy.com/media/B81XkL3dtnWTe/giphy.gif) 41 | 42 | ^ Nothing. I just made that up to have an excuse to use bird gifs. 43 | 44 | ^ And in between those gifs we're going to be talking about closures, the autoclosure and noescape attributes and the importance of default values. 45 | 46 | --- 47 | 48 | # Closures are *self-contained* blocks of functionality 49 | 50 | ^ Let's recap what closures are. Closures are self-contained blocks of functionality. 51 | 52 | --- 53 | 54 | # Using closures 55 | 56 | ```swift 57 | func showButton() { 58 | UIView.animateWithDuration(0.5, animations: { 59 | self.button.alpha = 1 60 | }, completion: { finished in 61 | if finished { 62 | self.button.enabled = true 63 | } 64 | }) 65 | } 66 | ``` 67 | 68 | ^ This is an example you might be familiar with. 69 | 70 | ^ animateWithDuration takes two closures. One to specify the animations, another to specify what to do when the animation finishes. 71 | 72 | ^ Closures are great for event callbacks like "animation completed". They're also useful to abstract implementation away. 73 | 74 | ^ The animateWithDuration function doesn't need to know the specifics of the animations to perform or the specifics of what to do when the animation finishes. 75 | 76 | --- 77 | 78 | # Trailing closures 79 | 80 | ```swift 81 | func showButton() { 82 | UIView.animateWithDuration(0.5, animations: { 83 | self.button.alpha = 1 84 | }) { finished in 85 | if finished { 86 | self.button.enabled = true 87 | } 88 | } 89 | } 90 | ``` 91 | 92 | ^ Closures are widely used in Swift and as such have lots of syntax optimizations. 93 | 94 | ^ One of my favorites is the trailing closure which allows us to write the last closure outside of the function call parenthesis and without the parameter name. 95 | 96 | --- 97 | 98 | # Closures *capture* the constants and variables used within 99 | 100 | ^ Another important characteristic of closures is that -unless told otherwise- they capture the constants and variables used within. 101 | 102 | --- 103 | 104 | ![fit](capture-error.png) 105 | 106 | ^ That means that in our example, self will be captured or retained by the closure until the completion closure executes. 107 | 108 | ^ This is made explicit by the compiler, who will complain if we don't use self to access instance variables. 109 | 110 | --- 111 | 112 | # Before closures 113 | ### *UIAlertView* 114 | 115 | ^ Of course, closures are not strictly needed but they can make our code better. 116 | 117 | ^ Let's look at how we could show an alert with APIs designed before closures. 118 | 119 | --- 120 | 121 | ```swift 122 | func showQuestionAlert() { 123 | questionAlert = UIAlertView(title: "Question", 124 | message: "Pigeons are the new cats?", 125 | delegate: self, 126 | cancelButtonTitle: nil, 127 | otherButtonTitles: "No", "Coo!") 128 | questionAlert.show() 129 | } 130 | ``` 131 | 132 | ^ You might be familiar with this code. We show an alert and we set a delegate on the alert to respond to events. 133 | 134 | --- 135 | 136 | ```swift 137 | // MARK: UIAlertViewDelegate 138 | 139 | func alertView(alertView: UIAlertView, 140 | clickedButtonAtIndex buttonIndex: Int) { 141 | if (alertView === questionAlert) { 142 | switch buttonIndex { ... } 143 | } 144 | } 145 | 146 | func alertView(alertView: UIAlertView, 147 | didDismissWithButtonIndex buttonIndex: Int) { 148 | if (alertView === questionAlert) { 149 | questionAlert = nil 150 | } 151 | } 152 | ``` 153 | 154 | ^ The delegate approach has a couple of problems the first of which is requiring to keep a reference to the alert view in case we show more than one alert with the same delegate. 155 | 156 | ^ Also we need to match buttons with actions by way of the button index. 157 | 158 | --- 159 | 160 | # After closures 161 | ### *UIAlertController* 162 | 163 | ^ Luckily iOS 7 introduced a closure-based API for alert views: UIAlertController. 164 | 165 | --- 166 | 167 | ```swift 168 | func showQuestionAlert() { 169 | let controller = UIAlertController(title: "Question", 170 | message: "Pigeons are the new cats?", 171 | preferredStyle: .Alert) 172 | 173 | let no = UIAlertAction(title: "No", style: .Default) { _ in 174 | ... 175 | } 176 | controller.addAction(no) 177 | 178 | let yes = UIAlertAction(title: "Coo!", style: .Default) { _ in 179 | ... 180 | } 181 | controller.addAction(yes) 182 | 183 | self.presentViewController(controller, animated: true) { ... } 184 | } 185 | ``` 186 | 187 | ^ This is how the same functionality looks like by leveraging closures and trailing closure notation. 188 | 189 | ^ We create an alert controller. Then we create actions, each of which has a closure to respond to their selection. Finally we present the view controller with a closure to handle the completion. 190 | 191 | ^ This code has none of the problems of the previous method. What you see here is the result of code locality. All the work fits in a single function. 192 | 193 | --- 194 | 195 | # Closures encourage *code locality* 196 | 197 | ^ This is one of the greatest benefits of closures: they encourage code locality. 198 | 199 | --- 200 | 201 | # Using closures in our APIs 202 | 203 | ^ By now we covered some of the benefits of closures. 204 | 205 | ^ Let's dig deeper on how to use them in our APIs. 206 | 207 | --- 208 | 209 | #@autoclosure 210 | 211 | ![inline, fit](http://media.giphy.com/media/HhjtNatuLEsP6/giphy.gif) 212 | 213 | ^ We begin with an attribute: autoclosure. 214 | 215 | --- 216 | 217 | # Inneficient `and` 218 | 219 | ```swift 220 | func getExpensiveBool() -> Bool { 221 | NSThread.sleepForTimeInterval(10.0) 222 | println("Birds!!!") 223 | return true 224 | } 225 | 226 | let result = and(false, getExpensiveBool()) 227 | 228 | > Birds!!! 229 | ``` 230 | 231 | ^ To illustrate think of an inefficient implementation of logical and that evaluates both values. If the right hand value is expensive to procure, we're doing more work than we need. 232 | 233 | --- 234 | 235 | # Short-circuit with closures 236 | 237 | ```swift 238 | func and(left: Bool, getRight : () -> Bool) -> Bool { 239 | if !left { 240 | return false 241 | } 242 | return getRight() 243 | } 244 | 245 | let result = and(false, { return getExpensiveBool() }) 246 | 247 | > 248 | ``` 249 | 250 | ^ We can use closures to implement short-circuiting. 251 | 252 | ^ We modify `and` to receive a closure that returns a boolean instead of a boolean directly. 253 | 254 | ^ Then we check the left value. If it's false, we simply return false. Only if the left value is true we check the right value. 255 | 256 | ^ This is great for performance, but a chore for the users of our API who would have to wrap the right value in a closure every time. 257 | 258 | --- 259 | 260 | # Use *@autoclosure* for parameter values that might not be used 261 | 262 | ^ Enter autoclosure. We use this attribute for parameter values that might not be needed. 263 | 264 | --- 265 | 266 | # Adding `@autoclosure` 267 | 268 | ```swift 269 | func and(left: Bool, @autoclosure getRight: () -> Bool) -> Bool 270 | 271 | let result = and(false, getExpensiveBool()) 272 | 273 | > 274 | ``` 275 | 276 | ^ Using autoclosure is as simple as adding it to the relevant parameter. 277 | 278 | ^ And so the API user doesn't need to wrap the parameter in a closure anymore. 279 | 280 | --- 281 | 282 | # `@autoclosure` in the Standard Library 283 | 284 | ```swift 285 | func &&(lhs: T, @autoclosure rhs: () -> Bool) -> Bool 286 | 287 | func assert(@autoclosure condition: () -> Bool, 288 | _ message: @autoclosure () -> String = default, 289 | file: StaticString = default, 290 | line: UWord = default) 291 | 292 | ``` 293 | 294 | ^ autoclosure is extensively used in the Standard Library. 295 | 296 | ^ The real `and` uses it. And so do assertions, which can be turned off and thus don't have to evaluate some of their arguments. 297 | 298 | --- 299 | 300 | # @noescape 301 | 302 | ![](http://julianware.com/main/wp-content/uploads/KarateKid.gif) 303 | 304 | ^ Another attribute of interest is noescape. 305 | 306 | ^ noescape was introduced recently in Swift 1.2. 307 | 308 | --- 309 | 310 | # `Loop` with closure 311 | 312 | ```swift 313 | func loop(duration: NSTimeInterval, 314 | reverse: Bool, 315 | animations: () -> Void) 316 | ``` 317 | 318 | ^ To present noescape imagine we're implementing an animation framework and we have a loop function. 319 | 320 | ^ The function takes a duration, a flag for reversing and the animations it must perform. 321 | 322 | --- 323 | 324 | # Using `loop` 325 | 326 | ```swift 327 | class MYView: UIView { 328 | 329 | func animate() { 330 | loop(duration: 0.5, reverse: true) { 331 | self.scale(1.25) 332 | } 333 | } 334 | 335 | func scale(scale: Double) { ... } 336 | 337 | } 338 | ``` 339 | 340 | ^ This is how we could use the loop function in a view. 341 | 342 | ^ Here we tell the view to scale up and down every half second. 343 | 344 | ^ Notice that the animation closure is capturing self. 345 | 346 | --- 347 | 348 | # How long are we capturing *self*? 349 | 350 | ![](http://media.giphy.com/media/FHMLhywDkPQCQ/giphy.gif) 351 | 352 | ^ The question is: how long are we capturing self? 353 | 354 | --- 355 | 356 | # What does `loop` do? 357 | 358 | ```swift 359 | loop(duration: 0.5, reverse: true) { 360 | self.scale(1.25) 361 | } 362 | ``` 363 | 364 | * Call `animations` on every cycle? 365 | * Call once, take snapshots and animate between them? 366 | 367 | ^ It actually depends on the implementation of the loop function. 368 | 369 | ^ If loop calls the animations closure on every cycle, then we are effectively retaining self forever. 370 | 371 | ^ But if it only calls animations once, takes snapshots and then animates back and forth, then we are capturing `self` only for the duration of the loop function call. 372 | 373 | ^ Wouldn't it be great if we could tell the API user our intent with the animations closure? 374 | 375 | --- 376 | 377 | # Use *@noescape* for closure parameters that will not outlive the function call 378 | 379 | ^ We can with the noescape attribute. We use it for closure parameters that will not outlive the function call. 380 | 381 | --- 382 | 383 | # Adding `@noescape` 384 | 385 | ```swift 386 | func loop(duration: NSTimeInterval, 387 | reverse: Bool, 388 | @noescape animations: () -> Void) 389 | ``` 390 | 391 | ^ Again, to use noescape we only need to add it to the corresponding closure parameter. 392 | 393 | ^ Then the compiler will make sure we use the animation closure accordingly. 394 | 395 | --- 396 | 397 | # No need for `self` 398 | 399 | ```swift 400 | class MYView: UIView { 401 | 402 | func doAnimations() { 403 | loop(duration: 0.5, reverse: true) { 404 | scale(1.25) 405 | } 406 | } 407 | 408 | func scale(scale: Double) { ... } 409 | 410 | } 411 | ``` 412 | 413 | ^ The compiler doesn't even need us to be explicit about self anymore as there are no capture risks involved. 414 | 415 | --- 416 | 417 | # `@noescape` in the Standard Library 418 | 419 | ```swift 420 | func reduce(initial: U, 421 | combine: @noescape (U, Self.Generator.Element) -> U) -> U 422 | ``` 423 | 424 | ^ As a recent addition, the noescape attribute is not as widespread as autoclosure. 425 | 426 | ^ You can start seeing it in sequence functions such as reduce, which uses a closure to reduce a collection into a single value. 427 | 428 | --- 429 | 430 | # *@autoclosure* implies *@noescape* 431 | 432 | ![](http://media.giphy.com/media/feqkVgjJpYtjy/giphy.gif) 433 | 434 | ^ Speaking of autoclosure, it's worth mentioning that the autoclosure attribute implies noescape. 435 | 436 | ^ This is done explicitly to limit autoclosure usage to lazy evaluation. 437 | 438 | --- 439 | 440 | # Forward compatibility 441 | 442 | * Removing `@noescape` might break user code 443 | * Only add if sure that the function will *never* call the closure asynchronously 444 | 445 | ^ Another thing to consider about noescape is forward compatibility. 446 | 447 | ^ Adding it has no negative consequences. But removing it might break user code as noescape closures can only be passed as noescape parameters. 448 | 449 | ^ Only add noescapae if you're if sure that the function will not change in the future and call the closure asynchronously 450 | 451 | --- 452 | 453 | # Default values 454 | 455 | ![inline](http://media.giphy.com/media/XP7OjbcCiubkc/giphy.gif) 456 | 457 | ^ Our last topic is default values for closure parameters. 458 | 459 | --- 460 | 461 | # Event callbacks in objective-C 462 | 463 | ```objectivec 464 | - (void)fetchImageWithSuccess:(void (^)(UIImage *image))successBlock 465 | failure:(void (^)(NSError *error))failureBlock; 466 | ``` 467 | 468 | ^ We'll be using event callbacks as an example. 469 | 470 | ^ If you are an Objective-C developer, then you might be used to methods like this. 471 | 472 | ^ We fetch an image and provide a callback for success and a callback for error. 473 | 474 | --- 475 | 476 | # Swift translation 477 | 478 | ```swift 479 | func fetchImage(success: (UIImage -> Void)?, 480 | failure: (NSError -> Void)?) 481 | 482 | fetchImage(success: { image -> Void in 483 | save(image) 484 | }, failure: nil) 485 | ``` 486 | 487 | ^ In Swift that method could look like this. Shorter and sweet. 488 | 489 | ^ If you're interested in the differences in naming between Swift and Objective-C, don't miss Radek's talk tomorrow. 490 | 491 | ^ Back to our function, say we don't care about handling the failure case. With our current signature we are forced to specify nil for the failure. 492 | 493 | --- 494 | 495 | # `nil` as default 496 | 497 | ```swift 498 | func fetchImage(success: (UIImage -> Void)? = nil, 499 | failure: (NSError -> Void)? = nil) { 500 | ... 501 | if let success = success { 502 | success(image) 503 | } 504 | } 505 | 506 | fetchImage(success: { image -> Void in 507 | save(image) 508 | }) 509 | ``` 510 | 511 | ^ Swift has default parameter values, and that applies to closures as well. 512 | 513 | ^ We could set the default value to nil. This is slightly inconvenient for the API developer because now we need to check if the closures are nil before calling them. 514 | 515 | ^ More importantly, it also makes the behavior of the function less clear. Can a `nil` value make the function act differently? 516 | 517 | --- 518 | 519 | # No optionals 520 | 521 | ```swift 522 | func fetchImage(success: UIImage -> Void, 523 | failure: NSError -> Void) 524 | 525 | 526 | fetchImage(success: { image -> Void in 527 | save(image) 528 | }, failure: { _ in }) 529 | ``` 530 | 531 | ^ If we want to be explicit we could remove optionals altogether. 532 | 533 | ^ Now our fetch image function expects both closures. The behavior is quite clear, but the API became more inconvenient to use as the consumer must set the closure parameters they don't care about to an empty closure. 534 | 535 | --- 536 | 537 | # Empty closure as default 538 | 539 | ```swift 540 | func fetchImage(success: UIImage -> Void = { _ in }, 541 | failure: NSError -> Void = { _ in }) { 542 | ... 543 | success(image) 544 | } 545 | 546 | fetchImage(success: { image -> Void in 547 | save(image) 548 | }) 549 | ``` 550 | 551 | ^ Luckily the default value can also be a closure, and particularly an empty closure. 552 | 553 | ^ Now both implementation and usage are delightful and there is no ambiguity, though the signature might be a bit harder to read. 554 | 555 | --- 556 | 557 | # Global function as default 558 | 559 | ```swift 560 | func fetchImage(success: UIImage -> Void = noop 561 | failure: NSError? -> Void = noop) 562 | ``` 563 | 564 | ^ To make the intention of the default value more clear and simplify the signature, we can set the default to a global function whose name states exactly our intent. 565 | 566 | --- 567 | 568 | # Defining `noop` 569 | 570 | ```swift 571 | func noop() {} 572 | 573 | func noop(value: T) {} 574 | ``` 575 | 576 | ^ Using generics and overloading we can have global no-op function that works for any closure with and without a parameter. 577 | 578 | --- 579 | 580 | # Defining `noop` 581 | 582 | ```swift 583 | func noop() {} 584 | 585 | func noop(value: T) {} 586 | 587 | func noop() -> T? { return nil } 588 | ``` 589 | 590 | ^ We can even make it work for closures that return an optional. 591 | 592 | --- 593 | 594 | # Defining `noop` 595 | 596 | ```swift 597 | func noop() {} 598 | 599 | func noop(value: T) {} 600 | 601 | func noop() -> T? { return nil } 602 | 603 | func noop(value: T) -> S? { return nil } 604 | ``` 605 | 606 | ^ And closures that expect a parameter and return an optional. 607 | 608 | --- 609 | 610 | # Trailing confusion 611 | 612 | ```swift 613 | fetchImage() { _ in 614 | // What closure is this? 615 | } 616 | ``` 617 | 618 | ![inline](http://media.giphy.com/media/yZMqjuJuUgWre/giphy.gif) 619 | 620 | ^ There's still an issue with our function signature. 621 | 622 | ^ Remember the trailing closure? Guess which closure we are calling here? 623 | 624 | ^ Hint: it's not the success closure. 625 | 626 | --- 627 | 628 | # Consider the *trailing closure* when defining parameter order 629 | 630 | ![](http://media.giphy.com/media/OZ4A83HctPwqY/giphy.gif) 631 | 632 | ^ This means that it's important to consider the trailing closure when defining parameter order. 633 | 634 | --- 635 | 636 | # Finally 637 | 638 | ```swift 639 | func fetchImage(failure: NSError -> Void = noop, 640 | success: UIImage -> Void = noop) 641 | ``` 642 | 643 | ![inline](http://media.giphy.com/media/MB863sKnJaHhm/giphy.gif) 644 | 645 | ^ So taking this into account, we switch the closure parameter order and we finally have a function signature we can be proud of. 646 | 647 | --- 648 | 649 | # Damn you Xcode! 650 | 651 | ![inline](xcode.gif) 652 | 653 | ^ Sadly to date Xcode is totally incompetent at letting developers know about default values. 654 | 655 | ^ Not only it fails to tells us about them when using code completion. 656 | 657 | --- 658 | 659 | # Damn you again Xcode! 660 | 661 | ![inline](quick-documentation.png) 662 | 663 | ^ It also doesn't tell us which is the default value when using Quick Documentation. 664 | 665 | --- 666 | 667 | # Be mindful of how you API looks in *code completion* and *Quick Documentation* 668 | 669 | --- 670 | 671 | # Compensating with documentation 672 | 673 | ```swift 674 | /// Fetches an image. 675 | /// 676 | /// :param: failure Closure to be called on failure. Default: noop. 677 | /// :param: success Closure to be called on success. Default: noop. 678 | func fetchImage(failure: NSError -> Void = noop, 679 | success: UIImage -> Void = noop) 680 | ``` 681 | 682 | ![inline](good-quick-doc.png) 683 | 684 | ^ Thus for now it's important to compensate with good code documentation that specifies the default values. 685 | 686 | --- 687 | 688 | ### Closures encourage *code locality* 689 | ### Use *@autoclosure* for parameter values that might not be used 690 | ### Use *@noescape* for closure parameters that will not outlive the function call 691 | 692 | --- 693 | 694 | ### Prefer *no-op closures* as default values for closure parameters 695 | ### Consider the *trailing closure* when defining parameter order 696 | ### Be mindful of how consumers will *discover* your APIs 697 | 698 | ^ To sum up: 699 | 700 | --- 701 | 702 | # Coming next 703 | 704 | ```swift 705 | fetchImage().onSuccess { image in 706 | save(image) 707 | }.onFailure { error in 708 | handle(error) 709 | } 710 | ``` 711 | 712 | ^ There are other ways in which we could have defined our fetchImage fuction. I would argue that this is a bit nicer to use than the version before. 713 | 714 | ^ In the next talk Javi will show us how to write functions that allow fluent code like this using futures. 715 | 716 | --- 717 | 718 | # BCPL: the origin of `{}` 719 | 720 | ``` 721 | GET "libhdr" 722 | 723 | LET start() = VALOF 724 | $( writes("Hello, World!*n") 725 | RESULTIS 0 726 | $) 727 | ``` 728 | 729 | Created by Martin Richards in 1966. 730 | 731 | ^ And regarding the origin of the curly brackets as block delimiters. 732 | 733 | ^ Internet research tells me that it comes from a language called BCPL created by Martin Richards of the University of Cambridge in 1966. 734 | 735 | ^ BCPL introduced the curly bracket notation but in practice used a dollar sign and parenthesis due to typographical limitations of the time. 736 | 737 | ^ It went on to inspire B and C, which in turn influenced most modern languages. 738 | 739 | --- 740 | 741 | # Thank you! 742 | 743 | Questions? 744 | 745 | ![inline, right](http://media.giphy.com/media/G78gqVIExPkYM/giphy.gif) 746 | 747 | --- 748 | 749 | # Needs more Swift 750 | 751 | ![inline](wikipedia.png) 752 | 753 | ^ One more thing. This is the Wikipedia page for closures. 754 | 755 | ^ It has a section for many popular languages, including Objective-C. But no mention of Swift. 756 | 757 | ^ I encourage us to fix it this weekend. --------------------------------------------------------------------------------