Cách viết ứng dụng iOS hoàn toàn bằng C


357

Tôi đọc ở đây Tìm hiểu C Trước Objective-C?

Thông thường sau đó tôi thay thế một số mã Obj-C bằng mã C thuần túy (sau tất cả những gì bạn có thể trộn chúng bao nhiêu tùy thích, nội dung của phương thức Obj-C hoàn toàn có thể là mã C thuần túy)

Điều này có đúng không?

Có thể xây dựng một ứng dụng iPhone hoàn toàn bằng ngôn ngữ lập trình C không?


9
@thilo có thể ... sử dụng thời gian chạy objc
Richard J. Ross III

60
Khả thi? Đúng. Và hoàn toàn vô nghĩa. Gần như tất cả các API và mẫu của hệ thống iOS đều được lấy từ API Objective-C & Objective-C. Bạn sẽ lãng phí thời gian của bạn; nếu bạn muốn tìm hiểu cách lập trình iOS, hãy bắt đầu với Objective-C và chọn C trên đường đi.
bbum

111
Một lập trình viên thực sự sẽ làm điều này bằng cách sử dụng trình biên dịch ARM.
Kristopher Johnson

12
@bbum Tôi sẽ không nói nó là vô nghĩa. Khi tôi chuyển trò chơi của mình sang PC, tôi rất vui vì tất cả được viết bằng C ++ (vâng, có thể làm mọi thứ trong C ++). Tôi có thể chuyển trò chơi của mình sau vài ngày, nếu tôi sử dụng Obj-c ở mọi nơi thì sẽ mất vài tháng.
fbafelipe

6
Tôi đã không đề xuất từ ​​xa rằng mục tiêu-c ở mọi nơi là một yêu cầu. Một kiến ​​trúc phổ biến là một công cụ C ++ di động với lớp khách quan, đôi khi rất mỏng. Tránh OBJC hoàn toàn là một sự lãng phí thời gian; bạn sử dụng nó để truy cập tất cả các loại tính năng iOS tiêu chuẩn mà ngay cả một trò chơi di động cũng có thể tận dụng.
bbum

Câu trả lời:


778

Chết tiệt, tôi phải mất một lúc nhưng tôi đã hiểu:

C chính:

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

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

Nó xấu, nhưng nó hoạt động.

Nếu bạn muốn tải xuống cái này, bạn có thể lấy nó từ dropbox của tôi ở đây

Bạn có thể lấy nó từ kho GitHub của tôi ở đây :

Ảnh chụp màn hình


124
Tuyệt quá. Vì vậy, để tránh học Objective-C (mà tôi nghĩ là ý chính của câu hỏi), bây giờ bạn phải tìm hiểu chi tiết triển khai và API cấp độ C của thời gian chạy Objective-C.
Thilo

5
Nếu bạn quyết định chuyển đổi nó thành lắp ráp, theo một số gợi ý, hãy đảm bảo thực hiện nó trong ARM (bộ hướng dẫn thông thường và ngón tay cái!) Và trong x86 để nó hoạt động trong trình giả lập. Có lẽ PowerPC cũng là một biện pháp tốt, nếu bạn muốn chuyển nó sang Mac OS X v10.4.
Adam Rosenfield

58
Về mặt kỹ thuật, đây không phải là C thuần túy! Đó @"AppDelegatelà một NSString không đổi và sẽ không biên dịch với trình biên dịch chỉ có C. Sử dụng CFSTR("AppDelegate")thay thế.

2
Không xúc phạm bạn đời. Bạn nhận thấy bạn vừa nhận được một upvote từ tôi? (Và vâng, tôn trọng vì có đại diện gấp 2 lần tôi mặc dù trẻ hơn 3 tuổi ...)

2
Chết tiệt ... càu nhàu ... Chà, tôi sẽ không bao giờ xóa câu trả lời của mình. BUAHAHAHAHAHAHAHA
CodaFi

40

Objective-C là một siêu ngôn ngữ của ngôn ngữ C, do đó về mặt lý thuyết có thể viết một chương trình hoàn toàn bằng C, trừ khi bạn thành thạo OpenGL ES, bạn sẽ cần phải thực hiện ít nhất một số objC ( Mẫu của Rich thậm chí có const NSString * trong đó ), nếu không bạn sẽ phải tự viết quan điểm.

OK, ở trên là hoàn toàn sai. Hãy để tôi nói, tôi rất ngạc nhiên Giàu đạt được mục tiêu cao cả này, vì vậy tôi đã chuyển nó sang mac (nguồn ở đây ). Các tệp bên dưới không có tiêu đề, không liên kết với Ca cao và dự án cũng không có ngòi nổ:

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

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

nhập mô tả hình ảnh ở đây


8
Không đúng, bạn có thể sử dụng thời gian chạy objc để xây dựng một ứng dụng trong C, cho tôi vài phút và tôi sẽ chỉ cho bạn
Richard J. Ross III

44
Vâng, và bạn có thể đào một nền tảng bằng một cái muỗng, nhưng điều đó không làm cho nó trở thành một ý tưởng tốt hoặc hiệu quả khủng khiếp.
bbum

10
@ MahmoudAl-Qudsi Tôi đã không từ bỏ :)
Richard J. Ross III

8
Chà, kỹ năng cũng có thể trở nên hữu dụng khi bạn tham gia vào sự cứu chuộc của Shawshank ...
Hejazzman

2
Vâng. Điều khiến tôi hiểu là nếu nó không có mã thời gian chạy hiện đại, thì điều này sẽ hoạt động trên mọi máy Mac có tên X trong tên phần mềm của nó.
CodaFi

14

Tôi đọc ở đây Tìm hiểu C Trước Objective-C?

Thông thường sau đó tôi thay thế một số mã Obj-C bằng mã C thuần túy (sau tất cả những gì bạn có thể trộn chúng bao nhiêu tùy thích, nội dung của phương thức Obj-C hoàn toàn có thể là mã C thuần túy)

Điều này có đúng không?

Tôi có thể xây dựng một ứng dụng iPhone hoàn toàn bằng ngôn ngữ lập trình C không?

Đoạn trích dẫn là đúng, nhưng câu trả lời cho câu hỏi của bạn là không.

Để minh họa câu trả lời của Mecki cho câu hỏi khác đang nói về:

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

Không có gì ngoài mã C thuần túy trong phương thức này, nhưng bản thân phương thức đó là mã Objective-C, cũng như lớp chứa phương thức này.

Vì vậy, có thể làm những gì Mecki đã nói, nhưng bạn không thể (thực tế là giáo sư như Richard J. Ross III đã chỉ ra, về mặt kỹ thuật có thể nhưng khá nhiều cách gõ) viết toàn bộ chương trình Cacao Touch bằng chữ C.


-4

Trên thực tế, một số mã được đăng ở đây, trong khi được viết bằng C, vẫn đang gọi mã object-C :). Tôi không biết điều đó có thực sự phù hợp với kịch bản từ poster ban đầu không khi anh ấy hỏi

Có thể xây dựng một ứng dụng iPhone hoàn toàn bằng ngôn ngữ lập trình C không?

nhưng tôi đồng ý với những người nói rằng, nói chung và đối với một ứng dụng có GUI, bạn sẽ cần phải viết GUI của mình trong OpenGL (đó là C).

Tôi nghĩ đó là những gì hầu hết các trò chơi làm, phải không? Mặc dù tôi không chắc có quyền truy cập vào I / O của iPhone (ví dụ như màn hình cảm ứng) trong C.

Cuối cùng nhưng không kém phần quan trọng, những kẻ đã viết mã trên đá! :)


1
theo yêu cầu, chúng tôi đang sử dụng mã c trong phát triển iPhone và iOS.
Thể thao

objc_msgSend()là thuần túy C. Thực tế là tôi gọi là initWithFrame:không quan trọng, vì việc triển khai phương thức cũng là các hàm C.
Gabriele Petronella

objc_msgSend () là một hàm C, vâng, nhưng nó là một phần của thời gian chạy Objective-C, phải không?
make.it.floss

Tôi không thể thấy bất kỳ cấu trúc Obj-C nào trong mã được đăng ở đó. Nhưng công việc này thậm chí còn gọi các thư viện obj-c theo cách "C"!
techcraver
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.