• 赚钱入口【需求资源】限时招募流量主、渠道主,站长合作;【合作模式】CPS长期分成,一次推广永久有收益。主动打款,不扣量;

将Objective-C类迁移到Swift:使用扩展

iOS cps12345 1年前 (2020-06-27) 278次浏览 0个评论

我目前正在开发历史悠久的大型iOS应用程序。我们尝试用Swift编写新代码,但是大约75–80%的代码库仍然是Objective-C。我们不仅仅为了它而在Swift中重写现有代码,仅当子系统无论如何都要进行大修时也是如此。该规则的例外情况是:当特定子系统达到临界点时(例如,其中的60%用Swift编写),也有必要转换其余子系统,以最大程度地减少混合两种语言所造成的麻烦。

Objective-C和Swift之间的互操作性通常很好

-实际上,考虑到它们之间的巨大差异,我很惊讶Apple能够使两种语言一起工作。也就是说,两种语言的混合总是会引起一定程度的摩擦。

扩展中没有存储的属性

我想谈一谈这种导致冲突的原因,尤其是与互操作性没有直接关系的事实,那就是扩展不能包含存储的属性;在Swift和Objective-C中,所有存储的属性/ 值必须是主类型定义的一部分。即使这是与互操作性无关的一般限制,但是当我处理Swift / Objective-C混合代码时,它对我的​​影响最大。

 

从Objective-C类到Swift的逐行转换

想象以下情况:

  • 您有一个相当大而复杂的Objective-C类。
  • 您想向现有类中添加新功能,或者要对类的一部分进行实质性更改。您更愿意在Swift中编写新的/更改的代码。
  • 用Swift重写整个类不是(立即)目标。不过,作为定期维护的一部分,进行零碎的迁移(可能需要一到两年的时间)会很好。

如果Swift扩展需要存储的属性怎么办?

我通常的策略是为Objective-C类编写一个Swift扩展。新代码进入扩展名。必要时,@objc注释会将扩展的代码公开给Objective-C。在新代码要求我向该类添加存储的属性之前,这非常有用。它不能在扩展中使用,我必须将其添加到Objective-C类定义中。

反过来,这意味着该属性必须具有与Objective-C兼容的类型,即使它只能由Swift代码在内部使用。**这是我经常遇到的一个相当大的限制:它意味着没有结构,没有枚举具有关联的值,没有泛型等。

解决方法

这是我使用的解决方法:在Swift中,我定义一个与Objective-C兼容的类,该类充当要在我的Swift扩展中使用的所有存储属性的包装。在Objective-C中,我将该类的实例的属性添加到主类定义中。一旦完成,其他所有事情都将在Swift代码中发生:这些属性可以使用仅限Swift的功能(假设您不需要从Objective-C访问它们)—只有类本身必须对Objective-C可见。

让我们来看一个例子。假设我要扩展的Objective-C类名为NetworkService。我需要存储对与Objective-C不兼容的闭包的引用,因为它使用了通用Result枚举。我定义了一个在我的Swift扩展旁边NetworkServiceProperties具有仅Swift onDownloadComplete属性的类:

@objc class NetworkServiceProperties: NSObject {
    var onDownloadComplete:
        ((Result<Data, NetworkError>) -> Void)? = nil
}

此类必须与Objective-C兼容,即使其属性不兼容也是如此。(我还尝试将类定义嵌套在扩展名中,因为这样可以很好地将其与所属的代码并置在一起。可悲的是,这没有用;引用嵌套类的Objective-C属性在Swift代码中不可见。 )

接下来,我props在Objective-C中添加一个名为主类定义的属性。这需要进行前向声明,NetworkServiceProperties因为无法将生成的Swift标头导入到Objective-C标头文件中。

@import Foundation;
@class NetworkServiceProperties;

@interface NetworkService : NSObject

@property (nonatomic, strong, readonly, nonnull)
    NetworkServiceProperties *props;

@end

最后,在NetworkService初始化程序中,我初始化了新属性:

#import "NetworkService.h"
#import "MyApp-Swift.h"

@implementation NetworkService

- (instancetype)init {
    self = [super init];
    if (self) {
        _props = [NetworkServiceProperties new];
    }
    return self;
}

// ...

只要有可能,我都会尝试为Properties该类提供一个与Objective-C兼容的初始化器,可以从主类的调用该初始化器init。如果由于某些属性需要特殊的初始化而无法正常工作,那么我swift_init在Swift扩展中定义了一个(或类似的)函数来初始化仅Swift的属性。然后swift_init,我从类的常规(Objective-C)初始化程序中调用。之所以可行,是因为类初始化规则不是由Objective-C中的编译器严格执行的。

就是这样。现在,我可以通过props以下方式在扩展程序的任何位置访问属性:

extension NetworkService {
    func doSomething() {
        // ...
        // let result = Result(...)
        props.onDownloadComplete?(result)
    }
}

最后的话

我真的很喜欢这个解决方案。这很简单;哎呀,您可能会说这很明显,我同意。尽管如此,我还是花了几个月的时间来思考这个项目。也许它也对您有帮助。

当需要完成迁移并完全删除Objective-C类定义时,我要做的就是将属性定义从NetworkServiceProperties类(现在用Swift编写)中移出并删除.props所有使用的地方。

看看这一系列的第二部分,我从不同的角度接近同样的问题。

  1. 我很想在这里链接到一个标题为“ 将Swift与Cocoa和Objective-C一起使用”的文档,我认为这是一个很好的资源,是Apple迄今为止发布的最好的Swift文档之一。可悲的是,Apple最近取消了它,而取代它的文档似乎不够全面。据我所知,新文档只谈论使用Swift中的C / Objective-C,而根本不谈论从Objective-C中调用Swift代码。
喜欢 (0)

您必须 登录 才能发表评论!