Why do this?
• Don’t have to change your call sites
• Keep using your dependencies as you always have
Slide 16
Slide 16 text
Why not do this?
• It’s really implicit and sneaky
• Your colleagues might not like you
• When the external API changes type signatures you need to update
Slide 17
Slide 17 text
Generics
Silence deprecation warnings
Slide 18
Slide 18 text
Protocols
Silence deprecation warnings
Slide 19
Slide 19 text
Protocols
Silence deprecation
warnings
#pragma clang diagnostic push
#pragma clang diagnostic ignored
“-Wdeprecated-declarations"
/* ... code using the deprecated API ... */
#pragma clang diagnostic pop
Slide 20
Slide 20 text
Protocols
Silence deprecation
warnings
struct Person {
let name: String
func attendFilmFestival() { /*...*/ }
}
let person = Person(name: "Ellie")
person.attendFilmFestival()
Slide 21
Slide 21 text
Protocols
Silence deprecation
warnings struct Person {
let name: String
@available(*, deprecated)
func attendFilmFestival() { /*...*/ }
}
let person = Person(name: "Ellie")
// warning: 'attendFilmFestival()' is deprecated
person.attendFilmFestival()
Slide 22
Slide 22 text
Protocols
Silence deprecation
warnings
protocol FestivalAttending {
func attendFilmFestival()
}
extension Person: FestivalAttending {}
let person = Person(name: "Ellie")
// warning: 'attendFilmFestival()' is deprecated
person.attendFilmFestival()
Slide 23
Slide 23 text
Protocols
Silence deprecation
warnings
protocol FestivalAttending {
func attendFilmFestival()
}
extension Person: FestivalAttending {}
let person = Person(name: "Ellie")
// No warning
(person as FestivalAttending).attendFilmFestival()
Slide 24
Slide 24 text
Why do this?
• Keep your codebase warning-free*
• Sometimes deprecated APIs don’t have equivalent replacements
Slide 25
Slide 25 text
Why not do this?
• Sweeps warnings under the rug
• Deprecated API might be fully removed in the near future and catch you by
surprise
Slide 26
Slide 26 text
No content
Slide 27
Slide 27 text
dlsym
with Wes Anderson
I make this face when
I find code using dlsym
Slide 28
Slide 28 text
dlsym
Access internals of your dependencies
Slide 29
Slide 29 text
dlsym
Access internals of
your dependencies
void (*swift
: :
swift_task_enqueueGlobal_hook)(
Job
*
job, swift_task_enqueueGlobal_original original) = nullptr;
Slide 30
Slide 30 text
dlsym
Access internals of
your dependencies
void swift
: :
swift_task_enqueueGlobal(Job
*
job) {
if (swift_task_enqueueGlobal_hook)
swift_task_enqueueGlobal_hook(job, swift_task_enqueueGlobalImpl);
else
swift_task_enqueueGlobalImpl(job);
}
void (*swift
: :
swift_task_enqueueGlobal_hook)(
Job
*
job, swift_task_enqueueGlobal_original original) = nullptr;
Slide 31
Slide 31 text
dlsym
Access internals of
your dependencies
typealias Original = @convention(thin) (UnownedJob)
- >
Void
typealias Hook = @convention(thin) (UnownedJob, Original)
- >
Void
let swift_task_enqueueGlobal_hook = dlsym(
dlopen(nil, RTLD_LAZY),
"swift_task_enqueueGlobal_hook"
)
.assumingMemoryBound(to: Hook
? .
self)
Slide 32
Slide 32 text
dlsym
Access internals of
your dependencies
typealias Original = @convention(thin) (UnownedJob)
- >
Void
typealias Hook = @convention(thin) (UnownedJob, Original)
- >
Void
let swift_task_enqueueGlobal_hook = dlsym(
dlopen(nil, RTLD_LAZY),
"swift_task_enqueueGlobal_hook"
)
.assumingMemoryBound(to: Hook
? .
self)
swift_task_enqueueGlobal_hook.pointee = { job, original in
print("hooked")
original(job)
}
Slide 33
Slide 33 text
dlsym
Access internals of
your dependencies
typealias Original = @convention(thin) (UnownedJob)
- >
Void
typealias Hook = @convention(thin) (UnownedJob, Original)
- >
Void
let swift_task_enqueueGlobal_hook = dlsym(
dlopen(nil, RTLD_LAZY),
"swift_task_enqueueGlobal_hook"
)
.assumingMemoryBound(to: Hook
? .
self)
swift_task_enqueueGlobal_hook.pointee = { job, original in
print("hooked")
original(job)
}
Task {
print("oh hi mark")
}
// Prints:
// hooked
// oh hi mark
Slide 34
Slide 34 text
dlsym
Access internals of
your dependencies
typealias Original = @convention(thin) (UnownedJob)
- >
Void
typealias Hook = @convention(thin) (UnownedJob, Original)
- >
Void
let swift_task_enqueueGlobal_hook = dlsym(
dlopen(nil, RTLD_LAZY),
"swift_task_enqueueGlobal_hook"
)
.assumingMemoryBound(to: Hook
? .
self)
swift_task_enqueueGlobal_hook.pointee = { job, original in
print("hooked")
original(job)
}
Task {
print("oh hi mark")
}
// Prints:
// hooked
// oh hi mark
pointfree.co/ep240
Slide 35
Slide 35 text
Why do this?
• Reach into your dependencies internals to customize /
fi
x their functionality
Slide 36
Slide 36 text
Why not do this?
• Library internals are not part of its API contract
• Can break at any time
• No compile-time feedback if broken
Slide 37
Slide 37 text
dlsym
Defer loading untrusted libraries
Slide 38
Slide 38 text
No content
Slide 39
Slide 39 text
How does it work?
• Embed dynamic framework in your app’s bundle
• But don’t link it with your app binary
• Just like any other static asset*
• *But it’s actually a code-signed executable
Slide 40
Slide 40 text
How does it work?
• Problem: you can’t import ThirdParty
• Problem: you can’t call into it the framework normally
🤔
Slide 41
Slide 41 text
How does it work?
• You can then call into functions using the same dlsym tricks shown earlier
• Codegen tools can help automate this
• Alternatively you can use the Objective-C runtime
• Alternatively, use
-
weak_link and rename the framework inside the bundle
before manually dlopen-ing it (h/t Kabir)
Slide 42
Slide 42 text
Why do this?
• Prevent untrusted 3rd party code from running
• Some 3rd party libraries do really sketchy stu
ff
• Don’t let your dependencies control you
Slide 43
Slide 43 text
Why not do this?
• Increases distribution size
• Dynamic libraries: no linker dead code stripping
• Kind of a lot of work and who’s got the time for that?
Slide 44
Slide 44 text
dlsym
Dynamically choose dependencies
Slide 45
Slide 45 text
dlsym
Dynamically choose
dependencies
struct DynamicLinkLibrary {
fileprivate let handle: UnsafeMutableRawPointer
func load(symbol: String) throws
- >
T {
if let sym = dlsym(handle, symbol) {
return unsafeBitCast(sym, to: T.self)
}
throw LoaderError.noSymbol(
symbol: symbol,
error: String(validatingUTF8: dlerror())
)
}
}
struct Loader {
let searchPaths: [String]
func load(path: String) throws
- >
DynamicLinkLibrary {
let fullPaths = searchPaths
.map { $0.appending(pathComponent: path) }
.filter { $0.isFile }
for fullPath in fullPaths + [path] {
if let handle = dlopen(fullPath, RTLD_LAZY) {
return DynamicLinkLibrary(handle: handle)
}
}
throw LoaderError.failed(path: path)
}
}
Slide 46
Slide 46 text
No content
Slide 47
Slide 47 text
Why do this?
• Dynamically choose dependencies
• A/B test two di
ff
erent versions of a dependency
• For Mac/CLI apps:
• Use a dependency they already have
• Reduce distribution size
• Avoids duplicate symbol con
fl
icts
Slide 48
Slide 48 text
Why not do this?
• Can increase distribution size
• Dynamic libraries: no linker dead code stripping
• Double the libraries: double the size
• Kind of a lot of work and who’s got the time for that?