且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

如何创建符合Swift和Objective-C之间共享协议的类方法?

更新时间:2021-11-10 22:35:04

在Objective-C中,我们总是在传递指针,而指针始终可以是nil.许多Objective-C程序员利用了以下事实:向nil发送消息没有任何作用,并返回了0/nil/NO. Swift处理nil的方式完全不同.对象要么存在(从不nil),要么不知道它们是否存在(这是Swift可选对象起作用的地方).

In Objective-C, we were always passing around pointers, and pointers could always be nil. Lots of Objective-C programmers made use of the fact that sending a message to nil did nothing and returned 0/nil/NO. Swift handles nil entirely differently. Objects either exist (never nil), or it is unknown whether or not they exist (which is where Swift optionals come into play).

因此,在Xcode 6.3之前,这意味着任何使用Objective-C代码的Swift代码都必须将所有对象引用视为Swift可选对象.关于Objective-C的语言规则,没有什么能阻止对象指针为nil.

Previous to Xcode 6.3, this therefore meant that any Swift code that used any Objective-C code would have to treat all object references as Swift optionals. Nothing about Objective-C's language rules prevented an object pointer from being nil.

这对于使用Swift中的Objective-C协议,类等意味着什么呢?我们必须在非完美解决方案之间进行选择.

What this meant for using Objective-C protocols, classes, etc., from Swift is that it was a massive mess. We had to choose between to non-perfect solutions.

给出以下Objective-C协议:

Given the following Objective-C protocol:

@protocol ObjCProtocol <NSObject>

@required + (id<ObjCProtocol>)classMethod;
@required - (id<ObjCProtocol>)instanceMethod;
@required - (void)methodWithArgs:(NSObject *)args;

@end

我们可以接受方法定义为包含隐式解包的可选内容:

We can either accept the method definition as containing implicitly unwrapped optionals:

class MyClass: NSObject, ObjCProtocol {
    func methodWithArgs(args: NSObject!) {
        // do stuff with args
    }
}

这可以使生成的代码更整洁(我们不必在体内拆开包装),但是,我们始终会面临发现nil,同时打开可选包装"错误的风险.

This makes the resulting code cleaner (we never have to unwrap within the body), however we will always be at risk of the "found nil while unwrapping optional" error.

或者,我们可以将方法定义为真正的可选方法:

Or alternatively, we can define the method as being a true optional:

class MyClass: NSObject, ObjCProtocol {
    func methodWithArgs(args: NSObject?) {
        // unwrap do stuff with args
    }
}

但这会给我们留下很多混乱的代码.

But this leaves us with a lot of mess unwrapping code.

Xcode 6.3解决了此问题,并为Objective-C代码添加了可空性注释".

Xcode 6.3 fixes this problem and adds "Nullability Annotations" for Objective-C code.

两个新引入的关键字是nullablenonnull.这些用于与您声明Objective-C代码的返回类型或参数类型相同的地方.

The two newly introduced keywords are nullable and nonnull. These are used in the same place you're declaring the return type or parameter type for your Objective-C code.

- (void)methodThatTakesNullableOrOptionalTypeParameter:(nullable NSObject *)parameter;
- (void)methodThatTakesNonnullNonOptionalTypeParameter:(nonnull NSObject *)parameter;
- (nullable NSObject *)methodReturningNullableOptionalValue;
- (nonnull NSObject *)methodReturningNonNullNonOptionalValue;

除了这两个注释关键字之外,Xcode 6.3还引入了一组宏,这些宏使将Objective-C代码的大部分内容标记为nonnull很容易(实际上完全没有注释的文件都假定为nullable ).为此,我们在本节顶部使用NS_ASSUME_NONNULL_BEGIN,在本节底部使用NS_ASSUME_NONNULL_END.

In addition to these two annotation keywords, Xcode 6.3 also introduces a set of macros that makes it easy to mark large sections of Objective-C code as nonnull (files with no annotations at all are effectively assumed as nullable). For this, we use NS_ASSUME_NONNULL_BEGIN at the top of the section and NS_ASSUME_NONNULL_END at the bottom of the section we wish to mark.

例如,我们可以将整个协议包装在该宏对中.

So, for example, we could wrap your entire protocol within this macro pair.

NS_ASSUME_NONNULL_BEGIN
@protocol CalcPrimesProtocol <NSObject>

- (void) calcPrimesWithComputeRecord: (ComputeRecord *) aComputeRecord
              withUpdateDisplayBlock: (updateDisplayBlock) theUpdateDisplayBlock
                  andCompletionBlock: (calcPrimesCompletionBlock) theCalcPrimesCompletionBlock;
+ (id <CalcPrimesProtocol> ) sharedInstance;

@end
NS_ASSUME_NONNULL_END

这与将所有指针参数和返回类型标记为nonnull(很少有例外,例如Apple的Swift博客中的该条目记录了).

This has the same effect as marking all of the pointer parameters and return types as nonnull (with a few exceptions, as this entry in Apple's Swift blog makes note of).

符合Objective-C协议的Swift类必须将该协议中的所有Objective-C类型都视为可选.

A Swift class that conforms to an Objective-C protocol must treat any Objective-C types in that protocol as optionals.

为了弄清楚这一点,我创建了以下Objective-C协议:

In trying to figure this out, I created the following Objective-C protocol:

@protocol ObjCProtocol <NSObject>

@required + (id<ObjCProtocol>)classMethod;
@required - (id<ObjCProtocol>)instanceMethod;
@required - (void)methodWithArgs:(NSObject *)args;

@end

然后,创建了一个继承自NSObject的Swift类,并声明自己符合此ObjCProtocol.

And then, created a Swift class which inherited from NSObject and declared itself as conforming to this ObjCProtocol.

然后我继续键入这些方法名称,并让Swift为我自动完成这些方法,这就是我所得到的(我放入了方法主体,其余则为自动完成):

I then proceeded to type these method names and let Swift autocomplete the methods out for me, and this is what I got (I put in the method bodies, the rest if autocomplete):

class ASwiftClass : NSObject, ObjCProtocol {
    class func classMethod() -> ObjCProtocol! {
        return nil
    }

    func instanceMethod() -> ObjCProtocol! {
        return nil
    }

    func methodWithArgs(args: NSObject!) {
        // do stuff
    }
}

现在,如果需要,我们可以使用常规的可选选项(与?一起使用),而不是使用这些自动展开的可选选项.编译器对这两者都很满意.关键是,尽管我们必须考虑nil的可能性,因为Objective-C协议无法阻止nil传递.

Now, we can use regular optionals (with the ?) instead of these automatically unwrapped optionals if we want. The compiler is perfectly happy with either. The point is though that we must allow for the possibility of nil, because Objective-C protocols cannot prevent nil from being passed.

如果此协议是在Swift中实现的,我们将选择返回类型是否为可选,而Swift将阻止我们将nil返回至未定义非可选返回类型的方法.

If this protocol were implemented in Swift, we'd get to choose whether the return type is optional or not and Swift would prevent us from returning nil to a method that didn't define a non-optional return type.