一个 iOS app 首先是由 main.m
内的 main 函数开始的. 现在就先创建 Single View App 项目, 然后把所有的 .m
文件都删掉, 建一个 main.c
文件.
通常我们看到的 main.m
的内的代码是这样的
// main.m
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
那先照着这个写就好了, 但是这个 @autoreleasepool
这个怎么处理?
我们晓得这是个语法糖, 在 ARC 出来之后编译器就不让我们使用 NSAutoreleasePool, 原先是这样的
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[pool release];
那就仿照着这玩意写成 C 版本的
int main(int argc, char **argv) {
id pool = objc_msgSend(
objc_msgSend((id) objc_getClass("NSAutoreleasePool"),
sel_registerName("alloc")),
sel_registerName("init"));
UIApplicationMain(argc, argv, nil, CFSTR("AppDelegate"));
objc_msgSend(pool, sel_registerName("drain"));
}
CFSTR
这个宏可以从 C 字符串创建一个 CFString
的引用(CFStringRef
), 这玩意可以用来代替我们这里的 NSStringFromClass([AppDelegate class])
.
现在已经抄作业抄了一个 main.c
, 不过还有个问题, UIApplicationMain
这个函数从哪里跑出来的.
这个是一个用于创建我们应用实例的函数, 但是我们没法直接使用它, 因为它是在 UIApplication.h
文件, 不过我们可以这样搞(这里顺便把 runtime 那些头文件补上吧)
#include <CoreFoundation/CoreFoundation.h>
#include <objc/runtime.h>
#include <objc/message.h>
extern int UIApplicationMain(int, ...);
int main(int argc, char **argv) {
id pool = objc_msgSend(
objc_msgSend((id) objc_getClass("NSAutoreleasePool"),
sel_registerName("alloc")),
sel_registerName("init"));
UIApplicationMain(argc, argv, nil, CFSTR("AppDelegate"));
objc_msgSend(pool, sel_registerName("drain"));
}
这里再讲一下 UIApplicationMain
这个函数, 它虽然有 int
类型的返回值, 但是它永远不会返回.
然后这玩意的前两个参数就不管了, 就是处理一下 main
函数传进来的参数, 第三个参数是需要传入 UIApplication
或者其子类的名称, 这里传 nil
就默认用 UIApplication
.
我们需要关注的是最后一个参数, 这个参数让我们传一个代理类的字符串, 就是给应用设置个代理, 也就是讲接下来我们要实现一个代理类.
所以我们现在来创建个 AppDelegate.c
的文件. 继续照之前的套路走, 先看 AppDelegate.m
代码, AppDelegate
这个类有个 window
的属性, 有下面这个函数
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
return YES;
}
我们先得实现一个 AppDelegate
class 才行. 一般稍微了解过 NSObject
定义的都晓得, 每个类有个 isa
用来标记这个类是什么, 具体怎样就不解释了, 反正很多 runtime 以及 header
文件的定义都能找到.
除了搞个 class, 我们还要实现那个 application:didFinishLaunchingWithOptions:
函数
// AppDelegate.c
#include <objc/runtime.h>
#include <objc/message.h>
#include <CoreGraphics/CoreGraphics.h>
typedef struct AppDelegate {
Class isa;
id window;
} AppDelegate;
Class AppDelegateClass;
BOOL applicationDidFinishLaunchingWithOptions(
AppDelegate *self, SEL _cmd, void *application, void *options) {
self->window = objc_msgSend((id) objc_getClass("UIWindow"), sel_getUid("alloc"));
self->window = objc_msgSend(self->window, sel_getUid("initWithFrame:"),
(struct CGRect) {0, 0, 320, 568});
id viewController = objc_msgSend(
objc_msgSend((id) objc_getClass("UIViewController"), sel_getUid("alloc")),
sel_getUid("init"));
id view = objc_msgSend(
objc_msgSend((id) objc_getClass("View"), sel_getUid("alloc")),
sel_getUid("initWithFrame:"),
(struct CGRect) {0, 0, 320, 568});
objc_msgSend(objc_msgSend(viewController, sel_getUid("view")), sel_getUid("addSubview:"), view);
objc_msgSend(self->window, sel_getUid("setRootViewController:"), viewController);
objc_msgSend(self->window, sel_getUid("makeKeyAndVisible"));
return YES;
}
__attribute__((constructor))
static void initAppDelegate() {
AppDelegateClass = objc_allocateClassPair((Class) objc_getClass("UIResponder"), "AppDelegate", 0);
class_addIvar(AppDelegateClass, "window", sizeof(id), 0, "@");
// - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
class_addMethod(AppDelegateClass, sel_registerName("application:didFinishLaunchingWithOptions:"), (IMP) applicationDidFinishLaunchingWithOptions, "i@:@@");
objc_registerClassPair(AppDelegateClass);
}
这里通过 __attribute__((constructor))
这个编译属性让这个函数在 main
函数之前走. 通过 runtime 搞了个 AppDelegateClass
出来. 由于我比较穷, 手机还是 iPhone 5s, 所以设了 (struct CGRect) {0, 0,320, 568})
.
通过引入 CoreGraphics.h
才可以让编译通过 CGRect
.
现在了解到创建一个 class 的套路之后, 这里在 applicationDidFinishLaunchingWithOptions
使用到了 View
class, 我们就创建一个 View.c 文件来自定义视图什么
// View.c
#include <objc/runtime.h>
#include <CoreGraphics/CoreGraphics.h>
Class ViewClass;
extern CGContextRef UIGraphicsGetCurrentContext();
void viewDrawRect(id self, SEL _cmd, CGRect rect) {
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColor(context, (CGFloat[]) {1, 0, 0, 1});
CGContextAddRect(context, (struct CGRect) {0, 0, 320, 568});
CGContextFillPath(context);
}
__attribute__((constructor))
static void initView() {
ViewClass = objc_allocateClassPair((Class) objc_getClass("UIView"), "View", 0);
class_addMethod(ViewClass, sel_getUid("drawRect:"), (IMP) viewDrawRect, "v@:");
objc_registerClassPair(ViewClass);
}
这里直接用 CoreGraphics
来绘制视图.
然后编译执行看看效果, 应该是一个空白的红色视图. 如果编译出错了, 可能是现在的 Xcode 禁止 objc_msgSend
函数的调用, 在 Build Settings 启用它就好了.
忘了还有个事要做, 那就是把这几个东西导入到项目中
其实就是通过 runtime 来各种调用函数, 这个拿来玩玩就好了. 好吧, 先这样吧.
评论 (0)