纯 C 写个 iOS App(误)

一个 iOS app 首先是由 main.m 内的 main 函数开始的. 如今就先建立 Single View App 项目, 而后把全部的 .m 文件都删掉, 建一个 main.c 文件.
一般咱们看到的 main.m 的内的代码是这样的app

// 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 版本的ui

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]).spa

如今已经抄做业抄了一个 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 类型的返回值, 可是它永远不会返回.code

而后这玩意的前两个参数就无论了, 就是处理一下 main 函数传进来的参数, 第三个参数是须要传入 UIApplication 或者其子类的名称, 这里传 nil 就默认用 UIApplication.
咱们须要关注的是最后一个参数, 这个参数让咱们传一个代理类的字符串, 就是给应用设置个代理, 也就是讲接下来咱们要实现一个代理类.cdn

因此咱们如今来建立个 AppDelegate.c 的文件. 继续照以前的套路走, 先看 AppDelegate.m 代码, AppDelegate 这个类有个 window 的属性, 有下面这个函数blog

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    return YES;
}
复制代码

咱们先得实现一个 AppDelegate class 才行. 通常稍微了解过 NSObject 定义的都晓得, 每一个类有个 isa 用来标记这个类是什么, 具体怎样就不解释了, 反正不少 runtime 以及 header 文件的定义都能找到.字符串

除了搞个 class, 咱们还要实现那个 application:didFinishLaunchingWithOptions: 函数get

// 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 启用它就行了.

设置 Xcode objc_msgSend

忘了还有个事要作, 那就是把这几个东西导入到项目中

导入库

其实就是经过 runtime 来各类调用函数, 这个拿来玩玩就行了. 好吧, 先这样吧.

相关文章
相关标签/搜索