-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathadapting-objectivec-apis-for-swift-using-nsrefinedforswift.html
293 lines (253 loc) · 15.1 KB
/
adapting-objectivec-apis-for-swift-using-nsrefinedforswift.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
<!DOCTYPE html>
<html lang="en">
<head>
<script src="https://use.fontawesome.com/afd448ce82.js"></script>
<!-- Meta Tag -->
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<!-- SEO -->
<meta name="author" content="Bruno Rocha">
<meta name="keywords" content="Software, Engineering, Blog, Posts, iOS, Xcode, Swift, Articles, Tutorials, OBJ-C, Objective-C, Apple">
<meta name="description" content="The compiler already does a good job translating Objective-C APIs to Swift's standards, but what if you want them to be translated to something completely different?">
<meta name="title" content="Adapting Objective-C APIs to Swift with NS_REFINED_FOR_SWIFT">
<meta name="url" content="https://swiftrocks.com/adapting-objectivec-apis-for-swift-using-nsrefinedforswift">
<meta name="image" content="https://swiftrocks.com/images/thumbs/thumb.jpg?4">
<meta name="copyright" content="Bruno Rocha">
<meta name="robots" content="index,follow">
<meta property="og:title" content="Adapting Objective-C APIs to Swift with NS_REFINED_FOR_SWIFT"/>
<meta property="og:image" content="https://swiftrocks.com/images/thumbs/thumb.jpg?4"/>
<meta property="og:description" content="The compiler already does a good job translating Objective-C APIs to Swift's standards, but what if you want them to be translated to something completely different?"/>
<meta property="og:type" content="website"/>
<meta property="og:url" content="https://swiftrocks.com/adapting-objectivec-apis-for-swift-using-nsrefinedforswift"/>
<meta name="twitter:card" content="summary_large_image"/>
<meta name="twitter:image" content="https://swiftrocks.com/images/thumbs/thumb.jpg?4"/>
<meta name="twitter:image:alt" content="Page Thumbnail"/>
<meta name="twitter:title" content="Adapting Objective-C APIs to Swift with NS_REFINED_FOR_SWIFT"/>
<meta name="twitter:description" content="The compiler already does a good job translating Objective-C APIs to Swift's standards, but what if you want them to be translated to something completely different?"/>
<meta name="twitter:site" content="@rockbruno_"/>
<!-- Favicon -->
<link rel="icon" type="image/png" href="images/favicon/iconsmall2.png" sizes="32x32" />
<link rel="apple-touch-icon" href="images/favicon/iconsmall2.png">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Source+Sans+3:ital,wght@0,200..900;1,200..900&display=swap" rel="stylesheet">
<!-- Bootstrap CSS Plugins -->
<link rel="stylesheet" type="text/css" href="css/bootstrap.css">
<!-- Prism CSS Stylesheet -->
<link rel="stylesheet" type="text/css" href="css/prism4.css">
<!-- Main CSS Stylesheet -->
<link rel="stylesheet" type="text/css" href="css/style48.css">
<link rel="stylesheet" type="text/css" href="css/sponsor4.css">
<!-- HTML5 shiv and Respond.js support IE8 or Older for HTML5 elements and media queries -->
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/html5shiv/3.7.3/html5shiv.min.js"></script>
<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
<![endif]-->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "BlogPosting",
"mainEntityOfPage": {
"@type": "WebPage",
"@id": "https://swiftrocks.com/adapting-objectivec-apis-for-swift-using-nsrefinedforswift"
},
"image": [
"https://swiftrocks.com/images/thumbs/thumb.jpg"
],
"datePublished": "2020-07-14T14:00:00+02:00",
"dateModified": "2020-07-14T14:00:00+02:00",
"author": {
"@type": "Person",
"name": "Bruno Rocha"
},
"publisher": {
"@type": "Organization",
"name": "SwiftRocks",
"logo": {
"@type": "ImageObject",
"url": "https://swiftrocks.com/images/thumbs/thumb.jpg"
}
},
"headline": "Adapting Objective-C APIs to Swift with NS_REFINED_FOR_SWIFT",
"abstract": "The compiler already does a good job translating Objective-C APIs to Swift's standards, but what if you want them to be translated to something completely different?"
}
</script>
</head>
<body>
<div id="main">
<!-- Blog Header -->
<!-- Blog Post (Right Sidebar) Start -->
<div class="container">
<div class="col-xs-12">
<div class="page-body">
<div class="row">
<div><a href="https://swiftrocks.com">
<img id="logo" class="logo" alt="SwiftRocks" src="images/bg/logo2light.png">
</a>
<div class="menu-large">
<div class="menu-arrow-right"></div>
<div class="menu-header menu-header-large">
<div class="menu-item">
<a href="blog">blog</a>
</div>
<div class="menu-item">
<a href="about">about</a>
</div>
<div class="menu-item">
<a href="talks">talks</a>
</div>
<div class="menu-item">
<a href="projects">projects</a>
</div>
<div class="menu-item">
<a href="software-engineering-book-recommendations">book recs</a>
</div>
<div class="menu-item">
<a href="games">game recs</a>
</div>
<div class="menu-arrow-right-2"></div>
</div>
</div>
<div class="menu-small">
<div class="menu-arrow-right"></div>
<div class="menu-header menu-header-small-1">
<div class="menu-item">
<a href="blog">blog</a>
</div>
<div class="menu-item">
<a href="about">about</a>
</div>
<div class="menu-item">
<a href="talks">talks</a>
</div>
<div class="menu-item">
<a href="projects">projects</a>
</div>
<div class="menu-arrow-right-2"></div>
</div>
<div class="menu-arrow-right"></div>
<div class="menu-header menu-header-small-2">
<div class="menu-item">
<a href="software-engineering-book-recommendations">book recs</a>
</div>
<div class="menu-item">
<a href="games">game recs</a>
</div>
<div class="menu-arrow-right-2"></div>
</div>
</div>
</div>
<div class="content-page" id="WRITEIT_DYNAMIC_CONTENT">
<!--WRITEIT_POST_NAME=Adapting Objective-C APIs to Swift with NS_REFINED_FOR_SWIFT-->
<!--WRITEIT_POST_HTML_NAME=adapting-objectivec-apis-for-swift-using-nsrefinedforswift-->
<!--Add here the additional properties that you want each page to possess.-->
<!--These properties can be used to change content in the template page or in the page itself as shown here.-->
<!--Properties must start with 'WRITEIT_POST'.-->
<!--Writeit provides and injects WRITEIT_POST_NAME and WRITEIT_POST_HTML_NAME by default.-->
<!--WRITEIT_POST_SHORT_DESCRIPTION=The compiler already does a good job translating Objective-C APIs to Swift's standards, but what if you want them to be translated to something completely different?-->
<!--WRITEIT_POST_SITEMAP_DATE_LAST_MOD=2020-07-14T14:00:00+02:00-->
<!--WRITEIT_POST_SITEMAP_DATE=2020-07-14T14:00:00+02:00-->
<title>Adapting Objective-C APIs to Swift with NS_REFINED_FOR_SWIFT</title>
<div class="blog-post">
<div class="post-title-index">
<h1>Adapting Objective-C APIs to Swift with <code>NS_REFINED_FOR_SWIFT</code></h1>
</div>
<div class="post-info">
<div class="post-info-text">
Published on 14 Jul 2020
</div>
</div>
<p>If your iOS project uses Objective-C SDKs, you'll find that the compiler does a good job translating those APIs to Swift. Whenever you try to use one of them in Swift, you'll be greeted with a <i>swiftified</i> version of it that has common Objective-C standards translated to Swift. <code>Foundation</code> types like <code>NSData</code> and <code>NSString</code> will be translated to their Swift variants (<code>Data</code>, <code>String</code>), <code>nullables</code> will become optionals, and the name of the method will change to follow Swift's naming conventions. If the method can fail (by having an error pointer), it will even become a throwing function:</p>
<pre><code>- (NSData *)dataForRow:(NSInteger)row error:(NSError **)error;
// becomes:
let data: Data = try data(for: row)</code></pre>
<div class="sponsor-article-ad-auto hidden"></div>
<p>For most cases, this does the trick. While I'm not a fan of how the naming can end up sometimes, there are attributes that you can use to finetune the final result, like <code>NS_SWIFT_NAME(customName)</code> to use a customized name or <code>NS_SWIFT_NOTHROW</code> to disable the <b>error pointer -> throws</b> conversion.</p>
<p>However, what if you want that Objective-C API to become something <b>completely</b> different? One example of a common architectural difference between the languages is the usage of methods versus properties -- most things in Objective-C are methods, while Swift will advise you to use <b>computed properties</b> for things that are computed but yet don't involve actually processing data. The compiler isn't <b>that</b> smart, so by default you'll end up with methods even if they are better defined as something else in Swift:</p>
<pre><code>MyClass.sharedInstance() // would work better in Swift as `MyClass.shared`!</code></pre>
<p>Additionally, the automatic API translation doesn't consider Swift-only features like default arguments and generics in methods, which are really good tools for developing APIs in Swift. Fortunately, the compiler provides you a way to completely customize how Objective-C APIs end up in Swift.</p>
<h2>NS_REFINED_FOR_SWIFT</h2>
<p>The <code>NS_REFINED_FOR_SWIFT</code> attribute can be added to Objective-C methods to indicate that you want to have full control on how this API is translated to Swift. When added to an Objective-C API, the compiler will still port it, but it will do so in the shape of a <b>special hidden method</b> that you can use to redefine it as something else. Here's an example of a singleton in Objective-C:</p>
<pre><code>@interface SRMyClass : NSObject
+ (instancetype)sharedInstance;
@end</code></pre>
<p>By default this will be converted to a <code>sharedInstance()</code> method in Swift, but in Swift standards, this would look better as a computed property instead.</p>
<p>To customize how it'll be translated, let's add the attribute to the Objective-C definition:</p>
<pre><code>@interface SRMyClass : NSObject
+ (instancetype)sharedInstance NS_REFINED_FOR_SWIFT;
@end</code></pre>
<p>As mentioned, using this attribute won't stop the method from being migrated to Swift -- but it'll be done in a special way. In the case of methods, this special way will be that the method's name will be prefixed by two underscores (<code>__</code>):</p>
<pre><code>let instance = SRMyClass.__sharedInstance()</code></pre>
<p>The reason for this is precisely to indicate that this method shouldn't be used as-is. In fact, if you try to implement this example you'll notice that while you <b>can</b> use it, it will <b>not</b> show in code completion at all. The intention, instead, is for you to <b>abstract</b> this special method into what you actually want this to look like in Swift. In the case of our singleton example, if we want it to become a computed property, we should define that property in our Swift code and implement it by calling the exposed unrefined method:</p>
<pre><code>extension SRMyClass {
var shared: SRMySingleton {
return __sharedInstance()
}
}</code></pre>
<p>Because the original unrefined method doesn't even show up in code completion, you can be sure that the developers will always use the correct <i>swiftified</i> version of it.</p>
<p>My personal favorite use of this attribute is to add default parameters to methods, which is something normally ignored in Objective-C for not being easy to implement, but extremely simple and useful in Swift. To do, we just need to create a version of the method that contains default parameters and internally call the original unrefined one:</p>
<pre><code>public extension SRKeychain {
func data(
forDomain domain: String,
andKey key: String,
accessGroup: String? = nil,
accessAttr: String? = nil,
synchronizable: Bool = false
) -> Data? {
return __data(
forDomain: domain,
andKey: key,
accessGroup: accessGroup,
accessAttr: accessAttr,
synchronizable: synchronizable
)
}
}</code></pre>
<div class="sponsor-article-ad-auto hidden"></div>
<p><code>NS_REFINED_FOR_SWIFT</code> is also a great way to enforce type-safety in places where it wouldn't be applicable in Objective-C. In Swift, you can easily abstract unsafe <code>id</code> (<code>Any</code> / <code>AnyObject</code>) Objective-C methods, for example, under generics.</p>
<pre><code>public extension SRPersistance {
func object<T>(forKey key: String) -> T? {
return __object(forKey: key) as? T
}
}</code></pre>
<p>Unfortunately you can't redefine <b>entire</b> types with <code>NS_REFINED_FOR_SWIFT</code> as only methods, properties and initializers are supported, but in my experience, that's enough to give legacy code a good Swift experience.</p>
</div></div>
<div class="blog-post footer-main">
<div class="footer-logos">
<a href="https://swiftrocks.com/rss.xml"><i class="fa fa-rss"></i></a>
<a href="https://twitter.com/rockbruno_"><i class="fa fa-twitter"></i></a>
<a href="https://github.com/rockbruno"><i class="fa fa-github"></i></a>
</div>
<div class="footer-text">
© 2025 Bruno Rocha
</div>
<div class="footer-text">
<p><a href="https://swiftrocks.com">Home</a> / <a href="blog">See all posts</a></p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Blog Post (Right Sidebar) End -->
</div>
</div>
</div>
<!-- All Javascript Plugins -->
<script type="text/javascript" src="js/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
<script type="text/javascript" src="js/prism4.js"></script>
<!-- Main Javascript File -->
<script type="text/javascript" src="js/scripts30.js"></script>
<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-H8KZTWSQ1R"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-H8KZTWSQ1R');
</script>
</body>
</html>