BT

Your opinion matters! Please fill in the InfoQ Survey!

What's new in iOS 9: Swift and Objective-C

| Posted by Sergio De Simone Follow 5 Followers on Sep 30, 2015. Estimated reading time: 21 minutes |

At WWDC 2015, Apple introduced iOS 9. Although the new SDK does not introduce as many new or enhanced features as iOS 8, which included more than 4,000 new APIs, it does still provide a wealth of new functionality and enhancements. Along with the new SDK, iOS 9 is also marked by new developer tools to support some of its features, and new releases of Apple’s major programming languages, Swift and Objective-C.

This series aims at introducing all that is essential for developers to know about building apps for the latest release of Apple’s mobile OS. It comprises five articles that will cover what’s new in iOS 9 SDK, new features in Swift, Objective-C, and developer tools, and Apple’s new bitcode.
This InfoQ article is part of the series “IOS 9 For Developers ”. You can subscribe to receive notifications via RSS.

In the first three installments of this series, we have reviewed new frameworks introduced with iOS 9 SDK, enhancements to existing frameworks, and the new Safari content blocking API. In this article, we are going to examine new features added to iOS main programming languages, the recently open sourced Swift and Objective-C.

Swift

Swift 2 introduces new features in five different areas:

  • language fundamentals, such as enums, scoping, arguments syntax, etc.
  • pattern matching
  • features availability checking
  • protocol extensions
  • error handling.

A few of those features, such as the new do scoping operator, are not backward compatible. To cope with this, Xcode 7 includes a new Swift migrator that will convert Swift 1.2 code into legal Swift 2.0.

Enums

In Swift 2, enums carry enough reflection information that it is possible to print them. Given:

enum Animals {
  case Dog, Cat, Troll, Dragon
}
let a = Animals.Dragon

The statement print(a) will now correctly output Animals.Dragon, whereas in previous Swift versions the output was a bare (Enum Value).

Another improvement related to Enums allows Swift to represent associated values of different types through an Enum. As an example, the following code is now a legal way to represent an Either type:

enum Either<T1, T2> {
  case First(T1)
  case Second(T2)
}

Finally, Enums have been extended to support a form of recursive nesting through the use of the indirect keyword:

enum Tree<T> {
   case Leaf(T)
   indirect case Node(Tree, Tree)
}

The example above still does not define a full-fledged recursive union, though, because of a limitation in Swift. Indeed, even in Swift 2 enum values are stored inline, so a recursive enum would have an infinite size. Previously, libraries were available to circumvent such limitations, but they came with a cost in terms of both readability and integration with other Swift features like pattern matching. The use of the indirect keyword allows to express recursion naturally and works perfectly with the rest of the language features.

Scoping operators

A new do statement allows developers to introduce an explicit scope. This can be useful to reuse a name already declared or ensure that some resource is released early. The do statement looks like this:

do {
  let a = Animals.Troll
  ...
}

To avoid any ambiguity with the do .. while statement present in earlier versions of Swift, Swift 2 renames the latter into repeat .. while.

Option sets

Options sets are a way to represent a set of booleans in Swift 1.x:

    viewAnimationOptions = nil
    viewAnimationOptions = .Repeat | .CurveEaseIn | .TransitionCurlUp
    if viewAnimationOptions & .TransitionCurlUp != nil { ...

This kind of syntax is widely used in Cocoa, but in actuality it is just a remnant of the C language. So, Swift 2 does away with it and introduces its own type for option sets, represented by the OptionSetType protocol:

struct MyFontStyle : OptionSetType {
   let rawValue : Int
   static let Bold = MyFontStyle(rawValue: 1)
   ...
   static let Strikethrough = MyFontStyle(rawValue: 8)
}

So, an option set can now be any Set or struct type conforming to the OptionSetType protocol. This leads to much cleaner syntax when using the option set:

myFont.style = []
myFont.style = [.Underline]
myFont.style = [.Bold, .Italic]
if myFont.style.contains(.StrikeThrough) {

This syntax does not rely on bitwise operations as the previous example, nor does it conflate the use of nil to represent an empty option set.

It should be noted that option sets rely on another new feature in Swift, namely default implementations for protocol extensions, so just by conforming to the OptionSetType protocol you get default implementations for, e.g., the contains method. We will cover protocol extensions later.

Functions and Methods

Swift 1.x syntax for declaring functions and methods inherited two distinct conventions derived respectively from C, where function arguments have no labels, and Objective-C, that introduced labels for method arguments. So you had the following type of declarations:

func save(name: String, encrypt: Bool) { ... }
class Widget {
  func save(name: String, encrypt: Bool) { ... }
  
save("thing", false)
widget.save("thing", encrypt: false)

In Swift 2, the above code would be:

func save(name: String, encrypt: Bool) { ... }
class Widget {
func save(name: String, encrypt: Bool) { ... }

save("thing", encrypt: false)
widget.save("thing", encrypt: false)

So, functions adopt the same conventions as methods:

  • the first argument name is implied by the function name;
  • subsequent arguments do carry labels.

Those changes, though, do not apply to functions imported from C and Objective-C APIs.

Additionally, the model to declare parameter labels has been streamlined by removing the # option, which was used in Swift 1.x to denote an parameter having the same internal and external name.

Unit Testing

A problem with unit testing Swift 1.x code is that Swift 1.x requires you to mark symbols Public to be visible to unit test bundles. A consequence of this is that many symbols ended up being Public that really were not meant to be.

Xcode 7 will automatically build Swift 2 code in a special “compile for testing” build mode that makes it easier to control visibility from unit tests without making symbols Public. Indeed, unit tests can use @testable and import <Module> statements to get access to both public and internal symbols in <Module>.

Release builds are not affected by this change to preserve the correct behavior as to both performance and access control.

Pattern matching

The most common form of pattern matching in Swift is possibly the if let statement to deal with optionals and bind them safely to a symbol. Using the if let statement often leads to what is known as the “pyramid of doom”, when too many if let statements are nested together. Swift 1.2 improved things a little by introducing compound conditions in if let statements:

if let dest = segue.destinationViewController as? BlogViewController
       let blogIndex = tableView.indexPathForSelectedRow()?.row
       where segue.identifier == blogSegueIdentifier {

But this does not solve the problem of early exits, and does not easily allow to carry through some alternative action when an optional cannot be unwrappewd.

In Swift 2, a new guard statement aims to make dealing with optionals much more natural:

guard let name = json["name"] as? String else {
        return .Second("missing name")
}

A guard statement conditionally unwraps an optional and binds it to a name, just like if let does. But instead of being visible in the scopr created by if let, the bound name shares the same scope as the executed guard. The scope introduced by the guard statement is solely used to exit the current scope, e.g. by returning, throwing, breaking, asserting, or in any other way. By doing this, the compiler has the guarantee that the names in the guard statement are bound and can be used in the fall-trough.

guard statements can also use compound conditions, just as if let:

guard let name = json["name"] as? String,
      let year = json["year"] as? Int else {
          return .Second(“bad input”)
}
let person = processPerson(name, year)

You can also use more advanced pattern matching with guard case statements. Consider the following type:

enum Entity {
    enum EntityType {
        case EntityTypeA
        case EntityTypeB
    }
    case EntityWithFamilyAndTrait(type: EntityType, family: Int,  trait: Int)
}

Now, when we have an Entity? optional, we can use pattern matching to ensure that it matches a desired EntityType and additionally bind a name to one of its properties:

    var entity : Entity?
    ...
    guard case let Entity.Entry(.EntityTypeA, _, t) = entity 
    where t < SOME_T
    else {
        <bail out>
    }
    <do something with t>

Similarly, the if case statement can be used, which is the dual of guard case.

Another form of pattern matching available in Swift is the switch statement, which allows to check against optionals, class hierarchies, ranges, etc.

switch bar() {
case .MyEnumCase(let value) where value != 42:
  doThing(value)
  
default: break
}

One of the drawbacks of switch is that it has to be exhaustive, and this can be a pain. So, Swift 2 has ported the switch/case pattern matching to other control statements. if case is one of those, and it allows to rewrite the above switch more succinctly as follows:

if case .MyEnumCase(let value) = bar() where value != 42 {
    doThing(value)
}

Another statement to support pattern matching is for .. in, so you can check for a boolean inline:

for value in mySequence where value != "" {
    doThing(value)
}

instead of the old equivalent:

for value in mySequence {
    if value != "" {
        doThing(value)
    }
}

for .. in also supports specifying a case filter using pattern matching to specigy more complex conditions:

for case .MyEnumCase(let value) in enumValues {
    doThing(value)
}

Similarly to for case, while case allows to use pattern matching in a while statement.

Other improvements to pattern matching in Swift 2 are support for x? pattern for optionals, improved switch exhaustiveness checker, and a new warning of “unreachable case”.

API availability

API availability is a new feature aiming to improve how programmers can ensure they do not use any unsupported API. This is usually done by using the respondsToSelector: method, but this is less than ideal.

In Swift 2 the compiler guarantees that you do not use any API that is not supported on your minimum deployment target by emitting a warning when you try to do that. For the compiler to let you use a more rencent API, it must be guarded by an if #available(...) check, e.g.:

if #available(OSX 10.10.3, *) {
    //-- 10.10.3-only API can be used here
}

The * in the above example makes sure that check will also succeed for any newer SDK version than 10.10.3.

Protocol Extensions

Protocol extensions are a Swift 2 feature that allows you to define behavior on protocols, rather than in each type that conforms to that protocol. Protocol extensions go hand in hand with class extensions, which is a mechanism available in Swift 1.0, and provide more flexibility. Say you want to create the following extension to an Array class:

extension Array {
    func countIf(match: Element ->maBtocohl:)T.-G>enIenrtat{or.Element -> Bool) -> Int {
        var n = 0
        for value in self {
            if match(value) { n++ }
        }
        return n
    }
}

This is perfectly fine. On the other hand, the countIf implementation does not really rely on anything specific to the Array class and might be used without change for other collections. In Swift 1, you either duplicate that implementation in every collection class, such as dictionaries, sets, etc., or you use a global function:

func countIf<T : CollectionType>(collection: T,
                                 match: T.Generator.Element -> Bool) -> Int {
    ...
}

This is not optimal on two accounts: it is not as easy to read as the Array extension, and it is no longer a method and does not feel like a real extension to the class. Furthermore, it is not as easily discoverable, e.g. it will not show up in autocompletion.

To fix this, Swift 2 allows you to define method implementations inside of a protocol, so they are inherited by all types conforming to that protocol:

extension CollectionType {
    func countIf(match: Element -> Bool) -> Int {
        var n = 0
        for value in self {
            if match(value) { n++ }
        }
        return n
    }
}

Protocol extensions have wide ranging effects, in that they have allowed to convert Swift 1.x global generic algorithms such as filter and map into methods:

let x = filter(map(numbers) { $0 * 3 }) { $0 >= 0 } //-- Swift 1
let x = numbers.map { $0 * 3 }.filter { $0 >= 0 } //-- Swift 2

Furthermore, protocol extension are at a base of a powerful new design approach, dubbed protocol-oriented programming, that tries to address a few issues found in OOP, such as: implicit sharing of referenced objects; rigidity and fragility of inheritance; frequent necessity of downcasting in overridden methods.

It may be worth noting that Swift will emit a warning in case a class tries to conform to two different protocols that happen to define a default implementation for a method that both define. This helps avoiding or taking under control the “diamond problem”.

Error handling

To understand Swift’s new features related to error handling, it can help to consider that there are three ways that a function can fail:

  • a lot of functions can fail in one fairly simple, sort-of innate way, such as when trying to convert a String into an Int; such cases are satisfactorily handled through an optional return value;
  • at the other end of the spectrum, there are logic errors in a program caused by coding errors, such as indexes out of bounds, unmet preconditions, etc., that are very much harder to deal with, because usually you do not know how to handle them.
  • a third case is that of detailed, recoverable errors, such as when a file is not found, or when there is a network failure, or a user cancelling an operation.

The handling of the third kind of situations is what Swift 2 tries to improve.

If we consider a typical way that error handling is carried through in Swift and Objective-C, we find the following pattern, where a function receives an inout NSError? argument and returns a Bool to represent the success or failure of the operation:

func preflight(inout error: NSError?) -> Bool {
    if (!url.checkResourceIsReachableAndReturnError(&error)) {
        return false
    }
    resetState()
    return true
}

This approach has some downsides, such as adding a lot of boilerplate, making it somewhat less clear what the method does, and, most importantly, requiring the manual implementation of a convention, i.e., what the Bool return value stands for. On the other hand, it also has some upsides, such as making it evident that both checkResourceIsReachableAndReturnError and preflight can fail, and having a very explicit, human understandable control flow.

Now in Swift 2, you can express the fact that a function can fail by using the throws keyword:

    func checkResourceIsReachable() throws {
       ...
    }

Any function that can fail must be called using the try keyword, otherwise a compiler diagnostics is emitted. When calling a function with try, you can handle the error locally using a do .. catch block:

func preflight() {
    do {
        try url.checkResourceIsReachable()
        resetState()
        return true
    } catch {
        return false
    }
}

Alternatively, you can skip error handling and allow the error to propagate to the caller:

func preflight() throws {
    try url.checkResourceIsReachable()
    resetState()
}

The do .. catch statement allows the use of pattern matching, so you could write:

func preflight() {
    do {
        try url.checkResourceIsReachable()
        resetState()
        return true
    } catch NSURLError.FileDoesNotExist {
        return true
    } catch let error {
        return false
    }
}

There are times when you want your program to fail when some anomalous condition arises, e.g., you cannot find a resource that should be present in your app bundle. For such cases, Swift provides a shorthand through the try! keyword:

func preflight() {
    try! url.checkResourceIsReachable()
    resetState()
}

When a function wants to signal a failure condition, it can use the throw statement specifying an error to be thrown. Any type that conforms to the ErrorType protocol can be thrown, which includes NSError. A new possibility opened by this is to define error types as enums, which can be done very easily:

enum DataError : ErrorType {
    case MissingName
    case MissingYear
    case SomethingMissing(String)
    // add more later
}

Enums are ideal to represent groups of related errors and can also carry data in a payload, as shown above.

Another important new feature related to the try catch mechanism is defer, which allows to make sure that some action is carried out whenever the current scope is exited. This is handy to handle cases such as the following one to ensure that didEndReadingSale is called both when the function exits normally and when it throws:

func processSale(json: AnyObject) throws {
  delegate?.didBeginReadingSale()
  defer { delegate?.didEndReadingSale() }
  guard let buyerJSON = json["buyer"] as? NSDictionary {
    throw DataError.MissingBuyer
  }
  let buyer = try processPerson(buyerJSON)
  guard let price = json["price"] as? Int {
    throw DataError.MissingPrice
  }
  return Sale(buyer, price)
}

Swift’s implementation of try .. catch is not as costly in terms of performance as it happens in other languages. Specifically, in Swift there is no table-based unwinding of the stack and the mechanism is similar in performance to an explicit “if” statement.

Using try .. catch in Swift 2 allows to streamline the declaration of methods that can fail, so a typical method taking an NSErrorPointer argument (the equivalent to Objective-C NSError**):

func loadMetadata(error: NSErrorPointer) -> Bool //-- Swift 1

now becomes:

func loadMetadata() throws //-- Swift 2

Similarly, a method that signals an error condition through an optional being nil:

class func bookmarkDataWithContentsOfURL(bookmarkFileURL: NSURL,
-> NSData? //-- Swift 1

can now be written as in the following example as a method that throws and returns a non optional type:

class func bookmarkDataWithContentsOfURL(bookmarkFileURL: NSURL) throws
-> NSData //-- Swift 2

The transformations above are applied automatically by Xcode Migrator, already mentioned above, and have been also propagated throughout the standard library.

Diagnostics

In addition to this, Swift 2 brings a great number of improvements to diagnostics and fix suggestions, such as correctly detecting when the developer is trying to change a var through a non-mutating method of a struct, or when a var property is never changed after initialization, or a let property never read, or when ignoring the result of a function call, and more.

Interoperability

When you write Swift code, a set of predefined rules determines whether and how methods, properties, etc. are exposed to Objective-C. Furthermore, a few attributes are available to get more control on that. Those are:

  • @IBOutlet and @IBAction to let Swift attributes and methods be usable in Interface Builder as outlets and actions;
  • dynamic, which will enable KVO for a given property;
  • @objc, which is used to make a class or a method callable from Objective-C.
class MyController : UIViewController {

  @IBOutlet var button : NSButton

  @IBAction func buttonClicked(sender) {
  }
  
  @objc func refresh() {
  }
}

In Swift 2, a new @nonobjc attribute is available to explicitly prevent a method or property from being exposed to Objective-C. This can be useful when, e.g., you define two Swift methods that only differ by their closure type – this is legal in Swift, whereas in Objective-C methods differ by name:

class CalculatorController : UIViewController {
  func performOperation(op: (Double) -> Double) {
    // ...
  }

  @nonobjc
  func performOperation(op: (Double, Double) -> Double) {
    // ...
  }
}

Another area where Swift 2 tries to improve interoperability is with C pointers. This aims to fix an annoying limitation of Swift that prevented it from fully working with important C-based frameworks such as Core Audio that makes extensive use of callback functions. In short, in Swift 1.x it was not possible to define a C function from a closure that captures context. In Swift 2.0, that limitation has been lifted and now a C function pointer is just a special kind of closure annotated with the @convention(c) attribute:

typedef void (*dispatch_function_t)(void *); //-- C
typealias dispatch_function_t =
    @convention(c) (UnsafeMutablePointer<Void>) -> Void //-- Swift

Objective-C

Apple has introduced three major new features to Objective-C in Xcode 7:

  • nullability;
  • lightweight generics;
  • __kindof types.

Nullability

Although introduced already in Xcode 6.3, it is worth mentioning that the Objective-C allows now to precisely characterize the behavior of any methods or properties as to their being allowed to be nil or not. This maps directly into Swift’s requiring an optional or non-optional type and makes an interface way more expressive. There are three nullability qualifiers:

  • nullable (__nullable for C pointers), meaning that a pointer can be nil and mapping to a Swift optional;
  • nonnull (__nonnull for C pointers), meaning that nil is not allowed and mapping to a Swift non optional type;
  • null_unspecified (__null_unspecified for C pointers), where no information is available as to what behavior is supported/required; in this case, such a pointer maps to an auto-wrapped Swift optional.

The qualifiers listed above can be used to annotate Objective-C classes as in the following example:

NS_ASSUME_NONNULL_BEGIN
@interface UIView
@property(nonatomic,readonly,nullable) UIView *superview;
@property(nonatomic,readonly,copy)
NSArray *subviews;
- (nullable UIView *)hitTest:(CGPoint)point withEvent:(nullable UIEvent *)event;
@end
NS_ASSUME_NONNULL_END

The example above uses an audited region, i.e., a region delimited by NS_ASSUME_NONNULL_BEGIN and NS_ASSUME_NONNULL_END, to select sensible defaults. This allows developers to only annotate non-default cases. In an audited region, defaults are:

  • single-level pointers are assumed to be nonnull;
  • NSError** parameters are assumed to be nullable for both levels.

Lightweight generics

Lightweight generics in Objective-C have problably been the most highly requested feature for the last decade of Objective-C, according to Apple’s engineer. They are motivated first and foremost by their use with collection types, such as NSArray, NSDictionary, etc. One issue that Objective-C collection types have is that when ported to Swift they lose almost every type information, defaulting to collection of AnyObject and forcing to lots of downcasts. So, this is how an array of view can be declared in Xcode 7:

@interface UIView
@property(nonatomic,readonly,copy) NSArray<UIView *> *subviews;
@end

which corresponds to the following Swift declaration:

class UIView {
var subviews: [UIView] { get }
}

CLANG is able to enforce type safety for typed collections and will emit a diagnostic when you try to add the wrong object type to a typed collection.

Type collections are just a specific case of generics, which are a generic feature of the language now. So, this is how you would declare a generic class:

interface NSArray<ObjectType> : NSObject
- (ObjectType)objectAtIndex:(NSUInteger)index;
- (instancetype)initWithObjects:(const ObjectType [])objects
count:(NSUInteger)cnt;
- (NSArray<ObjectType> *)arrayByAddingObject:(ObjectType)anObject;
@end

As usual in many languages, type parameters are specified in <..> and they can be used throughout the interface to represent the actual type. The same idea holds for class extensions and categories, that can be typed or non-typed.

This change to the language has been implemented in a way to provide binary compatibility, so no changes to the Objective-C runtime are required and any binary using generics will still run unmodified on older OS versions.

“Kindof” types

“Kindof” types are related to generics and are motivated by the following case. As it is known, the UIView class defines its subviews property as an array of UIView objects:

@interface UIView
@property(nonatomic,readonly,copy) NSArray<UIView *> *subviews;
@end

So, if we add a UIButton to a parent UIView as its bottom-most subview and try to send it a message that is only meant for a UIButton, then the compiler will emit a warning. This, on the other hand, is perfectly fine because we know that the bottom-most subview is a UIButton:

[view insertSubview:button atIndex:0];
//-- warning: UIView may not respond to setTitle:forState:
[view.subviews[0] setTitle:@“Yes” forState:UIControlStateNormal];

Using “kindof” type, we can introduce some flexibility in the Objective-C type system so implicit casts to both the superclass and to any subclass are allowed:

@interface UIView
@property(nonatomic,readonly,copy) NSArray< _kindof UIView *> *subviews;
@end

//-- no warnings here:
[view.subviews[0] setTitle:@“Yes” forState:UIControlStateNormal];
UIButton *button = view.subviews[0];

Generics and “kindof” types allow developers to remove id/AnyObject from almost anywhere in their APIs. id can still be required, though, mostly in the case when there is really no available information as to the type that is being handled:

@property (nullable, copy) NSDictionary<NSString *, id> *userInfo;

Next Article in Series

The next and final article in the series will cover what is new with Apple Developer Tools, including Xcode 7, Interface Builder, LLDB, etc.

About the Author

Sergio de Simone  is a software engineer. Sergio has been working as a software engineer for over fifteen years across a range of different projects and companies, including such different work environments as Siemens, HP, and small startups. For the last few years, his focus has been on development for mobile platforms and related technologies. He is currently working for BigML, Inc., where he leads iOS and OS X development.

 

At WWDC 2015, Apple introduced iOS 9. Although the new SDK does not introduce as many new or enhanced features as iOS 8, which included more than 4,000 new APIs, it does still provide a wealth of new functionality and enhancements. Along with the new SDK, iOS 9 is also marked by new developer tools to support some of its features, and new releases of Apple’s major programming languages, Swift and Objective-C.

This series aims at introducing all that is essential for developers to know about building apps for the latest release of Apple’s mobile OS. It comprises five articles that will cover what’s new in iOS 9 SDK, new features in Swift, Objective-C, and developer tools, and Apple’s new bitcode.
This InfoQ article is part of the series “IOS 9 For Developers ”. You can subscribe to receive notifications via RSS.

Rate this Article

Adoption Stage
Style

Hello stranger!

You need to Register an InfoQ account or or login to post comments. But there's so much more behind being registered.

Get the most out of the InfoQ experience.

Tell us what you think

Allowed html: a,b,br,blockquote,i,li,pre,u,ul,p

Email me replies to any of my messages in this thread

Oliver by Oliver Dodson

It's hard to think of any huge downsides to using Swift rather than Objective-C. The only thing I can think of is that some developers might not be so keen on converting their apps since they've invested so much time into learning and using Objective-C and their codebases are likely big. Apple hasn't given a future date for when they plan to drop support for Objective-C (maybe they never will?). But the interoperability of Swift and Objective-C and fun of learning a new language make that somewhat hard to believe. Also I Recommend read mlsdev.com/en/blog/51-7-advantages-of-using-swi...

Objective-C and Swift by John Oldman

Clang and Swift both use the same underlying runtime, and Swift doesn't have any equivalent to Objective-C++ for integrating things like Boost or Qt into an application. On top of that, there's a fairly large amount of legacy code in ObjC and Clang has a small but significant user base outside the Applesphere that would be very unhappy if Apple left them high and dry. More thoughts yalantis.com/blog/how-to-move-your-app-from-obj...

Swift rules by Petter Tchirkov

I think Swift is much more readable,
it's easier to maintain,
requires less code.
Proofs: www.infoworld.com/article/2920333/mobile-develo...
thunderrise.com/blog/swift-vs-objective-c:-the-...

Swift vs Objective-C by Alina Kem

Actually, both languages have their advantages and tradeoffs, each occupies its niche, yet they do have a possibility to work together.Swift gains poularity ...Some interesting information to read erminesoft.com/objectivec-vs-swift/

Allowed html: a,b,br,blockquote,i,li,pre,u,ul,p

Email me replies to any of my messages in this thread

Allowed html: a,b,br,blockquote,i,li,pre,u,ul,p

Email me replies to any of my messages in this thread

4 Discuss

Login to InfoQ to interact with what matters most to you.


Recover your password...

Follow

Follow your favorite topics and editors

Quick overview of most important highlights in the industry and on the site.

Like

More signal, less noise

Build your own feed by choosing topics you want to read about and editors you want to hear from.

Notifications

Stay up-to-date

Set up your notifications and don't miss out on content that matters to you

BT