Over the past week I’ve been converting AppRTC iOS from Objective-C to Swift which has proven lengthy, challenging and educational.
The original goals I set out with were the following:
- Create a working implementation of WebRTC in Swift
- Make sure the necessary code is available in the linked WebRTC library
- Make sure the implementation works fluidly from Swift
- Brush up on Swift
From diving into this code I’ve learned quite a lot about how Google writes their iOS code. Now I’m not sure if the sample here is representational of how all of their apps are done but regardless I’ve learned quite a bit.
For example, all UI is constructed programmatically. A neat takeaway from this
was learning how this can be done using CGRectZero
in objc or CGRect.zero
(which is often shortened to .zero
) in swift.
Idioms
Today’s blog post will be a short demo of common code idioms found in the Google AppRTC codebase and how some of these idioms convert into Swift.
The following are all code samples taken from the AppRTC demo (Objective-C) and converted into their Swift counterparts.
dispatch_async
and Queues
In obj-c:
dispatch_async(dispatch_get_main_queue(), ^{
[_delegate appClient:self didChangeConnectionState:newState];
});
In Swift:
In Swift, the syntax for queueing is handled differently, it’s much cleaner and more compact:
DispatchQueue.main.async {
self.delegate?.appClient(self, didChangeConnectionState: newState)
}
Notice that the syntax is now flipped. You pick which queue you want to use first.
Note: If you are testing in the playground don’t forget to set up the support for indefinite execution, otherwise your async on the main queue won’t be run:
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true
Also note that there is a DispatchQueue.main.sync
as well that can be used
for performing tasks on the main queue synchronously.
To apply something on a background thread we can now use
DispatchQueue.global().async {
// background thread operation async
}
// or
DispatchQueue.global().sync {
// background thread operation sync
}
More on queues at StackOverflow - How to create dispatch queue in Swift 3.
strongSelf
Idiom
The strongSelf
idiom in objective-c is a way for blocks to be passed a
weak reference to avoid retain cycles but avoid having the reference to that
strongSelf
being cleaned up during the execution of the block we are in.
What? I won’t go into detail about it in this post but here are some
articles if you care to understand more in depth about ARC and retain cycles:
- Apple: Use Weak References to Avoid Retain Cycles, Advanced Memory Management Programming Guide
- I finally figured out weakSelf and strongSelf
In objective-c the strongSelf
idiom looks like this:
// Request TURN.
__weak ARDAppClient *weakSelf = self;
[_turnClient requestServersWithCompletionHandler:^(NSArray *turnServers,
NSError *error) {
if (error) {
RTCLogError("Error retrieving TURN servers: %@",
error.localizedDescription);
}
ARDAppClient *strongSelf = weakSelf;
[strongSelf.iceServers addObjectsFromArray:turnServers];
strongSelf.isTurnComplete = YES;
[strongSelf startSignalingIfReady];
}];
In swift would look like:
// request turn
turnClient?.requestServers() {
[weak self] (turnServers: [RTCIceServer], error: Error?) in
if let err = error {
RTCLogEx(.error, "Error retrieving TURN servers, "
+ "\(err.localizedDescription)")
}
if let strongSelf = self {
strongSelf.iceServers.append(contentsOf: turnServers)
strongSelf.isTurnComplete = true
strongSelf.startSignalingIfReady()
}
}
Or, if you’re sick of indenting you could use a guard
statement to exit early
guard let strongSelf = self else { return }
And if that weren’t fancy enough, you could even replace self
all together
with the strong reference in a guard using:
guard let `self` = self else { return }
self.someMethodHere()
self.someOtherMethodHere()
But I would discourage replacing the reference to self. While it may look
cleaner I’m not sure it’s so obvious what’s going on here. In other words
for someone who’s expecting to see a strongSelf
they may think that there
is a bug in the code because they overlooked the guard
statement. It might
be better to be more explicit by having the strongSelf
somewhere in the code.
The strongSelf
conversion was taken from StackOverflow.
Preprocessor Changes
In objective-c we have a preprocessor which allows us to do a lot of things at compile time that we just can’t do in Swift.
Detecting iOS Simulator
One tough change that I had to make was in using a preprocessor macro to check if we are compiling for the simulator, since the simulator doesn’t have any sort of camera emulation support.
In objc this appeared as:
#if !TARGET_IPHONE_SIMULATOR
if (!_isAudioOnly) {
RTCMediaConstraints *cameraConstraints =
[self cameraConstraints];
RTCAVFoundationVideoSource *source =
[_factory avFoundationVideoSourceWithConstraints:cameraConstraints];
localVideoTrack =
[_factory videoTrackWithSource:source
trackId:kARDVideoTrackId];
}
#endif
In swift, I changed it to:
#if !((arch(i386) || arch(x86_64)) && os(iOS))
if (!self.isAudioOnly) {
let cameraConstraints = self.cameraConstraints
let source = self.factory.avFoundationVideoSource(with: cameraConstraints)
let localVideoTrack = self.factory.videoTrack(with: source,
trackId: kVideoTrackId)
}
#endif
Which I also pulled from a StackOverflow question
#if defined()
For some other parts of the code that read (objc):
#if defined(SOMETHING_OR_OTHER)
// ... some code here
#endif
I switched to:
#if SOMETHING_OR_OTHER
// ... some code here
#endif
Which also requires extra build flag changes to be able to detect if the flag is set or not.
Conclusion
As I come across more common idioms I will post those as well.