Cara menulis aplikasi iOS murni di C

357

Saya baca di sini Belajar C Sebelum Objective-C?

Biasanya saya kemudian mengganti beberapa kode Obj-C dengan kode C murni (setelah semua Anda dapat mencampurnya sebanyak yang Anda suka, isi metode Obj-C dapat sepenuhnya, kode C murni)

Apakah ini benar?

Apakah mungkin membuat aplikasi iPhone murni dalam bahasa pemrograman C?

001
sumber
9
@ Thilo itu mungkin ... menggunakan runtime objc
Richard J. Ross III
60
Bisa jadi? Iya. Dan sama sekali tidak ada gunanya. Hampir semua API dan pola sistem iOS berasal dari Objective-C & Objective-C API. Anda akan membuang-buang waktu Anda; jika Anda ingin mempelajari cara memprogram iOS, mulailah dengan Objective-C dan ambil C di sepanjang jalan.
bbum
111
Seorang Pemrogram Nyata akan melakukan ini menggunakan assembler ARM.
Kristopher Johnson
12
@ Bbum Saya tidak akan mengatakan itu tidak ada gunanya. Ketika saya port game saya ke PC, saya lebih dari senang semuanya ditulis dalam C ++ (ya, dimungkinkan untuk melakukan semuanya di C ++ juga). Saya bisa port game saya dalam beberapa hari, jika saya menggunakan Obj-c di mana-mana itu akan memakan waktu berbulan-bulan.
fbafelipe
6
Saya tidak menyarankan dari jauh bahwa tujuan-c di mana-mana adalah persyaratan. Arsitektur umum adalah mesin C ++ portabel dengan lapisan objektif-c, di atas. Menghindari OBJC sepenuhnya adalah buang-buang waktu; Anda menggunakannya untuk mengakses semua jenis fitur iOS standar yang bahkan dapat dimanfaatkan oleh gim portabel.
bbum

Jawaban:

778

Sial, butuh beberapa saat tapi saya mengerti:

main.c:

#include <CoreFoundation/CoreFoundation.h>

#include <objc/runtime.h>
#include <objc/message.h>

// This is a hack. Because we are writing in C, we cannot out and include 
// <UIKit/UIKit.h>, as that uses Objective-C constructs.
// however, neither can we give the full function declaration, like this:
// int UIApplicationMain (int argc, char *argv[], NSString *principalClassName, NSString *delegateClassName);
// So, we rely on the fact that for both the i386 & ARM architectures, 
// the registers for parameters passed in remain the same whether or not 
// you are using VA_ARGS. This is actually the basis of the objective-c 
// runtime (objc_msgSend), so we are probably fine here,  this would be
// the last thing I would expect to break.
extern int UIApplicationMain(int, ...);

// Entry point of the application. If you don't know what this is by now, 
// then you probably shouldn't be reading the rest of this post.
int main(int argc, char *argv[])
{
    // Create an @autoreleasepool, using the old-stye API. 
    // Note that while NSAutoreleasePool IS deprecated, it still exists 
    // in the APIs for a reason, and we leverage that here. In a perfect 
    // world we wouldn't have to worry about this, but, remember, this is C.
    id autoreleasePool = objc_msgSend(objc_msgSend(objc_getClass("NSAutoreleasePool"), sel_registerName("alloc")), sel_registerName("init"));

    // Notice the use of CFSTR here. We cannot use an objective-c string 
    // literal @"someStr", as that would be using objective-c, obviously.
    UIApplicationMain(argc, argv, nil, CFSTR("AppDelegate"));

    objc_msgSend(autoreleasePool, sel_registerName("drain"));
}

AppDelegate.c:

#import <objc/runtime.h>
#import <objc/message.h>

// This is equivalent to creating a @class with one public variable named 'window'.
struct AppDel
{
    Class isa;

    id window;
};

// This is a strong reference to the class of the AppDelegate 
// (same as [AppDelegate class])
Class AppDelClass;

// this is the entry point of the application, same as -application:didFinishLaunchingWithOptions:
// note the fact that we use `void *` for the 'application' and 'options' fields, as we need no reference to them for this to work. A generic id would suffice here as well.
BOOL AppDel_didFinishLaunching(struct AppDel *self, SEL _cmd, void *application, void *options)
{
    // we +alloc and -initWithFrame: our window here, so that we can have it show on screen (eventually).
    // this entire method is the objc-runtime based version of the standard View-Based application's launch code, so nothing here really should surprise you.
    // one thing important to note, though is that we use `sel_getUid()` instead of @selector().
    // this is because @selector is an objc language construct, and the application would not have been created in C if I used @selector.
    self->window = objc_msgSend(objc_getClass("UIWindow"), sel_getUid("alloc"));
    self->window = objc_msgSend(self->window, sel_getUid("initWithFrame:"), (struct CGRect) { 0, 0, 320, 480 });

    // here, we are creating our view controller, and our view. note the use of objc_getClass, because we cannot reference UIViewController directly in C.
    id viewController = objc_msgSend(objc_msgSend(objc_getClass("UIViewController"), sel_getUid("alloc")), sel_getUid("init"));

    // creating our custom view class, there really isn't too much 
    // to say here other than we are hard-coding the screen's bounds, 
    // because returning a struct from a `objc_msgSend()` (via 
    // [[UIScreen mainScreen] bounds]) requires a different function call
    // and is finicky at best.
    id view = objc_msgSend(objc_msgSend(objc_getClass("View"), sel_getUid("alloc")), sel_getUid("initWithFrame:"), (struct CGRect) { 0, 0, 320, 480 });

    // here we simply add the view to the view controller, and add the viewController to the window.
    objc_msgSend(objc_msgSend(viewController, sel_getUid("view")), sel_getUid("addSubview:"), view);
    objc_msgSend(self->window, sel_getUid("setRootViewController:"), viewController);

    // finally, we display the window on-screen.
    objc_msgSend(self->window, sel_getUid("makeKeyAndVisible"));

    return YES;
}

// note the use of the gcc attribute extension (constructor). 
// Basically, this lets us run arbitrary code before program startup,
// for more information read here: http://stackoverflow.com/questions/2053029
__attribute__((constructor))
static void initAppDel()
{
    // This is objc-runtime gibberish at best. We are creating a class with the 
    // name "AppDelegate" that is a subclass of "UIResponder". Note we do not need
    // to register for the UIApplicationDelegate protocol, that really is simply for 
    // Xcode's autocomplete, we just need to implement the method and we are golden.
    AppDelClass = objc_allocateClassPair(objc_getClass("UIResponder"), "AppDelegate", 0);

    // Here, we tell the objc runtime that we have a variable named "window" of type 'id'
    class_addIvar(AppDelClass, "window", sizeof(id), 0, "@");

    // We tell the objc-runtime that we have an implementation for the method
    // -application:didFinishLaunchingWithOptions:, and link that to our custom 
    // function defined above. Notice the final parameter. This tells the runtime
    // the types of arguments received by the function.
    class_addMethod(AppDelClass, sel_getUid("application:didFinishLaunchingWithOptions:"), (IMP) AppDel_didFinishLaunching, "i@:@@");

    // Finally we tell the runtime that we have finished describing the class and 
    // we can let the rest of the application use it.
    objc_registerClassPair(AppDelClass);
}

Lihat.c

#include <objc/runtime.h>

// This is a strong reference to the class of our custom view,
// In case we need it in the future.
Class ViewClass;

// This is a simple -drawRect implementation for our class. We could have 
// used a UILabel  or something of that sort instead, but I felt that this 
// stuck with the C-based mentality of the application.
void View_drawRect(id self, SEL _cmd, struct CGRect rect)
{
    // We are simply getting the graphics context of the current view, 
    // so we can draw to it
    CGContextRef context = UIGraphicsGetCurrentContext();

    // Then we set it's fill color to white so that we clear the background.
    // Note the cast to (CGFloat []). Otherwise, this would give a warning
    //  saying "invalid cast from type 'int' to 'CGFloat *', or 
    // 'extra elements in initializer'. Also note the assumption of RGBA.
    // If this wasn't a demo application, I would strongly recommend against this,
    // but for the most part you can be pretty sure that this is a safe move 
    // in an iOS application.
    CGContextSetFillColor(context, (CGFloat []){ 1, 1, 1, 1 });

    // here, we simply add and draw the rect to the screen
    CGContextAddRect(context, (struct CGRect) { 0, 0, 320, 480 });
    CGContextFillPath(context);

    // and we now set the drawing color to red, then add another rectangle
    // and draw to the screen
    CGContextSetFillColor(context, (CGFloat []) { 1, 0, 0, 1 });
    CGContextAddRect(context, (struct CGRect) { 10, 10, 20, 20 });
    CGContextFillPath(context);
}

// Once again we use the (constructor) attribute. generally speaking, 
// having many of these is a very bad idea, but in a small application 
// like this, it really shouldn't be that big of an issue.
__attribute__((constructor))
static void initView()
{
    // Once again, just like the app delegate, we tell the runtime to 
    // create a new class, this time a subclass of 'UIView' and named 'View'.
    ViewClass = objc_allocateClassPair(objc_getClass("UIView"), "View", 0);

    // and again, we tell the runtime to add a function called -drawRect: 
    // to our custom view. Note that there is an error in the type-specification
    // of this method, as I do not know the @encode sequence of 'CGRect' off 
    // of the top of my head. As a result, there is a chance that the rect 
    // parameter of the method may not get passed properly.
    class_addMethod(ViewClass, sel_getUid("drawRect:"), (IMP) View_drawRect, "v@:");

    // And again, we tell the runtime that this class is now valid to be used. 
    // At this point, the application should run and display the screenshot shown below.
    objc_registerClassPair(ViewClass);    
}

Itu jelek, tapi berhasil.

Jika Anda ingin mengunduh ini, Anda bisa mendapatkannya dari dropbox saya di sini

Anda bisa mendapatkannya dari repositori GitHub saya di sini :

ScreenShot

Richard J. Ross III
sumber
124
Bagus. Jadi untuk menghindari belajar Objective-C (yang saya pikir adalah inti dari pertanyaan) Anda sekarang harus mempelajari detail implementasi dan API tingkat-C dari runtime Objective-C.
Thilo
5
Jika Anda memutuskan untuk mengonversikan ini menjadi rakitan, sesuai beberapa saran, pastikan untuk melakukannya di ARM (set instruksi reguler dan ibu jari!) Dan di x86 sehingga berfungsi di simulator. Mungkin juga PowerPC untuk ukuran yang baik, jika Anda ingin port ke Mac OS X v10.4.
Adam Rosenfield
58
Secara teknis, ini bukan C murni! Itu @"AppDelegateadalah NSString yang konstan dan tidak akan dikompilasi dengan kompiler C-only. Gunakan CFSTR("AppDelegate")sebagai gantinya.
2
Tidak ada pasangan yang tersinggung. Anda perhatikan Anda baru saja mendapat dukungan dari saya? (Dan ya, rasa hormat untuk memiliki 2 kali lebih banyak rep daripada saya meskipun 3 tahun lebih muda ...)
2
Sial ... menggerutu menggerutu ... Yah, aku masih belum akan menghapus jawaban saya. BUAHAHAHAHAHAHAHA
CodaFi
40

Objective-C adalah superset dari bahasa-C, jadi secara teori dimungkinkan untuk menulis sebuah program seluruhnya dalam C, namun, kecuali Anda benar-benar berpengalaman OpenGL ES, Anda harus melakukan setidaknya beberapa objC ( Bahkan sampel Rich memiliki const NSString * di dalamnya ), kalau tidak Anda harus menulis sendiri pandangannya.

OKE, di atas benar-benar salah. Biar saya katakan, saya heran Rich mencapai tujuan yang tinggi ini, jadi saya memindahkannya ke mac (sumber di sini ). File-file di bawah ini tidak memiliki header, tidak memiliki pranala ke Kakao, juga proyek tidak memiliki nib:

AppDelegate.m

#include <objc/runtime.h>
#include <objc/message.h>

extern id NSApp;

struct AppDel
{
    Class isa;

    //Will be an NSWindow later, for now, it's id, because we cannot use pointers to ObjC classes
    id window;
};


// This is a strong reference to the class of the AppDelegate
// (same as [AppDelegate class])
Class AppDelClass;

BOOL AppDel_didFinishLaunching(struct AppDel *self, SEL _cmd, id notification) {
    //alloc NSWindow
    self->window = objc_msgSend(objc_getClass("NSWindow"),
                                sel_getUid("alloc"));
    //init NSWindow
    //Adjust frame.  Window would be about 50*50 px without this
    //specify window type.  We want a resizeable window that we can close.
    //use retained backing because this thing is small anyhow
    //return no because this is the main window, and should be shown immediately
    self->window = objc_msgSend(self->window,
                                sel_getUid("initWithContentRect:styleMask:backing:defer:"),(NSRect){0,0,1024,460}, (NSTitledWindowMask|NSClosableWindowMask|NSResizableWindowMask|NSMiniaturizableWindowMask),NSBackingStoreRetained,NO);

    //send alloc and init to our view class.  Love the nested objc_msgSends!
    id view = objc_msgSend(objc_msgSend(objc_getClass("View"), sel_getUid("alloc")), sel_getUid("initWithFrame:"), (struct CGRect) { 0, 0, 320, 480 });

    // here we simply add the view to the window.
    objc_msgSend(self->window, sel_getUid("setContentView:"), view);
    objc_msgSend(self->window, sel_getUid("becomeFirstResponder"));

    //makeKeyOrderFront: NSWindow to show in bottom left corner of the screen
    objc_msgSend(self->window,
                 sel_getUid("makeKeyAndOrderFront:"),
                 self);
    return YES;
}

static void initAppDel()
{
    //Our appDelegate should be NSObject, but if you want to go the hard route, make this a class pair of NSApplication and try initing those awful delegate methods!
    AppDelClass = objc_allocateClassPair((Class)
                                         objc_getClass("NSObject"), "AppDelegate", 0);
    //Change the implementation of applicationDidFinishLaunching: so we don't have to use ObjC when this is called by the system.
    class_addMethod(AppDelClass,
                    sel_getUid("applicationDidFinishLaunching:"),
                    (IMP) AppDel_didFinishLaunching, "i@:@");

    objc_registerClassPair(AppDelClass);
}

void init_app(void)
{
    objc_msgSend(
                 objc_getClass("NSApplication"),
                 sel_getUid("sharedApplication"));

    if (NSApp == NULL)
    {
        fprintf(stderr,"Failed to initialized NSApplication...  terminating...\n");
        return;
    }

    id appDelObj = objc_msgSend(
                                objc_getClass("AppDelegate"),
                                sel_getUid("alloc"));
    appDelObj = objc_msgSend(appDelObj, sel_getUid("init"));

    objc_msgSend(NSApp, sel_getUid("setDelegate:"), appDelObj);
    objc_msgSend(NSApp, sel_getUid("run"));
}

//there doesn't need to be a main.m because of this little beauty here.
int main(int argc, char** argv)
{
    //Initialize a valid app delegate object just like [NSApplication sharedApplication];
    initAppDel();
    //Initialize the run loop, just like [NSApp run];  this function NEVER returns until the app closes successfully.
    init_app();
    //We should close acceptably.
    return EXIT_SUCCESS;
}

Lihat.m

#include <objc/runtime.h>
#include <objc/message.h>
#include <ApplicationServices/ApplicationServices.h>

// This is a strong reference to the class of our custom view,
// In case we need it in the future.
Class ViewClass;


// This is a simple -drawRect implementation for our class. We could have
// used a UILabel  or something of that sort instead, but I felt that this
// stuck with the C-based mentality of the application.
void View_drawRect(id self, SEL _cmd, CGRect rect)
{
    //make a red NSColor object with its convenience method
    id red  = objc_msgSend(objc_getClass("NSColor"), sel_getUid("redColor"));

    // fill target rect with red, because this is it!
    NSRect rect1 = NSMakeRect ( 21,21,210,210 );
    objc_msgSend(red, sel_getUid("set"));
    NSRectFill ( rect1 );
}

// Once again we use the (constructor) attribute. generally speaking,
// having many of these is a very bad idea, but in a small application
// like this, it really shouldn't be that big of an issue.
__attribute__((constructor))
static void initView()
{

    // Once again, just like the app delegate, we tell the runtime to
    // create a new class, this time a subclass of 'UIView' and named 'View'.
    ViewClass = objc_allocateClassPair((Class) objc_getClass("NSView"), "View", 0);

    // and again, we tell the runtime to add a function called -drawRect:
    // to our custom view. Note that there is an error in the type-specification
    // of this method, as I do not know the @encode sequence of 'CGRect' off
    // of the top of my head. As a result, there is a chance that the rect
    // parameter of the method may not get passed properly.
    class_addMethod(ViewClass, sel_getUid("drawRect:"), (IMP) View_drawRect, "v@:");

    // And again, we tell the runtime that this class is now valid to be used.
    // At this point, the application should run and display the screenshot shown below.
    objc_registerClassPair(ViewClass);
}

prefix.pch

//
// Prefix header for all source files of the 'CBasedMacApp' target in the 'CBasedMacApp' project
//

#ifdef __OBJC__
#import <Foundation/Foundation.h>
#import <AppKit/AppKit.h>
#endif

masukkan deskripsi gambar di sini

CodaFi
sumber
8
Tidak benar, Anda dapat menggunakan runtime objc untuk membangun aplikasi dalam C, beri saya beberapa menit dan saya akan menunjukkan kepada Anda
Richard J. Ross III
44
Ya, dan Anda dapat menggali foundation dengan sendok, tetapi itu tidak membuatnya menjadi ide yang baik atau sangat efektif.
bbum
10
@ MahmoudAl-Qudsi Saya tidak menyerah :)
Richard J. Ross III
8
Nah, keterampilan itu mungkin juga berguna ketika Anda berada di penebusan Shawshank ...
Hejazzman
2
Ya. Hal yang membuat saya, adalah jika bukan karena kode runtime modern, ini akan bekerja pada setiap Mac dengan X dalam nama perangkat lunaknya.
CodaFi
14

Saya baca di sini Belajar C Sebelum Objective-C?

Biasanya saya kemudian mengganti beberapa kode Obj-C dengan kode C murni (setelah semua Anda dapat mencampurnya sebanyak yang Anda suka, isi metode Obj-C dapat sepenuhnya, kode C murni)

Apakah ini benar?

Bisakah saya membuat aplikasi iPhone murni dalam bahasa pemrograman C?

Kutipan yang dikutip itu benar, tetapi jawaban untuk pertanyaan Anda adalah tidak.

Untuk menggambarkan apa jawaban Mecki pada pertanyaan lain yang dibicarakan:

- (void) drawRect:(CGRect)dirtyRect { //Objective-C

    CGContextRef context = UIGraphicsGetCurrentContext();  //C
    CGContextSetRGBFillColor(context, 1.0, 0.0, 0.0, 1.0); //C
    CGContextFillRect(context, dirtyRect);                 //C

} //Objective-C (balances above “- (void) drawRect:…” line)

Tidak ada yang lain selain kode C murni dalam metode ini, tetapi metode itu sendiri adalah kode Objective-C, seperti kelas yang berisi metode ini.

Jadi adalah mungkin untuk melakukan apa yang dikatakan Mecki, tetapi Anda tidak bisa (secara praktis — seperti yang diperlihatkan Richard J. Ross III, secara teknis dimungkinkan tetapi cukup banyak mengetik) menulis seluruh program Cocoa Touch dalam C. murni

Peter Hosey
sumber
-4

Sebenarnya, beberapa kode yang diposting di sini, saat ditulis dalam C, masih memanggil kode objektif-C :). Saya tidak tahu apakah itu benar-benar cocok dengan skenario dari poster asli ketika dia bertanya

Apakah mungkin membuat aplikasi iPhone murni dalam bahasa pemrograman C?

tapi saya akan setuju dengan orang-orang yang mengatakan bahwa, secara umum dan untuk aplikasi dengan GUI, Anda perlu menulis GUI Anda di OpenGL (yang merupakan C).

Saya pikir itulah yang dilakukan sebagian besar game, bukan? Meskipun saya tidak yakin apakah ada akses ke I / O iPhone (layar sentuh misalnya) di C.

Last but not least, orang-orang yang menulis kode di atas batu! :)

make.it.floss
sumber
1
sesuai dengan persyaratan, kami menggunakan kode c dalam pengembangan iPhone dan iOS.
Olahraga
objc_msgSend()adalah murni C. Fakta bahwa saya memanggil initWithFrame:tidak masalah, karena implementasi metode adalah fungsi C juga.
Gabriele Petronella
objc_msgSend () adalah fungsi C, ya, tapi itu adalah bagian dari runtime Objective-C, kan?
make.it.floss
Saya tidak bisa melihat konstruk Obj-C dalam kode yang diposting di sana. Tapi tetap saja ini berhasil bahkan memanggil perpustakaan obj-c dengan cara "C"!
techcraver