AnyClass 是 NSObjectProtocol... 有时?

Posted

技术标签:

【中文标题】AnyClass 是 NSObjectProtocol... 有时?【英文标题】:AnyClass is NSObjectProtocol... sometimes? 【发布时间】:2017-04-07 14:26:19 【问题描述】:

在this question 之后,我对所描述的行为非常好奇,我做了一些调查,这让我很困惑。

问题

检查NSObjectProtocol 是否返回NSClassFromString 在任何情况下都返回true,NSClassFromString("WKNSURLRequest") 的返回除外。对于PureClassSwiftObject,所有结果都是真实的这一事实对我来说有点令人惊讶。

import UIKit
import WebKit
import ObjectiveC

class Sigh: NSObject  
class PureClass  

let sighClass = NSClassFromString(NSStringFromClass(Sigh.self))!
let pureClass = NSClassFromString(NSStringFromClass(PureClass.self))!
let nsObject = NSClassFromString("NSObject")!
let wkRequestClass = NSClassFromString("WKNSURLRequest")!
let swiftObject = NSClassFromString("SwiftObject")!

print("\n*NSObjectProtocol CONFORMANCE*")
print("NSObject: ", nsObject is NSObjectProtocol)
//print("WkRequestClass: ", wkRequestClass is NSObjectProtocol)
print("WkRequestClass: This would crash")
print("SighClass: ", sighClass is NSObjectProtocol)
print("PureClass: ", pureClass is NSObjectProtocol)
print("SwiftObject: ", swiftObject is NSObjectProtocol)

我们检查的不是这些类的实例,而是NSClassFromString 的返回,即 AnyClass?。

AnyClass 是AnyObject.Type 的类型定义。 为什么是NSObjectProtocol?为什么不给WkRequestClass

mecki said 是真的,我们可以通过阅读 webkit 源代码来检查它:WKNSURLRequest 继承自 WKObject 这是一个根类但符合 NSObjectProtocol,因为 WKObject 符合 WKObject(protocol)扩展 NSObject(protocol)。

@protocol WKObject <NSObject>

@property (readonly) API::Object& _apiObject;

@end

NS_ROOT_CLASS
@interface WKObject <WKObject>

- (NSObject *)_web_createTarget NS_RETURNS_RETAINED;

@end

来源:https://github.com/WebKit/webkit/blob/master/Source/WebKit2/Shared/Cocoa/WKObject.h

Mecki 对这种崩溃的最佳猜测是运行时错误,所以我试图以某种方式解释它。这是我的游乐场:

//: Playground - noun: a place where people can play

import UIKit
import WebKit
import ObjectiveC

class Sigh: NSObject  
class PureClass  

let sighClass: AnyClass = NSClassFromString(NSStringFromClass(Sigh.self))!
let pureClass: AnyClass = NSClassFromString(NSStringFromClass(PureClass.self))!
let nsObject: AnyClass = NSClassFromString("NSObject")!
let wkRequestClass: AnyClass = NSClassFromString("WKNSURLRequest")!
let swiftObject: AnyClass = NSClassFromString("SwiftObject")!

print("\n*NSObjectProtocol CONFORMANCE*")
print("NSObject: ", nsObject is NSObjectProtocol)
//print("WkRequestClass: ", wkRequestClass is NSObjectProtocol)
print("WkRequestClass: This would crash")
print("SighClass: ", sighClass is NSObjectProtocol)
print("PureClass: ", pureClass is NSObjectProtocol)
print("SwiftObject: ", swiftObject is NSObjectProtocol)

print("\n*ANYCLASS PRINT*")
print("NSObject: ", nsObject)
print("WkRequestClass: ", wkRequestClass)
print("SighClass: ", sighClass)
print("PureClass: ", pureClass)
print("SwiftObject: ", swiftObject)

print("\n*TYPE PRINT*")

print("Type of NSObject: ", type(of: nsObject))
print("Type of WkRequestClass: ", type(of: wkRequestClass))
print("Type of SighClass: ", type(of: sighClass))
print("Type of PureClass: ", type(of: pureClass))
print("Type of SwiftObject: ", type(of: swiftObject))

print("\n*.SELF PRINT*")

print("NSObject.self: ", nsObject.self)
print("WkRequestClass.self: ", wkRequestClass.self)
print("SighClass.self: ", sighClass.self)
print("PureClass.self: ", pureClass.self)
print("SwiftObject.self: ", swiftObject.self)

print("\n*SUPERCLASS PRINT*")

print("NSObject superClass: ", nsObject.superclass() ?? "nil")
//print("WkRequestClass superClass: ", wkRequestClass.superclass())
print("WkRequestClass superClass: This would crash")
print("SighClass superClass: ", sighClass.superclass() ?? "nil")
print("PureClass superClass: ", pureClass.superclass() ?? "nil")
print("SwiftObject superClass: ", swiftObject.superclass() ?? "nil")

print("\n*INTROSPECTION*\n")

var count: UInt32 = 0
var protocols = class_copyProtocolList(wkRequestClass, &count);

for i: Int in 0..<Int(count) 
    print("WkRequestClass implements", protocols![i]!)


print("WkRequestClass superClass is", class_getSuperclass(wkRequestClass))
print("Its super super class is", class_getSuperclass(class_getSuperclass(wkRequestClass)))

//Introspecting WKObject
protocols = class_copyProtocolList(class_getSuperclass(wkRequestClass), &count);

for i: Int in 0..<Int(count) 
    print("WKObject implements", protocols![i]!)


print("WKObject conforms the NSObjectProtocol? ", class_conformsToProtocol(class_getSuperclass(wkRequestClass), NSObjectProtocol.self))

在这个简单的操场上,我玩了一些不同的类类型,最后我尝试使用 Objective-c 运行时内省 WKNSURLRequestWKObject

如果崩溃是由运行时错误引起的,我预计内省部分也会崩溃,但没有。完全没有问题。

这是输出:

**NSObjectProtocol 一致性** - NSObject:真 - WkRequestClass:这会崩溃 - SighClass:真 - PureClass:真 - SwiftObject:真 **任何类别的打印** - NSObject:NSObject - WkRequestClass: WKNSURLRequest - SighClass:叹息 - 纯类:纯类 - SwiftObject:SwiftObject **打字打印** - NSObject 类型:NSObject.Type - WkRequestClass 的类型:WKNSURLRequest.Type - SighClass 的类型:Sigh.Type - PureClass 的类型:PureClass.Type - SwiftObject 类型:SwiftObject.Type **.自行打印** - NSObject.self:NSObject - WkRequestClass.self:WKNSURLRequest - SighClass.self:叹息 - PureClass.self:PureClass - SwiftObject.self:SwiftObject **超一流打印** - NSObject 超类:无 - WkRequestClass 超类:这会崩溃 - SighClass 超类:NSObject - PureClass 超类:SwiftObject - SwiftObject 超类:无 **内省** - WkRequestClass 实现 `` - WkRequestClass 超类是 WKObject - 它的超超类是零 - WKObject 实现 `` - WKObject 符合 NSObjectProtocol?真的

有趣的事实,如果我这样做了

wkRequestClass.isSubclass(of: class_getSuperclass(wkRequestClass))

我遇到了崩溃,这很荒谬。

这是否证明目标 c 运行时已损坏/无法正确处理这种情况?答案看起来并不容易(这就是我发布这个问题的原因),因为正如预期的那样,WKObject 符合 NSObjectProtocol,并且它是一个根类,因为它的超类为 nil。所有人都为这种内省工作。

还有待检查的是 swift 运行时。有什么方法可以检查吗?有什么我错过的可以解释这次崩溃的吗?我很想知道你对此的看法。

【问题讨论】:

你的第一个错误 - 在 swift 中使用 Objective-c 运行时。有很多不这样做的理由,主要是因为 swift 对无处不在的objective-c 类做了疯狂的魔法。如果要使用 objc-runtime,则在 Objective-c 中使用 objc-runtime,而不是 swift。沿着这条路走下去就是疯狂。 WKObject -isSubclassOfClass:(和-conformsToProtocol:应该崩溃,顺便说一句,因为在github.com/WebKit/webkit/blob/master/Source/WebKit2/Shared/…中没有实现它。仅仅因为objective-c 类符合协议并不意味着它完全实现了它——尤其是在自定义根类的情况下。 顺便说一句,这是使用自定义根类是危险的另一个原因,并且不应该在公共 API 中公开。对于不熟悉该对象的人来说,搞砸的空间太大了。 我在这里使用客观 C 运行时用于学术目的 Richard。我只是想明白。 子类为什么会崩溃?这就是问题所在。如果我通过逻辑发现是其父类的子类应该是真的 【参考方案1】:

您是正确的,WKObject 实现了NSObject protocol,因为它实现了WKObject protocol,并且此协议继承自NSObject protocol。但这在这里不起作用。

+isSubclassOfClass:+instancesRespondToSelector: 等某些方法没有在NSObject 协议中声明,这些只是NSObject class 的普通类方法,因此被NSObject 的所有子类继承,但只能由子类继承-NSObject 的类。如果其他根类想要与NSObject 兼容,它们必须自己实现这些,NSObject 协议不会强制它们这样做。

现在从单元测试类中查看这段代码:

SEL issubclasssel = @selector(isSubclassOfClass:);
Protocol * nsobjp = @protocol(NSObject);
Class c1 = NSClassFromString(@"NSObject");
XCTAssert(c1);
XCTAssert([c1 conformsToProtocol:nsobjp]);
XCTAssert([c1 instancesRespondToSelector:issubclasssel]);
XCTAssert([c1 isSubclassOfClass:[NSObject class]]);

Class c2 = NSClassFromString(@"WKNSURLRequest");
XCTAssert(c2);
XCTAssert([c2 conformsToProtocol:nsobjp]); // Line 1
XCTAssert([c2 instancesRespondToSelector:issubclasssel]); // Line 2
XCTAssert([c2 isSubclassOfClass:[NSObject class]]); // Line 3

此代码在第 2 行崩溃:

Thread 1: EXC_BAD_ACCESS (code=1, address=0x18)

如果我注释掉第 2 行,代码仍然会在第 3 行崩溃,并出现完全相同的错误。请注意,这不是 Swift 代码,也不是任何与 Swift 相关的代码,这是纯 Objective-C 代码。这只是错误的 Objective-C 代码,如下所示。

所以WKObject 确实实现了+conformsToProtocol:(第1 行不会崩溃),它必须这样做,因为这是NSObject protocol 的要求,但它没有实现+instancesRespondToSelector:+isSubclassOfClass:,并且它不必,所以这完全没问题。它是一个根类,它不继承自NSObject,并且没有协议要求它实现其中任何一个。上面调用这些方法是我的错误;在对象上调用不存在的方法是“未定义的行为”,这允许运行时几乎任何事情:忽略调用,只是记录错误,抛出异常,或者只是立即崩溃;由于objc_msgSend() 是一个高度优化的函数(它没有安全检查,这对于每次调用来说都太昂贵了),它只会崩溃。

但显然 Swift 有时似乎并不在意。在处理 Obj-C 对象时,Swift 似乎假设它总是可以调用 NSObject 及其任何子类实现的这些方法之一,即使没有协议会承诺这一点。这就是为什么某些 Swift 代码结构会导致没有从 NSObject 继承的 Objective-C 根对象崩溃的原因。因为这个假设是完全错误的。 Swift 绝不能调用根对象上的任何方法,因为它不确定是否也实现了这些方法。因此,我在另一个问题中将此称为 Swift-Objc-Bridge 中的错误。

更新 1:

Giuseppe Lanza 问:

为什么当我有一个纯 swift 类时,我从字符串中获取类 然后我测试 NSObjectProtocol 是真的吗?

我个人认为这也是 Swift Runtime 中的一个错误。纯 Swift 类不符合 NSObjectProtocol。实际上它甚至不符合它,请参见下面的答案。

Giuseppe Lanza 问:

请注意,如果我创建的协议继承自 NSObjectProtocol 然后我尝试使 PureClass 符合 协议编译器会抱怨 PureClass 不是 NSObjectProtocol 兼容

这是因为PureClass 必须实现所有必需的NSObjectProtocol 方法才能符合该协议;看到这个答案https://***.com/a/24650406/15809

但是,它甚至不能满足这个要求,因为NSObjectProtocol 的一个要求就是实现这个方法

func `self`() -> Self

这对于纯 Swift 类来说根本不可能,因为当你尝试这样做时,编译器会抱怨:

error: method cannot be an implementation of an @objc requirement
because its result type cannot be represented in Objective-C

这是正确的,纯 Swift 类不能用 Obj-C 表示,所以它不能返回所需的类型。

Swift 文档还说:

请注意,@objc 协议只能由以下类采用 从 Objective-C 类或其他 @objc 类继承。

目前@objc 强制您从NSObject 继承,而纯 Swift 类不会。

【讨论】:

为什么当我有一个纯 swift 类时,我从字符串中获取类,然后我测试 NSObjectProtocol 是否正确?请注意,如果我创建一个从 NSObjectProtocol 继承的协议,然后我尝试使 PureClass 符合该协议,编译器会抱怨 PureClass 不符合 NSObjectProtocol @GiuseppeLanza 更新了我的答案

以上是关于AnyClass 是 NSObjectProtocol... 有时?的主要内容,如果未能解决你的问题,请参考以下文章

Swift 元类型selfSelfAnyObjectAny和AnyClass

Swift 元类型selfSelfAnyObjectAny和AnyClass

Swift基础-AnyObject&Any&AnyClass

swift中Any,AnyObject,AnyClass的区别

如何在 Swift 中的 AnyClass 上使用 performSelector() 调用类方法?

Java(C#)基础差异-泛型