Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Swift and Objective-C: Best Friends Forever?

Swift and Objective-C: Best Friends Forever?

See how you can work with both languages in the same project, what works well and what the pitfalls are.

Most recently given at CocoaConf Atlanta, December 2014.

Original version presented October 14, 2014 at the Swift Language Users Group in San Francisco, CA.

Jonathan Blocksom

December 06, 2014
Tweet

More Decks by Jonathan Blocksom

Other Decks in Programming

Transcript

  1. Agenda • Swift - Obj-C Bridge Basics • Adding Swift

    to Objective-C • More Details • Adding Objective-C to Swift • Calling C from Swift
  2. Why? • There is a handy Swift library you want

    to use in Obj-C • There is a handy Obj-C library you want to use in Swift • You have a big Obj-C codebase and you want to learn Swift • You’ve been using Swift elsewhere and you like it better • It’s shiny and that affects your rationality
  3. Swift at Capital One • Native rewrite of mid sized

    iPhone App • Due in 2015, targeting iOS 7 and 8 • Skilled team that likes the bleeding edge • July 2, 2014:
 ~180 .m files in project
 October 9, 2014:
 190 .m files
 103 .swift files
 (including tests) 0 50 100 150 200 250 300 Commits over time Objective-C and Swift Class Count
  4. Overview: Swift Compatability • Swift built for Objective-C Compatibility •

    Also works with C • Will not accept C++’s friend request Swift C Obj-C C++
  5. Objective-C and Swift Type Bridging Objective-C Swift BOOL Bool NSInteger

    Int NSString * String SEL Selector id AnyObject NSArray * [AnyObject]! NSDictionary * [NSObject: AnyObject] ^{} {} Class AnyClass
  6. ? and ! • Objective-C: Pointers to objects can be

    nil • Swift: No nil objects • ? • Declaring parameter to be optional (may be nil) • Testing for nil • ! • Implicitly unwrapped optionals (won’t be nil)
  7. Swift Features Not Available to Obj-C • Generics • Tuples

    • Swift Enums • Swift Structs • Top-level Swift functions • Global variables defined in Swift • Typealiases defined in Swift • Swift-style variadics • Nested types • Curried functions
  8. Trying it Out:
 Adding Swift to Existing Obj-C • Simple

    Project • Add some Swift to do something interesting
  9. Xcode: Swift - ObjC Headers Swift Obj-C Product- Bridging- Header.h

    Product- Swift.h You fill this file in Xcode Makes this
  10. Adding Swift to Existing Obj-C Project • Add a Swift

    file • In Obj-C file: • #import “ProductName-Swift.h” • Use classes, functions as usual • Make sure target is 7.0 or above
  11. Using Swift Objects • Class methods: just call them •

    Instantiating: use class method or inherit from NSObject • Instance Methods: just call them
  12. Swift • Function must be in a class • No

    @objc keyword because of NSObject #import "DemoApp-Swift.h" - (void)viewDidLoad { [super viewDidLoad]; Foo *aFoo = [[Foo alloc] init]; [aFoo bar]; } import Foundation public class Foo : NSObject { public func bar() { println("Hi from Swift") } } • Include <ProductName>-Swift.h • Create object and send message as usual
  13. Bringing Swift to Obj-C • Simple Swift class • Subclasses

    NSObject • Name: String
 Weight: Int
 Type: Swift Enum public class CuriousWidget: NSObject { enum WidgetType { case FlobGangle case HuffTanker case GorbNorgle } var name: String var weight: Int var type = WidgetType.FlobGangle override init() { name = "Widget" weight = 1 } }
  14. Bringing Swift to Obj-C • Basic Usage • Allocate, initialize


    Set name
 Set weight • Cannot set type: self.widget = [[CuriousWidget alloc] init]; self.widget.name = @"thingybob"; NSLog(@"%@", self.widget); self.widget.weight = 2; NSLog(@"%ld", self.widget.weight);
  15. Bringing Swift to Obj-C • Obj-C Class Definition: __attribute__((objc_runtime_name("_TtC7BFFDemo13CuriousWidget"))) __attribute__((objc_subclassing_restricted))

    @interface CuriousWidget : NSObject @property (nonatomic, copy) NSString * name; @property (nonatomic) NSInteger weight; - (instancetype)init __attribute__((objc_designated_initializer)); @end Missing type enum property Can’t subclass * Actual Class name: BFFDemo.CuriousWidget
 (Check via NSStringFromClass or class_getName) More on name mangling:
 https://mikeash.com/pyblog/friday-qa-2014-08-15-swift-name-mangling.html
  16. Subclassing Swift @objc Classes • You can’t • You can

    add category methods @interface CuriousWidget(DupeWithName) - (CuriousWidget *)dupeWithName:(NSString *)newName; @end @implementation CuriousWidget(DupeWithName) - (CuriousWidget *)dupeWithName:(NSString *)newName { CuriousWidget *newWidget = [[CuriousWidget alloc] init]; newWidget.name = newName; newWidget.weight = self.weight; return newWidget; } @end (see the bug?)
  17. KVO • Can observe property changes in @objc properties [self.widget

    addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil]; - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { NSLog(@"Observed change, widget is %@“, self.widget); }
  18. Method Swizzling • Must use dynamic dispatch
 (Implicit with @objc)

    • Standard Obj-C swizzle w/ method_exchangeImplementatio n @objc public func incWeight() { weight += 1 } @objc public func decWeight() { weight -= 1 } NSLog(@"Weight: %ld", (long)self.widget.weight); [self.widget incWeight]; NSLog(@"Weight: %ld", (long)self.widget.weight); SwizzleMethod([self.widget class], @selector(incWeight), @selector(decWeight)); NSLog(@"Weight: %ld", (long)self.widget.weight); [self.widget incWeight]; NSLog(@"Weight: %ld", (long)self.widget.weight); 2014-10-18 10:36:38.287 BFFDemo[57175:2386745] Weight: 1 2014-10-18 10:36:38.288 BFFDemo[57175:2386745] Weight: 2 2014-10-18 10:36:38.288 BFFDemo[57175:2386745] Swizzled 2014-10-18 10:36:38.288 BFFDemo[57175:2386745] Weight: 2 2014-10-18 10:36:38.288 BFFDemo[57175:2386745] Weight: 1 Swift Functions in class: Output:
  19. Dynamic Dispatch • Using Obj-C Runtime to look up method

    implementation • Not default when using Swift classes • @objc enables by default
  20. Naming Rules • First parameter name is implicit
 - (void)initWithFoo:(MyFoo

    *)foo
 withFoo is dropped, becomes
 override public func init(foo: MyFoo)
  21. Product-Bridging-Header.h • #import all your Obj-C stuff that you want

    in Swift • One per project • Example: NSObject MyNSObject SwiftNSObj AnotherSwiftObj MyViewController creates #import "ViewController.h" #import "MyNSObject.h"
  22. Beware Circular Dependencies • Don’t include Product-Swift.h in Obj-C .h

    files MyObject MyVC Bridge Header SwiftVC Use forward declaration (@class)
  23. Optionals and Implicitly Unwrapped Optionals • Must know your ?s

    and !s • Many Cocoa APIs have been vetted • By default any NSObject return value should be ? var bar:SomeClass? if let aBar = bar { aBar.doSomething() }
  24. bridgeToObjectiveC() • Removed in Beta 5 • Do you remember

    beta 5? • Now implicitly bridged
 var str = "Hello, playground" str.bridgeToObjectiveC().length
  25. Swift and C • Search “Mike Ash NSSpain Swift and

    C” • Demonstrates using C with Swift • Pointers!
  26. Scenario • You are a grumpy old graybeard and the

    young hotshot has included AlamoFire for networking in your project • Your tech lead has forbidden NSURLConnection • You haven’t bothered to figure out NSURLSession • CocoaPods broke so you can’t include AFNetworking • You’re pretty sure running curl in the background won’t make it through app review
  27. AlamoFire • Swift networking • How to use from Obj-C?

    Response Handling Alamofire.request(.GET, "http://httpbin.org/get", parameters: ["foo": "bar"]) .response { (request, response, data, error) in println(request) println(response) println(error) }
  28. Types Alamofire.request(…).response{…} public func request(
 method: Method, 
 URLString: URLStringConvertible,

    
 parameters: [String: AnyObject]? = nil, 
 encoding: ParameterEncoding = .URL) 
 -> Request public class Request public func response( completionHandler: (…) -> Void) -> Self Method chain to create request object
  29. Creating the response instance • Hey look, a sharedInstance! public

    func request(…) -> Request { return request(encoding.encode(URLRequest(method, URLString), parameters: parameters).0) } /** Creates a request using the shared manager instance for the specified URL request. If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned. :param: URLRequest The URL request :returns: The created request. */ public func request(URLRequest: URLRequestConvertible) -> Request { return Manager.sharedInstance.request(URLRequest.URLRequest) }
  30. Manager sharedInstance • Use the singleton! Add @objc: • Call

    in ViewController.m: • Good, as can’t call alloc because it’s not an NSObject public class Manager { /** A shared instance of `Manager`, used by top-level Alamofire request methods, and suitable for use directly for any ad hoc requests. */ public class var sharedInstance: Manager { Manager *manager = [Manager sharedInstance]; @objc public class Manager {
  31. URLRequest • Two request calls • URLRequestConvertible: public func request(


    method: Method, 
 URLString: URLStringConvertible, 
 parameters: [String: AnyObject]? = nil, 
 encoding: ParameterEncoding = .URL) 
 -> Request public func request( URLRequest: URLRequestConvertible) -> Request /** Types adopting the `URLRequestConvertible` protocol can be used to construct URL requests. */ public protocol URLRequestConvertible { /// The URL request. var URLRequest: NSURLRequest { get } } extension NSURLRequest: URLRequestConvertible { public var URLRequest: NSURLRequest { return self } }
  32. Request method • Takes URLRequestable • Protocol declared by Alamofire

    • Alamofire extends NSURLRequest to conform to this… • …but compiler doesn’t really agree
  33. Wrap request method • Obj-C version of request method •

    Call from Obj-C: • Had to add @objc to Request class in Swift @objc public func requestFromObjC(req: NSURLRequest) -> Request { return request(req) } Manager *manager = [Manager sharedInstance]; NSURL *url = [NSURL URLWithString:@"http://httpbin.org/get"]; NSURLRequest *req = [NSURLRequest requestWithURL:url]; Request *r = [manager requestFromObjC:req];
  34. Response method • Sets response handler • Add @objc to

    response function and now we can call: • Not very idiomatic… [r response:^(NSURLRequest *r2, NSHTTPURLResponse *resp, id foo, NSError *err) { NSLog(@"Request: %@", r2); NSLog(@"Response: %@", resp); }];
  35. Better response method • @objc (setResponse:) … • Looks better

    on Obj-C side • Can even use property assignment! [r setResponse:… ]; r.response = ^(NSURLRequest *r2, NSHTTPURLResponse *resp, id foo, NSError *err) { NSLog(@"Request: %@", r2); NSLog(@"Response: %@", resp); };
  36. Final Result • Steps we took:
 Sprinkled @objc here and

    there
 Wrapped a function
 Set some Obj-C style method names Manager *manager = [Manager sharedInstance]; NSURL *url = [NSURL URLWithString:@"http://httpbin.org/get"]; NSURLRequest *req = [NSURLRequest requestWithURL:url]; Request *r = [manager requestFromObjC:req]; r.response = ^(NSURLRequest *r2, NSHTTPURLResponse *resp, id foo, NSError *err) { NSLog(@"Request: %@", r2); NSLog(@"Response: %@", resp); }; Swift Version
 Alamofire.request(.GET, "http://httpbin.org/get", parameters: ["foo": "bar"]) .response { (request, response, data, error) in println(request) println(response) }
  37. Gotchas & Lessons Learned • ProductName-Swift.h header
 Name mangling hard

    if ProductName has been changed • Wrong Import: don’t import Swift class, import -Swift.h • Optionals that aren’t • Regen Certificates • Go slow — integrate a bit at a time, especially on a big team
  38. Wrong import • Accidentally importing MyClass.swift instead of App-Swift.h: •

    Caused by this in SomeObjC.m: #import "Blah.swift" // Should have been #import "<Product>-Swift.h"
  39. <ProductName>-Swift.h Header • Must include to access Swift objects •

    May be name mangled: non alphanumeric chars become underscores • ProductName different for different targets • Use Product Module name build setting • Our experience: • Spurious compiler errors after successful build • Lots of clean & rebuild required
  40. Optionals (and things that should be optionals) • Easy to

    get properties that could be nil which don’t look that way in Swift • If they are nil at runtime your app crashes • Double check using if let constructs
  41. Example • currentLocation was nil, app crashed • Shouldn’t Swift

    type checker have noticed? • @property (copy, nonatomic) CLLocation currentLocation;

  42. Enabling Swift & New Signing Certificate • Need to enable

    “Embedded Content Contains Swift” build setting • Certificates made before Xcode 6 beta can’t include Swift for iOS 8 • Needed to regenerate certificate • Worked on iOS 7, only shows up in release builds…
  43. Other Gotchas • initWithCoder implementations • Int vs Integer •

    Lots of CGFloat casting • Module names are case sensitive
 (it’s not AlamoFire)
  44. WWDC Sessions on Swift & Obj-C • 406: Integrating Swift

    with Obj-C • 407: Swift Interoperability in Depth Watch this one!
  45. My Favorite Swift Resources in No Particular Order • Mike

    Ash NSBlog • Erica Sadun Blog
 http://ericasadun.com/ • Daniel Steinberg Swift Book • objc.io Swift Issue • Apple iBooks on Swift
 (Be sure to update from iBooks; may need to delete and redownload)