填平macOS和iOS之间的鸿沟
tags:开发随笔
mac and iOS虽然mac存在了很多年之后才有了iOS,但是对很多程序员而言,可能是先熟悉了 UIKit之后才去熟悉AppKit。
我自己也是这样。在开发了MarkNote之后,我想把MarkNote的笔记体验延伸到mac。
有没有工具或者库可以直接将iOS应用转换为macOS应用
最初我只是想让iOS版本的MarkNote照搬过来,于是我想看看是否有捷径可走。最理想的情况下,有一个工具,直接把iOS上的代码转换一下,就可以在mac下运行。或者直接在mac上给iOS应用提供一个runtime?
查了一下之后,发现真有人有类似的想法。
- chameleon将UIKit移植到OS X。然而,这个库只实现了UIKit中的大部分API。更可惜的是,这个库三年前就停止更新了。
- UMEKit也做了类似的事,然而,实现的更少。而且,7年前就停了。
这些库之所以夭折,在很大程度上是因为其复杂程度远远超过看起来的那样。桌面应用和移动应用的机制有很大的不同。虽然UIKit和AppKit共享了很多概念,但是API依然有很大的不同。比如UIView和 NSView虽然看起来很像,但是实现细节和使用上有很多不同。
MarkNote的macOS实现策略
虽然Chameleon看起来很有意思,但是我最终没有采用这个方案。一来我担心其局限性的制约,二来我找到了现在看来效果非常好的策略。
我的设计策略总结起来是以下两点:
- 正视macOS和iOS的不同。 mac有mac擅长的地方,在很多方面受的局限更小,应用之可以获得更好的用户体验。比如这篇文字,虽然在iOS上也可以撰写,还是不如mac上写起来畅快淋漓。
- 尽量在2个平台间复用代码。对于MarkNote而言,我希望核心的代码是完全通用的。比如markdown解析,笔记管理等等。
我采用了以下的办法来提高复用:
-
业务逻辑和UI操作彻底分离。 核心的业务逻辑在理想情况下应该只依赖于
Foundation
而不依赖于Cocoa
或者UIKit
。比如Markdown解析,就仅仅依赖于Foundation。 - 将通用的代码单独建project并打包为framewok。MarkNote的工程视图如下:
其中:
MarkNoteParserOC 是Obj-C版的markdown 解析器,已经开源。
MarkEditor是语法高亮编辑器,支持iOS和macOS。不仅为mac版和iOS版的MarkNote使用,同时也是�InstantCoder的代码编辑器。其一个早期版本也已经开源。
MarkNoteEngine是笔记管理和�iCloud同步的核心库,为mac版和iOS版的MarkNote使用。
MarkNotes是mac版的MarkNote。
MarkNote是iOS版的MarkNote。
-
一个Framework,两个target。
在同一个工程中,分别给macOS和iOS创建不同的target。
-
使用宏来区分不同的版本。
虽然代码很多相同,但是还是有很多系统之间的差异。这个时候可以用宏来处理。比如我需要给mac版的加openWithCompletionHandler,而无需给iOS版添加它,就可以用类似下面的代码:
#if !TARGET_OS_IPHONE
- (void)openWithCompletionHandler:(void (^)(BOOL success))completionHandler;
#endif
-
使用宏来融合UIKit和AppKit的不同。
AppKit和UIKit中很多API都是相似的。比如NSColor和UIColor,NSView和UIView,等等。这个时候,用宏定义一个别名,可能是融合使用的一个好办法。
比如,我的文本编辑器在本质上只是NSTextView或者 UITextViewUI的一个category。
因此我定义了一个宏BaseTextView来分别在macOS上指向NSTextView,而在iOS上指向UITextView:
#if TARGET_OS_IPHONE
#import <UIKit/UIKit.h>
#define BaseTextView UITextView
#else
#import <Cocoa/Cocoa.h>
#define BaseTextView NSTextView
#endif
这样,我后面也就直接用BaseTextView好了。比如
@interface BaseTextView(Editor)
MarkNote IOS版在2015年5月上线,mac版在2015年11月上线。之后两个版本都分别更新了十多次。上面所描述的策略和方法,使得我的代码维护起来清晰而简单。