├── docs ├── doc │ ├── _ad.inc │ ├── _ga.inc │ ├── common.js │ ├── index.html │ ├── common.css │ ├── initializer.html │ ├── class.html │ ├── blocks.html │ ├── optional.html │ ├── enum.html │ ├── protocol.html │ ├── method.html │ ├── misc.html │ ├── property.html │ └── statement.html ├── img │ └── ogp.png ├── example │ ├── hello-world.m │ ├── preprocess.m │ ├── overriding.m │ ├── computed-property.m │ ├── initializer.m │ ├── protocol-confliction.m │ ├── enumeration.m │ └── nullability.m ├── index.html ├── online.html ├── css │ ├── index.css │ └── online.css ├── demo.html └── js │ ├── demo.js │ └── online.js ├── src ├── browser.js ├── parser-util.js ├── ast-util.js ├── main.js ├── import-resolver.js ├── decl-info.js ├── preprocessor.js ├── type-info.js └── type-analyzer.js ├── makedoc.sh ├── webpack.config.js ├── .gitignore ├── LICENSE ├── spec ├── index.spec ├── spec.pegjs ├── class.spec ├── initializer.spec ├── blocks.spec ├── optional.spec ├── enum.spec ├── method.spec ├── protocol.spec ├── misc.spec ├── property.spec ├── statement.spec └── spec2doc.js ├── grammar └── preprocess.pegjs ├── bin ├── sdk-path.json └── cmd.js ├── package.json └── README.md /docs/doc/_ad.inc: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/browser.js: -------------------------------------------------------------------------------- 1 | O2S = require('./main'); 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/img/ogp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/okaxaki/objc2swift/HEAD/docs/img/ogp.png -------------------------------------------------------------------------------- /docs/example/hello-world.m: -------------------------------------------------------------------------------- 1 | @interface MyClass 2 | - (void)hello; 3 | @end 4 | 5 | @implementation MyClass 6 | - (void)hello{ 7 | NSLog(@"Hello the Swift World!"); 8 | } 9 | @end 10 | -------------------------------------------------------------------------------- /docs/example/preprocess.m: -------------------------------------------------------------------------------- 1 | @implementation MyClass 2 | #if DEBUG 3 | -(void)method { 4 | // debug code 5 | } 6 | #else 7 | -(void)method { 8 | // normal code 9 | } 10 | #endif 11 | @end -------------------------------------------------------------------------------- /makedoc.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | SPEC_DIR=./spec 3 | DOC_DIR=./docs/doc 4 | 5 | mkdir -p ${DOC_DIR} 6 | for F in ${SPEC_DIR}/*.spec ; do 7 | B=`basename $F` 8 | echo $B 9 | ${SPEC_DIR}/spec2doc.js $F -o ${DOC_DIR} 10 | done 11 | 12 | -------------------------------------------------------------------------------- /docs/example/overriding.m: -------------------------------------------------------------------------------- 1 | @interface SuperFoo 2 | -(void)method:(NSNumber *)arg; 3 | @end 4 | 5 | @interface Foo : SuperFoo 6 | -(void)method:(NSNumber *)arg; 7 | -(void)method2:(NSNumber *)arg; 8 | @end 9 | 10 | @implementation Foo 11 | -(void)method:(NSNumber *)arg { 12 | // ... 13 | } 14 | 15 | -(void)method2:(NSNumber *)arg { 16 | // ... 17 | } 18 | 19 | @end -------------------------------------------------------------------------------- /docs/example/computed-property.m: -------------------------------------------------------------------------------- 1 | @interface MyClass 2 | @property NSString *prop; 3 | @property (readonly) NSString *ro; 4 | @property NSNumber *comp; 5 | @end 6 | 7 | @implementation MyClass 8 | @synthesize comp = _rawValue; 9 | -(void)setComp:(NSNumber *)value { 10 | _rawValue = value / 2; 11 | } 12 | -(NSNumber *)comp { 13 | return _rawValue * 2; 14 | } 15 | @end 16 | -------------------------------------------------------------------------------- /docs/example/initializer.m: -------------------------------------------------------------------------------- 1 | // Initializer conversion is not perfect. 2 | // Only method signature is converted to `init` schema. 3 | @implementation MyClass 4 | -(instancetype)init { 5 | self = [super init]; 6 | if(self) { 7 | // ... 8 | } 9 | return self; 10 | } 11 | 12 | -(instancetype)initWithFrame:(CGFrame)frame { 13 | self = [super init]; 14 | if(self) { 15 | // ... 16 | } 17 | return self; 18 | } 19 | @end -------------------------------------------------------------------------------- /docs/example/protocol-confliction.m: -------------------------------------------------------------------------------- 1 | @interface Foo 2 | @end 3 | 4 | // Objective-C allows the same protocol name with 5 | // the class but Swift doesn't. 6 | // suffix `-Protocol` is added to the Swift 7 | // protocol if name confliction is found. 8 | @protocol Foo 9 | @end 10 | 11 | @protocol Bar 12 | @end 13 | 14 | @interface MyClass : Foo 15 | @property Foo *oFoo; 16 | @property id pFoo; 17 | @property id pBar; 18 | @end 19 | 20 | @implementation MyClass 21 | @end 22 | -------------------------------------------------------------------------------- /docs/doc/_ga.inc: -------------------------------------------------------------------------------- 1 | 5 | 11 | -------------------------------------------------------------------------------- /docs/example/enumeration.m: -------------------------------------------------------------------------------- 1 | typedef NS_ENUM(NSUInteger, Animal) { 2 | AnimalSwift, 3 | AnimalLion, 4 | AnimalPanther, 5 | AnimalTiger, 6 | }; 7 | 8 | @implementation MyClass 9 | -(NSString *)someMethod:(MyColor)color { 10 | switch(color) { 11 | case AnimalLion: 12 | case AnimalPanther: 13 | case AnimalTiger: 14 | return "OS X code name"; 15 | case AnimalSwift: 16 | return "Language name"; 17 | default: 18 | return NULL; 19 | } 20 | } 21 | @end -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const webpack = require("webpack"); 3 | 4 | module.exports = { 5 | mode: "production", 6 | entry: "./src/browser.js", 7 | output: { 8 | filename: "bundle.js", 9 | path: path.resolve(__dirname, "docs/js"), 10 | }, 11 | resolve: { 12 | fallback: { 13 | util: require.resolve("util/"), 14 | process: require.resolve("process"), 15 | }, 16 | }, 17 | plugins: [ 18 | new webpack.ProvidePlugin({ 19 | process: "process/browser", 20 | }), 21 | ], 22 | }; 23 | -------------------------------------------------------------------------------- /docs/example/nullability.m: -------------------------------------------------------------------------------- 1 | NS_ASSUME_NONNULL_BEGIN 2 | @interface AAPLList : NSObject 3 | - (nullable AAPLListItem *)itemWithName:(NSString *)name; 4 | - (NSInteger)indexOfItem:(AAPLListItem *)item; 5 | @property (copy, nullable) NSString *name; 6 | @property (copy, readonly) NSArray *allItems; 7 | @end 8 | NS_ASSUME_NONNULL_END 9 | 10 | @implementation AAPLList 11 | - (nullable AAPLListItem *)itemWithName:(NSString *)name { 12 | // ... 13 | } 14 | 15 | - (NSInteger)indexOfItem:(AAPLListItem *)item { 16 | // ... 17 | } 18 | @end 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 18 | .grunt 19 | 20 | # node-waf configuration 21 | .lock-wscript 22 | 23 | # Compiled binary addons (http://nodejs.org/api/addons.html) 24 | build/Release 25 | 26 | # Dependency directory 27 | node_modules 28 | 29 | # Optional npm cache directory 30 | .npm 31 | 32 | # Optional REPL history 33 | .node_repl_history 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, Mitsutaka Okazaki 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any 4 | purpose with or without fee is hereby granted, provided that the above 5 | copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -------------------------------------------------------------------------------- /spec/index.spec: -------------------------------------------------------------------------------- 1 | # Overview 2 | 3 |

This document describes the conversion specification by enumerating conversion patterns. All sections are generated automatically from Test Spec of Objc2Swift.js.

4 |

Each section consists of a Objective-C code fragment and its corresponding Swift code like following.

5 | 6 | ## Example Pattern 7 | 8 | ``` 9 | @interface MyClass 10 | - (void)hello; 11 | @end 12 | 13 | @implementation MyClass 14 | - (void)hello{ 15 | NSLog(@"Hello the Swift World!"); 16 | } 17 | @end 18 | ``` 19 | 20 | ``` 21 | class MyClass { 22 | func hello() { 23 | NSLog("Hello the Swift World!") 24 | } 25 | } 26 | ``` 27 | 28 |

Note that NSLog() is not converted into print() by default because they are not equivalent.

-------------------------------------------------------------------------------- /src/parser-util.js: -------------------------------------------------------------------------------- 1 | module.exports = (function(){ 2 | var SPACER = ' '; 3 | function makeIndentString(indent) { 4 | if(SPACER.lengthProperties declared in interface or anonymous category are converted into instance variables of a single class together.

48 |

Properties from the anonymous category are treated as private.

49 | 50 | ``` 51 | @interface Foo 52 | @property int bar; 53 | @end 54 | 55 | @interface Foo () 56 | @property int baz; 57 | @end 58 | 59 | @implementation Foo 60 | @end 61 | ``` 62 | 63 | ``` 64 | class Foo { 65 | var bar:Int 66 | private var baz:Int 67 | } 68 | ``` 69 | 70 | 71 | -------------------------------------------------------------------------------- /spec/initializer.spec: -------------------------------------------------------------------------------- 1 | # Initializer 2 | 3 | ## Alloc & Init 4 | 5 | 6 | ``` 7 | void test() { 8 | Foo *foo; 9 | foo = [[Foo alloc] init]; 10 | // ... 11 | } 12 | ``` 13 | 14 | 15 | ``` 16 | func test() { 17 | var foo:Foo! 18 | foo = Foo() 19 | // ... 20 | } 21 | ``` 22 | 23 | ## Designated Initializer 24 |

Designated Initializer is partially supported. The signature can be coverted but inner implementation is not modified.

25 | ``` 26 | @implementation Foo 27 | -(id)init { 28 | self = [super init]; // Remains in Swift code 29 | if(self) { // Remains in Swift code 30 | // ... 31 | } 32 | return self; // Remains in Swift code 33 | } 34 | 35 | -(instancetype)initWithFrame:(CGRect) frame { 36 | // ... 37 | } 38 | @end 39 | ``` 40 | 41 | 42 | ``` 43 | class Foo { 44 | init() { 45 | self = super.init() // Remains in Swift code 46 | if (self != nil) { // Remains in Swift code 47 | // ... 48 | } 49 | return self // Remains in Swift code 50 | } 51 | 52 | init(frame:CGRect) { 53 | // ... 54 | } 55 | } 56 | ``` -------------------------------------------------------------------------------- /grammar/preprocess.pegjs: -------------------------------------------------------------------------------- 1 | Start = m:(__ Unit)* n:__ { 2 | var buf = []; 3 | for(var i=0;iVoid = { (a:Int) in 13 | // ... 14 | } 15 | ``` 16 | 17 | ## Blocks as Property 18 | ``` 19 | @interface Foo 20 | @property void(^blk)(NSString *); 21 | @end 22 | 23 | @implementation Foo 24 | -(void)method { 25 | blk = ^(NSString *msg){ 26 | NSLog(@"In block: %@", msg); 27 | // ... 28 | }; 29 | } 30 | @end 31 | ``` 32 | 33 | ``` 34 | class Foo { 35 | var blk:(String!)->Void 36 | 37 | func method() { 38 | blk = { (msg:String!) in 39 | NSLog("In block: %@", msg) 40 | // ... 41 | } 42 | } 43 | } 44 | ``` 45 | 46 | ## Blocks as Parameter 47 | 48 | ``` 49 | @implementation Foo 50 | -(void)method:(void (^)(int))blk { 51 | // ... 52 | } 53 | @end 54 | ``` 55 | 56 | ``` 57 | class Foo { 58 | func method(blk:(Int)->Void) { 59 | // ... 60 | } 61 | } 62 | ``` 63 | 64 | ## Direct Blocks Declaration 65 |

This pattern is not supported yet.

66 | ``` 67 | /** Not Supported yet */ 68 | void (^blk)(int) { 69 | // ... 70 | }; 71 | ``` 72 | 73 | ``` 74 | /** Not Supported yet */ 75 | func blk() { 76 | // ... 77 | }; 78 | ``` 79 | 80 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "objc2swift", 3 | "version": "0.2.1", 4 | "description": "Objective-C to Swift Converter", 5 | "main": "src/main.js", 6 | "files": [ 7 | "bin", 8 | "src", 9 | "package.json", 10 | "LICENSE", 11 | "README.md" 12 | ], 13 | "bin": { 14 | "objc2swift": "bin/cmd.js" 15 | }, 16 | "dependencies": { 17 | "fs-extra": "^0.26.5", 18 | "glob": "^6.0.4", 19 | "node-getopt": "^0.3.2" 20 | }, 21 | "devDependencies": { 22 | "diff": "^5.0.0", 23 | "http-server": "^14.1.1", 24 | "pegjs": "^0.10.0", 25 | "process": "^0.11.10", 26 | "util": "^0.12.5", 27 | "webpack": "^5.88.1", 28 | "webpack-cli": "^5.1.4" 29 | }, 30 | "scripts": { 31 | "test": "echo \"Error: no test specified\" && exit 1", 32 | "build-pp-parser": "pegjs -o ./src/preprocess-parser.js ./grammar/preprocess.pegjs", 33 | "build-parser": "pegjs -o ./src/parser.js ./grammar/objc.pegjs", 34 | "build": "npm run build-parser && npm run build-pp-parser", 35 | "server": "http-server -c 0 -o ./docs", 36 | "webpack": "webpack" 37 | }, 38 | "keywords": [ 39 | "pegjs", 40 | "objective-c", 41 | "objectivec", 42 | "objc", 43 | "swift" 44 | ], 45 | "repository": { 46 | "type": "git", 47 | "url": "https://github.com/okaxaki/objc2swift" 48 | }, 49 | "homepage": "http://okaxaki.github.io/objc2swift/", 50 | "author": "Mitsutaka Okazaki", 51 | "license": "ISC", 52 | "engines": { 53 | "node": ">=10.0.0" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /spec/optional.spec: -------------------------------------------------------------------------------- 1 | # Optional Type 2 | ## Nullability Qualifier 3 | ``` 4 | @implementation A 5 | -(void)someMethod:(NSString *)arg1 :(nonnull NSString *)arg2 :(nullable NSString *)arg3 { 6 | //... 7 | } 8 | @end 9 | ``` 10 | 11 | ``` 12 | class A { 13 | func someMethod(arg1:String!, arg2:String, arg3:String?) { 14 | //... 15 | } 16 | } 17 | ``` 18 | 19 | ## Auto-unwrapping 20 | ``` 21 | @implementation A 22 | -(void)someMethod:(nullable NSString *)str { 23 | NSLog(@"%l",[str length]); 24 | } 25 | @end 26 | ``` 27 | 28 | ``` 29 | class A { 30 | func someMethod(str:String?) { 31 | NSLog("%l",str!.length()) 32 | } 33 | } 34 | ``` 35 | 36 | ## Nullability Annotation 37 | 38 | ``` 39 | NS_ASSUME_NONNULL_BEGIN 40 | @interface AAPLList : NSObject 41 | // ... 42 | - (nullable AAPLListItem *)itemWithName:(NSString *)name; 43 | - (NSInteger)indexOfItem:(AAPLListItem *)item; 44 | 45 | @property (copy, nullable) NSString *name; 46 | @property (copy, readonly) NSArray *allItems; 47 | // ... 48 | @end 49 | NS_ASSUME_NONNULL_END 50 | 51 | @implementation AAPLList 52 | - (nullable AAPLListItem *)itemWithName:(NSString *)name { 53 | // ... 54 | } 55 | 56 | - (NSInteger)indexOfItem:(AAPLListItem *)item { 57 | // ... 58 | } 59 | @end 60 | ``` 61 | 62 | ``` 63 | class AAPLList : NSObject { 64 | @NSCopying var name:String? 65 | @NSCopying private(set) var allItems:[AnyObject] 66 | 67 | func itemWithName(name:String) -> AAPLListItem? { 68 | // ... 69 | } 70 | 71 | func indexOfItem(item:AAPLListItem) -> Int { 72 | // ... 73 | } 74 | } 75 | ``` 76 | -------------------------------------------------------------------------------- /spec/enum.spec: -------------------------------------------------------------------------------- 1 | # Enumeration 2 |

3 | Note: Enumeration conversion specification will be fixed. Currently the converter treats the enum type name as the prefix of each enumerators. 4 | This results approximatly fine, but strictly, it is different from Swift's behavior. 5 |

6 | 7 | ## Enumeration Declaration 8 | 9 | ``` 10 | typedef NS_ENUM(NSUInteger, Animal) { 11 | AnimalSwift, 12 | AnimalLion, 13 | AnimalPanther, 14 | AnimalTiger, 15 | }; 16 | ``` 17 | 18 | ``` 19 | enum Animal:UInt { 20 | case Swift 21 | case Lion 22 | case Panther 23 | case Tiger 24 | } 25 | ``` 26 | 27 | ## Enumeration Reference 28 | 29 | ``` 30 | typedef NS_ENUM(NSUInteger, Animal) { 31 | AnimalSwift, 32 | AnimalLion, 33 | AnimalPanther, 34 | AnimalTiger, 35 | }; 36 | 37 | @implementation MyClass 38 | -(NSString *)someMethod:(MyColor)color { 39 | switch(color) { 40 | case AnimalLion: 41 | case AnimalPanther: 42 | case AnimalTiger: 43 | return "OS X code name"; 44 | case AnimalSwift: 45 | return "Language name"; 46 | default: 47 | return NULL; 48 | } 49 | } 50 | @end 51 | ``` 52 | 53 | ``` 54 | enum Animal:UInt { 55 | case Swift 56 | case Lion 57 | case Panther 58 | case Tiger 59 | } 60 | 61 | class MyClass { 62 | func someMethod(color:MyColor) -> String! { 63 | switch(color) { 64 | case Animal.Lion, 65 | Animal.Panther, 66 | Animal.Tiger: 67 | return "OS X code name" 68 | case Animal.Swift: 69 | return "Language name" 70 | default: 71 | return nil 72 | } 73 | } 74 | } 75 | ``` -------------------------------------------------------------------------------- /docs/doc/common.js: -------------------------------------------------------------------------------- 1 | function createSidebar() { 2 | 3 | var menu = document.querySelectorAll(".sidebar .menu")[0]; 4 | if(!menu) return; 5 | 6 | var items = [ 7 | {name:"Class and Category",href:"class.html"}, 8 | {name:"Property",href:"property.html"}, 9 | {name:"Method",href:"method.html"}, 10 | {name:"Protocol",href:"protocol.html"}, 11 | {name:"Enumeration",href:"enum.html"}, 12 | {name:"Initializer",href:"initializer.html"}, 13 | {name:"Statement",href:"statement.html"}, 14 | {name:"Blocks",href:"blocks.html"}, 15 | {name:"Optional Type",href:"optional.html"}, 16 | {name:"Miscellaneous",href:"misc.html"} 17 | ]; 18 | 19 | var buf = []; 20 | buf.push(''); 21 | buf.push(""); 26 | 27 | menu.innerHTML = buf.join(''); 28 | } 29 | 30 | function installTabs() { 31 | var items = document.querySelectorAll('.tab-items a'); 32 | for(var i=0;i Int { 15 | return 0 16 | } 17 | } 18 | ``` 19 | 20 | ## Instance method 21 | ``` 22 | @implementation Foo 23 | -(int)method { 24 | return 0; 25 | } 26 | @end 27 | ``` 28 | 29 | ``` 30 | class Foo { 31 | func method() -> Int { 32 | return 0 33 | } 34 | } 35 | ``` 36 | 37 | ## Method with params 38 | ``` 39 | @implementation Foo 40 | -(void)method:(int)arg1 label:(NSString *)arg2 {} 41 | -(void)method2:(int)arg1 :(NSString *)arg2 {} 42 | -(void)method3:(int)arg1 arg2:(NSString *)arg2 {} 43 | @end 44 | ``` 45 | ``` 46 | class Foo { 47 | func method(arg1:Int, label arg2:String!) {} 48 | func method2(arg1:Int, arg2:String!) {} 49 | func method3(arg1:Int, arg2:String!) {} 50 | } 51 | ``` 52 | 53 | ## Override Completion 54 | 55 | ``` 56 | @interface SuperFoo 57 | -(void)someMethod:(NSNumber *)arg; 58 | @end 59 | 60 | @interface Foo : SuperFoo 61 | -(void)someMethod:(NSNumber *)arg; 62 | @end 63 | 64 | @implementation Foo 65 | -(void)someMethod:(NSNumber *)arg { 66 | // ... 67 | } 68 | @end 69 | ``` 70 | 71 | ``` 72 | class Foo : SuperFoo { 73 | override func someMethod(arg:NSNumber!) { 74 | // ... 75 | } 76 | } 77 | ``` 78 | 79 | ## Respect Interface declaration 80 |

If the signature of the method implementation is different from interface, 81 | the generated swift code follows interface declaration. Such patterns are mostly apparel with nullability specifiers. 82 |

83 | 84 | ``` 85 | @interface Foo 86 | -(NSString * nonnull)method; 87 | @end 88 | 89 | @implementation Foo 90 | -(NSString *)method { 91 | // ... 92 | } 93 | @end 94 | ``` 95 | 96 | ``` 97 | class Foo { 98 | func method() -> String { 99 | // ... 100 | } 101 | } 102 | ``` 103 | 104 | -------------------------------------------------------------------------------- /spec/protocol.spec: -------------------------------------------------------------------------------- 1 | # Protocol 2 | 3 | ## Protocol Declaration 4 | 5 | Note the converter does not generate Swift protocol code if the protocol declaration is found in the imported header. 6 | If you would like to get Swift protocol code, pass the header file where the protocol is declared to the converter. 7 | 8 | ``` 9 | @protocol MyProtocol 10 | @property int property; 11 | @property (readonly) int readonlyProperty; 12 | +(void)classMethod; 13 | -(void)method; 14 | @optional 15 | @property int optionalProperty; 16 | -(void)optionalClassMethod; 17 | -(void)optionalMethod; 18 | @end 19 | ``` 20 | 21 | ``` 22 | protocol MyProtocol { 23 | var property:Int { get set } 24 | var readonlyProperty:Int { get } 25 | class func classMethod() 26 | func method() 27 | 28 | optional var optionalProperty:Int { get set } 29 | optional func optionalClassMethod() 30 | optional func optionalMethod() 31 | } 32 | ``` 33 | 34 | ## Protocol Reference 35 | 36 | ``` 37 | @interface Foo : NSObject 38 | @end 39 | 40 | @implementation Foo 41 | // ... 42 | @end 43 | ``` 44 | 45 | ``` 46 | class Foo : NSObject, Bar, Baz { 47 | // ... 48 | } 49 | ``` 50 | 51 | ## Protocol Name Confliction 52 | 53 | ``` 54 | @interface Foo 55 | @end 56 | 57 | // Objective-C allows the same protocol name with 58 | // the class but Swift doesn't. 59 | // suffix `-Protocol` is added to the Swift 60 | // protocol if name confliction is found. 61 | @protocol Foo 62 | @end 63 | 64 | @protocol Bar 65 | @end 66 | 67 | @interface MyClass : Foo 68 | @property Foo *oFoo; 69 | @property id pFoo; 70 | @property id pBar; 71 | @end 72 | 73 | @implementation MyClass 74 | @end 75 | ``` 76 | 77 | ``` 78 | // Objective-C allows the same protocol name with 79 | // the class but Swift doesn't. 80 | // suffix `-Protocol` is added to the Swift 81 | // protocol if name confliction is found. 82 | 83 | 84 | class MyClass : Foo, FooProtocol, Bar { 85 | var oFoo:Foo! 86 | var pFoo:FooProtocol! 87 | var pBar:Bar! 88 | } 89 | ``` 90 | -------------------------------------------------------------------------------- /spec/misc.spec: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | 3 | ## Assignment analysis 4 | ``` 5 | @implementation MyClass 6 | -(void)test { 7 | NSString *foo = @"FOO"; 8 | NSString *bar = @"BAR"; 9 | 10 | foo = @"baz"; 11 | // ... 12 | return foo; 13 | } 14 | @end 15 | ``` 16 | 17 | ``` 18 | class MyClass { 19 | func test() { 20 | var foo:String! = "FOO" 21 | let bar:String! = "BAR" 22 | 23 | foo = "baz" 24 | // ... 25 | return foo 26 | } 27 | } 28 | ``` 29 | 30 | ## NULL check idiom 31 | 32 | ``` 33 | @implementation MyClass 34 | -(void)test:(NSString *)str { 35 | if(!str) { 36 | NSLog(@"str is NULL"); 37 | } 38 | } 39 | @end 40 | ``` 41 | 42 | ``` 43 | class MyClass { 44 | func test(str:String!) { 45 | if (str == nil) { 46 | NSLog("str is NULL") 47 | } 48 | } 49 | } 50 | ``` 51 | 52 | ## class Keyword 53 | 54 | ``` 55 | void test() { 56 | 57 | NSString *str; 58 | 59 | // Get type from class. 60 | [NSString class]; 61 | NSString.class; 62 | 63 | // Get dynamic type from instance. 64 | [str class]; 65 | str.class; 66 | 67 | } 68 | ``` 69 | 70 | ``` 71 | func test() { 72 | 73 | var str:String! 74 | 75 | // Get type from class. 76 | String.self 77 | String.self 78 | 79 | // Get dynamic type from instance. 80 | str.dynamicType 81 | str.dynamicType 82 | 83 | } 84 | ``` 85 | 86 | ## isKindOfClass 87 | ``` 88 | void test(id obj) { 89 | if([obj isKindOfClass:[NSString class]]) { 90 | // Do something 91 | } 92 | } 93 | ``` 94 | 95 | ``` 96 | func test(obj:AnyObject!) { 97 | if (obj is NSString) { 98 | // Do something 99 | } 100 | } 101 | ``` 102 | 103 | ## instancetype 104 | ``` 105 | @implementation Foo 106 | -(instancetype)method { 107 | // ... 108 | return self; 109 | } 110 | @end 111 | ``` 112 | 113 | ``` 114 | class Foo { 115 | func method() -> Self { 116 | // ... 117 | return self 118 | } 119 | } 120 | ``` 121 | 122 | ## @available 123 | ``` 124 | @implementation MyClass 125 | -(void)test { 126 | if(@available(iOS 10.0, *)) { 127 | // >= iOS10 128 | } else { 129 | // <= iOS9 130 | } 131 | } 132 | @end 133 | ``` 134 | 135 | ``` 136 | class MyClass { 137 | func test() { 138 | if #available(iOS 10.0, *) { 139 | // >= iOS10 140 | } else { 141 | // <= iOS9 142 | } 143 | } 144 | } 145 | ``` 146 | 147 | -------------------------------------------------------------------------------- /spec/property.spec: -------------------------------------------------------------------------------- 1 | # Property 2 | 3 | ## Readonly Property 4 |

The property with readonly attribute is converted to a variable and its setter is set private.

5 | ``` 6 | @interface MyClass 7 | @property (readonly) NSString *str; 8 | @end 9 | 10 | @implementation MyClass 11 | @end 12 | ``` 13 | 14 | ``` 15 | class MyClass { 16 | private(set) var str:String! 17 | } 18 | ``` 19 | 20 | ## Copy Property 21 | ``` 22 | @interface MyClass 23 | @property (copy) NSString *str; 24 | @end 25 | 26 | @implementation MyClass 27 | @end 28 | ``` 29 | 30 | ``` 31 | class MyClass { 32 | @NSCopying var str:String! 33 | } 34 | ``` 35 | 36 | ## Private Property 37 |

The property declared in any anonymous category is converted into a private variable. Other properies are treated as public.

38 | ``` 39 | @interface MyClass 40 | @property int bar; 41 | @end 42 | 43 | @interface MyClass () 44 | @property int baz; 45 | @end 46 | 47 | @implementation MyClass 48 | @end 49 | ``` 50 | 51 | ``` 52 | class MyClass { 53 | var bar:Int 54 | private var baz:Int 55 | } 56 | ``` 57 | 58 | ## Auto-synthesizing Aware 59 |

Objective-C automatically synthesizes instantce variable _foo for @property foo declaration. Objc2swift checks the reference of _foo in any instance method and if so, explicit setter and getter functions are generated for safe. 60 | 61 | ``` 62 | @interface MyClass 63 | @property NSString *foo; // This property implicitly generates `_foo`. 64 | @end 65 | 66 | @implementation MyClass 67 | -(void)someMethod { 68 | NSLog("%@", _foo); // reference of auto-synthesized variable. 69 | } 70 | @end 71 | ``` 72 | 73 | ``` 74 | class MyClass { 75 | private var _foo:String! 76 | var foo:String! { 77 | get { return _foo } 78 | set { _foo = newValue } 79 | } 80 | 81 | func someMethod() { 82 | NSLog("%@", _foo) // reference of auto-synthesized variable. 83 | } 84 | } 85 | ``` 86 | 87 | ## Setter and Getter 88 |

If there are setter and getter code in the class implementation, they are converted into Swift's variables with setter and getter function.

89 | 90 | ``` 91 | @interface MyClass 92 | @property int foo; 93 | @end 94 | 95 | @implementation MyClass 96 | -(void)setFoo:(int)value { 97 | _foo = foo * 2; 98 | } 99 | -(int)foo { 100 | return _foo / 2; 101 | } 102 | @end 103 | ``` 104 | 105 | ``` 106 | class MyClass { 107 | private var _foo:Int 108 | var foo:Int { 109 | get { 110 | return _foo / 2 111 | } 112 | set(value) { 113 | _foo = foo * 2 114 | } 115 | } 116 | 117 | // `setFoo:` has moved as a setter. 118 | // `foo` has moved as a getter. 119 | } 120 | ``` -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Objc2Swift.js [![npm version](https://badge.fury.io/js/objc2swift.svg)](https://badge.fury.io/js/objc2swift) 2 | 3 | Objc2Swift.js is an Objective-C to Swift converter written in JavaScript. ([Live Demo][]) 4 | 5 | This project is still highly experimenal and does not aim to provide human-less conversion. 6 | However, the most of Objective-C code can be converted to proper Swift code with less compilation errors. 7 | 8 | Note: the author currently has no plan to support full features of Swift 3 or greater. 9 | 10 | ## Features 11 | 12 | - Generate good-looking Swift 2.1 code from Objective-C code. 13 | - Full Objective-C parser which accepts large source code, not for toy-problem. 14 | - Preserve indents and comments in the original Objective-C code. 15 | - Reduce compilation errors with semantics analysis. See the [Document](http://okaxaki.github.io/objc2swift/) for detail. 16 | - Command-line version supports `#import` delective with pre-compiled header cache. 17 | 18 | [PEG.js]: http://www.pegjs.org/ 19 | [Live Demo]: http://okaxaki.github.io/objc2swift/demo.html 20 | 21 | ## Install 22 | 23 | ```sh 24 | $ npm install -g objc2swift 25 | ``` 26 | 27 | Then, run the `objc2swift` command with `--init` option to setup the default configuration. 28 | 29 | ```sh 30 | $ objc2swift --init ios 31 | ``` 32 | 33 | Note that the parameter `ios` means to setup objc2swift for using Xcode's iOS SDK. If you want to target other SDK, use `osx`, `tvos` or `watchos`. 34 | 35 | ## Convert 36 | 37 | Pass the target source file to the command. The conversion result will be written to the current directory with extension `.swift`. 38 | 39 | ```sh 40 | $ objc2swift foo.m 41 | ``` 42 | The first conversion is very slow since there is no pre-compiled header cache. It will be speed-up later. 43 | 44 | By default, the command searches user headers from the current directory and its subdirectories. 45 | To import more headers, use `-I` option can be used to specifiy include paths. For example, 46 | 47 | ```sh 48 | $ objc2swift -I ~/git/myproject/ foo.m 49 | ``` 50 | The path specified by `-I` is recursively traversed. 51 | 52 | ## Configuration 53 | 54 | The default config path is `~/.objc2swift/config.json`. A typical content of config.json is like following. 55 | If you want to add user header search path permanently instead of `-I`, add the path to `includePaths` array. 56 | 57 | ``` 58 | { 59 | "systemIncludePaths": [ 60 | "${Xcode.app}/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/System/Library/Frameworks", 61 | "${Xcode.app}/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/usr/include", 62 | "${Xcode.app}/Contents/Developer/Platforms/iPhoneOS.platform/usr/lib/clang/3.5/include" 63 | ], 64 | "includePaths": [] 65 | } 66 | ``` 67 | 68 | ## How to Build 69 | ```sh 70 | git clone https://github.com/okaxaki/objc2swift.git 71 | cd objc2swift 72 | npm install 73 | npm run build 74 | ``` 75 | -------------------------------------------------------------------------------- /docs/doc/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Objc2Swift.js - Document 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 28 | 29 | 30 | 31 | 41 | 42 |
43 | 44 | 45 |

Overview

46 |

This document describes the conversion specification by enumerating conversion patterns. All sections are generated automatically from Test Spec of Objc2Swift.js.

47 |

Each section consists of a Objective-C code fragment and its corresponding Swift code like following.

48 | 49 |

Example Pattern

50 |

Objective-C

@interface MyClass
51 | - (void)hello;
52 | @end
53 | 
54 | @implementation MyClass
55 | - (void)hello{
56 |     NSLog(@"Hello the Swift World!");
57 | }
58 | @end
59 | 
60 |

Swift

class MyClass {
61 |     func hello() {
62 |         NSLog("Hello the Swift World!")
63 |     }
64 | }
65 | 
66 |

Note that NSLog() is not converted into print() by default because they are not equivalent.

67 |
68 | 72 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Objc2Swift.js 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 29 | 30 |
31 |

32 | Objc2Swift.js is an Objective-C to Swift converter written in Javascript, usable from your browser and the command-line.

33 |

Requirements

34 |
    35 |
  • Chrome browser is recommended. Other browsers are not tested enough.
  • 36 |
  • Mac OS X and node.js 4.x or later for command-line version.
  • 37 |
38 |

Features

39 |
    40 |
  • Generate good-looking Swift 2.1 code from Objective-C code.
  • 41 |
  • Full Objective-C parser which accepts large source code, not for toy-problem.
  • 42 |
  • Preserve indents and comments in the original Objective-C code.
  • 43 |
  • Reduce compilation errors with semantics analysis. See the Document for detail.
  • 44 |
  • Command-line version supports #import delective with pre-compiled header cache.
  • 45 |
46 |

Install for Command-line

47 |

See README on GitHub.

48 | 49 | 53 |
54 | 55 | 59 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /docs/doc/common.css: -------------------------------------------------------------------------------- 1 | * {padding:0;margin:0;box-sizing:border-box;} 2 | html,body {width:100%;height:100%;} 3 | 4 | body { 5 | font-family:Helvetica, Arial, sans-serif; 6 | font-size:15px; 7 | background-color:#fafafa; 8 | min-width:900px; 9 | } 10 | 11 | a { 12 | color:#3a76ff; 13 | text-decoration: none; 14 | } 15 | 16 | body { 17 | margin:0 auto; 18 | min-width:900px; 19 | min-height:100%; 20 | font-size:14px; 21 | background-color:#fafafa; 22 | clear:both; 23 | font-family:Helvetica, sans-serif; 24 | overflow-y:scroll; 25 | } 26 | 27 | body:after { 28 | display:block; 29 | content:''; 30 | clear:both; 31 | } 32 | 33 | .content h1 {font-size:240%; font-weight:normal;} 34 | .content h2 {font-size:150%;} 35 | .content h3 {font-size:110%;} 36 | .content h1 {margin:24px 0 1em 0; padding-bottom:0.5em;border-bottom:1px solid #eee;} 37 | .content h2 {margin:1.5em 0 1em 0; padding-bottom:0.5em; border-bottom:1px solid #eee;} 38 | .content h3 {margin:1.5em 0 1em 0;} 39 | .content p {margin:1em 0; line-height:160%;} 40 | .content ul {margin:1em;} 41 | .content code {font-family: Consolas, 'Courier New', Courier, Monaco, monospace;} 42 | .content p code {border-radius:4px;background-color:#eee;padding:.25em .5em;} 43 | .content pre {width:100%;white-space:pre-wrap;margin:1em 0;} 44 | .content pre code {display:block;border:1px solid #eee;border-radius:4px;width:100%;padding:1em;overflow:auto;} 45 | .content pre code.hljs {padding:1em;} 46 | .content code.swift {background-color:#efeff4;} 47 | 48 | section.page-header { 49 | position:relative; 50 | width:100%; 51 | color:#fff; 52 | background-color:#3a76ff; 53 | background-image: linear-gradient(120deg, #3a76ff, #155799); 54 | padding:0 72px; 55 | height:48px; 56 | position:fixed; 57 | top:0; 58 | } 59 | 60 | section.page-header a { 61 | color:#fff; 62 | text-decoration: none; 63 | } 64 | 65 | nav.root-nav { 66 | position:absolute; 67 | left:72px;right:72px; 68 | bottom:0; 69 | } 70 | 71 | .root-nav a { 72 | cursor:pointer; 73 | text-decoration: none; 74 | color:white; 75 | transition:opacity 0.15s linear; 76 | } 77 | .root-nav ul { 78 | margin:0;padding:0; 79 | margin-left:-8px; 80 | } 81 | .root-nav a:hover { 82 | text-decoration: none; 83 | opacity:0.5; 84 | } 85 | 86 | .root-nav li { 87 | display:inline-block; 88 | font-size:14px; 89 | padding:0 8px; 90 | height:48px; 91 | line-height:48px; 92 | list-style-type: none; 93 | text-transform: uppercase; 94 | } 95 | 96 | .root-nav li.selected { 97 | border-bottom:2px solid yellow; 98 | } 99 | 100 | .elapsed {width:100%;font-size:75%;padding:1em;color:#666;background-color:#f4f4f4;} 101 | 102 | .sidebar { 103 | width:240px; 104 | min-height:100%; 105 | float:left; 106 | top:56px; 107 | padding:24px 0 24px 72px; 108 | position:fixed; 109 | } 110 | 111 | .sidebar .menu ul { 112 | margin:1em 0; 113 | } 114 | 115 | .sidebar .menu li { 116 | line-height:160%; 117 | list-style-type: none; 118 | } 119 | 120 | .sidebar a { 121 | text-decoration: none; 122 | color:#007aff; 123 | } 124 | 125 | .content { 126 | margin:56px 0 0 240px; 127 | width:776px; 128 | padding:24px; 129 | } 130 | 131 | hr { 132 | border:none; 133 | border-top:1px dotted #ccc; 134 | margin:16px 0; 135 | } 136 | 137 | footer { 138 | font-size:10px; 139 | color:#777; 140 | margin:56px 0 0px 0; 141 | } 142 | -------------------------------------------------------------------------------- /docs/online.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Objc2Swift.js - Online Version 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 |
27 |
28 |

Objective-C

29 |
30 |
31 |
32 |
33 |
34 |

Swift

35 |
36 |
37 |
38 |
39 |
40 |
41 | 42 | 52 | 53 | 56 | 57 |
58 |
59 | 60 | 61 | 62 | 63 |
64 |
65 | 66 | 67 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | module.exports = (function(){ 2 | "use strict" 3 | 4 | var util = require('util'); 5 | var PreProcessor = require('./preprocessor'); 6 | var parser = require('./parser'); 7 | var TypeAnalyzer = require('./type-analyzer'); 8 | var Generator = require('./generator'); 9 | var ParserUtil = require('../src/parser-util'); 10 | 11 | var O2S = function() {}; 12 | 13 | var _estimateTabSize = function(source) { 14 | var tabSizeCountMap = {}; 15 | var estimatedTabSize = 4, maxTabSizeCount = -1; 16 | source = source.replace(/\t/g,' '); 17 | source.replace(/\n(\s*).*?\{\n(\s*)/g,function(m,p1,p2){ 18 | var size = p2.length - p1.length; 19 | if(!tabSizeCountMap[size]) tabSizeCountMap[size]=0; 20 | var newCount = tabSizeCountMap[size]++; 21 | if(maxTabSizeCount < newCount) { 22 | maxTabSizeCount = newCount; 23 | estimatedTabSize = size; 24 | } 25 | }); 26 | return 2 * Math.ceil(estimatedTabSize/2); 27 | }; 28 | 29 | O2S.prototype.preprocessOnly = function(source) { 30 | return new PreProcessor().process(source); 31 | }; 32 | 33 | O2S.prototype.buildErrorMessage = function(e, source, file) { 34 | return ParserUtil.buildErrorMessage(e, source, file); 35 | }; 36 | 37 | O2S.prototype.parse = function(source,options) { 38 | 39 | options = options || {}; 40 | 41 | var start = Date.now(); 42 | var pp = new PreProcessor(options); 43 | this.typeAnalyzer = new TypeAnalyzer(options); 44 | var p_source = pp.process(source,this.typeAnalyzer); 45 | 46 | if(!options.quiet) { 47 | console.error("Preprocess : " + (Date.now() - start) + "ms"); 48 | } 49 | 50 | start = Date.now(); 51 | var ast = parser.parse(p_source,options); 52 | if(!options.quiet) { 53 | console.error("Parse : " + (Date.now() - start) + "ms"); 54 | } 55 | 56 | start = Date.now(); 57 | this.typeAnalyzer.analyze(ast); 58 | if(!options.quiet) { 59 | console.error("Analyze : " + (Date.now()-start) + "ms"); 60 | } 61 | 62 | if(options.ast) { 63 | console.log(JSON.stringify(ast,function(k,v){ 64 | if(k=="ast") { 65 | return "(...)"; 66 | } 67 | if(!options.verbose) { 68 | if(k=="_typeInfo") { 69 | return v?(v.kind+" "+v.name):"(error)"; 70 | } 71 | if(k=="_declInfo") { 72 | return v?(v.kind+" "+v.name):"(error)"; 73 | } 74 | } 75 | return v; 76 | },' ')); 77 | } 78 | 79 | if(options.query) { 80 | var qs = options.query.split(','); 81 | var result = {} 82 | for(var i=0;i 2 | 3 | 4 | Objc2Swift.js - Document 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 28 | 29 | 30 | 31 | 41 | 42 |
43 | 44 | 45 |

Initializer

46 |

Alloc & Init

47 |

Objective-C

void test() {
 48 |     Foo *foo;
 49 |     foo = [[Foo alloc] init];
 50 |     // ...
 51 | }
 52 | 
53 |

Swift

func test() {
 54 |     var foo:Foo!
 55 |     foo = Foo()
 56 |     // ...
 57 | }
 58 | 
59 | 60 |

Designated Initializer

61 |

Designated Initializer is partially supported. The signature can be coverted but inner implementation is not modified.

62 |

Objective-C

@implementation Foo
 63 | -(id)init {
 64 |     self = [super init]; // Remains in Swift code
 65 |     if(self) { // Remains in Swift code
 66 |         // ...
 67 |     }
 68 |     return self; // Remains in Swift code
 69 | }
 70 | 
 71 | -(instancetype)initWithFrame:(CGRect) frame {
 72 |     // ...
 73 | }
 74 | @end
 75 | 
76 |

Swift

class Foo {
 77 |     init() {
 78 |         self = super.init() // Remains in Swift code
 79 |         if (self != nil) { // Remains in Swift code
 80 |             // ...
 81 |         }
 82 |         return self // Remains in Swift code
 83 |     }
 84 | 
 85 |     init(frame:CGRect) {
 86 |         // ...
 87 |     }
 88 | }
 89 | 
90 | 91 |
92 | 96 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /docs/doc/class.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Objc2Swift.js - Document 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 28 | 29 | 30 | 31 | 41 | 42 |
43 | 44 | 45 |

Class and Category

46 |

Basic Class Definition

47 |

Objective-C

@interface Foo
 48 | @property int bar;
 49 | -(void)method;
 50 | @end
 51 | 
 52 | @implementation Foo 
 53 | -(void)method {
 54 |     // do something
 55 | }
 56 | @end
 57 | 
58 |

Swift

class Foo { 
 59 |     var bar:Int
 60 | 
 61 |     func method() {
 62 |         // do something
 63 |     }
 64 | }
 65 | 
66 | 67 |

Inheritance

68 |

Objective-C

@interface Foo : Bar
 69 | @end
 70 | 
 71 | @implementation Foo
 72 | // ...
 73 | @end
 74 | 
75 |

Swift

class Foo : Bar {
 76 | // ...
 77 | }
 78 | 
79 | 80 |

Anonymous Category

81 |

Properties declared in interface or anonymous category are converted into instance variables of a single class together.

82 |

Properties from the anonymous category are treated as private.

83 | 84 |

Objective-C

@interface Foo
 85 | @property int bar;
 86 | @end
 87 | 
 88 | @interface Foo ()
 89 | @property int baz;
 90 | @end
 91 | 
 92 | @implementation Foo 
 93 | @end
 94 | 
95 |

Swift

class Foo { 
 96 |     var bar:Int
 97 |     private var baz:Int
 98 | }
 99 | 
100 | 101 |
102 | 106 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /src/import-resolver.js: -------------------------------------------------------------------------------- 1 | module.exports = (function(){ 2 | "use strict"; 3 | 4 | var fs = require('fs-extra'); 5 | var glob = require('glob'); 6 | var crypto = require('crypto'); 7 | var path = require('path'); 8 | 9 | 10 | var ImportResolver = function(options) { 11 | this.verbose = options.verbose; 12 | this.buildFrameworkHeaderMap(options.frameworkPath||[]); 13 | this.buildHeaderMap(options.includePath||[]); 14 | }; 15 | 16 | ImportResolver.VERSION = "0.0.1"; 17 | 18 | ImportResolver.prototype.loadFile = function(path) { 19 | try { 20 | return fs.readFileSync(path, 'utf-8'); 21 | } catch(e) { 22 | return null; 23 | } 24 | }; 25 | 26 | var _getCacheRoot = function() { 27 | return (process.env.HOME||process.env.USERPROFILE) + "/.objc2swift/cache"; 28 | }; 29 | ImportResolver.getCacheRoot = _getCacheRoot; 30 | 31 | var _getCacheDir = function() { 32 | return _getCacheRoot() + '/' + ImportResolver.VERSION + '/'; 33 | }; 34 | ImportResolver.getCacheDir = _getCacheDir; 35 | 36 | function hash_name(name) { 37 | var sha = crypto.createHash('md5'); 38 | sha.update(name, 'utf8'); 39 | return name.replace(/^.*\//,'') + "_" + sha.digest('hex'); 40 | } 41 | 42 | ImportResolver.prototype.readCache = function(path) { 43 | try { 44 | var cacheName = _getCacheDir() + hash_name(path); 45 | 46 | var stat = fs.statSync(path); 47 | var cstat = fs.statSync(cacheName); 48 | if(cstat.mtime.getTime() < stat.mtime.getTime()) { 49 | if(this.verbose) { 50 | console.error("Cache expired: " + path.replace(/^.*\//,'')); 51 | } 52 | return null; 53 | } 54 | var text = fs.readFileSync(cacheName,'utf-8'); 55 | return JSON.parse(text); 56 | } catch(e) { 57 | return null; 58 | } 59 | } 60 | 61 | ImportResolver.prototype.writeCache = function(path,data) { 62 | try { 63 | var cacheName = _getCacheDir() + hash_name(path); 64 | var json = JSON.stringify(data,function(k,v){ 65 | if(k=="ast") { 66 | return null; 67 | } 68 | return v; 69 | },' ') 70 | fs.writeFileSync(cacheName,json,'utf-8'); 71 | } catch(e) { 72 | } 73 | }; 74 | 75 | ImportResolver.prototype.buildFrameworkHeaderMap = function(frameworkPathList) { 76 | 77 | this.frameworkHeaderMap = {}; 78 | for(var i=0;i 2 | 3 | 4 | Objc2Swift.js - Document 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 28 | 29 | 30 | 31 | 41 | 42 |
43 | 44 | 45 |

Blocks

46 |

Blocks as Variable

47 |

Objective-C

void (^blk)(int) = ^(int a) {
 48 |     // ...
 49 | };
 50 | 
51 |

Swift

let blk:(Int)->Void = { (a:Int) in 
 52 |     // ...
 53 | }
 54 | 
55 | 56 |

Blocks as Property

57 |

Objective-C

@interface Foo
 58 | @property void(^blk)(NSString *);
 59 | @end
 60 | 
 61 | @implementation Foo
 62 | -(void)method {
 63 |     blk = ^(NSString *msg){
 64 |         NSLog(@"In block: %@", msg);
 65 |         // ...
 66 |     };
 67 | }
 68 | @end
 69 | 
70 |

Swift

class Foo {
 71 |     var blk:(String!)->Void
 72 | 
 73 |     func method() {
 74 |         blk = { (msg:String!) in 
 75 |             NSLog("In block: %@", msg)
 76 |             // ...
 77 |         }
 78 |     }
 79 | }
 80 | 
81 | 82 |

Blocks as Parameter

83 |

Objective-C

@implementation Foo
 84 | -(void)method:(void (^)(int))blk {
 85 |     // ...
 86 | }
 87 | @end
 88 | 
89 |

Swift

class Foo {
 90 |     func method(blk:(Int)->Void) {
 91 |         // ...
 92 |     }
 93 | }
 94 | 
95 | 96 |

Direct Blocks Declaration

97 |

This pattern is not supported yet.

98 |

Objective-C

/** Not Supported yet */
 99 | void (^blk)(int) {
100 |     // ...
101 | };
102 | 
103 |

Swift

/** Not Supported yet */
104 | func blk() {
105 |     // ...
106 | };
107 | 
108 | 109 |
110 | 114 | 120 | 121 | 122 | -------------------------------------------------------------------------------- /docs/doc/optional.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Objc2Swift.js - Document 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 28 | 29 | 30 | 31 | 41 | 42 |
43 | 44 | 45 |

Optional Type

46 |

Nullability Qualifier

47 |

Objective-C

@implementation A
 48 | -(void)someMethod:(NSString *)arg1 :(nonnull NSString *)arg2 :(nullable NSString *)arg3 {
 49 |     //...
 50 | }
 51 | @end
 52 | 
53 |

Swift

class A {
 54 |     func someMethod(arg1:String!, arg2:String, arg3:String?) {
 55 |         //...
 56 |     }
 57 | }
 58 | 
59 | 60 |

Auto-unwrapping

61 |

Objective-C

@implementation A
 62 | -(void)someMethod:(nullable NSString *)str {
 63 |     NSLog(@"%l",[str length]);
 64 | }
 65 | @end
 66 | 
67 |

Swift

class A {
 68 |     func someMethod(str:String?) {
 69 |         NSLog("%l",str!.length())
 70 |     }
 71 | }
 72 | 
73 | 74 |

Nullability Annotation

75 |

Objective-C

NS_ASSUME_NONNULL_BEGIN
 76 | @interface AAPLList : NSObject 
 77 | // ...
 78 | - (nullable AAPLListItem *)itemWithName:(NSString *)name;
 79 | - (NSInteger)indexOfItem:(AAPLListItem *)item;
 80 | 
 81 | @property (copy, nullable) NSString *name;
 82 | @property (copy, readonly) NSArray *allItems;
 83 | // ...
 84 | @end
 85 | NS_ASSUME_NONNULL_END
 86 | 
 87 | @implementation AAPLList
 88 | - (nullable AAPLListItem *)itemWithName:(NSString *)name {
 89 | 	// ...
 90 | }
 91 | 
 92 | - (NSInteger)indexOfItem:(AAPLListItem *)item {
 93 |     // ...	
 94 | }
 95 | @end
 96 | 
97 |

Swift

class AAPLList : NSObject {
 98 |     @NSCopying var name:String?
 99 |     @NSCopying private(set) var allItems:[AnyObject]
100 | 
101 |     func itemWithName(name:String) -> AAPLListItem? {
102 |     	// ...
103 |     }
104 | 
105 |     func indexOfItem(item:AAPLListItem) -> Int {
106 |         // ...	
107 |     }
108 | }
109 | 
110 | 111 |
112 | 116 | 122 | 123 | 124 | -------------------------------------------------------------------------------- /docs/doc/enum.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Objc2Swift.js - Document 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 28 | 29 | 30 | 31 | 41 | 42 |
43 | 44 | 45 |

Enumeration

46 |

47 | Note: Enumeration conversion specification will be fixed. Currently the converter treats the enum type name as the prefix of each enumerators. 48 | This results approximatly fine, but strictly, it is different from Swift's behavior. 49 |

50 | 51 |

Enumeration Declaration

52 |

Objective-C

typedef NS_ENUM(NSUInteger, Animal) {
 53 |     AnimalSwift,
 54 |     AnimalLion,
 55 |     AnimalPanther,
 56 |     AnimalTiger,
 57 | };
 58 | 
59 |

Swift

enum Animal:UInt {
 60 |     case Swift
 61 |     case Lion
 62 |     case Panther
 63 |     case Tiger
 64 | }
 65 | 
66 | 67 |

Enumeration Reference

68 |

Objective-C

typedef NS_ENUM(NSUInteger, Animal) {
 69 |     AnimalSwift,
 70 |     AnimalLion,
 71 |     AnimalPanther,
 72 |     AnimalTiger,
 73 | };
 74 | 
 75 | @implementation MyClass
 76 | -(NSString *)someMethod:(MyColor)color {
 77 |     switch(color) {
 78 |         case AnimalLion:
 79 |         case AnimalPanther:
 80 |         case AnimalTiger:
 81 |             return "OS X code name";
 82 |         case AnimalSwift:
 83 |             return "Language name";
 84 |         default:
 85 |             return NULL;
 86 |     }
 87 | }
 88 | @end
 89 | 
90 |

Swift

enum Animal:UInt {
 91 |     case Swift
 92 |     case Lion
 93 |     case Panther
 94 |     case Tiger
 95 | }
 96 | 
 97 | class MyClass {
 98 |     func someMethod(color:MyColor) -> String! {
 99 |         switch(color) { 
100 |             case Animal.Lion,
101 |                  Animal.Panther,
102 |                  Animal.Tiger:
103 |                 return "OS X code name"
104 |             case Animal.Swift:
105 |                 return "Language name"
106 |             default:
107 |                 return nil
108 |         }
109 |     }
110 | }
111 | 
112 | 113 |
114 | 118 | 124 | 125 | 126 | -------------------------------------------------------------------------------- /spec/statement.spec: -------------------------------------------------------------------------------- 1 | # Statement 2 | 3 | ## If Statement 4 | ``` 5 | @implementation Foo 6 | -(NSString *)method { 7 | if(Condition) { 8 | return @"OK"; 9 | } else { 10 | return @"NG"; 11 | } 12 | } 13 | @end 14 | ``` 15 | 16 | ``` 17 | class Foo { 18 | func method() -> String! { 19 | if Condition { 20 | return "OK" 21 | } else { 22 | return "NG" 23 | } 24 | } 25 | } 26 | ``` 27 | 28 | ## Switch Statement 29 | ``` 30 | @implementation Foo 31 | -(NSString *)method { 32 | switch(Condition) { 33 | case Apple: 34 | return @"apple"; 35 | case Orange: 36 | return @"orange"; 37 | default: 38 | return @"else"; 39 | } 40 | } 41 | @end 42 | ``` 43 | 44 | ``` 45 | class Foo { 46 | func method() -> String! { 47 | switch(Condition) { 48 | case Apple: 49 | return "apple" 50 | case Orange: 51 | return "orange" 52 | default: 53 | return "else" 54 | } 55 | } 56 | } 57 | ``` 58 | 59 | ## Switch Statement (sequential cases) 60 | ``` 61 | @implementation Foo 62 | -(NSString *)method { 63 | switch(Condition) { 64 | case Apple: 65 | case Orange: 66 | return @"fruit"; 67 | default: 68 | return @"else"; 69 | } 70 | } 71 | @end 72 | ``` 73 | ``` 74 | class Foo { 75 | func method() -> String! { 76 | switch(Condition) { 77 | case Apple, 78 | Orange: 79 | return "fruit" 80 | default: 81 | return "else" 82 | } 83 | } 84 | } 85 | ``` 86 | 87 | ## For Statement 88 | ``` 89 | @implementation Foo 90 | -(void)method { 91 | for(int i=0;i<100;i++) { 92 | NSLog("%d",i); 93 | } 94 | } 95 | @end 96 | ``` 97 | ``` 98 | class Foo { 99 | func method() { 100 | for var i:Int=0 ; i<100 ; i++ { 101 | NSLog("%d",i) 102 | } 103 | } 104 | } 105 | ``` 106 | 107 | ## For-In Statement 108 | ``` 109 | @implementation Foo 110 | -(void)method { 111 | for(int bar in Foo) { 112 | NSLog("%d",bar); 113 | } 114 | } 115 | @end 116 | ``` 117 | 118 | ``` 119 | class Foo { 120 | func method() { 121 | for bar:Int in Foo { 122 | NSLog("%d",bar) 123 | } 124 | } 125 | } 126 | ``` 127 | 128 | ## While Statement 129 | ``` 130 | @implementation Foo 131 | -(void)method { 132 | while(flag) { 133 | // Do Something 134 | } 135 | } 136 | @end 137 | ``` 138 | 139 | ``` 140 | class Foo { 141 | func method() { 142 | while flag { 143 | // Do Something 144 | } 145 | } 146 | } 147 | ``` 148 | 149 | ## Do-White Statement 150 | ``` 151 | @implementation Foo 152 | -(void)method { 153 | do { 154 | // Do Something 155 | } while(flag); 156 | } 157 | @end 158 | ``` 159 | ``` 160 | class Foo { 161 | func method() { 162 | repeat { 163 | // Do Something 164 | } while flag 165 | } 166 | } 167 | ``` 168 | 169 | ## Try-Catch Statment 170 |

Since Swift's try-catch statement has different semantics from Objective-C's, @try, @catch and @finally keywords are not be converted. 171 |

172 | ``` 173 | @implementation Foo 174 | -(void)method { 175 | @try { 176 | // ... 177 | } 178 | @catch(Error *err) { 179 | // ... 180 | } 181 | @finally { 182 | // ... 183 | } 184 | } 185 | @end 186 | ``` 187 | ``` 188 | class Foo { 189 | func method() { 190 | @try { 191 | // ... 192 | } 193 | @catch(err:Error!) { 194 | // ... 195 | } 196 | @finally { 197 | // ... 198 | } 199 | } 200 | } 201 | ``` 202 | 203 | ## Jump and Labeled Statement 204 |

205 | ``` 206 | @implementation Foo 207 | -(void)method { 208 | for(int i = 0; i < 100; i++) { 209 | if (i == 50) { 210 | goto bar; 211 | } 212 | } 213 | bar: 214 | return; 215 | } 216 | @end 217 | ``` 218 | ``` 219 | class Foo { 220 | func method() { 221 | for var i:Int=0 ; i < 100 ; i++ { 222 | if i == 50 { 223 | goto bar 224 | } 225 | } 226 | bar: 227 | return 228 | } 229 | } 230 | ``` 231 | 232 | -------------------------------------------------------------------------------- /docs/doc/protocol.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Objc2Swift.js - Document 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 28 | 29 | 30 | 31 | 41 | 42 |
43 | 44 | 45 |

Protocol

46 |

Protocol Declaration

47 | Note the converter does not generate Swift protocol code if the protocol declaration is found in the imported header. 48 | If you would like to get Swift protocol code, pass the header file where the protocol is declared to the converter. 49 | 50 |

Objective-C

@protocol MyProtocol
 51 | @property int property;
 52 | @property (readonly) int readonlyProperty;
 53 | +(void)classMethod;
 54 | -(void)method;
 55 | @optional
 56 | @property int optionalProperty;
 57 | -(void)optionalClassMethod;
 58 | -(void)optionalMethod;
 59 | @end
 60 | 
61 |

Swift

protocol MyProtocol {
 62 |     var property:Int { get set }
 63 |     var readonlyProperty:Int { get }
 64 |     class func classMethod()
 65 |     func method()
 66 | 
 67 |     optional var optionalProperty:Int { get set }
 68 |     optional func optionalClassMethod()
 69 |     optional func optionalMethod()
 70 | }
 71 | 
72 | 73 |

Protocol Reference

74 |

Objective-C

@interface Foo : NSObject <Bar, Baz>
 75 | @end
 76 | 
 77 | @implementation Foo
 78 | // ...
 79 | @end
 80 | 
81 |

Swift

class Foo : NSObject, Bar, Baz {
 82 | // ...
 83 | }
 84 | 
85 | 86 |

Protocol Name Confliction

87 |

Objective-C

@interface Foo
 88 | @end
 89 | 
 90 | // Objective-C allows the same protocol name with 
 91 | // the class but Swift doesn't. 
 92 | // suffix `-Protocol` is added to the Swift 
 93 | // protocol if name confliction is found.
 94 | @protocol Foo 
 95 | @end
 96 | 
 97 | @protocol Bar
 98 | @end
 99 | 
100 | @interface MyClass : Foo<Foo,Bar>
101 | @property Foo *oFoo;
102 | @property id<Foo> pFoo;
103 | @property id<Bar> pBar;
104 | @end
105 | 
106 | @implementation MyClass 
107 | @end
108 | 
109 |

Swift

// Objective-C allows the same protocol name with 
110 | // the class but Swift doesn't. 
111 | // suffix `-Protocol` is added to the Swift 
112 | // protocol if name confliction is found.
113 | 
114 | 
115 | class MyClass : Foo, FooProtocol, Bar { 
116 |     var oFoo:Foo!
117 |     var pFoo:FooProtocol!
118 |     var pBar:Bar!
119 | }
120 | 
121 | 122 |
123 | 127 | 133 | 134 | 135 | -------------------------------------------------------------------------------- /docs/doc/method.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Objc2Swift.js - Document 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 28 | 29 | 30 | 31 | 41 | 42 |
43 | 44 | 45 |

Class

46 |

Class method

47 |

Objective-C

@implementation Foo
 48 | +(int)method {
 49 |     return 0;
 50 | }
 51 | @end
 52 | 
53 |

Swift

class Foo {
 54 |     class func method() -> Int {
 55 |         return 0
 56 |     }
 57 | }
 58 | 
59 | 60 |

Instance method

61 |

Objective-C

@implementation Foo
 62 | -(int)method {
 63 |     return 0;
 64 | }
 65 | @end
 66 | 
67 |

Swift

class Foo {
 68 |     func method() -> Int {
 69 |         return 0
 70 |     }
 71 | }
 72 | 
73 | 74 |

Method with params

75 |

Objective-C

@implementation Foo 
 76 | -(void)method:(int)arg1 label:(NSString *)arg2 {}
 77 | -(void)method2:(int)arg1 :(NSString *)arg2 {}
 78 | -(void)method3:(int)arg1 arg2:(NSString *)arg2 {}
 79 | @end
 80 | 
81 |

Swift

class Foo { 
 82 |     func method(arg1:Int, label arg2:String!) {}
 83 |     func method2(arg1:Int, arg2:String!) {}
 84 |     func method3(arg1:Int, arg2:String!) {}
 85 | }
 86 | 
87 | 88 |

Override Completion

89 |

Objective-C

@interface SuperFoo
 90 | -(void)someMethod:(NSNumber *)arg;
 91 | @end
 92 | 
 93 | @interface Foo : SuperFoo
 94 | -(void)someMethod:(NSNumber *)arg;
 95 | @end
 96 | 
 97 | @implementation Foo
 98 | -(void)someMethod:(NSNumber *)arg {
 99 |     // ...
100 | }
101 | @end
102 | 
103 |

Swift

class Foo : SuperFoo {
104 |     override func someMethod(arg:NSNumber!) {
105 |         // ...
106 |     }
107 | }
108 | 
109 | 110 |

Respect Interface declaration

111 |

If the signature of the method implementation is different from interface, 112 | the generated swift code follows interface declaration. Such patterns are mostly apparel with nullability specifiers. 113 |

114 | 115 |

Objective-C

@interface Foo
116 | -(NSString * nonnull)method;
117 | @end
118 | 
119 | @implementation Foo
120 | -(NSString *)method {
121 |     // ...
122 | }
123 | @end
124 | 
125 |

Swift

class Foo {
126 |     func method() -> String {
127 |         // ...
128 |     }
129 | }
130 | 
131 | 132 |
133 | 137 | 143 | 144 | 145 | -------------------------------------------------------------------------------- /docs/css/index.css: -------------------------------------------------------------------------------- 1 | * {padding:0;margin:0;box-sizing:border-box;} 2 | html,body {width:100%;height:100%;} 3 | 4 | body { 5 | font-family:Helvetica, Arial, sans-serif; 6 | font-size:15px; 7 | background-color:#fafafa; 8 | min-width:900px; 9 | } 10 | 11 | a { 12 | color:#3a76ff; 13 | text-decoration: none; 14 | } 15 | 16 | section.page-header { 17 | position:relative; 18 | width:100%; 19 | color:#fff; 20 | background-color:#3a76ff; 21 | background-image: linear-gradient(120deg, #3a76ff, #155799); 22 | padding:72px; 23 | } 24 | 25 | .page-header h1 { 26 | font-weight:bold; 27 | font-size:48px; 28 | line-height:72px; 29 | vertical-align:middle; 30 | } 31 | 32 | .page-header h2 { 33 | font-weight:300; 34 | font-size:16px; 35 | line-height:24px; 36 | } 37 | 38 | .page-header .btn { 39 | display:inline-block; 40 | padding:0 8px; 41 | height:36px; 42 | line-height:36px; 43 | border-radius:2px; 44 | border:1px solid #fff; 45 | font-size:16px; 46 | font-weight:normal; 47 | margin:16px 0; 48 | vertical-align:middle; 49 | } 50 | 51 | .page-header a.btn { 52 | color:white; 53 | text-decoration: none; 54 | transition:opacity 0.15s linear; 55 | } 56 | 57 | .page-header a.btn:hover { 58 | opacity:0.5; 59 | } 60 | 61 | nav.root-nav { 62 | position:absolute; 63 | left:72px;right:72px; 64 | bottom:0; 65 | } 66 | 67 | .root-nav a { 68 | cursor:pointer; 69 | text-decoration: none; 70 | color:white; 71 | transition:opacity 0.15s linear; 72 | } 73 | 74 | .root-nav a:hover { 75 | text-decoration: none; 76 | opacity:0.5; 77 | } 78 | 79 | .root-nav ul { 80 | margin-left:-8px; 81 | } 82 | 83 | .root-nav li { 84 | display:inline-block; 85 | font-size:14px; 86 | padding:0 8px; 87 | height:48px; 88 | line-height:48px; 89 | list-style-type: none; 90 | text-transform: uppercase; 91 | } 92 | 93 | .root-nav li.selected { 94 | border-bottom:2px solid yellow; 95 | } 96 | 97 | section.content { 98 | padding:0 72px 56px 72px; 99 | line-height:160%; 100 | } 101 | 102 | .content ul { 103 | 104 | } 105 | 106 | .content li { 107 | margin-left:24px; 108 | } 109 | 110 | .content code { 111 | border-radius:2px; 112 | padding:.25em .5em; 113 | background-color:#efeff4; 114 | } 115 | 116 | .clearfix:after { 117 | content: "."; 118 | display: block; 119 | height: 0; 120 | font-size:0; 121 | clear: both; 122 | visibility:hidden; 123 | } 124 | 125 | .source { 126 | width:50%; 127 | float:left; 128 | padding-right:1em; 129 | } 130 | .result { 131 | width:50%; 132 | float:left; 133 | padding-left:1em; 134 | } 135 | 136 | .source pre, .result pre, .source #editor { 137 | height:280px; 138 | } 139 | 140 | p { 141 | margin:.75em 0; 142 | line-height:160%; 143 | } 144 | 145 | p.elapsed { 146 | color:rgba(0,0,0,.54); 147 | font-size:12px; 148 | } 149 | 150 | p.note { 151 | font-size:90%; 152 | font-weight:bold; 153 | } 154 | 155 | pre { 156 | width:100%; 157 | height:100%; 158 | white-space: pre-wrap; 159 | } 160 | 161 | pre code { 162 | font-size:12px; 163 | font-weight:normal; 164 | display:block; 165 | width:100%; 166 | height:100%; 167 | padding:.5em; 168 | overflow:auto; 169 | line-height:130%; 170 | } 171 | 172 | code { 173 | font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', monospace; 174 | } 175 | 176 | code.objectivec { 177 | background-color:#ffffff; 178 | } 179 | 180 | code.swift { 181 | background-color:#efeff4; 182 | } 183 | 184 | .content h1 { 185 | font-size:24px; 186 | color:rgba(0,0,0,0.87); 187 | margin:56px 0 24px 0; 188 | } 189 | 190 | .content h3 { 191 | font-size:16px; 192 | color:#3a76ff; 193 | margin:16px 0; 194 | } 195 | 196 | .source-header select { 197 | margin:16px 0 16px 16px; 198 | float:left; 199 | font-size:16px; 200 | } 201 | 202 | .source-header h3 { 203 | float:left; 204 | } 205 | 206 | input.url { 207 | font-size:12px; 208 | width:75%; 209 | padding:.5em; 210 | } 211 | 212 | button { 213 | font-size:14px; 214 | padding:0 8px; 215 | line-height:36px; 216 | height:36px; 217 | border-radius:2px; 218 | color:white; 219 | background-color:#3a76ff; 220 | border:none; 221 | } 222 | 223 | button:focus, select:focus, .swift:focus { 224 | outline:none; 225 | } 226 | 227 | button:hover { 228 | box-shadow:0 3px 8px 0 rgba(0,0,0,.25); 229 | } 230 | 231 | button:disabled { 232 | font-size:14px; 233 | background-color:#f0f0f0; 234 | color:#ccc; 235 | box-shadow:none; 236 | } 237 | 238 | button#retrieve { 239 | min-width:120px; 240 | margin-bottom:1em; 241 | } 242 | 243 | button#convert { 244 | width:100%; 245 | margin:1em 0; 246 | cursor:pointer; 247 | } 248 | 249 | hr { 250 | border:none; 251 | border-top:1px dotted #ccc; 252 | margin:16px 0; 253 | } 254 | 255 | footer { 256 | font-size:10px; 257 | color:#777; 258 | margin:56px 0 0px 0; 259 | } 260 | -------------------------------------------------------------------------------- /docs/demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Objc2Swift.js - Live Demo 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 38 | 39 |
40 |

Live Demo

41 |

42 | Choose a code sample from the menu, or type your code to the input box. Then press [Convert] to generate Swift code.
43 | You can also drag-and-drop your source file to the box. 44 |

45 |

Note: This demo NEVER send your code to the server. The conversion process is executed locally in your browser.

46 |
47 |
48 |
49 |

Objective-C

50 | 60 |
61 |
@interface MyClass 62 | - (void)hello; 63 | @end 64 | 65 | @implementation MyClass 66 | - (void)hello { 67 | NSLog(@"Hello the Swift World!"); 68 | } 69 | @end
70 | 71 |
72 |
73 |

Swift

74 |
75 |
76 |
77 | 78 |

The command-line version is also available for OS X. It is capable to analyze header files and provides more accurate conversion. See GitHub Repository.

79 | 80 | 84 |
85 | 86 | 90 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /docs/doc/misc.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Objc2Swift.js - Document 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 28 | 29 | 30 | 31 | 41 | 42 |
43 | 44 | 45 |

Miscellaneous

46 |

Assignment analysis

47 |

Objective-C

@implementation MyClass
 48 | -(void)test {
 49 |     NSString *foo = @"FOO";
 50 |     NSString *bar = @"BAR";
 51 | 
 52 |     foo = @"baz";    
 53 |     // ... 
 54 |     return foo;
 55 | }
 56 | @end
 57 | 
58 |

Swift

class MyClass {
 59 |     func test() {
 60 |         var foo:String! = "FOO"
 61 |         let bar:String! = "BAR"
 62 | 
 63 |         foo = "baz"    
 64 |         // ... 
 65 |         return foo
 66 |     }
 67 | }
 68 | 
69 | 70 |

NULL check idiom

71 |

Objective-C

@implementation MyClass
 72 | -(void)test:(NSString *)str {
 73 |     if(!str) {
 74 |         NSLog(@"str is NULL");
 75 |     }
 76 | }
 77 | @end
 78 | 
79 |

Swift

class MyClass {
 80 |     func test(str:String!) {
 81 |         if (str == nil) {
 82 |             NSLog("str is NULL")
 83 |         }
 84 |     }
 85 | }
 86 | 
87 | 88 |

class Keyword

89 |

Objective-C

void test() {
 90 | 
 91 |     NSString *str;
 92 | 
 93 |     // Get type from class.
 94 |     [NSString class];
 95 |     NSString.class;
 96 | 
 97 |     // Get dynamic type from instance.
 98 |     [str class];
 99 |     str.class;
100 | 
101 | }
102 | 
103 |

Swift

func test() {
104 | 
105 |     var str:String!
106 | 
107 |     // Get type from class.
108 |     String.self
109 |     String.self
110 | 
111 |     // Get dynamic type from instance.
112 |     str.dynamicType
113 |     str.dynamicType
114 | 
115 | }
116 | 
117 | 118 |

isKindOfClass

119 |

Objective-C

void test(id obj) {
120 |     if([obj isKindOfClass:[NSString class]]) {
121 |         // Do something
122 |     }
123 | }
124 | 
125 |

Swift

func test(obj:AnyObject!) {
126 |     if (obj is NSString) {
127 |         // Do something
128 |     }
129 | }
130 | 
131 | 132 |

instancetype

133 |

Objective-C

@implementation Foo
134 | -(instancetype)method {
135 |     // ...
136 |     return self;
137 | }
138 | @end
139 | 
140 |

Swift

class Foo {
141 |     func method() -> Self {
142 |         // ...
143 |         return self
144 |     }
145 | }
146 | 
147 | 148 |

@available

149 |

Objective-C

@implementation MyClass
150 | -(void)test {
151 |     if(@available(iOS 10.0, *)) {
152 |         // >= iOS10
153 |     } else {
154 |         // <= iOS9
155 |     }
156 | }
157 | @end
158 | 
159 |

Swift

class MyClass {
160 |     func test() {
161 |         if #available(iOS 10.0, *) {
162 |             // >= iOS10
163 |         } else {
164 |             // <= iOS9
165 |         }
166 |     }
167 | }
168 | 
169 | 170 |
171 | 175 | 181 | 182 | 183 | -------------------------------------------------------------------------------- /docs/css/online.css: -------------------------------------------------------------------------------- 1 | * {padding:0;margin:0;box-sizing:border-box;} 2 | html { 3 | width:100%;height:100%; 4 | } 5 | 6 | button:focus { 7 | outline:none; 8 | } 9 | 10 | .material-icons { 11 | font-size:inherit; 12 | } 13 | 14 | body { 15 | font-family:Helvetica, Arial, sans-serif; 16 | background-color:#808080; 17 | min-width:900px; 18 | height:100%; 19 | width:100%; 20 | overflow:hidden; 21 | } 22 | 23 | a { 24 | color:#3a76ff; 25 | text-decoration: none; 26 | } 27 | 28 | section { 29 | min-width:800px; 30 | } 31 | 32 | section.page-header { 33 | position:relative; 34 | width:100%; 35 | color:#fff; 36 | background-color:#3a76ff; 37 | background-image: linear-gradient(120deg, #3a76ff, #155799); 38 | padding:0 72px; 39 | height:24px; 40 | top:0; 41 | } 42 | 43 | nav.root-nav { 44 | position:absolute; 45 | left:72px;right:72px; 46 | bottom:0; 47 | } 48 | 49 | .root-nav a { 50 | cursor:pointer; 51 | text-decoration: none; 52 | color:white; 53 | transition:opacity 0.15s linear; 54 | } 55 | 56 | .root-nav a:hover { 57 | text-decoration: none; 58 | opacity:0.5; 59 | } 60 | 61 | .root-nav ul { 62 | margin-left:-8px; 63 | } 64 | 65 | .root-nav li { 66 | display:inline-block; 67 | font-size:12px; 68 | padding:0 8px; 69 | height:24px; 70 | line-height:24px; 71 | list-style-type: none; 72 | text-transform: uppercase; 73 | } 74 | 75 | .root-nav li.selected { 76 | border-bottom:2px solid yellow; 77 | } 78 | 79 | section.toolbar { 80 | position:absolute; 81 | height:44px; 82 | width:100%; 83 | background-color:#fafafa; 84 | border-top:1px solid #fff; 85 | border-bottom:1px solid #333; 86 | } 87 | 88 | section.toolbar .left:after { 89 | content:''; 90 | clear:both; 91 | } 92 | 93 | section.toolbar .left { 94 | width:50%; 95 | padding:4px 8px; 96 | } 97 | 98 | section.toolbar button { 99 | min-width:56px; 100 | height:32px; 101 | font-size:12px; 102 | line-height:22px; 103 | border-radius: 2px; 104 | background:none; 105 | color:rgba(0,0,0,.84); 106 | cursor: pointer; 107 | padding:0 12px 0 8px; 108 | margin:auto 0 auto 8px; 109 | transition:0.25s all; 110 | border:none; 111 | } 112 | 113 | section.toolbar button .material-icons { 114 | font-size:20px; 115 | margin-top:-1px; 116 | vertical-align:middle; 117 | } 118 | 119 | section.toolbar button:hover { 120 | background-color:#f0f0f0; 121 | } 122 | 123 | section.page-footer { 124 | position:absolute; 125 | width:100%; 126 | bottom:0; 127 | height:48px; 128 | line-height:48px; 129 | background-color:#555; 130 | border-top:1px solid #888; 131 | font-size:12px; 132 | color:#fff; 133 | font-weight:normal; 134 | padding-left:20px; 135 | } 136 | 137 | section.page-header h1 { 138 | position:relative; 139 | left:16px; 140 | font-weight:normal; 141 | font-size:12px; 142 | line-height:24px; 143 | text-align:center; 144 | } 145 | 146 | section.content-wrap { 147 | position:absolute; 148 | top:0;bottom:0;left:0;right:0; 149 | padding:80px 0 48px 0; 150 | } 151 | 152 | .content { 153 | position:relative; 154 | height:100%; 155 | border-bottom:1px solid #444; 156 | } 157 | 158 | .content h3 { 159 | font-size:10px; 160 | line-height:24px; 161 | font-weight:bold; 162 | text-align:center; 163 | width:120px; 164 | background-color:#fff; 165 | margin:4px 4px 0 8px; 166 | border-radius:2px 2px 0 0; 167 | } 168 | 169 | .content .left { 170 | width:50%; 171 | height:100%; 172 | float:left; 173 | } 174 | 175 | .content .right { 176 | width:50%; 177 | height:100%; 178 | float:left; 179 | } 180 | 181 | .clearfix { 182 | content:''; 183 | clear:both; 184 | } 185 | 186 | .editor-wrap { 187 | position:relative; 188 | width:100%; 189 | height:100%; 190 | padding-bottom:28px; 191 | } 192 | 193 | #editor, #viewer { 194 | width:100%;height:100%; 195 | font-size:11px; 196 | } 197 | 198 | #modal-background { 199 | position:absolute; 200 | width:100%;height:100%; 201 | background-color:rgba(0,0,0,.5); 202 | z-index:998; 203 | display:none; 204 | } 205 | 206 | #modal-stage { 207 | position:absolute; 208 | width:100%;height:100%; 209 | z-index:999; 210 | display:none; 211 | } 212 | 213 | .dialog { 214 | position:absolute; 215 | padding:20px; 216 | background-color:#555; 217 | border-radius:2px; 218 | border:1px solid #444; 219 | margin:auto; 220 | top:0px;left:0;right:0;bottom:120px; 221 | box-shadow:0 2px 8px 0 rgba(0,0,0,0.25); 222 | color:white; 223 | font-size:12px; 224 | line-height: 160% 225 | } 226 | 227 | .dialog h1 { 228 | font-size:14px; 229 | line-height:20px; 230 | margin-bottom:12px; 231 | } 232 | 233 | .dialog .button-box { 234 | margin:8px 0; 235 | } 236 | 237 | .dialog .button-box button { 238 | float:right; 239 | } 240 | 241 | .dialog button { 242 | margin:0 0 0 8px; 243 | padding:0 8px; 244 | line-height:36px; 245 | font-size:14px; 246 | color:white; 247 | border:none; 248 | background:none; 249 | cursor: pointer; 250 | border-radius: 2px; 251 | transition: background-color 0.5s; 252 | min-width:64px; 253 | } 254 | 255 | .dialog button:hover { 256 | background-color:#3a76ff; 257 | } 258 | 259 | .dialog p { 260 | margin:12px 0; 261 | } 262 | 263 | .dialog p a { 264 | text-decoration: underline; 265 | color:#f0f0f0; 266 | } 267 | 268 | #open-url-dialog { 269 | width:640px; 270 | height:160px; 271 | } 272 | 273 | #open-url-dialog input { 274 | width:100%; 275 | font-size:12px; 276 | padding:4px 8px; 277 | } -------------------------------------------------------------------------------- /docs/doc/property.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Objc2Swift.js - Document 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 28 | 29 | 30 | 31 | 41 | 42 |
43 | 44 | 45 |

Property

46 |

Readonly Property

47 |

The property with readonly attribute is converted to a variable and its setter is set private.

48 |

Objective-C

@interface MyClass 
 49 | @property (readonly) NSString *str;
 50 | @end
 51 | 
 52 | @implementation MyClass
 53 | @end
 54 | 
55 |

Swift

class MyClass {
 56 |     private(set) var str:String!
 57 | }
 58 | 
59 | 60 |

Copy Property

61 |

Objective-C

@interface MyClass 
 62 | @property (copy) NSString *str;
 63 | @end
 64 | 
 65 | @implementation MyClass
 66 | @end
 67 | 
68 |

Swift

class MyClass {
 69 |     @NSCopying var str:String!
 70 | }
 71 | 
72 | 73 |

Private Property

74 |

The property declared in any anonymous category is converted into a private variable. Other properies are treated as public.

75 |

Objective-C

@interface MyClass
 76 | @property int bar;
 77 | @end
 78 | 
 79 | @interface MyClass ()
 80 | @property int baz;
 81 | @end
 82 | 
 83 | @implementation MyClass 
 84 | @end
 85 | 
86 |

Swift

class MyClass { 
 87 |     var bar:Int
 88 |     private var baz:Int
 89 | }
 90 | 
91 | 92 |

Auto-synthesizing Aware

93 |

Objective-C automatically synthesizes instantce variable _foo for @property foo declaration. Objc2swift checks the reference of _foo in any instance method and if so, explicit setter and getter functions are generated for safe. 94 | 95 |

Objective-C

@interface MyClass 
 96 | @property NSString *foo; // This property implicitly generates `_foo`.
 97 | @end
 98 | 
 99 | @implementation MyClass 
100 | -(void)someMethod {
101 | 	NSLog("%@", _foo); // reference of auto-synthesized variable.
102 | }
103 | @end
104 | 
105 |

Swift

class MyClass { 
106 |     private var _foo:String!
107 |     var foo:String! {
108 |         get { return _foo }
109 |         set { _foo = newValue }
110 |     }
111 | 
112 |     func someMethod() {
113 |     	NSLog("%@", _foo) // reference of auto-synthesized variable.
114 |     }
115 | }
116 | 
117 | 118 |

Setter and Getter

119 |

If there are setter and getter code in the class implementation, they are converted into Swift's variables with setter and getter function.

120 | 121 |

Objective-C

@interface MyClass
122 | @property int foo;
123 | @end
124 | 
125 | @implementation MyClass 
126 | -(void)setFoo:(int)value {
127 |     _foo = foo * 2;
128 | }
129 | -(int)foo {
130 |     return _foo / 2;
131 | }
132 | @end
133 | 
134 |

Swift

class MyClass { 
135 |     private var _foo:Int
136 |     var foo:Int {
137 |         get { 
138 |             return _foo / 2
139 |         }
140 |         set(value) { 
141 |             _foo = foo * 2
142 |         }
143 |     }
144 | 
145 |     // `setFoo:` has moved as a setter.
146 |     // `foo` has moved as a getter.
147 | }
148 | 
149 | 150 |
151 | 155 | 161 | 162 | 163 | -------------------------------------------------------------------------------- /docs/js/demo.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | var editor; 4 | 5 | function showError(message) { 6 | document.getElementById("result").value = message; 7 | } 8 | 9 | function doConvert() { 10 | var button = document.getElementById("convert"); 11 | var btnText = button.innerText; 12 | button.innerText = "Wait"; 13 | button.disabled = true; 14 | setTimeout(function(){ 15 | compile(editor.getValue()); 16 | button.innerText = btnText; 17 | button.disabled = false; 18 | },100); 19 | } 20 | 21 | function retrieve(url, complete) { 22 | var baseUrl = url.replace(/^(https?:\/\/)raw.githubusercontent\.com\/([^\/]+\/[^\/]+)\/(.*)$/,'$1github.com/$2/blob/$3').replace(/[^\/]*$/,''); 23 | var rawUrl = url.replace(/^(https?:\/\/)github\.com\/(.*)\/blob\/(.*)$/,'$1raw.githubusercontent.com/$2/$3'); 24 | var start = Date.now(); 25 | var xhr = new XMLHttpRequest(); 26 | xhr.open("GET",rawUrl); 27 | xhr.addEventListener('load',function(){ 28 | if(xhr.status == 200 || xhr.status == 304 || xhr.status == 0) { 29 | initSourceForm(xhr.responseText); 30 | if(complete) complete(); 31 | } else { 32 | var err = new Error("Error:" + xhr.statusText); 33 | initSourceForm(err); 34 | if(complete) complete(err); 35 | } 36 | }); 37 | xhr.addEventListener('error',function(e){ 38 | var err = new Error("Error: Check 'Access-Control-Allow-Origin' header is present for the target resource. See browser's development panel for detail. If you run this script local Chrome, `--allow-file-access-from-files` option is required."); 39 | initSourceForm(err); 40 | if(complete) complete(err); 41 | }); 42 | xhr.send(); 43 | } 44 | 45 | var o2s = new O2S(); 46 | 47 | function initSourceForm(inp) { 48 | if(typeof(inp) == "string") { 49 | editor.setValue(inp,-1); 50 | document.getElementById("result").innerText = ''; 51 | applyHighlight(); 52 | } else { 53 | editor.setValue("// " + inp.message); 54 | document.getElementById("result").innerText = ""; 55 | } 56 | } 57 | 58 | function compile(text) { 59 | console.log(text); 60 | convert(text); 61 | } 62 | 63 | var _SPACER = ' '; 64 | function makeIndentString(indent) { 65 | if(_SPACER.length/g,'>') 26 | } 27 | 28 | if(!opt.options.outdir || opt.argv.length == 0) { 29 | printUsageAndExit(); 30 | } 31 | 32 | if(!fs.existsSync(opt.options.outdir)) { 33 | console.error(opt.options.outdir + " does not exist."); 34 | process.exit(1); 35 | } 36 | 37 | var inputFile = opt.argv[0]; 38 | var outputFile = path.resolve(opt.options.outdir, path.basename(inputFile,'.spec') + '.html'); 39 | 40 | var gaText='', adText=''; 41 | try { 42 | gaText = fs.readFileSync(path.resolve(opt.options.outdir,'_ga.inc'),'utf-8'); 43 | adText = fs.readFileSync(path.resolve(opt.options.outdir,'_ad.inc'),'utf-8'); 44 | } catch(e) { 45 | } 46 | 47 | var text = fs.readFileSync(inputFile,'utf-8'); 48 | var a; 49 | try { 50 | a = parser.parse(text); 51 | } catch(e) { 52 | console.log(e); 53 | console.log(ParserUtil.buildErrorMessage(e,text)); 54 | process.exit(1); 55 | } 56 | 57 | var buf = []; 58 | 59 | buf.push(F2T(DOC_HEAD_TMPL)); 60 | buf.push("

" + a.title + "

\n"); 61 | buf.push(a.desc); 62 | 63 | for(var i=0;i" + spec.title + "\n"); 67 | buf.push(spec.prefix); 68 | buf.push("

Objective-C

"); 69 | buf.push("
" + escapeCode(spec.input) + "
\n"); 70 | buf.push(spec.midfix); 71 | buf.push("

Swift

"); 72 | buf.push("
" + escapeCode(spec.expect) + "
\n"); 73 | buf.push(spec.suffix); 74 | 75 | var output; 76 | try { 77 | output = o2s.convert(spec.input,{quiet:true}); 78 | } catch(e) { 79 | if(e.location) { 80 | output = ParserUtil.buildErrorMessage(e,spec.input); 81 | } else { 82 | output = e.message; 83 | } 84 | } 85 | 86 | if(spec.expect != output) { 87 | 88 | console.log("FAIL: '" + spec.title + "'' in " + inputFile); 89 | 90 | buf.push("

Test Failed

"); 91 | var diff = jsdiff.diffChars(spec.expect,output); 92 | buf.push("
"); 93 | buf.push("
"); 94 | buf.push("Diff"); 95 | buf.push("Raw"); 96 | buf.push("
\n"); 97 | buf.push("
\n") 98 | buf.push("
" + output + "
\n"); 99 | var code = []; 100 | diff.forEach(function(part){ 101 | if(part.added) { 102 | code.push("" + escapeCode(part.value) + ""); 103 | } else if(part.removed) { 104 | code.push("" + escapeCode(part.value) + ""); 105 | } else { 106 | code.push(escapeCode(part.value)); 107 | } 108 | }); 109 | buf.push("
" + code.join('') + "
\n"); 110 | buf.push("
\n"); 111 | buf.push("
"); 112 | } 113 | 114 | buf.push("\n"); 115 | 116 | } 117 | 118 | buf.push(F2T(DOC_FOOT_TMPL)); 119 | 120 | var outText = buf.join('') 121 | .replace('', adText) 122 | .replace('', gaText); 123 | 124 | fs.writeFileSync(outputFile,outText,'utf-8'); 125 | 126 | function F2T(f){return f.toString().replace(/^function.*?\n|\n?\*\/\}$/g,'');} 127 | 128 | function DOC_HEAD_TMPL(){/* 129 | 130 | 131 | 132 | Objc2Swift.js - Document 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 156 | 157 | 158 | 159 | 169 | 170 |
171 | 172 | 173 | */} 174 | 175 | function DOC_FOOT_TMPL(){/* 176 |
177 | 178 | 179 | 180 | */}; -------------------------------------------------------------------------------- /src/decl-info.js: -------------------------------------------------------------------------------- 1 | module.exports = (function(){ 2 | "use strict" 3 | 4 | var util = require('util'); 5 | var ASTUtil = require('./ast-util'); 6 | var TypeInfo = require('./type-info'); 7 | 8 | /** 9 | * DeclInfo is intended to be (de)serializable as pure JSON. 10 | * JS prototype chain is not used to define the DeclInfo and its derived classes. 11 | * 12 | * Thus do not use `obj instanceof DeclInfo`. Use `obj.kind == DeclInfo.KIND_XXXX` instead. 13 | */ 14 | var DeclInfo = function(kind) { 15 | this.kind = kind; 16 | }; 17 | 18 | DeclInfo.KIND_CLASS = "decl:class"; 19 | DeclInfo.KIND_METHOD = "decl:method"; 20 | DeclInfo.KIND_FUNC_PARAM = "decl:func_param"; 21 | DeclInfo.KIND_METHOD_PARAM = "decl:method_param"; 22 | DeclInfo.KIND_FUNC = "decl:func"; 23 | DeclInfo.KIND_PROTOCOL = "decl:protocol"; 24 | DeclInfo.KIND_DECLARATION = "decl:declaration"; 25 | DeclInfo.KIND_TYPEDEF = "decl:typedef"; 26 | DeclInfo.KIND_PROPERTY = "decl:property"; 27 | DeclInfo.KIND_ENUM = "decl:enum"; 28 | DeclInfo.KIND_ENUM_ITEM = "decl:enum_item"; 29 | 30 | var _createObjectsFromEnumSpecifier = function(enumType, spec) { 31 | var result = []; 32 | for(var i=0;i 2 | 3 | 4 | Objc2Swift.js - Document 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 28 | 29 | 30 | 31 | 41 | 42 |
43 | 44 | 45 |

Statement

46 |

If Statement

47 |

Objective-C

@implementation Foo
 48 | -(NSString *)method {
 49 |     if(Condition) {
 50 |         return @"OK";
 51 |     } else {
 52 |         return @"NG";
 53 |     }
 54 | }
 55 | @end
 56 | 
57 |

Swift

class Foo {
 58 |     func method() -> String! {
 59 |         if Condition {
 60 |             return "OK"
 61 |         } else {
 62 |             return "NG"
 63 |         }
 64 |     }
 65 | }
 66 | 
67 | 68 |

Switch Statement

69 |

Objective-C

@implementation Foo
 70 | -(NSString *)method {
 71 |     switch(Condition) {
 72 |         case Apple:
 73 |             return @"apple";
 74 |         case Orange:
 75 |             return @"orange";
 76 |         default:
 77 |             return @"else";    
 78 |     }
 79 | }
 80 | @end
 81 | 
82 |

Swift

class Foo {
 83 |     func method() -> String! {
 84 |         switch(Condition) { 
 85 |             case Apple:
 86 |                 return "apple"
 87 |             case Orange:
 88 |                 return "orange"
 89 |             default:
 90 |                 return "else"    
 91 |         }
 92 |     }
 93 | }
 94 | 
95 | 96 |

Switch Statement (sequential cases)

97 |

Objective-C

@implementation Foo
 98 | -(NSString *)method {
 99 |     switch(Condition) {
100 |         case Apple:
101 |         case Orange:
102 |             return @"fruit";
103 |         default:
104 |             return @"else";
105 |     }
106 | }
107 | @end
108 | 
109 |

Swift

class Foo {
110 |     func method() -> String! {
111 |         switch(Condition) { 
112 |             case Apple,
113 |                  Orange:
114 |                 return "fruit"
115 |             default:
116 |                 return "else"
117 |         }
118 |     }
119 | }
120 | 
121 | 122 |

For Statement

123 |

Objective-C

@implementation Foo
124 | -(void)method {
125 |     for(int i=0;i<100;i++) {
126 |         NSLog("%d",i);
127 |     }
128 | }
129 | @end
130 | 
131 |

Swift

class Foo {
132 |     func method() {
133 |         for var i:Int=0 ; i<100 ; i++ {  
134 |             NSLog("%d",i)
135 |          }
136 |     }
137 | }
138 | 
139 | 140 |

For-In Statement

141 |

Objective-C

@implementation Foo
142 | -(void)method {
143 |     for(int bar in Foo) {
144 |         NSLog("%d",bar);
145 |     }
146 | }
147 | @end
148 | 
149 |

Swift

class Foo {
150 |     func method() {
151 |         for bar:Int in Foo {  
152 |             NSLog("%d",bar)
153 |          }
154 |     }
155 | }
156 | 
157 | 158 |

While Statement

159 |

Objective-C

@implementation Foo
160 | -(void)method {
161 |     while(flag) {
162 |         // Do Something
163 |     }
164 | }
165 | @end
166 | 
167 |

Swift

class Foo {
168 |     func method() {
169 |         while flag {
170 |             // Do Something
171 |         }
172 |     }
173 | }
174 | 
175 | 176 |

Do-White Statement

177 |

Objective-C

@implementation Foo
178 | -(void)method {
179 |     do {
180 |         // Do Something
181 |     } while(flag);
182 | }
183 | @end
184 | 
185 |

Swift

class Foo {
186 |     func method() {
187 |         repeat {
188 |             // Do Something
189 |         } while flag
190 |     }
191 | }
192 | 
193 | 194 |

Try-Catch Statment

195 |

Since Swift's try-catch statement has different semantics from Objective-C's, @try, @catch and @finally keywords are not be converted. 196 |

197 |

Objective-C

@implementation Foo
198 | -(void)method {
199 |     @try {
200 |         // ...
201 |     }
202 |     @catch(Error *err) {
203 |     	// ...
204 |     }
205 |     @finally {
206 |     	// ...
207 |     }
208 | }
209 | @end
210 | 
211 |

Swift

class Foo {
212 |     func method() {
213 |         @try {
214 |             // ...
215 |         }
216 |         @catch(err:Error!) {
217 |         	// ...
218 |         }
219 |         @finally {
220 |         	// ...
221 |         }
222 |     }
223 | }
224 | 
225 | 226 |

Jump and Labeled Statement

227 |

228 |

Objective-C

@implementation Foo
229 | -(void)method {
230 |     for(int i = 0; i < 100; i++) {
231 |         if (i == 50) {
232 |             goto bar;
233 |         }
234 |     }
235 |     bar:
236 |         return;
237 | }
238 | @end
239 | 
240 |

Swift

class Foo {
241 |     func method() {
242 |         for var i:Int=0 ; i < 100 ; i++ {  
243 |             if i == 50 {
244 |                 goto  bar
245 |             }
246 |          }
247 |         bar:
248 |             return
249 |     }
250 | }
251 | 
252 | 253 |
254 | 258 | 264 | 265 | 266 | -------------------------------------------------------------------------------- /bin/cmd.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | "use strict" 3 | function requireIfExist(m){try{return require(m);}catch(e){return null;}} 4 | 5 | var fs = require('fs-extra'); 6 | var util = require('util'); 7 | var path = require('path'); 8 | var Getopt = require('node-getopt'); 9 | var Objc2Swift = require('../src/main'); 10 | var ImportResolver = require('../src/import-resolver'); 11 | var Tracer = requireIfExist('pegjs-backtrace'); 12 | 13 | if((process.platform != 'darwin') && (process.platform != 'linux')) { 14 | console.error("`" + process.platform + "` platform is not supported."); 15 | process.exit(1); 16 | } 17 | 18 | function getUserHome() { 19 | return (process.env.HOME||process.env.USERPROFILE); 20 | } 21 | 22 | function prependItems(items, newItems) { 23 | if(Array.isArray(newItems)) { 24 | newItems.reverse().forEach(function(e) { 25 | var idx = items.indexOf(e); 26 | if(0<=idx) { 27 | items.splice(idx,1); 28 | } 29 | items.unshift(e); 30 | }); 31 | } 32 | } 33 | 34 | function resolveMetaPathString(pathList) { 35 | return pathList.map(function(e){ 36 | return e.replace(/\$\{Xcode\.app\}/i,xcode) 37 | .replace("~/",getUserHome()+"/"); 38 | }); 39 | } 40 | 41 | function loadConfig(config, file, skipIfNotExist) { 42 | try { 43 | if(!fs.existsSync(file)) { 44 | if(skipIfNotExist) return true; 45 | throw new Error("File Not Found: " + file); 46 | } 47 | var json = fs.readFileSync(file,'utf-8'); 48 | var data = JSON.parse(json); 49 | 50 | if(Array.isArray(data.systemIncludePaths)) { 51 | prependItems(config.systemIncludePaths, data.systemIncludePaths); 52 | } else { 53 | throw new Error("systemIncludePaths is not defined."); 54 | } 55 | if(Array.isArray(data.includePaths)) { 56 | prependItems(config.includePaths, data.includePaths); 57 | } else { 58 | throw new Error("includePaths is not defined."); 59 | } 60 | } catch(e) { 61 | console.error('Failed to load: ' + file); 62 | console.error(e); 63 | process.exit(1); 64 | } 65 | } 66 | 67 | var optdefs = [ 68 | ['o','output=FILE','specifiy output filename.'], 69 | ['','init=ios|osx|tvos|watchos|none','Create config for the specified platform.'], 70 | ['','xcode=PATH','Specify Xcode.app path. [default:/Applications/Xcode.app]'], 71 | ['c','config=PATH','Load config from PATH. [default:~/.objc2swift/config]'], 72 | ['h','help','show this help.'], 73 | ['','isystem=PATH+','Add PATH to system include search path.'], 74 | ['I','include=PATH+','Add PATH to include search path.'], 75 | ['','optional-chaining','Use optional-chaining instead of forced-unwrapping.'], 76 | ['','skip-header','Skip to import all headers.'], 77 | ['','show-missing-header','Print out missing header file names.'], 78 | ['','show-include-path','Print out include path settings.'], 79 | ['','verbose','Show verbose messages.'], 80 | ['','quiet','Suppress trace messages.'], 81 | ['','no-cache','Do not generate pre-compiled header cache.'], 82 | ['','clear-cache','Clear the cached pre-compiled headers.'], 83 | ['S','preprocess-only','Only run preprocess step.'], 84 | ['','parse-only','Do not generate swift source.'], 85 | ['','stacktrace','Print out JS stack trace when converter fails.'], 86 | ['','inline-dump','Generate type analysis information as comment to Swift source inline.'], 87 | ['q','query=CLASS','Print out type information of the specified class to console.'], 88 | ['a','ast','Print out the internal syntax tree to console.'], 89 | ]; 90 | 91 | if(Tracer) { 92 | if(Objc2Swift.canUseTracer()) { 93 | optdefs = optdefs.concat([ 94 | ['b','backtrace','dump back trace after parse.'], 95 | ['t','tree','dump full parse tree.'], 96 | ['','trace','show parser trace.'], 97 | ]); 98 | } 99 | } 100 | 101 | var getopt = new Getopt(optdefs).bindHelp(); 102 | getopt.setHelp("\nUsage: objc2swift [OPTION] file\n\nOptions:\n[[OPTIONS]]\n"); 103 | var opt = getopt.parseSystem(); 104 | 105 | function printUsageAndExit() { 106 | getopt.showHelp(); 107 | process.exit(0); 108 | } 109 | 110 | // 111 | // Prepare Cache 112 | // 113 | if(opt.options['clear-cache']) { 114 | var dir = ImportResolver.getCacheRoot(); 115 | if(!/\.objc2swift/.test(dir)) { 116 | console.error("Invalid cache directory: " + dir); 117 | process.exit(1); 118 | } 119 | fs.removeSync(dir); 120 | console.log("Removed: " + dir); 121 | if(opt.argv.length==0) { 122 | process.exit(0); 123 | } 124 | } 125 | fs.mkdirsSync(ImportResolver.getCacheDir()); 126 | 127 | // 128 | // Load or Setup configuration 129 | // 130 | var xcode = opt.options.xcode||"/Applications/Xcode.app"; 131 | var configFile = opt.options.config || getUserHome() + "/.objc2swift/config.json"; 132 | var config; 133 | 134 | function makeConfig(platform) { 135 | var config = { 136 | systemIncludePaths:[], 137 | includePaths:[], 138 | }; 139 | var systemPathsMap = JSON.parse(fs.readFileSync(__dirname+"/sdk-path.json",'utf-8')); 140 | prependItems(config.systemIncludePaths, systemPathsMap[platform]); 141 | return config; 142 | } 143 | 144 | function addIncludePaths(config, systemIncludePaths, includePaths) { 145 | if(systemIncludePaths) { 146 | prependItems(config.systemIncludePaths, systemIncludePaths); 147 | } 148 | if(includePaths) { 149 | prependItems(config.includePaths, includePaths); 150 | } 151 | } 152 | 153 | if(opt.options.init) { 154 | config = makeConfig(opt.options.init); 155 | addIncludePaths(config, opt.options.isystem, opt.options.include); 156 | fs.writeFileSync(configFile, JSON.stringify(config,null," "), 'utf-8'); 157 | console.log("Generated: " + configFile); 158 | process.exit(0); 159 | } 160 | 161 | if(fs.existsSync(configFile)) { 162 | config = makeConfig("none"); 163 | loadConfig(config, configFile, true); 164 | addIncludePaths(config, opt.options.isystem, opt.options.include); 165 | } else { 166 | console.log("Warning: " + configFile + " does not exist. The default `ios` platform configuration is applied.\n`--init` option can be used to generate the config file."); 167 | config = makeConfig("ios"); 168 | addIncludePaths(config, opt.options.isystem, opt.options.include); 169 | } 170 | 171 | if(opt.argv.length!=0) { 172 | config.includePaths.unshift(path.dirname(path.resolve(opt.argv[0]))); 173 | } 174 | 175 | if(opt.options['show-include-path']) { 176 | resolveMetaPathString(config.systemIncludePaths).forEach(function(e){console.log("[System] " + e);}); 177 | resolveMetaPathString(config.includePaths).forEach(function(e){console.log(e);}); 178 | if(opt.argv.length == 0) process.exit(0); 179 | } 180 | 181 | if(opt.argv.length == 0) { 182 | printUsageAndExit(); 183 | } 184 | 185 | // 186 | // Setup options 187 | // 188 | var options = { 189 | ast:opt.options['ast'], 190 | skipHeader:opt.options['skip-header'], 191 | forceUnwrap:!opt.options['optional-chaining'], 192 | inlineDump:opt.options['inline-dump'], 193 | query:opt.options['query'], 194 | verbose:opt.options.verbose, 195 | sourcePath:path.resolve(opt.argv[0]), 196 | cacheHeader:!opt.options['no-cache'], 197 | platform:opt.options.platform, 198 | quiet:opt.options.quiet, 199 | showMissingHeader:opt.options['show-missing-header'], 200 | }; 201 | 202 | options.importResolver = new ImportResolver({ 203 | frameworkPath:resolveMetaPathString(config.systemIncludePaths), 204 | includePath:resolveMetaPathString(config.includePaths), 205 | verbose:options.verbose 206 | }); 207 | 208 | // 209 | // Load source file 210 | // 211 | var source; 212 | try { 213 | source = fs.readFileSync(options.sourcePath,'utf-8'); 214 | } catch(e) { 215 | console.error(e.message); 216 | process.exit(1); 217 | } 218 | 219 | // 220 | // Setup tracer if available 221 | // 222 | var tracer; 223 | if(opt.options.tree||opt.options.backtrace||opt.options.trace) { 224 | tracer = new Tracer(source,{hiddenPaths:['Identifier/.*','__','.*Token'],verbose:true, showTrace:opt.options.trace||false}); 225 | options.tracer = tracer; 226 | } else { 227 | options.tracer = {trace:function(){}}; 228 | } 229 | 230 | // 231 | // Parse source 232 | // 233 | var o2s = new Objc2Swift(); 234 | 235 | if(opt.options['preprocess-only']) { 236 | console.log(o2s.preprocessOnly(source)); 237 | process.exit(0); 238 | } 239 | 240 | var ast; 241 | try { 242 | ast = o2s.parse(source, options); 243 | if(opt.options.tree) { 244 | console.log(tracer.getParseTreeString()); 245 | } 246 | } catch(e) { 247 | console.log(o2s.buildErrorMessage(e, source, opt.argv[0])); 248 | if(opt.options.stacktrace) { console.log(e.stack); } 249 | if(opt.options.backtrace) { console.log(tracer.getBacktraceString()); } 250 | process.exit(1); 251 | } 252 | 253 | if(opt.options["parse-only"]||opt.options['query']||opt.options['ast']) { 254 | process.exit(0); 255 | } 256 | 257 | // 258 | // Generate Swift code 259 | // 260 | 261 | var swift = o2s.generate(ast, options); 262 | 263 | var outfile = opt.options['output'] || 264 | path.basename(options.sourcePath).replace(/\.m$/,'') + ".swift"; 265 | 266 | if(outfile == '-' || outfile == '/dev/stdout') { 267 | console.info(swift); 268 | } else if(outfile == '/dev/stderr') { 269 | console.error(swift); 270 | } else { 271 | fs.writeFileSync(outfile,swift,'utf-8'); 272 | if(!opt.options.quiet) { 273 | console.error("Output : " + outfile); 274 | } 275 | } 276 | -------------------------------------------------------------------------------- /src/preprocessor.js: -------------------------------------------------------------------------------- 1 | module.exports = (function() { 2 | "use strict" 3 | 4 | var cpp = require('./preprocess-parser'); 5 | var parser = require('./parser'); 6 | var TypeAnalyzer = require('./type-analyzer'); 7 | var DeclInfo = require('./decl-info'); 8 | var ParserUtil = require('./parser-util'); 9 | 10 | var MACRO_MAP = { 11 | "CA_EXTERN":"extern", 12 | "CF_AVAILABLE":"", 13 | "CF_BRIDGED_MUTABLE_TYPE":"", 14 | "CF_BRIDGED_TYPE":"", 15 | "CF_CONSUMED":"", 16 | "CF_ENUM_AVAILABLE":"", 17 | "CF_EXPORT":"", 18 | "CF_EXTERN_C_BEGIN":"", 19 | "CF_EXTERN_C_END":"", 20 | "CF_RETURNS_NOT_RETAINED":"", 21 | "FOUNDATION_EXPORT":"", 22 | "NS_AUTOMATED_REFCOUNT_UNAVAILABLE":"", 23 | "NS_AVAILABLE":"", 24 | "NS_AVAILABLE_IOS":"", 25 | "NS_AVAILABLE_MAC":"", 26 | "NS_CALENDAR_DEPRECATED_MAC":"", 27 | "NS_CLASS_AVAILABLE":"", 28 | "NS_CLASS_AVAILABLE_IOS":"", 29 | "NS_CLASS_DEPRECATED":"", 30 | "NS_CLASS_DEPRECATED_IOS":"", 31 | "NS_DEPRECATED":"", 32 | "NS_DEPRECATED_IOS":"", 33 | "NS_DEPRECATED_MAC":"", 34 | "NS_DESIGNATED_INITIALIZER":"__attribute__((objc_designated_initializer))", 35 | "NS_ENUM_AVAILABLE":"", 36 | "NS_ENUM_AVAILABLE_IOS":"", 37 | "NS_ENUM_DEPRECATED":"", 38 | "NS_ENUM_DEPRECATED_IOS":"", 39 | "NS_EXTENSION_UNAVAILABLE_IOS":"", 40 | "NS_FORMAT_ARGUMENT":"", 41 | "NS_FORMAT_FUNCTION":"", 42 | "NS_INLINE":"", 43 | "NS_REPLACES_RECEIVER":"", 44 | "NS_REQUIRES_NIL_TERMINATION":"", 45 | "NS_REQUIRES_PROPERTY_DEFINITIONS":"", // NSManagedObject.h 46 | "NS_REQUIRES_SUPER":"", 47 | "NS_RETURNS_INNER_POINTER":"", 48 | "NS_SWIFT_UNAVAILABLE":"", 49 | "NS_UNAVAILABLE":"", 50 | "OBJC_ARC_UNAVAILABLE":"", 51 | "OBJC_EXPORT":"", 52 | "OBJC_ISA_AVAILABILITY":"", 53 | "OBJC_ROOT_CLASS":"", 54 | "OBJC_SWIFT_UNAVAILABLE":"", 55 | "UIKIT_AVAILABLE_TVOS_ONLY":"", 56 | "UIKIT_CLASS_AVAILABLE_IOS_TVOS":"", 57 | "UI_APPEARANCE_SELECTOR":"", 58 | "UNAVAILABLE_ATTRIBUTE":"", 59 | "_MAC":"", 60 | "_STRUCT_SIGALTSTACK":"struct __darwin_sigaltstack", //_sigaltstack.h 61 | "_STRUCT_TIMESPEC":"struct timespec", // _timespec.h 62 | "_STRUCT_TIMEVAL":"struct timeval", // _timeval.h 63 | "_STRUCT_UCONTEXT":"struct __darwin_ucontext", // _ucontext.h 64 | "__BEGIN_DECLS":"", 65 | "__DARWIN_1050":"", 66 | "__DARWIN_ALIAS":"", 67 | "__DARWIN_ALIAS_C":"", 68 | "__DARWIN_ALIAS_STARTING":"", 69 | "__DARWIN_EXTSN":"", 70 | "__END_DECLS":"", 71 | "__OSX_AVAILABLE_BUT_DEPRECATED":"", 72 | "__OSX_AVAILABLE_BUT_DEPRECATED_MSG":"", 73 | "__OSX_AVAILABLE_STARTING":"", 74 | "__POSIX_C_DEPRECATED":"", // strings.h 75 | "__TVOS_PROHIBITED":"", 76 | "__WATCHOS_PROHIBITED":"", 77 | "__const":"const", 78 | "__dead":"", 79 | "__dead2":"", 80 | "__deprecated":"__attribute__((deprecated))", 81 | "__deprecated_msg":"", // _stdio.h 82 | "__printflike":"", // _stdio.h 83 | "__pure2":"", 84 | "__restrict":"restrict", 85 | "__scanflike":"", // _stdio.h 86 | "__strftimelike":"", // _time.h 87 | "__unavailable":"__attribute__((unavailable))", 88 | "__unused":"__attribute__((deprecated))", 89 | "__used":"__attribute__((used))", 90 | "NS_DURING":"@try {", 91 | "NS_HANDLER":"} @catch (NSException * localException) {", 92 | "NS_ENDHANDLER":"}", 93 | 94 | }; 95 | 96 | function expand_NS_ENUM(type,name) { 97 | if(name) { 98 | // NS_ENUM(T,N) := enum N:T N;enum N:T 99 | return "enum " + name + ":" + type + " " + name + ";enum " + name + ":" + type; 100 | } else { 101 | // NS_ENUM(T) := enum : T; 102 | return "enum : " + type; 103 | } 104 | }; 105 | 106 | function expand(name, argv) { 107 | if(name == "NS_ENUM" || name == "NS_OPTIONS" || name == "CF_ENUM" || name == "CF_OPTIONS") { 108 | return expand_NS_ENUM(argv[0],argv[4]); 109 | } 110 | return MACRO_MAP[name]; 111 | }; 112 | 113 | function normalizeSource(source) { 114 | return source.replace(/\r\n/g,'\n'); 115 | } 116 | 117 | var PreProcessor = function(options) { 118 | this.options = options||{}; 119 | this.importResolver = this.options.importResolver; 120 | this.headerInfoMap = {}; 121 | }; 122 | 123 | PreProcessor.prototype.enumerateHeaders = function(source, from) { 124 | var exp = /^ *# *(?:import|include)\s*([<"])(.*?)[>"]/gm, m, result = []; 125 | while(m = exp.exec(source)) { 126 | var file = this.importResolver.findHeaderPath(m[2],m[1]=="<"); 127 | if(file) { 128 | result.push(file) 129 | } else { 130 | if(this.options.showMissingHeader) { 131 | console.log("[debug] " + m[2] + " is not found" + (from?(" from "+from):".")); 132 | } 133 | } 134 | } 135 | return result; 136 | }; 137 | 138 | var addDeclInfo = function(headerInfo, declInfo) { 139 | if(declInfo.kind != DeclInfo.KIND_PROTOCOL) { 140 | headerInfo.declInfoMap[declInfo.name] = declInfo; 141 | } else { 142 | headerInfo.declInfoMap["@protocol " + declInfo.name] = declInfo; 143 | } 144 | }; 145 | 146 | PreProcessor.prototype.printStatus = function(name, value) { 147 | if(!this.options.quiet) { 148 | if(this.options.verbose) { 149 | process.stderr.write("\x1b[K"); 150 | console.log(name + (value?" : " + value:"")); 151 | } else { 152 | process.stderr.write("\x1b[K" + name + (value?" : " + value:"") + "\x1b[G"); 153 | } 154 | } 155 | }; 156 | 157 | PreProcessor.prototype.processHeader = function(headerPath) { 158 | 159 | var headerName = headerPath.replace(/^.*\/(.*)$/,'$1'); 160 | 161 | var headerInfo = this.headerInfoMap[headerPath]; 162 | if(headerInfo) return headerInfo; 163 | 164 | headerInfo = { 165 | path:headerPath, 166 | name:headerName, 167 | declInfoMap:{}, 168 | children:[], 169 | }; 170 | this.headerInfoMap[headerPath] = headerInfo; 171 | 172 | this.printStatus(headerName); 173 | 174 | var source = this.importResolver.loadFile(headerPath); 175 | source = normalizeSource(source); 176 | 177 | var targets = this.enumerateHeaders(source, headerPath); 178 | 179 | for(var i=0;i