en
de

What I Like in Swift 2 — The Return (Part 2 of 3)

9 July 2015
| |
Reading time: 5 minutes

This is part 2 of 3 of this post series about Swift 2. You can read part 1, if it is not already the case.

Today we will be discussing the following topics:

  • Protocol extensions
  • for … in … with filtering
  • Unit testing internal classes and methods
  • Faster Swift compiler
  • Objective-C generics

Protocol Extensions

We briefly talked about that aspect in the last paragraph of part 1. Protocol extensions open a whole new set of possibilities. And there is a lot more to them than only extending a protocol with a method. From the extension, we can have access to various generic parameters known to the base protocol and formulate conditions when the extension should be active.

But let’s look at a basic example of a protocol extension first.

// basic idea of a protocol extension

protocol FooProtocol {
    func someMethod() -> String
}

extension FooProtocol {
   
    func someMethod() -> String {
        return "hi there"
    }
   
    func anotherMethod() -> String {
        return "hey guys"
    }

}

class Foo: FooProtocol {
   
}

let f = Foo()
print(f.someMethod())
print(f.anotherMethod())
// prints out
// “hi there"
// “hey guys"

To briefly comment what we did: we defined a protocol with a method signature someMethod. We extended the protocol, provided an implementation for someMethod and declared another method anotherMethod. When we instantiated an object of a class implementing FooProtocol, we got automatically the two methods from the extension.

As I said, within the extension we have access to the generic types known to the protocol and we can restrict the validity of the extension using the where keyword.

extension CollectionType 
          where Generator.Element == Int {
   
    func sum() -> Generator.Element {
        return self.reduce(0) { (a,b) in a + b }
    }
   
}

let ints = [1,2,3,4]

// sum is a valid method on ints, 
// since we have an array of Int
ints.sum()

let strings = ["hello", "world"]

// not allowed, sum() is only defined 
// for collections of Int
//strings.sum()

If we need to get the actual type of the class implementing the protocol (say we have an Array, which is also a CollectionType, the actual type of our array is, trivially, Array), we can use the keyword Self.

extension CollectionType 
          where Generator.Element: Comparable {
 
    func firstIsHigherThan(other: Self) -> Bool {
        let thisFirst  = self.first
        let otherFirst = other.first
        return thisFirst > otherFirst
    }
   
}

[1,2,3].firstIsHigherThan([3,2,1])
[3,2,1].firstIsHigherThan([1,2,3])

(1...3).firstIsHigherThan(3...6)
(3...6).firstIsHigherThan(1...3)

// won’t compile
//[1,2,3].firstIsHigherThan(3...6)

In this example the last line won’t compile, since the given parameter – of type Range<Int> – is not of the same type than the object on which we call the method.

The term protocol extension, trivially, is used in relation to extending a protocol. But the extension keyword is more general, and can be used to extend a protocol, a struct or a class.

More info: WWDC talk ‘Protocol-Oriented Programming in Swift’

for … in … with Filtering

Elaborating over for … in … seen previously, it is now possible to express an additional condition regarding the items from the collection which should be iterated over, using a where clause.

let nbs = [5,42,442,4442]

for nb in nbs where nb >= 10 && nb <= 1000 {
    print(nb)
}
// will print out only 42 and 442

Looks a bit similar to the for comprehension we know in Scala, though a lot less powerful, since a for comprehension in Scala can chain iterations over several collections, and express each time a condition over the items we should iterate over. That kind of behaviour is not possible in Swift 2.

I tried to express the previous code using pattern matching, with a range, inside the loop. It works, excepted that I did not manage to grab the iteration item (using a let somewhere within the case). The working version looks like this.

for case (10...1000) in nbs {
    print("hello")
}
// will print twice “hello"

More info: WWDC talk ‘What’s new in Swift’

Unit Testing Internal Classes and Methods with @testable

In Xcode, when you write unit tests inside a project, you have a separate target for the tests, as an own module. Previously if you wanted to have access to classes from the main target from within the test target, you had to do one of the following two things:

  • Include all classes from the main target that you wanted to test, and their dependencies, to the test target
  • Or make all classes and methods you wanted to access from the test target public. The internal visibility (without modifier) was not enough.

Rather painful.

Fortunately Swift 2 has a solution to that problem. You can import from a test class the main module using the @testable prefix modifier. This will make all internal and, of course, public classes and methods visible to your test class.

In case of a main module called ‘Swift2’ it would give this code.

@testable import Swift2

A lot cleaner than before.

More info: WWDC talk ‘What’s new in Swift’

Faster Swift Compiler

The first version of Swift (until 1.1) had a quite slow compiler, which was not able to do incremental compilation of Swift classes – that means each time you were hitting the build key, all Swift classes were compiled again, even if you had only made a trivial change in a Swift class. This was very painful with build times easily flirting with the 2 minutes mark, if you had a couple (like a dozen) Swift classes.

That improved already drastically in Swift 1.2 with incremental compilation. The compile time of a pure Swift or mixed Swift & Objective-C project was again in the same range than for a pure Objective-C project.

The compile time for Swift 2 has been apparently improved again. Always nice to have. I have not noticed a huge difference in the couple of projects I have compiled with Xcode 7 so far though.

Objective-C Lightweight Generics

This is actually not directly a Swift feature, rather an Objective-C one, but which has a huge impact on the API visible to Swift, hence worth mentioning here. Previously in Objective-C the collections where not typed, hence you had always a collection of NSObjects, where you had to downcast each item to the right class when accessing it.

That was visible in the auto-generated Swift API where every method accepting or returning e.g. an NSArray had the array type declared in Swift as [AnyObject]. This was causing a fair amount of additional work in Swift downcasting the collections or individual objects.

Now the generic type of a collection can be specified in Objective-C. A lightweight support for generics, as they say.

For instance the following Objective-C class declaration:

@interface ObjCWithGenerics : NSObject

- (nonnull NSArray<NSString *> *)names;
- (void)addViews:(nullable NSArray<UIView *> *)views;

@end

will automatically be translated in Swift into

class ObjCWithGenerics : NSObject {
    func names() -> [String]
    func addViews(views: [UIView]?)
}

You will notice that the first method names returns directly an array of String. And that it is a non-optional collection, since it is marked in Objective-C with the nonnull keyword (see section about Optionals at the beginning of part 1 for more details about that).

Covariance of Objective-C Typed Collections

The chosen covariance rule for typed collections in Objective-C is quite sensible.

If we work only with read-only collections, there are few things that we can go wrong having covariant collections, hence they are covariant.

In this example a read-only array of buttons is also a read-only array of views. The compiler does not complain.

NSArray<UIButton *> * buttons = @[[UIButton new], 
                                  [UIButton new]];
NSArray<UIView *> * views = buttons;
for (UIView *view in views) {
    NSLog(@"This is a view: %@", view);
}

Mutable collections have another behaviour, they are not covariant (or at least the compiler warns us that something could go wrong). Let’s look at an example first.

NSMutableArray<UIButton *> *mutableButtons = 
        [NSMutableArray new];
[mutableButtons addObject:[UIButton new]];
   
NSMutableArray<UIView *> *mutableViews = 
        mutableButtons; // compiler warning
[mutableViews addObject:[UILabel new]];

Here we are breaking the original collection of buttons mutableButtons by adding a label to mutableViews. If we still hold the first reference to the collection mutableButtons, we might think that we still have a collection of buttons, hence we might later get a runtime error when handling a label as a button. The compiler warning is a good indication that we should pay attention.

More info: WWDC talk ’Swift and Objective-C Interoperability’

This is the end of this second post (2 of 3) about Swift 2. Make sure you check the upcoming article where we will be talking about topics like the new error handling mechanism in Swift 2 and early exit with guard, among others.

References

Comments (0)

×

Sign up for our Updates

Sign up now for our updates.

This field is required
This field is required
This field is required

I'm interested in:

Select at least one category
You were signed up successfully.

Receive regular updates from our blog

Subscribe

Or would you like to discuss a potential project with us? Contact us »