Wednesday, April 06, 2016

Convert ObjC to Swift (5)

My project conversion from ObjC to Swift is in good process. I think that I gained great knowledge and experiences about Swift during the process. I started from simple classes.   My basic principle is to keep ObjC codes as-they-are as much as possible. In another word, I would not make changes in the remaining ObjC codes, while part of ObjC being converted to Swift.

There are many ways to convert ObjC elements to Swift. During the process, I gradually learned many better ways or tips. In this article, I will talk about constants, macros, enum, private data and methods, and selector from ObjC to Swift.

Constants

I have many constants defined by #define in ObjC. If those constants are private to the class in .m, I would use Swift struct in module level as private data members in .swift file.

For example, the following are some constants in a .m file:

  1. #define kEntity @"Entity"
  2. #define kEntityChild @"EnityChild"
  3. @interface MyEntity
  4. + (Entity* ) createEntityByName:(NSString *)name
  5.      inManagedObjectContex:(NSManagedObjectContext *) moc {
  6.    Entity* entity;
  7.    entity = [NSEntityDescripton insertNewObjectForEntityForName: kEntity
  8.      inManagedObjectContext: moc];
  9.    entity.name = name;
  10.    return entity;
  11.    }
  12. ...
  13. @end

The converted .swift codes are:

  1. private struct MyEntityConstants {
  2.  let kEntity = "Entity"
  3.  let kEntityChild = "EnityChild"
  4. }
  5. class MyEntity : NSObject
  6.  static func createEntityByName(name : String,
  7.      inManagedObjectContex moc: NSManagedObjectContext) -> Entity {
  8.    let c = MyEntityConstatns()
  9.    let entity NSEntityDescripton.insertNewObjectForEntityForName(c.kEntity,
  10.      inManagedObjectContext: moc) as! Entity
  11.    entity.name = name
  12.    return entity
  13.  }
  14. ...
  15. @end

If some #define constants are used in other ObjC codes, I would either move them to my ObjC2SwiftHelper.h file. Or I prefer to add a method to my Swift class to encapsulate constants as private in the same .swift file.
Note
There is no #define type in Swift. You could use let to define constants in Swift. I prefer to use struct to group them as private constants together.

Here I use a local constant based on struct within the method. This will take a memory on heap and will be released on exit. I think this is better than memory on stack for #define constants.

Macros


In ObjC or C, #define can be used to define a macro (function call-like), which is not available in Swift. I have two very useful macros to get calling context class and method names.

  1. #define CALLER_SELECTOR_NAME NSStringFromSelector(_cmd)
  2. #define CALLER_CONTEXT       NSStringFromClass([self class])
  3. ...
  4. - (MyLogger*) logger {
  5.    if (!_logger) {
  6.        _logger = [MyLogger instance:CALLER_CONTEXT];
  7.    }
  8.    return _logger;
  9. }
  10. ...
  11. - (void) viewWillAppear:(BOOL)animated {
  12.    [self.logger debug:
  13.     ^{
  14.         return [NSString stringWithFormat:@"'%@' is called!", CALLER_SELECTOR_NAME];
  15.     }];
  16.    ...
  17. }

I found equivalent ways in Swift to get class and method names.

  1. // module level logger constant. Use String(ClassName) to get class name.
  2. - private let logger = MyLogger.instance(String(MyClass))
  3. ...
  4. class MyClass : NSObject {
  5.  func viewWillAppear(animated: Bool) {
  6.    logger.debug() {
  7.        //Use #function to get calling method name
  8.        return String(format: "'\(#function)' is called!"))
  9.     }
  10.    ...
  11.  }
  12.  ...
  13. }

Use Swift enum in ObjC


During my conversion, I have some enum types in Swift, but they are still used in some other ObjC codes. At first, my Swift enum types are not available in in ObjC, even they have the same enum types (Swift enum raw value as Int). There is a way to resolve the issue, by pasting enum name to the front of each enumerator names as in a Stackoverflow QA.

I prefer try to keep ObjC codes as much as possible as-they-are. My temporary solution is to copy ObjC enum definitions to my ObjC2SwiftHelper.h file. As a result, the ObjC enum types are still available as-they-were, and the converted Swift enum types are only used in Swift codes.

Private data members and methods


In ObjC, all public data members and methods are exposed to outside by using .h file. In .m file, you can add more data members and methods, which are not published to outside. In theory, they are still available to outside if you know how to call they correctly. This is a way to add private data or methods to ObjC class.

In Swift, you can use private to hide data members and methods completely. By default, all data members and func are public. I could add data and func in a class with private restriction. However, I prefer to move all private members to the module level, outside of a class. In this way, all the members within the class are public and it is much simple and clean.

Selector Support in Swift


According to the information of a Stackoverflow QA (as in reference), Selector type defined in Swift is different from that in ObjC. Therefore, even Selector type is supported in most cases in Swift by using #selector(ClassName.methodName), but property setter has to use another way (Selector(setFoo:)).

I like to use #selector() than Selector(), because compiler can verify the first case, not the later one. I find a way to avoid to reference to property setter by Selector: add a func in Swift class and call the setter within the func. In case I have to use Selector to the property setter, I use #selector() with the func.

For example:

  1. class MyClass : NSObject {
  2.  var entity : Entity // property
  3.  func setEntity(obj: Entity) {
  4.    self.entity = obj // call setter to set property value
  5.  }
  6.  ...
  7. }
  8. // in another Swift module
  9. document.save(entity, notify:entity, withSelect:#selector(MyClass.setEntity(_:)))

References


0 comments: