Wednesday, March 30, 2016

Convert ObjC to Swift (3)

In Swift, a class does not need to be based on NSObject, unlike ObjC. However, in order to make Swift class accessible to ObjC codes, a Swift class has to be based on NSObject. I found that if not, I would not see my swift class in [PrjectName]-Swift.h hidden header file. As a result, you would get compile time error about your class name is unknown identity.

NSObject as a Base Class for Swift Class


During the conversion process, I have to make my Swift class based on NSObject. This is quite easy to do.

  1. @objc
  2. class MyClass: NSManagedObject {
  3.  ...
  4. }

Note
After all my ObjC classed converted to Swift, none of Swift classes is referenced by ObjC codes, I think I have to remove the base NSObject inheritance.

CoreData NSManagedObject

Xcode provides interface to generate Swift codes for managed object classes. For example, MyItem entity in Xcode data model, the ObjC files are:

MyItem.h
MyItem.m


corresponding Swift files are:

MyItem+CoreDataProperties.swift
MyItem.swift


For the current Xcode version 7.2 and Swift 2.0, I found that there is one thing missing when you convert ObjC entity class with one to many relations to Swift. For examsle, the following ObjC codes are generated by Xcode long time ago (new Xcode 7.3 generated codes are different but similar):

  1. #import <Foundation/Foundation.h>
  2. #import <CoreData/CoreData.h>
  3. #import "MyItems.h"
  4. @interface MyEntity : NSManagedObject
  5. @property (nonatomic, retain) NSString * name;
  6. @property (nonatomic, retain) NSSet *myItems;
  7. @end
  8. @interface MyEntity (CoreDataGeneratedAccessors)
  9. - (void)addMyItemsObject:(MyItem *)value;
  10. - (void)removeMyItemsObject:(MyItem *)value;
  11. - (void)addMyItems:(NSSet *)values;
  12. - (void)removeMyItems:(NSSet *)values;
  13. @end


However, the auto-generated Swift codes in (Swift file, for example, MyItem+CoreDataProperties.swift) do not contain the corresponding methods.

  1. import Foundation
  2. import CoreData
  3. extension MyItem {
  4.    @NSManaged var name: String?
  5.    @NSManaged var myItems: NSSet?
  6. }

After searching from Internet, I found a solution to manually add those Swift codes. In stead of adding codes to the auto-generated codes, I think that it is better to create a new extension Swift file and to add the missing codes there.

  1. import Foundation
  2. extension MyItemItem {
  3.    // The current Xcode does not add the following methods
  4.    // This is what I manually added
  5.    @NSManaged func addMyItemsObject(value:MyItem)
  6.    @NSManaged func removeMyItemsObject(value:MyItem)
  7.    @NSManaged func addMyItems(value:Set<MyItem>)
  8.    @NSManaged func removeMyItems(value:Set<MyItem>)
  9. }

References


Read More...

Saturday, March 26, 2016

Convert ObjC to Swift (2)

There are some differences between Objective-C and Swift. During the conversion, I found that the customized constructor or initializer in ObjC is different from Swift. For example, in ObjC, the initializer is in the format of initWith...:... parameters, while in Swift, all initializers are in the same name of init(...), like other modern languages.

This presents a challenge, how this kind of initWith...:... ObjC constructor be directly converted to Swift?

Class Factory


I come to Design Pattern to find a solution: Class Factory. In Swift, a static function can be defined as class factory to create an instance of the class!

For example, in the following swift file, all init() constructors are private. As a result, I force to use class methods to create instance of the class MyLogger:

  1. import Foundation
  2. //FIXME: Remove objc and inheritance from NSObject after convertion to Swift is completed
  3. @objc
  4. class MyLogger : NSObject {
  5. //MARK: Init methods
  6.    //Make all init as private constructors
  7.    private override init() {
  8.        self.level = MyLoggerLevel.LogLevelDebug
  9.    }
  10.    private convenience init(loggingContext: String) {
  11.        self.init()
  12.        self.context = loggingContext
  13.    }

  14.    //Force to use these two class methods to create instance!
  15.    static func instance(loggingContext: String) -> MyLogger {
  16.        return MyLogger(loggingContext:loggingContext)
  17.    }
  18. ...
I have to make some changes in my ObjC codes to adopt this design pattern. This actually makes my ObjC codes much clean and beautiful, getting rid of [[MyLogger alloc] initWith...]. In this way, the converted ObjC codes looks like this.

  1. #import "MyiOSApp-Swift.h"
  2. #import "ObjC2SwiftHelper.h"
  3. @interface MyObjCClass ()
  4. @property (strong, nonatomic) MyLogger* logger;
  5. @synthesize logger = _logger;
  6. - (MyLogger *) logger {
  7.    if (!_logger) {
  8.        /* Remove alloc init codes
  9.        _logger = [[MyLogger alloc] init];
  10.        _logger.context = CALLER_CONTEXT; */
  11.        //Use class method to get instance
  12.        _logger = [MyLogger instance:CALLER_CONTEXT];
  13.    }
  14.    return _logger;
  15. }
  16. ...
Note
Even I made my init(...) constructor in swift as private, but I found this is still accessible from ObjC. Not sure why.

Actually it is good to find it out. If I did not write this blog, I would not go so deep to find the issue.

Method Signature Mapping

In Swift, the method signature includes both method name and parameter names. Therefore, the conversion to ObjC has to be matched completely in both.

I find out that a method definition in Swift, as in the following example:

  1. //Case 1: method with two named parameters
  2. func swiftMethod(para1 sValue : String, para2 aInt : Int) {
  3. ...
  4. }

where: swiftMethod is the name of method, para1 and para2 are parameter names, and sValue and aInt are parameter value names.

The first parameter name can be omitted without a name, which is very common in both swift and ObjC:

  1. //Case 1: method with 1st parameter with no name, 2nd with a name
  2. func swiftMethod(sValue : String, para2 aInt : Int) {
  3. ...
  4. }

The 2nd case is very simple to map to ObjC:

  1. [swiftMethod:@"Test" para2: 1];

How about the first case, the first parameter with a specified name? I found out the interesting point, the first parameter name has to be linked with With in between method name and parameter name.

  1. [swiftMethodWithPara1:@"Test" para2: 1];

Note
Notice that camel case of capital letter of the parameter name in part of method name(swiftMethodWithPara1), even it is defined in Swift in lower case as para1.

The above swift example can be further simplified with all parameters with no names
  1. //Case 3: method with all parameters with no names
  2. func swiftMethod(sValue : String, para2 : Int) {
  3. ...
  4. }

To convert the 3rd case to ObjC, the parameter names after 1st parameter have to use the swift parameter value names as default names:

  1. [swiftMethod:@"Test" para2: 1];

Based on above findings, it seems that an alternative ObjC solution for swift init(...) customized constructors is found, as in the following example:

  1. let defaultInitInt = 1
  2. class MyClass {
  3.  //init with two named parameters
  4.  init(para1 sValue : String, para2 aInt : Int) {
  5.  ...
  6.  }
  7.  //init with only one parameter without name
  8.  convenience init(sValue : String) {
  9.    self.init(para1: sValue, para2: defaultInitInt)
  10.  }
  11. }


The corresponding ObjC code can be something like this:


  1. //Create instance with init of two named parameters
  2. MyClass* instance1 = [[MyClass alloc] initWithPara1: @"Test" para2:1];
  3. //Create instance with init of only one parameter
  4. MyClass* instance2 = [[MyClass alloc] initWithSValue:@"Test2"];

Read More...

Friday, March 25, 2016

Convert ObjC to Swift (1)

Recently I started to convert my previous iOS app in Objectiv-C to Swift. There are many tools available for conversion, however, I prefer to do it manually myself. I think this will be good opportunity to learn/review Swift and to understand better both programming languages.

The start did take some tough time, but it worth the try. Here I would like to take some notes on issues I have experienced.

Swift Bridging Header File


I started from my Objective-C project in Xcode. The first time I tried to add a Swift file, I got this prompt asking to create a Swift bridging header file:



This header file is a blank file when it is created. From the comments in this header, it looks like that the purpose of this bridging header is mainlly for my ObjC classes to be visible to my Swift classes, therefore, I have to add any header files if I would like to expose ObjC classes to Swift. Since I am going to convert all my .h and .m files to swift files, I don't need to add any header to this bridging header file.



However, I found one case I do need to use this bridging header. I have the following C #define macros, which are not supported in swift file:


  1. #define CALLER_SELECTOR_NAME NSStringFromSelector(_cmd)
  2. #define CALLER_CONTEXT       NSStringFromClass([self class])


The above macros were defined in a pair of .h and .m file. After conversion, I moved the above codes to a temporary header file called as ObjC2SwiftHelper.h and included this file in the bridging header file. Then removed the pair of .h and .m file from my project. With this helper header file, the macros are available for other ObjC files.
Note
You don't need to add any codes in your ObjC or Swift files to include this bridging header file. The Xcode will automatically make this header files available for your project classes.

The bridging header file has to be named in this format: [PrjectName]-Bridging-Header.h

Hidden Swift Header: Making Swift class visible to ObjC


During the conversion, for each new conversion swift class based on a pair of .h and .m ObjC class, the class in swift has to be visible to ObjC if the class is used in ObjC. There is no header file for swift class. The way to make a swift class available for an ObjC class is to include a special hidden header in the ObjC .h or .m file.



For example, in my ObjC class I refer to a swift class, a special hidden swift header file has to be included:

  1. // This is a .m file. MyLogger class has been converted to swift.
  2. // Inorder to access to MyLogger class, include this special
  3. // header to make all swift class available
  4. #import "MyiOSApp-Swift.h"
  5. #import "Objc2SwiftHelper.h" // Helper header to provide macros
  6. @implementation MyEntity (Create) // Extend MyEnity class for creating feature
  7. #pragma mark - Private methods
  8. + (MyLogger*) logger {
  9.    MyLogger* log = [MyLogger instance:CALLER_CONTEXT];
  10.    return log;
  11. }
  12. ...

Note
The special header file is in the format of [ProjectName]-Swift.h

It is not visible in Xcode project. However, I found you can still access to it if you highlight the header and open it from context menu Jump to Definition.

Swift String and ObjC NSString


In swift, String is equivalent to ObjC NSString. According to Apple Development documentation, swift String is automatically mapped to NSString if you refer to String in swift codes.

However, I found in one case, I have use NSString class in swift instead of String. In my project, I made extension to NSString class. When I tried to convert my extension to swift, I used String extension. I found that this extension is not available in my ObjC codes.

After struggling for a while, I realized that instead of extension to String in swift, I have to explicitly extend NSString in swift. I think that I have to make final revision to extension to String after all ObjC codes converted.

  1. import Foundation
  2. extension NSString {
  3.    // Helper getter to convert NSString to String
  4.    private var swift : String { return self as String }
  5.    func isNumeric() -> Bool {
  6.        var retVal: Bool = false
  7.        let sc: NSScanner = NSScanner(string: self.swift)
  8.        if sc.scanFloat(nil) {
  9.            retVal = sc.atEnd
  10.        }
  11.        return retVal
  12.    }
  13. ...

I also found another case that NSString is not automatically mapped to String. For example, in ObjC codes, NSFileWrapper class initializer takes NSMutalbleDictionary as parameter:

  1. NSMutableDictionary* wrappers = [NSMutableDictionary dictionary];
  2. ...
  3. NSFileWrapper* fWrapper = [[NSFileWrapper alloc] initDirectoryWithFileWrapper: wrappers];


Convert the above codes to Swift, NSMutalbleDictionary actually is the type or dictionary of [NSString : NSFileWrapper] in Swift. However, NSFileWrapper initializer takes the dictionary of [String : NSFileWrapper]. Those two are different in Swift. The converted codes have to be like:
  1. var wrappers : [String : NSFileWrapper] = [:]
  2. ...
  3. fileWrapper = NSFileWrapper(directoryWithFileWrappers: wrappers)


References


Read More...

Friday, March 18, 2016

Change Blogger Content Width Via Template

The default width of blog content for Blogger seems small, comparing large margin on both left and right sides. Recently I created another blog, where several people would be authors. I would like to increase the width for the main body of blog content.

After analysis on the blog template, I found a way to do it. As indicated in the section, I made changes of the value for name: content.width, and the value for main.column.right.width:



There are some resources on web regarding this request, but I found most of them out of date. This change might be out again in the future, but it is good for my template case. The clue to this solution si to search for "width" in my template.

By the way, I have not changed the width for this programming blog. I think the default width is OK for my English blog.

Read More...