object_setClass in Swift

Hello, everyone!

I tried to use object_setClass in Playgrounds.

import UIKit

final class MyView: UIView {
    
    var mySize = CGSize(width: 100, height: 100)
    
    override func sizeThatFits(_ size: CGSize) -> CGSize {
        return mySize
    }
    
}

let size = CGSize(width: 320, height: 640)
let view = UIView(frame: CGRect(origin: .zero, size: size))

// Change class of view variable
object_setClass(view, MyView.self)

print(view.sizeThatFits(size)) // (0.0, 0.0) 
// in different playground executions it prints different values (near zero)

(view as! MyView).mySize = CGSize(width: 500, height: 500)
print(view.sizeThatFits(size)) // (500.0, 500.0)

And similar example with reference type variable. But with different behaviour:

import UIKit

final class MyView: UIView {
    
    var myLayer = CAShapeLayer()
    
    override var layer: CALayer {
        return myLayer
    }
    
}

let size = CGSize(width: 320, height: 640)
let view = UIView(frame: CGRect(origin: .zero, size: size))

object_setClass(view, MyView.self)

print(view.layer)
// error: Execution was interrupted, reason: EXC_BAD_ACCESS (code=1, address=0x0).
// The process has been left at the point where it was interrupted, use "thread return -x" to return to the state before expression evaluation.

In the first example there is no crash. Is it expected behaviour or bug?

P.S. I know that one should carefuly use objc runtime methods.

If you are trying to use the objective-c runtime, the routine you are looking for is objc_setClass, not object_setClass.

Not sure what you are trying to do by setting the class using objective-c runtime. You can use MyView as a UIView. All you have to do is

let view = MyView(...)

since you've defined MyView as inheriting from UIView. The compiler and run-time take care of everything else.

I know that I can instantiate MyView directly.

This happens because setClass cannot add instance variables to an object that has already been created. setClass is only safe if the new class does not have additional ivars. This is not specific to Swift, regular ObjC is limited in the same way.

A related problem is that setClass will not call init again, so even if you did get space for your ivars (which you only would out of pure chance), they wouldn’t be initialized.

You cause undefined behaviour by setting your object’s class to a larger type, and you read/write whatever happens to be next in memory at that location. A read for a double will only crash if your object was right at the end of a virtual memory page and the next page is unmapped. A write may crash for the same reason, and may eventually cause a crash if you write a double to a location that your program assumes to be a pointer elsewhere, and you eventually hit that elsewhere. Pointers typically look like near-zero double values, so it’s likely to be what you’re seeing.

A read or write of a reference type is likely to crash right away due to compiler-inserted retain count calls that can’t deal with object pointers that turn out to not be object pointers.

It is “safe” to use setClass to change the type of your object to a superclass, or to a subclass that only adds and overrides methods or properties without creating new ivars.

2 Likes

This is all true, but for Swift classes, it is furthermore unsafe to change a type from an ObjC class to a Swift class, or from a Swift class to a different Swift class, because ObjC class objects do not have a Swift vtable, and different Swift classes may not have compatible vtable layouts.

4 Likes

Thanks everyone for answers

Terms of Service

Privacy Policy

Cookie Policy