قالب وردپرس درنا توس
Home / IOS Development / A Cross-Approach – Ole Begemann

A Cross-Approach – Ole Begemann



I'm currently working on a fairly large iOS app with a long story. We try to write new code in Swift, but about 75-80% of the code base is still Objective-C. We do not rewrite existing code in Swift just for the sake of it, but only if a subsystem is due to a greater overhaul anyway. The exception to the rule: When a particular subsystem reaches a tipping point – say 60% of it is written in Swift – it may make sense to transfer the rest also to minimize the pain caused by blending the two languages. [19659002] The interoperability between Objective-C and Swift is generally pretty good – In fact, I'm surprised at how well Apple was able to get the two languages ​​to work together, given how different they are. When it comes to mixing two languages, it always gives a certain friction.

I would like to talk about such a friction cause that is not even directly related to interoperability, and it is the fact that extensions can not contain stored properties; In both Swift and Objective-C, all stored properties / ivars must be part of the main type display. Although this is a general limitation that has nothing to do with interoperability, it affects me most when I work with mixed Swift / Objective-C code.

Consider the following scenario:

  • You have a fairly large and complex Objective-C class.
  • You want to add a new feature to the existing class, or you will make significant changes to a part of the class. You prefer to type the new / changed code in Swift.
  • Rewriting the entire class in Swift is not the target (immediate). A transient migration, perhaps over a period of one or two years as part of regular maintenance, would be fine though.

My common strategy for this is to write a Swift extension for the Objective-C class. The new code enters the extension. If necessary, @objc annotations reveal the extension code to Objective-C. This works well until the new code requires that I add a saved property to the class. It can not be extended, I have to add it to objective-C-class definition.

This means again that the property must have an objective C compliant type even if it is only for internal use with the Swift code. ** This is a fairly big limitation that I regularly run into: it means no structures, no enumeration with associated values, no generics and more.

Here's the solution I use: In Swift, I define an objective-C compliant class that acts as a wrapper for all stored properties that I want to use in my Swift extension. In Target C, I add a property of an instance of this class to the main class definition. When done, anything else happens in the Swift code: the properties can use Swift-only features (assuming you do not need to access them from Objective-C) ̵

1; only the class itself must be visible to Objective-C. [19659011] Example

Let's go through an example. Suppose the Objective-C class I want to expand is called NetworkService . I must save a reference to a non-Objective-Clocking closure because it uses a generic Result enum. I define a class NetworkServiceProperties that has the Swift-only feature onDownloadComplete next to my Swift extension:

  @objc    class    NetworkServiceProperties : [19659013]] 
      
      
      var    onDownloadComplete : 
          ((  Result   <  data     NetworkError >  ]    ->    Void )    =    null 
} 

This class must be compatible with Objective-C, although the properties are not. (I also tried the next class definition in the extension as it would nicely coloculate it with the code it belongs to. Unfortunately, it did not work, the Objective-C property referring to the nested class would not be visible in the Swift code.) [19659034] Then I add a property called props to the main class definition in Objective-C. This requires a forwarding declaration for NetworkServiceProperties because the generated Swift header can not be imported to Objective-C header files.

  @import    Foundation ; 
  @ class   ] NetworkServiceProperties ; 

  @interface    Network  :    NSObject 

  @property    (  nonatomic     strong  [19659039] ]    nonnull ) 
      NetworkServiceProperties    *   props ; 

  @end 

Finally, in NetworkService I initialize the new property:

  #import "NetworkService.h"
#import "MyApp-Swift.h"
  [implementation]    

  

  [   [  instancetype )   init    {
      19659077] init ]; 
      
      [ ] ]    {
          _props    =    [  NetworkServiceProperties    New    
    ] 
      return    yourself ; 
} 

  // ... 

When possible, I try to give Properties class an objective C compliant initialiser that I can call from the main class init ]. If it does not work because some properties require special initialization, I define a swift_init (or similar) feature in the Swift extension that initiates the Swift-only properties. I call swift_init from the classical (Objective-C) initialiser. This works because the class initialization rules are not so strictly enforced by the Objective-C compiler.

And that's it. I can now access the properties throughout my extension via props :

  extension    NetworkService    {
      func    doSomething  ()    {[19659097]] 
          // leave result = Result (...) 
          props .   onDownloadComplete ? (  result ) 
      19659035]} 

I really like this solution. It is very fine; Heck, you can say it is quite obvious and I agree. Nevertheless, it took me months to work on this project to think about it. Maybe it will help you too.

And when it's time to complete the migration and completely remove the Objective-C class, all I need to do is move the property definitions from NetworkServiceProperties into the class written in Swift) and delete .props wherever it is used.

Review part 2 of this series where I approach the same problem from another angle.


Source link