Comment écrire une application iOS uniquement en C

357

J'ai lu ici Apprendre C avant Objective-C?

Habituellement, je remplace ensuite du code Obj-C par du code C pur (après tout, vous pouvez les mélanger autant que vous le souhaitez, le contenu d'une méthode Obj-C peut être entièrement du code C pur)

Est-ce vrai?

Est-il possible de construire une application iPhone uniquement dans le langage de programmation C?

001
la source
9
@thilo c'est possible ... en utilisant le runtime objc
Richard J. Ross III
60
Possible? Oui. Et tout à fait inutile. Presque toutes les API et les modèles du système iOS sont dérivés des API Objective-C et Objective-C. Vous perdrez votre temps; si vous voulez apprendre à programmer iOS, commencez par Objective-C et choisissez C en cours de route.
bbum
111
Un vrai programmeur ferait cela en utilisant l'assembleur ARM.
Kristopher Johnson
12
@bbum je ne dirais pas que c'est inutile. Lorsque j'ai porté mon jeu sur PC, j'étais plus qu'heureux que tout soit écrit en C ++ (oui, il est possible de tout faire aussi en C ++). Je pourrais porter mon jeu en quelques jours, si j'utilisais Obj-c partout, cela prendrait des mois.
fbafelipe
6
Je n'ai pas suggéré à distance que l'objectif-c partout était une exigence. Une architecture courante est un moteur C ++ portable avec une couche d'objectif-c, parfois très mince, au-dessus. Éviter complètement OBJC est une perte de temps; vous l'utilisez pour accéder à toutes sortes de fonctionnalités iOS standard dont même un jeu portable pourrait profiter.
bbum

Réponses:

778

Merde, ça m'a pris du temps mais je l'ai compris:

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

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

C'est moche, mais ça marche.

Si vous souhaitez télécharger ceci, vous pouvez l'obtenir depuis ma boîte de dépôt ici

Vous pouvez l'obtenir à partir de mon référentiel GitHub ici :

Capture d'écran

Richard J. Ross III
la source
124
Génial. Donc, afin d'éviter d'apprendre Objective-C (ce qui, je pense, était l'essentiel de la question), vous devez maintenant apprendre les détails d'implémentation et l'API de niveau C du runtime Objective-C.
Thilo
5
Si vous décidez de convertir cela en assembleur, selon plusieurs des suggestions, assurez-vous de le faire dans ARM (jeux d'instructions standard et pouce!) Et en x86 pour qu'il fonctionne dans le simulateur. Peut-être aussi PowerPC pour faire bonne mesure, si vous voulez le porter sur Mac OS X v10.4.
Adam Rosenfield
58
Techniquement, ce n'est pas du pur C! C'est @"AppDelegateune chaîne NSString constante et ne compilera pas avec un compilateur C uniquement. Utilisez CFSTR("AppDelegate")plutôt.
2
Aucun compagnon d'offense. Vous avez remarqué que vous venez de recevoir un vote positif de ma part? (Et oui, le respect d'avoir 2 fois plus de représentants que moi malgré 3 ans de moins ...)
2
Bon sang ... grognement grognement ... Eh bien, je ne supprimerai jamais ma réponse. BUAHAHAHAHAHAHAHAHA
CodaFi
40

Objective-C est un sur-ensemble du langage C, il est donc théoriquement possible d'écrire un programme entièrement en C, cependant, à moins que vous ne soyez parfaitement familiarisé avec OpenGL ES, vous devrez faire au moins un certain objC ( même l'échantillon de Rich a un const NSString * ), sinon vous devrez écrire les vues vous-même.

OK, ce qui précède est complètement faux. Permettez-moi de dire que je suis stupéfait que Rich ait atteint ce noble objectif, alors je l'ai porté sur le mac (source ici ). Les fichiers ci-dessous n'ont pas d'en-tête, ne sont pas liés à Cocoa et le projet n'a pas de plume:

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

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

entrez la description de l'image ici

CodaFi
la source
8
Ce n'est pas vrai, vous pouvez utiliser le runtime objc pour créer une application en C, donnez-moi quelques minutes et je vais vous montrer
Richard J. Ross III
44
Oui, et vous pouvez creuser une fondation avec une cuillère, mais cela n'en fait pas une bonne idée ni terriblement efficace.
bbum
10
@ MahmoudAl-Qudsi Je n'ai pas abandonné :)
Richard J. Ross III
8
Eh bien, la compétence pourrait également être utile lorsque vous êtes dans le rachat de Shawshank ...
Hejazzman
2
Ouais. Ce qui m'attire, c'est que s'il n'y avait pas le code d'exécution moderne, cela fonctionnerait sur chaque Mac avec un X dans son nom de logiciel.
CodaFi
14

J'ai lu ici Apprendre C avant Objective-C?

Habituellement, je remplace ensuite du code Obj-C par du code C pur (après tout, vous pouvez les mélanger autant que vous le souhaitez, le contenu d'une méthode Obj-C peut être entièrement du code C pur)

Est-ce vrai?

Puis-je créer une application iPhone uniquement dans le langage de programmation C?

Le passage cité est vrai, mais la réponse à votre question est non.

Pour illustrer le propos du répondeur Mecki sur cette autre question:

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

Il n'y a rien d'autre que du code C pur dans cette méthode, mais la méthode elle-même est du code Objective-C, tout comme la classe qui contient cette méthode.

Il est donc possible de faire ce que Mecki a dit, mais vous ne pouvez pas (pratiquement - comme Richard J. Ross III l'a montré, c'est techniquement possible mais beaucoup de dactylographie) écrire un programme Cocoa Touch entier en C. pur

Peter Hosey
la source
-4

En fait, une partie du code affiché ici, alors qu'il est écrit en C, appelle toujours du code objectif-C :). Je ne sais pas si cela correspond vraiment au scénario de l'affiche originale quand il a demandé

Est-il possible de construire une application iPhone uniquement dans le langage de programmation C?

mais je serais d'accord avec les gens qui disent que, d'une manière générale et pour une application avec une interface graphique, vous devez écrire votre interface graphique en OpenGL (qui est C).

Je pense que c'est ce que font la plupart des jeux, non? Bien que je ne sais pas s'il y a accès aux E / S de l'iPhone (l'écran tactile par exemple) en C.

Last but not least, les gars qui ont écrit le code ci-dessus rock! :)

make.it.floss
la source
1
selon les exigences, nous utilisons le code c dans le développement iPhone et iOS.
Sport
objc_msgSend()est pur C. Le fait que i appelle initWithFrame:n'a pas d'importance, car les implémentations de méthodes sont également des fonctions C.
Gabriele Petronella
objc_msgSend () est une fonction C, oui, mais elle fait partie du runtime Objective-C, non?
make.it.floss
Je ne pouvais voir aucune construction Obj-C dans le code affiché ici. Mais cela fonctionne même en invoquant les bibliothèques obj-c de la manière "C"!
techcraver