├── 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 | 
15 |
16 | ---
17 |
18 | ## Creating a new framework
19 |
20 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
746 |
747 | ---
748 |
749 | # Needs more Swift
750 |
751 | 
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.
--------------------------------------------------------------------------------