johnno1962 / HotReloading

Hot reloading as a Swift Package

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

iOS device injection not working when running as a Mac app

byohay opened this issue · comments

I'm trying to use injection when running an iOS app as a Mac app, but I don't see the changes on screen. Is running an iOS app as Mac supported? I assumed it is because I saw that code in HotReloading that use isiOSAppOnMac.

Attached is a simple app that shows the problem. Whether I'm changing the textView's text in ViewController or ViewController's background color I don't see the change on screen.
ExampleApp.zip

I'll take a look, you're covering all the bases. It's nice when someone checks them from time to time. This has worked but I remember there were complications as you cannot turn off "library validation" (there are notes in the InjectionIII project README) and things in the OS sometimes change with time. I'll take a look at your example app tomorrow, it's possible I may switch this configuration to use "device" injection since it exists now even though it is on the mac though that would create other problems. What do you plan to use this for? What macOS version are you on?

MacOS 12.6.1, Xcode 14.1.
Thanks a lot!

Turns out this already uses device injection as getting the codesigning right is a huge pain. This is less reliable so I wouldn't spend too much time on it. You never told me what you are planning on using this for?

OK, there seems to be a perfect storm where you're using "device injection" which is a work in progress by trying to run iOS on Mac and you're expecting the initialiser to inject which for some reason it doesn't. If you change your code to do the things you want to be injectable in a separate configureView() method you call from the init() it should work. It's not clear why injecting the initialiser is not working (I tried some things and it does in the simulator) but they are a bit different from normal methods so I'll give up at this point on this particular combination though I may think about it.

... Obviously, to do this you need to make textView an instance variable but less obvious is it needs to be a var and not a let. These weird problems crop up with device injection which could be described as "ambitious". I could switch it back to dynamic loading a .dylib but as I mentioned that was so ponderous to get the code signing right one wonders if it is worth supporting (The situation is better using the "HotReloading" project where you would only you need to set a default on the InjectionIII app with the code signing identity as a build phase):

defaults write com.johnholdsworth.InjectionIII "$PROJECT_FILE_PATH" $EXPANDED_CODE_SIGN_IDENTITY

It seems like the problem with the initializer occurs with device injection on a real device too, not only on the Mac.
What do you mean by using the HotReloading project?
If dynamically loading a .dylib works as good as in the simulator, I would prefer to use it. Why is it ponderous to get the code signing right?

I tried to make Injection to dynamically load a .dylib, by deleting deviceUnlock from defaults and adding the command you mentioned to the build phase, and removing InjectionScrach from HotReloading dependencies. But when I changed a file the Injection icon's color was stuck on green.

ok I also had to replace InjectionServer.startServer(INJECTION_ADDRESS) with:

        DeviceServer.multicastServe(HOTRELOADING_MULTICAST,
                                    port: HOTRELOADING_PORT)
        DeviceServer.startServer("*"+HOTRELOADING_PORT)

and it works perfectly.

Is there an easier way to make Injection load a dylib even when running on device?

You can get a reliable injection experience by using the InjectionIII app and loading a bundle instead of adding the HotReloading project (which happens to resort to device injection as the code is currently). To code sign you need to follow the instructions in the InjectionIII README. This works. I'm of two minds about leaving "device injection" in as there are many issues to resolve one day but it does work quite well with SwiftUI and allows me to say you can have something like previews on a real device. Leaving it in also generate feedback about what it's problems are. I may put in a message saying "your milage may vary" when it starts up. I'm closing this now as this isn't a very important area of injection where the focus has to be on iOS in the simulator.

The reason for InjectionScratch and all the problems it creates is because you can't load a .dylib on a iPhone since iSO 10 so I simulate it. Perhaps it is possible you can load a .dylib on a Mac if you code sign it correctly. Do you know what #if I can use to detect the situation where an iOS app is being run on an M1 Mac? But as I say, this isn't a priority.

I may have closed this prematurely as I have a terrible flu today. If you can come up with a PR that avoids using the device injection code in the specific configuration of iOS on Mac using HotReloading I'll take a look at it. Thanks!

The easiest place to turn off device injection is here if you can modify the #if correctly.
https://github.com/johnno1962/InjectionScratch/blob/main/Sources/InjectionScratch/injected_code.s#L17

There isn't an easy way to check if running an iOS app on Mac in compile time - The only way I found was to check the environment variables TARGET_DEVICE_PLATFORM_NAME == macosx && PLATFORM_NAME == iphoneos. But I don't know how to set a macro based on environment variables in a Swift package.
Is there a place which would make sense to do this check at runtime?

hmm if I use Injection as an iOS bundle instead of a Swift package it will connect to a server and use the .dylib injection method, which is exactly what I needed!

I wondered if it wouldn't be possible to detect at compile time. You can do a run time check to switch off device injection in InjectionClient.swift when using HotReloading as below.

        #if canImport(InjectionScratch)
        var isiOSAppOnMac = false
        if #available(iOS 14.0, *) {
            isiOSAppOnMac = ProcessInfo.processInfo.isiOSAppOnMac
        }
        if !isiOSAppOnMac, let scratch = loadScratchImage(nil, 0, self, nil) {
            writeCommand(InjectionResponse
                            .scratchPointer.rawValue, with: nil)
            writePointer(scratch)
        }
        #endif

But you need this as a build phase to get the code signing right:

defaults write com.johnholdsworth.InjectionIII "$PROJECT_FILE_PATH" $EXPANDED_CODE_SIGN_IDENTITY

I may commit this to the repo but how to document??

I've pushed a commit implementing this change. No more device injection for iOS on Mac when using HotReloading.

Thank you so much!