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


Read More...

Friday, April 01, 2016

Convert ObjC to Swift (4)

In Swift, constants are indicated by let, which means value would not be changed, while variables are indicated by var, which will be changed later on. This is a very efficient way to optimize codes and to enhance memory management. The concept is very simple and I had no problems in most cases. I got several times by giving hints to convert variables to let when they are not changed. I like this.


Variable Parameters in Swift func

In Swift func, parameters by default are constants, even they are collection objects. I had a case that I have to make some changes after my codes converted from ObjC to Swift.

For example, the following method in ObjC takes NSMutableDictionay as parameter and a new object is added to the dictionary:

  1. - (void) encodeObject:(id<NSCoding> object
  2.    withKey: (NSString *) key
  3.    toDirctionary: (NSMutableDictionary *)wrappers {
  4.    NSFileWrapper *aWrapper;
  5.    ...
  6.    [wrappers setObject:aWrapper forKey:key];
  7. }

Note
The parameter is a type of mutable dictionary. As a result, an object can be added to the dictionary within the method.

However, the default parameter would generate a compile error in Swift codes. It complains that the parameter is a let constant! I have to change the parameter with inout attribute to make the parameter as variable, not a constant.

  1. func encodeObject(object : AnyObject,
  2.    withKey key : String,
  3.    inout toDirctionary wrappers : [String : NSFileWrapper]) {
  4.    let aWrapper = NSFileWrapper(...)
  5.    ...
  6.    wrappers.updateValue(aWrapper forKey:key)
  7. }

Throw Errors


To convert exception/errors throw from a method in ObjC, the syntax of func in Swift is much more clear and simplified.

For example, the following method in ObjC includes a possible error generated from the method call

  1. @implementation MyClass
  2. ...
  3. - (id) mapToSomethingForName:(NSString *)aName error:(NSError *__autoreleasing *)outError {
  4.  ...
  5.  }
  6. @end

The corresponding Swift codes are as follows.

  1. enum MyClassError : ErrorType {
  2.    case InvalidContent
  3.    case EmptyContent
  4. }
  5. class MyClass {
  6.  func mapToSomethingByName(aName: String) throws {
  7.    if (...) { // Invalid cases or use guard (...) else {...}
  8.      throw MyClassError.InvalidContent
  9.    }
  10.    ...
  11.  }
  12. }

Note
I added a struct for error types at the beginning and throw errors in the func in case of invalid.

To handle errors in Swift is much clean and simple. To ignore errors thrown, the following is an example.

  1. let x = try? instance.methodWithErrors()
  2. if x == nil {
  3.  ...
  4. }


To catch errors, the following is another example.

  1. do {
  2.  let x = try instance.methodWithErrors()
  3.  ...
  4. }
  5. catch let error as NSERROR! {
  6.  logger.debug(){
  7.      return String(format: "error: \(error.localizedDescription)")
  8.      }
  9. }

References


Read More...