Objective-C Idioms in Swift

Posted by Grego on December 21, 2016

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:

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.