Silencing NSLog

When your app has a lot of third-party dependencies, what often happens is that those libraries log a bunch of things to the Xcode console to help their own debugging. Unfortunately, a lot of these logs are useful only to the developers of the library, but not the developers of apps that integrate the library. For example, they log things like <SomeLibrary> (version 1.2.3) initialized, or <SomeLibrary> started <primary functionality>, sometimes with a long list of parameters or input sources that are irrelevant to you.

Finding your own log statements in a jungle of other logs can then be very difficult and adds to the frustration of not being able to work the debugger as you would like to.

If a library is open source you can suggest a change by removing the log or otherwise make it less obtrusive. However, if your change gets accepted at all, that doesn't solve the immediate problem of being able to debug your own code using the console.

Meet _NSSetLogCStringFunction(). This C function has been around in Foundation for a long time, and while there is some documentation on it, it's still a private method. However, that doesn't mean you can't use it in debug mode when your logs are the most valuable!

In short, this function lets you set a function pointer that can log C strings, which NSLog then uses instead of the normal implementation. You can do this in two ways.

The first one is by adding this to your Objective-C bridging header:

1
2
3
#if DEBUG
extern void _NSSetLogCStringFunction(void(*)(const char*, unsigned, BOOL));
#endif

and then use it like this:

1
2
3
4
5
6
func disableNSLog() {
#if DEBUG
    _NSSetLogCStringFunction { message, _, _ in
        // no actual logging, message just gets lost
    }
#endif

If you want to stick to pure Swift, you can do so by adding this to your code somewhere:

1
2
@_silgen_name("_NSSetLogCStringFunction")
func setNSLogFunction(_: @convention(c) (UnsafePointer<Int8>, UInt32, ObjCBool) -> Void)

and then use it like this:

1
2
3
4
5
6
7
func disableNSLog() {
#if DEBUG
    setNSLogFunction { message, _, _ in
        // no actual logging, message just gets lost
    }
#endif
}

Obviously, you can do anything you want inside the closure, including writing to a file, annotating the message with a date/time, passing it to your custom logging library, etc.

One downside of this is that Apple's frameworks use NSLog extensively as well, so in the above case of completely disabling logging, helpful messages get lost as well. You won't be able to use NSLog yourself either anymore, so I suggest you use print() or a custom logging framework that's not NSLog based.

If you're not afraid of doing (more) horrible things in your codebase, you can avoid losing Apple frameworks' messages by parsing the stack trace and looking at the framework that called this function and see if it's something you want to let through:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
func disableNSLog() {
#if DEBUG
    _NSSetLogCStringFunction { message, _, _ in
        // message is of type UnsafePointer<Int8> so first see if we can get a
        // normal String from that. Safety first!

        guard let message = String.fromCString(message) else {
            return
        }

        let callStack = NSThread.callStackSymbols()
        let sourceString = callStack[6]
        let separatorSet = NSCharacterSet(charactersInString: " -[]+?.,")
        let stackFrame = sourceString.componentsSeparatedByCharactersInSet(separatorSet)
        let frameworkName = stackFrame[3]

        if frameworkName == "UIKit" || frameworkName == "Foundation" {
            MyCustomLogger.log(message)
        }
    }
#endif
}

This discards all logs, except if they're coming from UIKit or Foundation. The stack trace parsing is by no means safe (its format could change, for example), but since it's wrapped in #if DEBUG directives it won't mess with anything in the App Store build.

Note that static libraries are part of your main app's target, which means you have to filter out logs from your own target to hide those.

You could even go a bit farther and check the message for keywords you like or don't like and make a decision on whether you want to log or not. Keep in mind, though, that any work you do here needs to be fast as you don't always know just how much is being logged.

Outlets: strong! or weak?

There are a lot of styles out there when it comes to using Interface Builder outlets in Swift. Even Apple's documentation and sample code isn't always consistent. The most common one, the one Apple uses in its sample code, follows this pattern:

@IBOutlet private weak var someLabel: UILabel!

Let's break this down by keyword:

While at first this seems like a solid approach, at Lyft we quickly realized we weren't fans of this one-size-fits-all way of defining outlets. Instead, the behavior and consequences of the different elements should define the outlet's exact syntax, just like any other variable.

For example, if there is a code path that removes an outlet from its superview, or the outlet is (intentionally) not hooked up in the storyboard, it needs to be an optional because the outlet is not guaranteed to be there when it's accessed.

@IBOutlet private var someLabel: UILabel?

If there is no code path that re-adds the outlet to the view hierarchy, it would also be good to make it weak to not hold on to it unnecessarily when it gets removed:

@IBOutlet private weak var someLabel: UILabel?

This ensures that if the label is removed from the superview, it's not being kept in memory by the strong reference in the view controller. In the most common case, where there is an outlet that will always be there, a strong, implicitly unwrapped optional is appropriate:

@IBOutlet private var someLabel: UILabel!

The outlet isn't weak in case the code ever changes so that there is a code path that removes the view from the view hierarchy but you forget to update the optionality of the property. The object will stay in memory and using it won't crash your app.

These examples all follow 3 simple rules:

  1. ! needs a guarantee that the view exists, so always use strong to provide that guarantee
  2. If it's possible the view isn't part of the view hierarchy, use ? and appropriate optional-handling (optional binding/chaining) for safety
  3. If you don't need a view anymore after removing it from the view hierarchy, use weak so it gets removed from memory.

Applying these three rules means you properly use the optional semantics. After all, using ! for a view that may not exist is no different than defining any other property as an implicitly unwrapped optional that may not exist.

@objc class prefixes fixed in Xcode 7 beta 4

Back in December I wrote about what I thought was a bug in the Swift compiler that would expose the wrong class name for a Swift class in Objective-C. I then later found out everything worked as intended and I had just misunderstood what @objc() exactly did. Apparently it was never supposed to modify the class name at compile time, but only at runtime.

I'm sure changing the class name just at runtime has its uses, but in my opinion, this would be most helpful if it also affected the compile time name of Objective-C classes. It allows you to namespace your classes in Objective-C using your three-letter prefix, without needing that prefix in Swift because you could namespace by way of modules.

And fortunately, in Xcode 7 beta 4, they have actually modified the @objc() notation so that it does do this. For example, if I have a Swift module that I want others to be able to use in their Objective-C codebase, I could write a class like this:

1
2
3
4
@objc(SDCMyClass)
class MyClass: NSObject {
    // ...
}

And in MyProject-Swift.h the Objective-C class is defined as:

1
2
3
4
SWIFT_CLASS_NAMED("MyClass")
@interface SDCMyClass : NSObject
- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER;
@end

In Swift I can simply use the class as MyClass, but in Objective-C its name is SDCMyClass, which ensures it doesn't collide with other classes named MyClass. Needless to say, I'm very happy they changed this behavior, it makes much more sense now.

The power of UIStackView

I was in the audience for "Implementing UI Designs in Interface Builder" at WWDC, where Apple touted UIStackView as a new, better way to lay out your views that in a lot of cases didn't require Auto Layout.

While the presentation looked good, I wasn't quite sure this solved a real problem, as the required constraints weren't too complicated to build in the first pace. However, after I found some time to play around with UIStackView, I'm convinced. This view is very, very powerful and can make building common UIs a lot simpler. Take, for example, a number grid:

1
2
3
4
[1] [2] [3]
[4] [5] [6]
[7] [8] [9]
    [0]

It would take a while to build this kind of UI in Auto Layout. You would have to create constraints between individual labels, make the labels the same size everywhere, make the spacing between all the labels equal, and avoid hardcoding any numbers to keep the whole thing adaptive. This can be a tedious process in Interface Builder, but imagine you're building this in code. It would take forever, especially after debugging your UI.

With UIStackView, doing all of this takes about 30 seconds. After dragging out all the labels, you just need to embed every row in its own stack view. Set the distribution (in code that's UIStackViewDistribution) for these stack views to "fill equally" (.FillEqually), then embed all four stack views in their own stack view.

UIStackView example

The top-level, vertical stack view should get top/leading/trailing/bottom space to its superview (or the layout guides), the 4 horizontal stack views an "equal width" constraint to their superview. Finally, center the text of the individual labels (you can select all labels at once and do this with 1 action). Done - that's it.

But it gets better. Say you want to conditionally show or hide the 0 label at the bottom. I don't know why you would want that, but just go with it 😛

To do this, all you have to is toggle the hidden property of that label. The containing stack view will automatically reposition its subviews. If you put that line in an animation block, it will reposition its subviews in an animated fashion.

That last part is a very welcome feature for developers that work with a lot of dynamic views. Instead of creating redundant constraints with lower priorities, removing views from their view hierarchies, and re-adding the view and all its constraints if the new views needs to be re-shown, you can simply toggle hidden instead.

As more people get their hands on UIStackView I'm sure it will show off more of its powers, but needless to say I'm sold. Too bad I can't use it for a while...

@objc creates a wrong class name in Objective-C

A few months ago, I decided I'd get started porting SDCAlertView to Swift, but I was only a few minutes in until I ran into a problem I had no idea how to solve: I couldn't get my class names right in both Swift and Objective-C. Even though there are copious amounts of documentation covering the interoperability of Swift and Objective-C, somehow I couldn't get it to work and I brushed it off as me being stupid.

Tonight I tried again, and after some more research it turned out that the behavior I saw was actually a bug in the compiler! I'm surprised that the latest Xcode version is 6.1.1 and that apparently not enough people have run into this problem for Apple to make it a priority.

The bug can easily be reproduced by creating an Objective-C project in Xcode, and then adding the following Swift class:

1
2
3
4
5
import UIKit

@objc(SDCMyLabel)
class MyLabel: UILabel {
}

You would expect that, once you import MyProject-Swift.h in your Objective-C class, you could instantiate a class named SDCMyLabel. However, instead, you can only instantiate a class named MyLabel.

I found this very recent Stack Overflow question which pretty much asks the same thing. "milos" gives a great answer by explaining the observed behavior in Xcode 6.0.1, and the expected behavior according to the WWDC session video.

After reading the answer and watching the relevant parts of the vide, I slightly changed my previously-drawn conclusion from "I'm stupid" to "Xcode's stupid" and filed rdar://19261044. If you're running into the same problem, please duplicate and cross your fingers the engineers at the fruit company will fix it soon.