问题
Following on this question I got very curious about the described behavior and I did some investigation that left me quite puzzled.
The problem
Checking the is NSObjectProtocol
for the return of NSClassFromString
returns true in any case except for the return of NSClassFromString("WKNSURLRequest")
. The fact that all the results are true is a bit surprising for me also for PureClass
and SwiftObject
.
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)
We are checking not an instance of those classes, but the return of NSClassFromString
which is AnyClass?.
AnyClass is a typedef to AnyObject.Type
. Why is it NSObjectProtocol
? Why not for WkRequestClass
?
What mecki said is true and we can check it by reading the webkit source code: WKNSURLRequest
inherits from WKObject
which is a root class BUT conforming the NSObjectProtocol
, because WKObject
conforms WKObject(protocol) that extends NSObject(protocol).
@protocol WKObject <NSObject>
@property (readonly) API::Object& _apiObject;
@end
NS_ROOT_CLASS
@interface WKObject <WKObject>
- (NSObject *)_web_createTarget NS_RETURNS_RETAINED;
@end
source: https://github.com/WebKit/webkit/blob/master/Source/WebKit2/Shared/Cocoa/WKObject.h
Mecki best guess to this kind of crash is a runtime error so I tried to explain it somehow. Here is my playground:
//: 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))
In this easy playground I play a bit with different class types, and at the end I try to introspect WKNSURLRequest
and WKObject
using objective-c runtime.
If the crash is due to a runtime bug I was expecting a crash in the introspection section as well, but nothing. No problems at all.
This is the output:
**NSObjectProtocol CONFORMANCE** - NSObject: true - WkRequestClass: This would crash - SighClass: true - PureClass: true - SwiftObject: true **ANYCLASS PRINT** - NSObject: NSObject - WkRequestClass: WKNSURLRequest - SighClass: Sigh - PureClass: PureClass - SwiftObject: SwiftObject **TYPE PRINT** - Type of NSObject: NSObject.Type - Type of WkRequestClass: WKNSURLRequest.Type - Type of SighClass: Sigh.Type - Type of PureClass: PureClass.Type - Type of SwiftObject: SwiftObject.Type **.SELF PRINT** - NSObject.self: NSObject - WkRequestClass.self: WKNSURLRequest - SighClass.self: Sigh - PureClass.self: PureClass - SwiftObject.self: SwiftObject **SUPERCLASS PRINT** - NSObject superClass: nil - WkRequestClass superClass: This would crash - SighClass superClass: NSObject - PureClass superClass: SwiftObject - SwiftObject superClass: nil **INTROSPECTION** - WkRequestClass implements `` - WkRequestClass superClass is WKObject - Its super super class is nil - WKObject implements `` - WKObject conforms the NSObjectProtocol? true
Funny fact, if I do
wkRequestClass.isSubclass(of: class_getSuperclass(wkRequestClass))
I get a crash, which is absurd.
Does this proves that the objective c runtime is broken/doesn't handle correctly this case? The answer doesn't look easy (that0s why I'm posting this question) because, as expected, WKObject is conforming to NSObjectProtocol, and it is a root class as its superClass is nil. All worked for this kind of introspections.
What remains to check is the swift runtime. Is there any way to check it? Is there anything I missed that would explain this crash? I'm curious to know your opinion about that.
回答1:
You are correct that WKObject
implements the NSObject protocol
as it implements the WKObject protocol
and this protocol inherits from the NSObject protocol
. But that plays no role here.
Certain methods like +isSubclassOfClass:
or +instancesRespondToSelector:
are not declared in the NSObject
protocol, these are just normal class methods of the NSObject class
and thus inherited by all sub-classes of NSObject
but only by sub-classes of NSObject
. Other root classes must implement these themselves if they want to be NSObject
compatible, the NSObject
protocol won't force them to do so.
Now check out this code from a unit test class:
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
This code crashes at Line 2:
Thread 1: EXC_BAD_ACCESS (code=1, address=0x18)
And if I comment out Line 2, the code still crashes at Line 3 with exactly the same error. Note that this is not Swift code, nor in any way Swift related, this is pure Objective-C code. It's just wrong Objective-C code, as you will see below.
So WKObject
does implement +conformsToProtocol:
(Line 1 does not crash), it has to, as this is a requirement of the NSObject protocol
, but it doesn't implement +instancesRespondToSelector:
or +isSubclassOfClass:
, and it doesn't have to, so this is perfectly okay. It is a root class, it doesn't inherit from NSObject
and there is no protocol that would require it to implement any of these. It was my mistake above to call these methods; calling not-existing methods on objects is "undefined behavior" and this allows the runtime pretty much anything: ignoring the call, just logging an error, throwing an exception, or just crashing right away; and as objc_msgSend()
is a highly optimized function (it has no security checks, that would too expensive for every call), it just crashes.
But apparently Swift sometimes doesn't seem to care. When dealing with Obj-C objects, Swift seems to assume that it can always call one of theses methods that NSObject
and any sub-class of it implement, even though no protocol would promise that. And that's why certain Swift code constructs cause a crash with Objective-C root objects that don't inherit from NSObject
. And as this assumption is simply wrong. Swift must never call any methods on root objects where it doesn't know for sure that these methods are also implemented. Thus I called this a bug in the Swift-Objc-Bridge at the other question.
Update 1:
Giuseppe Lanza asked:
Why then when I have a pure swift class, I get the class from string and then I test is NSObjectProtocol I get true?
Personally I think this is also a bug in the Swift Runtime. A pure Swift class does not conform to the NSObjectProtocol. Actually it cannot even conform to it, see answer below.
Giuseppe Lanza asked:
Please note that if I create a protocol that inherits from NSObjectProtocol and then I try to make PureClass conformance to that protocol the compiler will complain that PureClass is not NSObjectProtocol compliant
That's because PureClass
would have to implement all required NSObjectProtocol
methods to conform to that protocol; see this answer https://stackoverflow.com/a/24650406/15809
However, it cannot even satisfy that requirement, as one requirement of NSObjectProtocol
is to implement this method
func `self`() -> Self
and that's simply not possible for a pure Swift class, as when you try to do that, the compiler will complain:
error: method cannot be an implementation of an @objc requirement
because its result type cannot be represented in Objective-C
which is correct, a pure Swift class cannot be represented in Obj-C, so it cannot return the required type.
The Swift documentation also says:
Note that @objc protocols can be adopted only by classes that inherit from Objective-C classes or other @objc classes.
And currently @objc
forces you to inherit from NSObject
, which a pure Swift class does not.
来源:https://stackoverflow.com/questions/43280615/anyclass-is-nsobjectprotocol-sometimes