Reputation: 186
Using the Objective C runtime, I am trying to create an AppDelegate for my iOS app at runtime. This is just for research purposes, I have no intent to ship this.
My steps so far are:
AppDelegate
._window
.window
that uses the instance variable as a backing variable and use two C functions as getters and setters.application:didFinishLaunchingWithOptions:
with an implementation in C that returns YES
. At this point, the class implements the UIApplicationDelegate
protocol.However, when I launch the program on a connected iPhone, the screen stays black, despite the program actually not crashing. The debugger shows that my implementations get called. Following the documentation, the OS first checks if my window
property is nil
(which it is), then creates a UIWindow itself and uses my setter to assign the UIWindow to my delegate instance.
When I access this window, it seems fully functional: It has the usual bounds (NSRect: Height = 667; Width = 375; X = 0; Y = 0;) and my custom ViewController gets initiated, viewDidLoad
is called.
Can anybody help me to find out why the screen stays black, though?
Calls to [self.window makeKeyAndVisible]
don't work, the screen stays black. If I create a window myself in application:didFinishLaunchingWithOptions:
and assign the rootViewController of the previous UIWindow
, that works. So it's definitely the UIWindow in my delegate that's somehow broken.
Here is the code I am using:
#import <UIKit/UIKit.h>
#import <objc/runtime.h>
#import <stdio.h>
id getter(id self, SEL _cmd) {
Ivar ivar = class_getInstanceVariable(objc_getClass("AppDelegate"), "_window");
id var = object_getIvar(self, ivar);
printf("is nil: %s\n", var == nil ? "true" : "false");
if (var != nil) {
printf("%s\n", [[NSString stringWithFormat:@"%@", CGRectCreateDictionaryRepresentation(((UIWindow *) var).bounds)] UTF8String]);
}
return object_getIvar(self, ivar);
}
void setter(id self, SEL _cmd, id new) {
printf("setter...\n");
Ivar ivar = class_getInstanceVariable(objc_getClass("AppDelegate"), "_window");
object_setIvar(self, ivar, new);
}
BOOL didFinishLaunching(id self, SEL _cmd, id launchOptions) {
printf("didFinishLaunching called\n");
return YES;
}
int main(int argc, char * argv[]) {
@autoreleasepool {
Class delegate = objc_allocateClassPair([NSObject class], "AppDelegate", 0);
class_addIvar(delegate, "_window", sizeof(UIWindow *), rint(log2(sizeof(UIWindow *))), @encode(UIWindow *));
objc_property_attribute_t type = { "T", "@\"UIWindow\"" };
objc_property_attribute_t strength = { "&", "" };
objc_property_attribute_t atomic = { "N", "" };
objc_property_attribute_t backingVar = { "V", "_window" };
objc_property_attribute_t attrs[] = { type, strength, atomic, backingVar };
class_addProperty(delegate, "window", attrs, 4);
class_addMethod(delegate, @selector(window), (IMP) getter, "@@:");
class_addMethod(delegate, @selector(setWindow:), (IMP) setter, "v@:@");
class_addMethod(delegate, @selector(application:didFinishLaunchingWithOptions:),
(IMP) didFinishLaunching, "B@:@");
objc_registerClassPair(delegate);
return UIApplicationMain(argc, argv, nil, @"AppDelegate");
}
}
And this is the stdout output:
is nil: true
setter...
didFinishLaunching called
is nil: false
{
Height = 667;
Width = 375;
X = 0;
Y = 0;
}
is nil: false
{
Height = 667;
Width = 375;
X = 0;
Y = 0;
}
ViewController.viewDidLoad() called
is nil: false
{
Height = 667;
Width = 375;
X = 0;
Y = 0;
}
Upvotes: 2
Views: 359
Reputation: 186
Found the issue: ARC was releasing my UIWindow.
Changing the setter to
void setter(id self, SEL _cmd, id new) {
Ivar ivar = class_getInstanceVariable(object_getClass(self), "_window");
id old = object_getIvar(self, ivar);
if (![old isEqual: new]) {
if(old != nil) {
objc_msgSend(old, sel_getUid("release"));
}
object_setIvar(self, ivar, new);
objc_msgSend(new, sel_getUid("retain"));
}
}
fixes this.
Upvotes: 2