-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathdetecting-broken-constraints-in-swift.html
328 lines (277 loc) · 15.9 KB
/
detecting-broken-constraints-in-swift.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
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
<!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="In this article, I'd like to show a way you can intercept UIKit's exceptions directly in code (not using breakpoints!) and what you can use an implementation like this for.">
<meta name="title" content="Detecting Broken Constraints in Swift">
<meta name="url" content="https://swiftrocks.com/detecting-broken-constraints-in-swift">
<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="Detecting Broken Constraints in Swift"/>
<meta property="og:image" content="https://swiftrocks.com/images/thumbs/thumb.jpg?4"/>
<meta property="og:description" content="In this article, I'd like to show a way you can intercept UIKit's exceptions directly in code (not using breakpoints!) and what you can use an implementation like this for."/>
<meta property="og:type" content="website"/>
<meta property="og:url" content="https://swiftrocks.com/detecting-broken-constraints-in-swift"/>
<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="Detecting Broken Constraints in Swift"/>
<meta name="twitter:description" content="In this article, I'd like to show a way you can intercept UIKit's exceptions directly in code (not using breakpoints!) and what you can use an implementation like this for."/>
<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/detecting-broken-constraints-in-swift"
},
"image": [
"https://swiftrocks.com/images/thumbs/thumb.jpg"
],
"datePublished": "2021-11-22T14:00:00+02:00",
"dateModified": "2021-11-22T14: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": "Detecting Broken Constraints in Swift",
"abstract": "In this article, I'd like to show a way you can intercept UIKit's exceptions directly in code (not using breakpoints!) and what you can use an implementation like this for."
}
</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=Detecting Broken Constraints in Swift-->
<!--WRITEIT_POST_HTML_NAME=detecting-broken-constraints-in-swift-->
<!--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=In this article, I'd like to show a way you can intercept UIKit's exceptions directly in code (not using breakpoints!) and what you can use an implementation like this for.-->
<!--DateFormat example: 2021-04-12T14:00:00+02:00-->
<!--WRITEIT_POST_SITEMAP_DATE_LAST_MOD=2021-11-22T14:00:00+02:00-->
<!--WRITEIT_POST_SITEMAP_DATE=2021-11-22T14:00:00+02:00-->
<title>Detecting Broken Constraints in Swift</title>
<div class="blog-post">
<div class="post-title-index">
<h1>Detecting Broken Constraints in Swift</h1>
</div>
<div class="post-info">
<div class="post-info-text">Published on 22 Nov 2021</div>
</div>
<p>Broken constraints are common, but there are good reasons you might wanted to get them sorted out. Even though iOS can sometimes provide a functional UI after breaking a constraint, the additional layout passes that this requires are usually a performance issue. If you care about your users' battery usage, you'll want to make sure your UI is always configured and rendered properly.</p>
<div class="sponsor-article-ad-auto hidden"></div>
<p>In this article, I'd like to show a way you can intercept UIKit's exceptions directly in code (not using breakpoints!) and what you can use an implementation like this for.</p>
<h2>How do broken constraint alerts work?</h2>
<p>The alert you get in Xcode when a constraint breaks is a really interesting little detail of UIKit. Although the alert describes it as an "exception", <code>UIViewAlertForUnsatisfiableConstraints</code> is actually a global C function:</p>
<div class="post-image">
<img src="https://i.imgur.com/lGt3BqP.png" alt="Alt">
</div>
<p>I imagine the reason they made that choice is because that would be the easiest way to allow you, a developer outside UIKit, to debug such issues -- if you the name of the method that causes something, you can attach a symbolic lldb breakpoint to it. And that's exactly what the alert suggests you to do.</p>
<p>At the time this article was written, UIKit contains four "exceptions":</p>
<ul>
<li>_UIViewAlertForUnsatisfiableConstraints</li>
<li>_UITableViewAlertForForcedLayout</li>
<li>_UITableViewAlertForLayoutOutsideViewHierarchy</li>
<li>_UITableViewAlertForVisibleCellsAccessDuringUpdate</li>
</ul>
<p>Although you can easily intercept these issues in a debug build by creating a symbolic breakpoint, I spent quite a while trying to figure out how to do that directly in the code. The reason for that is because I wanted to also be able to detect broken constraints in <b>internal releases</b> so that our beta testers could also find and report these issues.</p>
<p>When it comes to altering framework execution, the answer is clearly <b>method swizzling</b>. Unfortunately, after a long search, I couldn't find any easy way to swizzle a global C function. I did eventually find <a href="https://github.com/facebook/fishhook/blob/main/fishhook.c">fishhook</a>, which is an utility created by Facebook that is capable of modifying dynamic loader load commands, but it didn't work for UIKit's content for caching reasons. I don't think this is impossible to do per-se, but it's definitely complicated enough to make me not want to bother with it.</p>
<p>Instead, what we need to is find the closest Obj-C method that calls these methods and swizzle that instead. Luckily, in the case of the constraints, we have a very good candidate right off the bat!</p>
<div class="post-image">
<img src="https://i.imgur.com/KUQyaXr.png" alt="Alt">
</div>
<p>The <code>engine:willBreakConstraint:dueToMutuallyExclusiveConstraints:</code> private method does nothing but to call the relevant C function, making it a perfect swizzling candidate.</p>
<h2>Swizzling in Swift</h2>
<p>To swizzle this method, I've decided to create a central "interception" object that would bootstrap the swizzling and listen for any broken constraints in the form of a notification. This is because the method above is inside <code>UIView</code>, so notifications work as an easy way to transmit that information somewhere else.</p>
<pre><code>import Foundation
import UIKit
extension Notification.Name {
static let willBreakConstraint = Notification.Name(
rawValue: "NSISEngineWillBreakConstraint"
)
}
final class ConstraintWarningCatcher {
func startListening() {
// Note: Only call this **once**!
let sel = NSSelectorFromString("engine:willBreakConstraint:dueToMutuallyExclusiveConstraints:")
let method = class_getInstanceMethod(UIView.self, sel)!
let impl = method_getImplementation(method)
let replSel = #selector(UIView.willBreakConstraint(_:_:_:))
let replMethod = class_getInstanceMethod(UIView.self, replSel)!
let replImpl = method_getImplementation(replMethod)
class_replaceMethod(UIView.self, sel, replImpl, method_getTypeEncoding(replImpl))
class_replaceMethod(UIView.self, replSel, impl, method_getTypeEncoding(impl))
NotificationCenter.default.addObserver(
self,
selector: #selector(didReceiveBrokenConstraintNotification),
name: .willBreakConstraint,
object: nil
)
}
@objc func didReceiveBrokenConstraintNotification(notification: NSNotification) {
guard let constraint = notification.object as? NSLayoutConstraint else {
return
}
// do something with this information
}
}
extension UIView {
@objc func willBreakConstraint(_ engine: Any, _ constraint: NSLayoutConstraint, _ conflict: Any) {
willBreakConstraint(engine, constraint, conflict) // swizzled, will call original impl instead
NotificationCenter.default.post(
name: .willBreakConstraint,
object: constraint
)
}
}</code></pre>
<p>From here, you can keep an instance of <code>ConstraintWarningCatcher</code> alive and start the observation. When a constraint breaks, the instance will receive a notification.</p>
<pre><code>let catcher = ConstraintWarningCatcher()
catcher.startListening()
let v = UIView()
v.heightAnchor.constraint(equalToConstant: 50).isActive = true
v.heightAnchor.constraint(equalToConstant: 100).isActive = true
view.addSubview(v) // A constraint will break and the catcher will be notified</code></pre>
<p><b>An important note to be made is that you should never push code involving private APIs to production.</b> In the event that Apple doesn't reject your app (they probably will), there's nothing guaranteeing that Apple won't change how this API works, which could have immediate and devastating effects in your app. Keep this only in your debug builds.</p>
<h2>What can you use this for?</h2>
<div class="sponsor-article-ad-auto hidden"></div>
<p>For me, what I find this most useful for is that it allows you to have your own backlog of constraint issues. Instead of relying solely on breakpoints, you could use this interception to send this information somewhere (ex: Firebase). Just remember to never push this to actual production builds!</p>
<p>Unfortunately, I couldn't find a good way to swizzle the other exceptions mentioned in this article. Although they are indeed called from Obj-C methods deeper down, these methods are also handling tons of other things, which makes swizzling them a very dangerous idea. Still, being able to do that to constraints alone is already a very good win in my book.</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/[email protected]/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>