johnno1962 / HotReloading

Hot reloading as a Swift Package

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Crashing on injecting to device

denil-ct opened this issue Β· comments

App crashes immediately after hitting save. Its a swift project based on UIKit running on a iOS device.

πŸ”₯ HotReloading connected /Users/denilct/Documents/Sharechat/HotReload/HotReload.xcodeproj
πŸ”₯ Watching files under the directory /Users/denilct/Documents/Sharechat/HotReload
πŸ”₯ πŸ’‰ ⚠️ Your project file seems to be in the Desktop or Documents folder and may prevent InjectionIII working as it has special permissions.
πŸ”₯ Compiling /Users/denilct/Documents/Sharechat/HotReload/HotReload/ViewController.swift
πŸ”₯ Loaded .dylib - Ignore any duplicate class warning ⬆️
πŸ”₯ Interposed 2 function references.
πŸ”₯ ⚠️ Number of class refs 2 does not equal []
πŸ”₯ Injected type #1 'HotReload.ViewController'
objc[1025]: Attempt to use unknown class 0x109db0340.
import UIKit

class ViewController: UIViewController {
    let label = UILabel(frame: CGRect(x: 30, y: 200, width: 200, height: 150))

    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .red
        view.addSubview(label)
        label.text = "hello"
        // Do any additional setup after loading the view.
    }
    
    
    @objc func injected() {
        view.backgroundColor = UIColor.yellow
        label.text = "anyoooo"
        label.backgroundColor = .red
        view.layoutIfNeeded()
    }
}

Same code works fine for simulator, but crashes on device on hitting save.

image

Hi, I know this sounds odd but try removing the super.viewDidLoad(). As I say, your millage is going to vary a lot more with the device version though the specific bit of code where it is crashing changed recently. Please try rolling back to version 4.6.0 (any minor version) and let me know how you get on.

Rolled back to 4.6.0, and it doesn't crash anymore, but doesn't behave as expected.
For example setting the colors in the injected method, makes it clear, regardless of what color I set.
The text value in the label does not change from the initial value at all.

Thanks I'll check out what's happened with 4.7.0+ There are lots of non-apparent limitations to the device injection. Re the colours thing: you should be able to use something like (objc_getClass("UIColor") as? UIColor.Type)?.red instead. The clear color is coming from messaging a nil class reference. Referring to classes (which is also part of messaging super) relies on a reference to a non-function symbol which is not a part of the current implementation though the message πŸ”₯ ⚠️ Number of class refs 2 does not equal [] could be trying to tell us something related. I'll look into it. Anything else you find, let me know please.

Sure, thanks for looking into it.

I've updated the message above with a little more detail. What version of Xcode is this?

Getting the class and setting the color seems to work. However, I am unable to do anything to the label with a similar approach.

Xcode version: 14.0.1
Device version: 16.1

OK, I've pushed a few minor changes required by the 14.0 toolchain. Can you give them a try? There were a few minor issues with class references. To make it easier for you to pick up I've created a branch xcode-14.

It seems to be working, even without the objc_getClass method. The view background changes successfully. But I am unable to change any of the properties of the label which is added as a subview.

Great! Thanks for taking the time to raise the issue - this needed to be fixed. Can you show me some of the code you're trying to get working?

Same as my initial comment

import UIKit

class ViewController: UIViewController {
    let label = UILabel(frame: CGRect(x: 30, y: 200, width: 200, height: 150))

    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .red
        view.addSubview(label)
        label.text = "hello"
        // Do any additional setup after loading the view.
    }
    
    
    @objc func injected() {
        view.backgroundColor = UIColor.yellow
        label.text = "anyoooo"
        label.backgroundColor = .red
        view.layoutIfNeeded()
    }
}

It seems something is still wrong with class references
I added another UIView, and tried to change its color, and resulted in a unrecognized selector for the color.

import UIKit

class ViewController: UIViewController {
    var label = UILabel(frame: CGRect(x: 30, y: 100, width: 200, height: 100))
    var tempView = UIView(frame: CGRect(x: 30, y: 300, width: 200, height: 100))

    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .red
        label.backgroundColor = .orange
        tempView.backgroundColor = .blue
        view.addSubview(label)
        view.addSubview(tempView)
        label.text = "hello"
    }
    
    
    @objc func injected() {
        view.backgroundColor = .green
        label.text = "anyo"
        label.backgroundColor = .cyan
        tempView.backgroundColor = .yellow
    }
}
πŸ”₯ HotReloading connected /Users/denilct/Documents/Sharechat/HotReload/HotReload.xcodeproj
πŸ”₯ Watching files under the directory /Users/denilct/Documents/Sharechat/HotReload
πŸ”₯ πŸ’‰ ⚠️ Your project file seems to be in the Desktop or Documents folder and may prevent InjectionIII working as it has special permissions.
πŸ”₯ Compiling /Users/denilct/Documents/Sharechat/HotReload/HotReload/ViewController.swift
πŸ”₯ Loaded .dylib - Ignore any duplicate class warning ⬆️
πŸ”₯ Interposed 2 function references.
πŸ”₯ Injected type #1 'HotReload.ViewController'
πŸ”₯ As class ViewController has an @objc injected() method, HotReloading will perform a "sweep" of live instances to determine which objects to message. If this fails, subscribe to the notification "INJECTION_BUNDLE_NOTIFICATION" instead.
πŸ”₯ (note: notification may not arrive on the main thread)
πŸ”₯ Starting sweep [HotReload.ViewController], []...
2022-10-31 13:49:42.854940-0400 HotReload[522:20005] +[UIView greenColor]: unrecognized selector sent to class 0x2167a0428
2022-10-31 13:49:42.855121-0400 HotReload[522:20005] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '+[UIView greenColor]: unrecognized selector sent to class 0x2167a0428'
*** First throw call stack:
(0x1c00c5e88 0x1b93fb8d8 0x1c023a784 0x1c00dbfa0 0x1c0144350 0x10bb070c4 0x10bb07270 0x104def710 0x104deb340 0x104ded1dc 0x104dee8dc 0x104deec60 0x104dee70c 0x104deed04 0x104ded280 0x104ded1a0 0x104dec578 0x104dec288 0x104deaa84 0x104ddf2ac 0x104da5fd8 0x104da5230 0x1053d05a8 0x1053d205c 0x1053e2810 0x1053e2354 0x1c01566f8 0x1c0138058 0x1c013ced4 0x1f943e368 0x1c261b3d0 0x1c261b034 0x1c8c0b308 0x104d95608 0x104d95590 0x104d9568c 0x1de7a4960)
libc++abi: terminating with uncaught exception of type NSException
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '+[UIView greenColor]: unrecognized selector sent to class 0x2167a0428'
terminating with uncaught exception of type NSException

image

My bad there, the fix for the class references problem has to be compiled into the app so I've rolled a new release https://github.com/johnno1962/InjectionIII/releases/tag/4.5.0RC6 which you should use. The problem with the initialised properties is a strange one. If you add the type as follows it works with the new 4.7.3 HotReloading.

     var label: UILabel! = UILabel(frame: CGRect(x: 30, y: 100, width: 200, height: 100))
     var tempView: UIView! = UIView(frame: CGRect(x: 30, y: 300, width: 200, height: 100))

Why this is and why the type has to be a force unwrap and why the problem is specific to devices I can't say I understand at this stage. I'll sleep on it but you should be able to make some more progress for now.

Yes, seems to be working with the force unwrap. As you said these might be the quirks of running it on device. Anyways, thanks for the quick help in solving this issue.

Yes, sometimes it takes a bit of imagination when you encounter a problem. I'm at sea as to why this is happening if I'm honest, the getter functions are returning nil. I'll have to wait for some random inspiration on how to fix this.

It seems when a real device is involved, everything goes haywire. I would love to help on this project, but have to read up on the black magic powering this entire system.

Feel free to close the issue if you like.

I'll leave the issue open for now as it would be nice to fix this particular issue. Device injection used to work fine then Apple made it so you couldn't dynamically load from a writable area on devices then much later I worked out how to simulate dynamic loading but the implementation is not complete which is where the problems come from. It works well enough particularly for SwiftUI that it might be useful for some but there can be seemingly random issues which can be difficult to debug. Note the note on p HotReloading.stack in the README for more complete stack traces.

Do you know when apple made that change? Would iOS 12 devices work better for this?

It was probably fair enough as a security measure to make a blanket ban on the possibility of code modification on a device after review. The change was made in iOS 10. Device injection works as some other protections are relaxed provided you are debugging an app, see: https://github.com/johnno1962/InjectionScratch/blob/main/Sources/InjectionScratch/InjectionLoader.mm.

I've pushed version HotReloading 4.7.4 which should resolve this last problem you were seeing. It seems you can't inject getters/setters on a device. No need to update the app this time.

Yes, it is working without the force unwrap workaround.
Thank you for the quick fix.

Excellent!