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. }

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. }

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. }