Showing posts with label Objective-C. Show all posts
Showing posts with label Objective-C. Show all posts

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

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

Thursday, February 28, 2013

Communication Between MVC Controllers

When there are more than two MVC controllers, controllers need to pass data or send notifications between two controllers. For example, a table list controller, parent, may push to an item edit controller, child, when an item is selected. The easiest way to is to set a property in child controller and pass the reference to parent controller to child's property. However, this strategy has one drawback: strong binding between parent and child controllers. In case the parent controller is changed to another kind of controller, the property of child has to be updated.

The alternative way is to implement delegate in Objective-C. Define a delegate in .h, then keep communication by defined methods in the delegate. Here is the steps of this strategy.

Define a Delegate

The delegate provides APIs to pass data or to send notifications. For example, the following codes define a update method:

@protocol MyEntityEditDelegate <NSObject>

- (void) updateEntity:(NSIndexPath *)index;

@end

Add a Delegate Property in Parent Controller

The next step is to implement the delegate property in the parent controller. This breaks the strong binding between the parent and child. The child will call parent by the delegate APIs.


@interface ParentListViewController :
    UITableViewController <..., MyEntityEditDelegate>

....
@end

Define a Delegate Property in Child Controller

The last step is to add delegate property in the child controller. This will let the child hold a reference to its parent by delegate.

@interface ChildEntityEditViewController : ...

@property (nonatomic, weak) id<MyEntityEditDelegate> delegate;

@end

Notice that the property type is id<...> type. This prevents the coupling between the parent and child.

Read More...

Monday, September 03, 2012

Update: MyLog with Block

I wrote a utility class to log messages in Objective-C long time ago. I updated the class with block early this year, which is similar as delegate in C#. The reason I like to use blocks is that I think, it will improve my app performance.

Here is what I did before, taking info as an example:

- (MyLogger*) info:(NSString*)messageFormat, ...;

A message string is passed to info method by using a list of dynamic string parameters. No matter if I enabled info logging level or not, the message string would be built on call.

By using a block, the string is built within a dynamic block of codes. The bock is actually a pointer to a function. The block of codes may not be executed at all if the logging level is not enabled. Therefore, I think this strategy would improve app performance.

typedef NSString* (^MessageBuildBlock_t)();
...
- (MyLogger*) info:(NSString* (^)(void))msgBuildBlock;

The block is defined as a function returning a string. Then the block is used as a parameter for info method.

Reference

Read More...

Sunday, August 26, 2012

Objective-C: Using Swapping to Mock Method Impelmentaion

Recently I read an interest blog on Unit test in Objective-C. A customized class is defined as method-swizzling helper. With this helper class, two methods in two difference class can be easily swapped at runtime. Therefore, by using this strategy, you can easily mock method calls avoid calling to database or making HTTP requests.

The key points for this helper class is based on Objective-C runtime functions.

class_getClassMethod(...)
method_exchangeImplementations(...)

The first method is used to get a class method as a data type Method. The original and swapped methods can be obtained by this function call. Then the second method is used to make an exchange of method implementation.

The blog has an example of this usage. It looks very easy to make a mock of a method implementation, which is a key practice in unit tests.

Reference


Read More...

Saturday, July 17, 2010

MyLogger Class in Objective-C (4)

In this final wrap of my MyLogger class, I'll show you my experience and usages. As most Objective-C developers know, NSLog is a C function to print out messages. It is very useful, however, as it is a function, where parameters limit its usage. It possible to pass complicated parameters, however, that' may be too difficult or just impossible. In case if you want to define different storage or format for logging message, class is the way to go. That's my initial intension to define a wrapper class for NSLog: MyLogger.

MyLogger's logging engine still uses NSLog. You can easily extend it to other storage. The API provides methods to set intention and logging message with various levels.

Usage

Normally, I initialize MyLogger settings initially in main(), where you rarely make changes. This will keep all the logging messages in the same format and consistency style. The settings are default logging level, and optional indention char and format. The great advantage of strategy is that you can easily set logging level to none if you want to disable the logging feature and you would not need to remove codes from your projects all over the places.

Here is an example of MyLogger settings:

#import <UIKit/UIKit.h>
#import "MyLogger.h"

int main(int argc, char *argv[]) {

  NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
  // Setup the default settings for logger
  MyLogger.defaultLoggingLevel = LogLevelDebug;
  MyLogger.indentChar = '-';

  int retVal = UIApplicationMain(argc, argv, nil, nil);
  [pool release];
  return retVal;
}

Then you are ready to use MyLogger in other places in your project. For me as a newbie of Objective-C developer, Cocoa framework and Objective-C are overwhelming to digest. It was very easy to get lost and frustrated. The way I used MyLogger is to place it in each method in my classes in a pair: logging at the first line of the method and logging at the exist point of the method, and set indent at the entry and outdent at the exit. I do get a lots debug messages; however, since all the messages are in a nice indention structured layout, it makes much easy to read and understand the flow of my class. That has been great help for me.

Another practice I have is that for each class where I want to do logging, I define a private var MyLogger* member. In the class constructor, I create the var and initialize it with a context name of that class. In this way, all the logging messages will have a clear context string to identify the messages. To set context in one place makes my logging much easy to maintain in case I need to rename my class as an example.

Taking MyClass as example, here is the way I add logging feature to the class:

// .h file
#import <UIKit/UIKit.h>
@class MyLogger;
...

@interface MyViewController : UITableViewController
  <NSFetchedResultsControllerDelegate, UITableViewDelegate> {

  ...
  @private
    MyLogger* mLogger;
  ...
}
...
@end

// .m file
#import "MyLogger.h"
...
@implementation MyViewController
...
- (id)initWithStyle:(UITableViewStyle)style {
 if (self = [super initWithStyle:style]) {
   mLogger = [[MyLogger alloc] initWithContext:@"MyViewController"];
   [[mLogger debug:@"initWithStyle: %@", style == UITableViewStylePlain ? @"Plain" : @"Group"] indent:YES];
   ...
   [[mLogger indent:NO] debug:@"initWithStyle: Done"];
 }
 return self;
}

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
  [[mLogger debug:@"numberOfSectionsInTableView:"] indent:YES];
  NSInteger count = [[self.fetchedResultsController sections] count];
  [[mLogger indent:NO] debug:@"numberOfSectionsInTableView: DONE"];
  return count;
}

- (NSFetchedResultsController*) fetchedResultsController {
  [[mLogger debug:@"fetchedResultsController"] indent:YES];
  if (mFetchedResultsController == nil) {
    ...
      [mLogger debug:@"created new NSFetchResultsConroller obj: %@"
        mFetchedResultsController];
    ...
  }
  [[mLogger indent:NO] debug:@"fetchedResultsController DONE"];
  return mFetchedResultsController;
}
...
@end

Here is a list of my loggging messages:
...
2010-07-09 16:05:28.203 ExpenseLog[2648:207] [DEBUG] MyViewController - initWithStyle: Plain
2010-07-09 16:05:28.209 ExpenseLog[2648:207] [DEBUG] MyViewController - initWithStyle: Done
2010-07-09 16:05:28.210 ExpenseLog[2648:207] [DEBUG] MyViewController - viewDidLoad
2010-07-09 16:05:28.211 ExpenseLog[2648:207] --[DEBUG] MyViewController - fetchedResultsController
2010-07-09 16:05:28.211 ExpenseLog[2648:207] ----[DEBUG] MyViewController - managedObjectContext
2010-07-09 16:05:28.212 ExpenseLog[2648:207] ------[DEBUG] MyViewController - creating new managedObjectContext
2010-07-09 16:05:28.212 ExpenseLog[2648:207] ----[DEBUG] MyViewController - managedObjectContext DONE
2010-07-09 16:05:28.212 ExpenseLog[2648:207] ----[DEBUG] MyViewController - managedObjectContext
2010-07-09 16:05:28.213 ExpenseLog[2648:207] ----[DEBUG] MyViewController - managedObjectContext DONE
2010-07-09 16:05:28.213 ExpenseLog[2648:207] ----[DEBUG] MyViewController - created NSFetchedResultsController obj: <NSFetchedResultsController: 0x8611340>
2010-07-09 16:05:28.214 ExpenseLog[2648:207] --[DEBUG] MyViewController - fetchedResultsController DONE
2010-07-09 16:05:28.215 ExpenseLog[2648:207] [DEBUG] MyViewController - viewDidLoad DONE
2010-07-09 16:05:28.215 ExpenseLog[2648:207] [DEBUG] MyViewController - numberOfSectionsInTableView:
2010-07-09 16:05:28.216 ExpenseLog[2648:207] --[DEBUG] MyViewController - fetchedResultsController
2010-07-09 16:05:28.216 ExpenseLog[2648:207] --[DEBUG] MyViewController - fetchedResultsController DONE
2010-07-09 16:05:28.216 ExpenseLog[2648:207] [DEBUG] MyViewController - numberOfSectionsInTableView: DONE
2010-07-09 16:05:28.218 ExpenseLog[2648:207] [DEBUG] MyViewController - numberOfSectionsInTableView:
2010-07-09 16:05:28.218 ExpenseLog[2648:207] --[DEBUG] MyViewController - fetchedResultsController
2010-07-09 16:05:28.219 ExpenseLog[2648:207] --[DEBUG] MyViewController - fetchedResultsController DONE
2010-07-09 16:05:28.219 ExpenseLog[2648:207] [DEBUG] MyViewController - numberOfSectionsInTableView: DONE
2010-07-09 16:05:28.219 ExpenseLog[2648:207] [DEBUG] MyViewController - tableView:numberOfRowsInSection:
2010-07-09 16:05:28.220 ExpenseLog[2648:207] --[DEBUG] MyViewController - fetchedResultsController
2010-07-09 16:05:28.220 ExpenseLog[2648:207] --[DEBUG] MyViewController - fetchedResultsController DONE
2010-07-09 16:05:28.220 ExpenseLog[2648:207] [DEBUG] MyViewController - tableView:numberOfRowsInSection: DONE
2010-07-09 16:05:28.221 ExpenseLog[2648:207] [DEBUG] MyViewController - tableView:cellForRowAtIndexPath:
2010-07-09 16:05:28.221 ExpenseLog[2648:207] --[DEBUG] MyViewController - fetchedResultsController
2010-07-09 16:05:28.222 ExpenseLog[2648:207] --[DEBUG] MyViewController - fetchedResultsController DONE
2010-07-09 16:05:28.223 ExpenseLog[2648:207] [DEBUG] MyViewController - tableView:cellForRowAtIndexPath: DONE
...

I think it is really worthwhile to spend some time design this helper class. The benefits I have been received are tremendous. It greatly helps me to understand Cocoa framework, saves me enormous amount of time, and keeps my mind sharp on the business logic I want to implement.

With new iOS available, I think this class can be further enhanced with block feature to improve the performance. For example, I could put all the messages into a block so that when a logging level is disabled, the block would not be evaluated or executed at all.

Reference

Read More...

Saturday, July 10, 2010

MyLogger Class in Objective-C (3)

In my previous log, I have discussed the class level methods and some of instance methods, which as overwrites of NSObject. MyLogger class has a list of instance methods, most of them in similar structure. Those methods are defined mainly to provide convenience APIs for usage.

MyLogger Instance Methods

This snap-shot is a list of instance methods:


Here is the partial codes in .h:

- (MyLogger*) indent:(BOOL)indent;

- (BOOL) levelEnabled:(MyLoggerLevel) intentLevel;
- (BOOL) infoEnabled;
- (BOOL) debugEnabled;
- (BOOL) warningEnabled;
- (BOOL) errorEnabled;

- (MyLogger*) debug:(NSString*)messageFormat, ...;
- (MyLogger*) warning:(NSString*)messageFormat, ...;
- (MyLogger*) error:(NSString*)messageFormat, ...;
- (MyLogger*) info:(NSString*)messageFormat, ...;


I group them into 3 sections. The first one is indent. The first group contains only one simple method. This method takes only flag as parameter: indent or outdent. The implementation is very simple: it sets the global static variable (int). This integer number is used to insert number indent chars.

static int gIndent = 0;
...
@implementation MyLogger {
  ...
- (MyLogger*) indent:(BOOL)indent {
  if (indent) {
    gIndent += gIndentChars;
  }
  else if (gIndent >= gIndentChars) {
    gIndent -= gIndentChars;
  }

  return self;
}


The implementation is straightforward. One thing I should mention is that I applied the Fluent Interface pattern in this class so that some methods can be chained together to simplify its usage. I enjoy this practice very much as you can see my examples. However, it is a very controversial issue in Objective-C. I posted a question to my SO. I got some experts insights.

The next group is to get logging level status. Method levelEnabled: is a generic method to check if an intend level (MyLoggerLevel enum type) is enabled or not, and others are convenient methods to check if info, debug, warning or error logging level is enabled or not.

- (BOOL) levelEnabled:(MyLoggerLevel) intentLevel {
  BOOL enabled = NO;
  if (isValidLevel(intentLevel)) {
    enabled = self.level <= intentLevel;
  }
  return enabled;
}

- (BOOL) infoEnabled {
  return [self levelEnabled:LogLevelInfo];
}
...

here only infoEnabled is there. Other three are in the similar way. One interesting and great feature of Objective-C is that it interoperates with C well. I defined a static C function isValidLevel(...). Within the function, Objective-C types are recognized. This feature brings great power and speed into Objective-C.

The main reason I mixed C function into MyLogger class is to define private methods. Objective-C class does not provide any way or directive for private methods. All the methods in a class are public. This simplifies the compile and run time performance, no need to verify function's accessibility. Static C function is a perfect candidate for defining private methods. You will see three C functions later.

The last group of methods provides APIs to log messages. Those methods use FI pattern and C functions, as well variable arguments. Here is the method of info:, simple and straightforward again:

- (MyLogger*) info:(NSString*)messageFormat, ... {
  if ([self infoEnabled]) {
    va_list args;
    /* Initializing arguments to store all values after messageFormat */
    va_start(args, messageFormat);
    logAt(self, LogLevelInfo, messageFormat, args);
    va_end(args);
  }
  return self;
}


var_start and va_end are C macros, and logAt(...) is my C function. In the beginning part of MyLogger.m, I have the following C functions:

static NSString* nameOfLevel(MyLoggerLevel intentLevel) {
  NSString* name = kLogLevelNameUnknown;
  switch (intentLevel) {
    case LogLevelDebug:
      name = kLogLevelNameDebug;
      break;
    case LogLevelWarning:
      name = kLogLevelNameWarning;
      break;
    case LogLevelError:
      name = kLogLevelNameError;
      break;
    case LogLevelInfo:
      name = kLogLevelNameInfo;
      break;
    default:
      break;
  }

  return name;
}

static void logAt(MyLogger* logger, MyLoggerLevel intentLevel,  NSString* messageFormat, va_list argList) {
  if ( [logger levelEnabled:intentLevel]) {
    NSString* s = [[NSString alloc] initWithFormat:messageFormat arguments:argList];
    NSString* indent = [NSString stringWithRepeatedChar:MyLogger.indentChar times:gIndent];
    NSLog(gFormat, indent, nameOfLevel(intentLevel), [logger context], s);
    [s release];
  }
}

static BOOL isValidLevel(MyLoggerLevel intentLevel)
{
  BOOL valid = NO;
  switch (intentLevel) {
    case LogLevelDebug:
    case LogLevelWarning:
    case LogLevelError:
    case LogLevelInfo:
      valid = YES;
      break;
    default:
      break;
  }
  return valid;
}


This almost concludes my posts on MyLogger class. I'll wrap it up in my next post with its usages and complete codes for downloading.

Reference

Read More...

Thursday, October 09, 2008

Apple Programming: User Defaults

Just started to look at some Apple Xcode application examples. That's a whole new huge framework for OS X. One application is about user defaults. In Windows there are many ways to store application defaults or values such as Registry, XML file, Ini file or Active Directory.

In Mac OS X, Apple provides a plist and bundle for user preferences and in Objective-C, there are some classes can be used to get user default values, NSUserDefaults for example:

NSUserDefaults *defaults;
// Get all the defautls for the current app
defaults = [NSUserDefaults standardUserDefaults];
// Register 2 defaults in case they are not available in user defaults
// Note, the registerDefaults does not change defaults.
[defaults registerDefaults:[NSDictionary dictionaryWithObjectsAndKeys:
@"Joe", @"first_name",
@"NO", @"is_married",
nil]];
//...

NSString firstName;
BOOL married;

// the following two calls will return the user's individual preferences,
// if they are available. Otherwise, it will just return the values we
// registered previously. Saves us some hassle!
firstName = [defaults stringForKey:@"first_name"];
married = [defaults boolForKey:@"is_married"];


That's quit difference way to do in Mac. Very interesting! Some good links about User Preference Defaults:

Read More...