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..m
file:- #define kEntity @"Entity"
- #define kEntityChild @"EnityChild"
- @interface MyEntity
- + (Entity* ) createEntityByName:(NSString *)name
- inManagedObjectContex:(NSManagedObjectContext *) moc {
- Entity* entity;
- entity = [NSEntityDescripton insertNewObjectForEntityForName: kEntity
- inManagedObjectContext: moc];
- entity.name = name;
- return entity;
- }
- ...
- @end
The converted
.swift
codes are:- private struct MyEntityConstants {
- let kEntity = "Entity"
- let kEntityChild = "EnityChild"
- }
- class MyEntity : NSObject
- static func createEntityByName(name : String,
- inManagedObjectContex moc: NSManagedObjectContext) -> Entity {
- let c = MyEntityConstatns()
- let entity NSEntityDescripton.insertNewObjectForEntityForName(c.kEntity,
- inManagedObjectContext: moc) as! Entity
- entity.name = name
- return entity
- }
- ...
- @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.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.- #define CALLER_SELECTOR_NAME NSStringFromSelector(_cmd)
- #define CALLER_CONTEXT NSStringFromClass([self class])
- ...
- - (MyLogger*) logger {
- if (!_logger) {
- _logger = [MyLogger instance:CALLER_CONTEXT];
- }
- return _logger;
- }
- ...
- - (void) viewWillAppear:(BOOL)animated {
- [self.logger debug:
- ^{
- return [NSString stringWithFormat:@"'%@' is called!", CALLER_SELECTOR_NAME];
- }];
- ...
- }
I found equivalent ways in Swift to get class and method names.
- // module level logger constant. Use String(ClassName) to get class name.
- - private let logger = MyLogger.instance(String(MyClass))
- ...
- class MyClass : NSObject {
- func viewWillAppear(animated: Bool) {
- logger.debug() {
- //Use #function to get calling method name
- return String(format: "'\(#function)' is called!"))
- }
- ...
- }
- ...
- }
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:
- class MyClass : NSObject {
- var entity : Entity // property
- func setEntity(obj: Entity) {
- self.entity = obj // call setter to set property value
- }
- ...
- }
- // in another Swift module
- document.save(entity, notify:entity, withSelect:#selector(MyClass.setEntity(_:)))
References
- StackOverFlow QA: @selector in Swift
- StackOverFlow QA: Is it possible to use Swift's Enum in Obj-C?
- Apple Developer Site: Migration Your Objective-C Code to Swift