Many had expected Swift to be more an Objective-C 3.0 than it turned out to be. But what could we have expected such a hypothetical language to look like?
Before Swift
Possibly unknown to many developers is that once upon a time, when NeXT was brought into to create what eventually would be OS X, there was an attempt to give ObjC a “Modern” syntax (read: more Java-like).
It had
object = [[MyClass alloc] init]; [object doSomething:foo withBar:bar];
Turning into:
object = (MyClass.alloc).init; object.doSomething(foo, bar);
I should admit I’m a bit unsure of the exact syntax, the ObjC-Java bridge would have you use object.doSomethingWithBar(foo, bar), so that might be more accurate.
The idea was to attract developers that might be put off by the unfamiliar Objective-C syntax (does this sound familiar?).
Swift goes for a similar dot.functionName(… params …) syntax, but luckily recognises the need for parameter names. I think it’s probably the best compromise this side of the divide. (The other way would have been to drop the brackets and move the syntax closer to Smalltalk. Many of the alternatives mentioned in my previous post did exactly that.)
Transforming ObjC in a Swift manner
What interests me would be what Swift could have looked like if it had been an Objective-C 3.0 instead of a whole new language.
I’m going to start with a sample in Objective-C, transform it piece-by-piece and see what it would look like.
Here’s the code we’re going to start with.
@interface Foo
- (instancetype)initWithBar:(Bar *)bar;
- (void)iron:(NSString *)type using:(Tool *)tool;
@property (nonatomic, weak) id delegate;
@end
// ---
@interface Foo ()
@property (nonatomic, strong) NSString *name;
@end
@implementation Foo
{
Bar *_bar;
}
- (instancetype)initWithBar:(Bar *)bar {
if ((self = [super init])) {
_bar = bar;
}
return self;
}
- (void)iron:(NSString *)type using:(Tool *)tool {
if ([_bar wield:type using:tool]) {
NSLog(@"Horray %@", self.name);
}
}
@end
We’re ready to start.
Swift-style method calls
We start by transforming the Objective-C message passing syntax of [foo doSomething:bar] into foo.doSomething(bar) just like Swift does, but we’re holding off putting the variable declaration last:
@interface Foo
- (instancetype)initWithBar(Bar *bar);
- (void)iron(NSString *type, using: Tool *tool);
@property (nonatomic, weak) id delegate;
@end
// ---
@interface Foo ()
@property (nonatomic, strong) NSString *name;
@end
@implementation Foo
{
Bar *_bar;
}
- (instancetype)initWithBar(Bar *bar) {
if ((self = super.init())) {
_bar = bar;
}
return self;
}
- (void)iron(NSString *type, using:Tool *tool) {
if (_bar.wield(type, using:tool)) {
NSLog(@"Horray %@", self.name);
}
}
@end
Defining variables
Next up, let’s allow defining variables in the implementation directly, we’re going to borrow the syntax a from Eero. This is how Eero does it:
String name {readonly}
And our code afterwards:
@interface Foo
- (instancetype)initWithBar(Bar *bar);
- (void)iron(NSString *type, using: Tool *tool);
id delegate {weak};
@end
// ---
@implementation Foo
Bar *_bar;
NSString *name;
- (instancetype)initWithBar(Bar *bar) {
if ((self = super.init())) {
_bar = bar;
}
return self;
}
- (void)iron(NSString *type, using:Tool *tool) {
if (_bar.wield(type, using:tool)) {
NSLog(@"Horray %@", self.name);
}
}
@end
We’ve skimmed over one detail though. For the ObjC original we get to choose if we generate accessors or not. In our example, _bar was an ivar without any accessors at all.
At this point we need to decide what’s a reasonable default:
- Create getter/setter by default.
- Create getter/setter only by annotation.
// Alternative 1
Bar *_bar {noaccessor};
NSString *name;
// Alternative 2
Bar *_bar;
NSString *name {readwrite};
I’m going to assume that the former is the best alternative, although it could be argued that the opposite is more reasonable. Note that non-atomic rather than atomic is considered the default (unlike normal ObjC).
Eliminating the *
Since classes are never statically allocated, we can actually decide to drop the pointer symbol. Our code now looks like this:
@interface Foo
- (instancetype)initWithBar(Bar bar);
- (void)iron(NSString type, using: Tool tool);
id delegate {weak};
@end
// ---
@implementation Foo
Bar _bar {noaccessor};
NSString name;
- (instancetype)initWithBar(Bar bar) {
if ((self = super.init())) {
_bar = bar;
}
return self;
}
- (void)iron(NSString type, using:Tool tool) {
if (_bar.wield(type, using:tool)) {
NSLog(@"Horray %@", self.name);
}
}
@end
Default values
Nothing prevents us from adding default values as syntactic sugar. If we imagine the function doSomething:(Foo *)foo using:(Bar *)bar, we could imagine the compiler generating a doSomething:(Foo *)foo which would call the former method with a default value for the second parameter. This would correspond to Swift’s default parameters:
@interface Foo
- (instancetype)initWithBar(Bar bar);
- (void)iron(NSString type, [using: Tool tool]);
id delegate {weak};
@end
// ---
@implementation Foo
Bar _bar {noaccessor};
NSString name;
- (instancetype)initWithBar(Bar bar) {
if ((self = super.init())) {
_bar = bar;
}
return self;
}
- (void)iron(NSString type, using:Tool tool = Shovel.new() ) {
if (_bar.wield(type, using:tool)) {
NSLog(@"Horray %@", self.name);
}
}
@end
Swapping @ for braces
Swift uses braces to enclose the class definition, we can do the same and drop the @ prefix. At the same time we can let “” default to NSString type strings an c”” to represent c style strings:
interface Foo
{
- (instancetype)initWithBar(Bar bar);
- (void)iron(NSString type, [using: Tool tool]);
id delegate {weak};
}
// ---
implementation Foo
{
Bar _bar {noaccessor};
NSString name;
- (instancetype)initWithBar(Bar bar) {
if ((self = super.init())) {
_bar = bar;
}
return self;
}
- (void)iron(NSString type, using:Tool tool = Shovel.new() ) {
if (_bar.wield(type, using:tool)) {
NSLog("Horray %@", self.name);
}
}
}
Some more tweaks
if ((self = [super init])) is such a common pattern that we could make special syntax and behaviour for init methods. Let’s make init methods special by a) removing -(instancetype), b) implicitly transforming super.initXXXX() into if (!(self = super.initXXXX())) return nil; and returning self by default.
In other words this:
- (instancetype)initWithBar(Bar bar) {
if ((self = super.init())) {
_bar = bar;
}
return self;
}
Becomes:
initWithBar(Bar bar) {
super.init();
_bar = bar;
}
Furthermore, let us replace -(MyClass) with MyClass, and +(MyClass) with class MyClass. This is the result:
interface Foo
{
initWithBar(Bar bar);
void iron(NSString type, [using: Tool tool]);
id delegate {weak};
}
// ---
implementation Foo
{
Bar _bar {noaccessor};
NSString name;
initWithBar(Bar bar) {
super.init();
_bar = bar;
return self;
}
void iron(NSString type, using:Tool tool = Shovel.new() ) {
if (_bar.wield(type, using:tool)) {
NSLog("Horray %@", self.name);
}
}
}
We can compare this with the corresponding Swift:
class Foo
{
private let bar: Bar
weak var delegate: AnyObject?
private name : NSString?
public init(bar: Bar)
{
self.bar = bar
super.init()
}
public iron(type: NSString, using tool: Tool = new Shovel()) {
if _bar.wield(type, using:tool) {
NSLog("Horray %@", self.name ?? "")
}
}
}
Our final version retains the header, which we obviously could fold into our code as well, and we haven’t adopted Swift’s type-after-variable declarations, but otherwise they are fairly similar.
What we’ve done here is simply adopting the Swift syntax for Objective-C. Nothing forces us to stop there. All the languages mentioned in my previous blog entry go further, adding reasonable extensions that easily could fit into an Objective-C 3.0.
Lots of the initial buzz around Swift was how it was going to make development simpler than ObjC. With some syntactic changes, we could have an ObjNext with a syntax like Swift, but a runtime model like Objective-C.
The question is what really made people think Objective-C was difficult – was it the old school syntax, or was its lack of strong static typing? – Because Swift is betting on the latter.
What do you think?
I think people who never really tried Objective-C thought it was difficult due to the odd syntax. For those who actually tried it I don’t think syntax was an issue. But even as a long term Objective-C fan I’d say something had to change. While Objective-C was pretty good for UI development it was e.g. too cumbersome to write an algorithm that needed to work correctly. Lack of generics, lax typing etc made it difficult to do. But on a similar not I think Swift shows some problems for pure UI style development. When you are putting together losely coupled parts such a strict language as Swift does cause a bit of annoyance. Still overal I think Swift is clearly worth it. I think Swift will mean less bugs and more correct programs.
LikeLiked by 1 person
For me it was definitely the odd syntax. I think Apple could have done 4 things to make obj-c more palatable for devs.
1 – adopt dot notation syntax for both methods and properties
2 – implement better language constructs i.e. static typing, generics, etc
3 – do away with header files/interface sections and use public, private, internal keywords
4 – make property declarations more straight-forward. The whole ivar-synthesize thing is a bit confusing. In other languages I can understand property syntax/declarations in about 30 seconds but I’m still confused as to how they work with obj-c (granted my only experience with obj-c is a few intro tutorials)
But as someone coming from a .Net background these 4 changes would’ve gone an extremely long way in making obj-c easier for me to learn and use.
LikeLike