Cómo escribir una aplicación iOS solo en C

357

Leí aquí Aprender C antes de Objective-C?

Por lo general, luego reemplazo algún código Obj-C con código C puro (después de todo, puede mezclarlos tanto como desee, el contenido de un método Obj-C puede ser completamente, código C puro)

¿Es esto cierto?

¿Es posible construir una aplicación para iPhone únicamente en el lenguaje de programación C?

001
fuente
99
@thilo es posible ... usando el tiempo de ejecución objc
Richard J. Ross III
6060
¿Posible? Si. Y completamente inútil. Casi todas las API y patrones del sistema iOS se derivan de las API Objective-C y Objective-C. Estarás perdiendo tu tiempo; Si desea aprender a programar iOS, comience con Objective-C y seleccione C en el camino.
bbum
111
Un programador real haría esto usando el ensamblador ARM.
Kristopher Johnson
12
@bbum No diría que no tiene sentido. Cuando transfirí mi juego a la PC, estaba más que feliz de que todo estuviera escrito en C ++ (sí, también es posible hacer todo en C ++). Podría portar mi juego en unos pocos días, si estuviera usando Obj-c en todas partes, llevaría meses.
fbafelipe
66
No sugerí remotamente que el objetivo-c en todas partes fuera un requisito. Una arquitectura común es un motor C ++ portátil con una capa, a veces muy delgada, de Objective-C en la parte superior. Evitar OBJC por completo es una pérdida de tiempo; lo usa para acceder a todo tipo de características estándar de iOS que incluso un juego portátil podría aprovechar.
bbum 01 de

Respuestas:

778

Maldición, me tomó un tiempo pero lo entendí:

C Principal:

#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);
}

View.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);    
}

Es feo, pero funciona.

Si desea descargar esto, puede obtenerlo desde mi Dropbox aquí

Puede obtenerlo de mi repositorio de GitHub aquí :

Captura de pantalla

Richard J. Ross III
fuente
124
Excelente. Entonces, para evitar aprender Objective-C (que creo que era la esencia de la pregunta) ahora debe aprender los detalles de implementación y la API de nivel C del tiempo de ejecución de Objective-C.
Thilo
55
Si decide convertir esto a ensamblaje, según varias de las sugerencias, asegúrese de hacerlo en ARM (¡conjuntos de instrucciones regulares y manuales!) Y en x86 para que funcione en el simulador. Tal vez también PowerPC para una buena medida, si desea portarlo a Mac OS X v10.4.
Adam Rosenfield
58
Técnicamente, esto no es puro C! Esa @"AppDelegatees una NSString constante y no se compilará con un compilador C-only. Usar en su CFSTR("AppDelegate")lugar.
2
Sin ofender compañero. ¿Te diste cuenta de que acabas de recibir un voto de mi parte? (Y sí, respeto por tener 2 veces más representante que yo a pesar de ser 3 años más joven ...)
2
Maldita sea ... gruñir gruñir ... Bueno, todavía nunca borraré mi respuesta. BUAHAHAHAHAHAHAHA
CodaFi
40

Objective-C es un superconjunto del lenguaje C, por lo que es teóricamente posible escribir un programa completamente en C, sin embargo, a menos que esté completamente versado OpenGL ES, deberá hacer al menos algo de objC ( incluso la muestra de Rich tiene un const NSString * en él ), de lo contrario, tendrá que escribir las vistas usted mismo.

OK, lo anterior está completamente mal. Permítanme decir que estoy asombrado de que Rich haya logrado este elevado objetivo, así que lo porté a la Mac (fuente aquí ). Los archivos a continuación no tienen encabezados, no se vinculan a Cocoa, ni el proyecto tiene una punta:

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;
}

View.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);
}

prefijo.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

ingrese la descripción de la imagen aquí

CodaFi
fuente
8
No es cierto, puede usar el tiempo de ejecución objc para crear una aplicación en C, deme unos minutos y le mostraré
Richard J. Ross III,
44
Sí, y puedes cavar una base con una cuchara, pero eso no lo convierte en una buena idea ni terriblemente eficaz.
bbum
10
@ MahmoudAl-Qudsi No me rendí :)
Richard J. Ross III
8
Bueno, la habilidad también puede ser útil cuando estás en la redención de Shawshank ...
Hejazzman
2
Si. Lo que me sorprende es que si no fuera por el código de tiempo de ejecución moderno, esto funcionaría en todas las Mac con una X en su nombre de software.
CodaFi
14

Leí aquí Aprender C antes de Objective-C?

Por lo general, luego reemplazo algún código Obj-C con código C puro (después de todo, puede mezclarlos tanto como desee, el contenido de un método Obj-C puede ser completamente, código C puro)

¿Es esto cierto?

¿Podría construir una aplicación para iPhone únicamente en el lenguaje de programación C?

El pasaje citado es verdadero, pero la respuesta a su pregunta es no.

Para ilustrar de qué estaba respondiendo Mecki en esa otra pregunta:

- (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)

No hay nada más que código C puro dentro de este método, pero el método en sí mismo es código Objective-C, al igual que la clase que contiene este método.

Por lo tanto, es posible hacer lo que dijo Mecki, pero no puede (prácticamente, como lo demostró Richard J. Ross III, es técnicamente posible pero bastante tipeado) escribir un programa completo de Cocoa Touch en puro C.

Peter Hosey
fuente
-4

En realidad, parte del código publicado aquí, aunque está escrito en C, todavía llama código de objetivo-C :). No sé si eso realmente se ajusta al escenario del póster original cuando preguntó

¿Es posible construir una aplicación para iPhone únicamente en el lenguaje de programación C?

pero estaría de acuerdo con las personas que dicen que, en términos generales y para una aplicación con una GUI, necesitaría escribir su GUI en OpenGL (que es C).

Creo que eso es lo que hacen la mayoría de los juegos, ¿verdad? Aunque no estoy seguro de si hay acceso a las E / S del iPhone (la pantalla táctil, por ejemplo) en C.

Por último, pero no menos importante, ¡los chicos que escribieron el código sobre rock! :)

make.it.floss
fuente
1
De acuerdo con los requisitos, estamos utilizando el código C en el desarrollo de iPhone e iOS.
Deporte
objc_msgSend()es puro C. El hecho de que llame initWithFrame:no importa, ya que las implementaciones de métodos también son funciones de C.
Gabriele Petronella
objc_msgSend () es una función C, sí, pero es parte del tiempo de ejecución de Objective-C, ¿verdad?
make.it.floss
No pude ver ninguna construcción Obj-C en el código publicado allí. ¡Pero aún así funciona incluso invocando bibliotecas obj-c en forma "C"!
techcraver