Swift 2.0 Set not working as expected when containing NSObject subclass
I played with your code a bit. I was able to get it working by no longer subclassing NSObject, but instead conforming to the Hashable protocol:
class A: Hashable {
let h: Int
init(h: Int) {
self.h = h
}
var hashValue: Int {
return h
}
}
func ==(lhs: A, rhs: A) -> Bool {
return lhs.hashValue == rhs.hashValue
}
let a = A(h: 1)
let b = A(h: 1)
var sa = Set([a])
let sb = Set([b])
sa.subtract(sb).count // Swift1.2 prints 0, Swift 2 prints 1
sa.contains(a) // Swift1.2 true, Swift 2 true
sa.contains(b) // Swift1.2 true, Swift 2 false
a.hashValue == b.hashValue
When you were inheriting from NSObject, your ==
overload wasn't actually being executed. If you want this to work with NSObject, you'd have to override isEquals:
override func isEqual(object: AnyObject?) -> Bool {
if let object = object as? A {
return object.h == self.h
} else {
return false
}
}
Swift native base class or NSObject
Swift classes that are subclasses of NSObject:
- are Objective-C classes themselves
- use
objc_msgSend()
for calls to (most of) their methods - provide Objective-C runtime metadata for (most of) their method implementations
Swift classes that are not subclasses of NSObject:
- are Objective-C classes, but implement only a handful of methods for NSObject compatibility
- do not use
objc_msgSend()
for calls to their methods (by default) - do not provide Objective-C runtime metadata for their method implementations (by default)
Subclassing NSObject in Swift gets you Objective-C runtime flexibility but also Objective-C performance. Avoiding NSObject can improve performance if you don't need Objective-C's flexibility.
Edit:
With Xcode 6 beta 6, the dynamic attribute appears. This allows us to instruct Swift that a method should use dynamic dispatch, and will therefore support interception.
public dynamic func foobar() -> AnyObject {
}
Why does object become NSZombie only when inherit from NSObject?
tl;dr: because NSZombies
are implemented to only affect NSObject
and its subclasses. (This doesn't have to do with Swift, either: Obj-C objects which aren't subclasses of NSObject
also won't become zombies.)
Upon initialization (__CFInitialize
, called when the framework is loaded), the CoreFoundation framework sets up a lot low-level Foundation and CoreFoundation behaviors; among other things, it looks for the NSZombieEnabled
environment variable, and if present, enables zombies by calling the __CFZombifyNSObject
function:
; Disassembly from Hopper on /System/Library/Frameworks/CoreFoundation.framework/CoreFoundation on macOS Catalina
0000000000001cc0 lea rdi, qword [aNszombieenable] ; argument #1 for method ___CFgetenv, "NSZombieEnabled"
0000000000001cc7 call ___CFgetenv ; ___CFgetenv
0000000000001ccc test rax, rax
0000000000001ccf je loc_1cee
0000000000001cd1 mov al, byte [rax] ; DATA XREF=sub_1bb8ec
0000000000001cd3 or al, 0x20
0000000000001cd5 cmp al, 0x79
0000000000001cd7 jne loc_1cee
0000000000001cd9 cmp byte [___CFZombieEnabled], 0x0 ; ___CFZombieEnabled
0000000000001ce0 jne loc_1cee
0000000000001ce2 mov byte [___CFZombieEnabled], 0xff ; ___CFZombieEnabled
0000000000001ce9 call ___CFZombifyNSObject ; ___CFZombifyNSObject
When zombies are enabled, __CFZombifyNSObject
replaces the implementation of -[NSObject dealloc]
with a different implementation (__dealloc_zombie
):
// Hopper-generated pseudo-code:
void ___CFZombifyNSObject() {
rax = objc_lookUpClass("NSObject");
method_exchangeImplementations(class_getInstanceMethod(rax, @selector(dealloc)), class_getInstanceMethod(rax, @selector(__dealloc_zombie)));
return;
}
This means that all subclasses of NSObject
and their descendants, upon deallocation, will call through to __dealloc_zombie
. So what does __dealloc_zombie
do?
// Hopper-generated pseudo-code:
/* @class NSObject */
-(void)__dealloc_zombie {
rbx = self;
if ((rbx & 0x1) != 0x0) goto loc_175ed5;
loc_175e3f:
if (*(int8_t *)___CFZombieEnabled == 0x0) goto loc_175eee;
loc_175e4c:
rax = object_getClass(rbx);
var_20 = 0x0;
rax = asprintf(&var_20, "_NSZombie_%s", class_getName(rax));
rax = objc_lookUpClass(var_20);
r14 = rax;
if (rax == 0x0) {
r14 = objc_duplicateClass(objc_lookUpClass("_NSZombie_"), var_20, 0x0);
}
free(var_20);
objc_destructInstance(rbx);
object_setClass(rbx, r14);
if (*(int8_t *)___CFDeallocateZombies != 0x0) {
free(rbx);
}
goto loc_175ed5;
loc_175ed5:
if (**___stack_chk_guard != **___stack_chk_guard) {
__stack_chk_fail();
}
return;
loc_175eee:
if (**___stack_chk_guard == **___stack_chk_guard) {
_objc_rootDealloc(rbx);
}
else {
__stack_chk_fail();
}
return;
}
In more human-readable terms, it:
- Looks up
[self class]
- If there isn't already a class named
_NSZombie_<our class name>
, it creates one by duplicating the_NSZombie_
class and gives the duplicate a new name (this creates a copy of the class with all of its method implementations, or lack thereof) - It tears down
self
, and replaces its class with the new class, so that if you message it in the future, you dispatch to_NSZombie_<whatever>
_NSZombie_
is a class which implements no methods, so sending it any message (method call) ends up falling into a code path in message forwarding which prints out the "message sent to deallocated instance" message.
Effectively, this method of implementing zombies hinges on inheritance from NSObject
(because all NSObject
subclasses should call [super dealloc]
on deallocation, eventually reaching [NSObject dealloc]
); things which don't inherit from NSObject
don't inherit this implementation. (You can also actually see this by implementing an NSObject
subclass which doesn't call [super dealloc]
in its -dealloc
implementation — the object won't get zombified on release.)
Do NSZombies
have to be implemented this way? No, it's certainly possible to imagine other schemes which would allow pure Swift objects to participate (Swift runtime initialization could also look up the NSZombieEnabled
environment variable and do something similar), but there's somewhat less of a benefit to putting in the effort. As Rob mentions in his answer, this works largely because we're able to swizzle out the class of the deallocated instance (this is actually possible with the Swift runtime, but not exposed externally), but crucially, even if we did this, it wouldn't help cases of static method dispatch, which is possible on object types in Swift (e.g. for final
classes). [Alexander alludes to this in his comment.]
So, largely, it's really easy to implement this way for Obj-C, and there are somewhat limited benefits for taking the time to do this for pure Swift classes too.
Return an array created from custom objects in Swift where they aren't known until runtime?
You can use generics.
func fillAnObjectClassArrayGivenURL<T: AnyObject>(urlString: String, className: T.Type) -> [T]
Why is executeFetchRequest not returning subclass objects when run under XCTest using Swift?
I had a similar question. Making my entity class public solves the issue and I get the right class type.(CoreData class miss match in unit test)
Can I avoid subclassing NSObject to repetitively call a method?
Your code does not cause a stack overflow, because dispatch_after()
only
dispatches the block for later execution and then returns.
A possible disadvantage of your solution is that it starts a new timer every
60 seconds. Timers are not 100% precise, so small differences could accumulate.
A better solution (if you want to avoid NSTimer
) would be to use a dispatch
timer, as described in this answer
to Do something every x minutes in Swift.
Related Topics
Is Calling Cellforrowatindexpath: Ever Practical
I Get Nil When Using Nsdateformatter in Swift
How to Detect If My Device Is an iPhone x in Swift 4
How to Open Application Using Url
iOS 8 [Uiapplication Sharedapplication].Scheduledlocalnotifications Empty
Conforming to UItableviewdelegate and UItableviewdatasource in Swift
How to Sort Core Data Array Based on Date Entered
How to Create Custom Album in Swift 3 iOS 10
How to Get Original Nsdata of UIimage
How to Fetch Images from Photo Library Within Range of Two Dates in iOS
Cfbundledocumenttype Is Not Working in Myproject-Info.Plist File
Shared Iad Banner Bannerviewdidloadad Not Being Called
Mkannotation Not Getting Selected in iOS5
Apn Custom Notification Sound Issue