純 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 來各類調用函數, 這個拿來玩玩就行了. 好吧, 先這樣吧.

相關文章
相關標籤/搜索