C로 iOS 앱을 작성하는 방법


357

나는 Objective-C 전에 Learn C를 읽는가?

일반적으로 일부 Obj-C 코드를 순수한 C 코드로 바꿉니다 (결국 원하는대로 혼합 할 수 있습니다.

이것이 사실입니까?

C 프로그래밍 언어로 iPhone 앱을 빌드 할 수 있습니까?


9
@thilo 그것은 가능합니다 ... objc 런타임을 사용하여
리차드 J. 로스 III

60
가능한? 예. 그리고 전혀 무의미합니다. iOS 시스템의 거의 모든 API와 패턴은 Objective-C 및 Objective-C API에서 파생됩니다. 당신은 당신의 시간을 낭비합니다; iOS 프로그래밍 방법을 배우려면 Objective-C로 시작하여 C를 선택하십시오.
bbum

111
실제 프로그래머는 ARM 어셈블러를 사용하여이 작업을 수행합니다.
Kristopher Johnson

12
@bbum 나는 그것이 무의미하다고 말하지 않을 것입니다. 게임을 PC로 이식 할 때 모든 것이 C ++로 작성되어 기뻤습니다 (예, C ++로도 모든 작업을 수행 할 수 있음). 며칠 만에 게임을 이식 할 수있었습니다. Obj-c를 어디에서나 사용한다면 몇 달이 걸릴 것입니다.
fbafelipe

6
나는 어디에서나 objective-c가 요구 사항이라고 제안하지 않았다. 일반적인 아키텍처는 때때로 아주 얇은 objective-c 레이어가있는 휴대용 C ++ 엔진입니다. OBJC를 완전히 피하는 것은 시간 낭비입니다. 휴대용 게임에서도 활용할 수있는 모든 종류의 표준 iOS 기능에 액세스하는 데 사용합니다.
bbum

답변:


778

젠장, 시간이 좀 걸렸지 만 알았습니다.

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

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

추악하지만 작동합니다.

이것을 다운로드 하려면 여기 내 드롭 박스에서 다운로드 하십시오.

당신은 내 GitHub의 저장소에서 그것을 얻을 수 있습니다 여기에 :

스크린 샷


124
큰. 따라서 Objective-C를 배우는 것을 피하기 위해서는 Objective-C 런타임의 구현 세부 사항과 C 레벨 API를 알아야합니다.
Thilo

5
여러 제안에 따라 이것을 어셈블리로 변환하기로 결정한 경우 ARM (일반 및 Thumb 명령어 세트!) 및 x86에서 시뮬레이터에서 작동하도록해야합니다. Mac OS X v10.4로 포팅하려는 경우 PowerPC를 적절히 측정 할 수도 있습니다.
Adam Rosenfield

58
기술적으로 이것은 순수한 C가 아닙니다! 이는 @"AppDelegate상수 NSString이며 C 전용 컴파일러로 컴파일되지 않습니다. CFSTR("AppDelegate")대신 사용하십시오 .

2
범죄자 없음. 당신은 당신이 나에게서 공감대를 얻었다는 것을 알아 차렸다? (그리고 예, 3 년이 젊음에도 불구하고 저보다 2 배 더 많은 담당자를 갖는 것에 대한 존중 ...)

2
젠장 .. 엉망진창 ... 글쎄, 난 여전히 내 대답을 삭제하지 않을 것이다. BUAHAHAHAHAHAHAHA
CodaFi

40

당신이 철저에 정통한하지 않는, 그러나, C에서 완전히 프로그램을 작성 이론적으로는 가능하므로 오브젝티브 C는 C 언어의 상위 집합입니다 OpenGL ES당신은 적어도 수행해야합니다, 일부 objC을 ( 심지어 리치의 샘플은있다 const NSString * ) 그렇지 않으면 뷰를 직접 작성해야합니다.

위의 내용은 완전히 틀 렸습니다. 나를 내가 (소스 맥으로 포팅 있도록 놀라게 리치,이 높은 목표를 달성 해요 가정 해 봅시다 여기 ). 아래 파일에는 헤더가 없으며 Cocoa에 연결되지 않으며 프로젝트에 펜촉이 없습니다.

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

여기에 이미지 설명을 입력하십시오


8
사실은 아닙니다. objc 런타임을 사용하여 C로 앱을 빌드하고 몇 분만
Richard J. Ross III

44
예, 숟가락으로 파운데이션을 파낼 수는 있지만 좋은 아이디어 나 끔찍한 효과는 아닙니다.
bbum

10
@ MahmoudAl-Qudsi 나는 포기하지 않았다 :)
Richard J. Ross III

8
글쎄, 당신이 Shawshank 상환에있을 때이 기술은 편리하게 올 수 있습니다 ...
Hejazzman

2
네. 나를 얻는 것은 최신 런타임 코드가 아니라면 소프트웨어 이름에 X가있는 모든 Mac에서 작동한다는 것입니다.
CodaFi

14

나는 Objective-C 전에 Learn C를 읽는가?

일반적으로 일부 Obj-C 코드를 순수한 C 코드로 바꿉니다 (결국 원하는대로 혼합 할 수 있습니다.

이것이 사실입니까?

C 프로그래밍 언어로 iPhone 앱을 만들 수 있습니까?

인용구는 사실이지만 귀하의 질문에 대한 대답은 아니오입니다.

다른 질문에 대한 답변자 Mecki가 무엇에 대해 이야기하고 있는지 설명하려면 :

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

이 아무것도하지만, 순수 C 코드입니다 내에서 이 방법은 있지만,이 메소드를 포함하는 클래스입니다 같은 방법 자체가 목표 - C 코드입니다.

따라서 Mecki가 말한 것을 수행하는 것이 가능하지만 (실제로 Richard J. Ross III가 보여 주듯이 기술적으로 가능하지만 많은 타이핑이 가능합니다) 전체 Cocoa Touch 프로그램을 순수한 C로 작성할 수는 없습니다.


-4

실제로 여기에 게시 된 일부 코드는 C로 작성되었지만 여전히 objective-C 코드를 호출합니다. :) 그가 요청했을 때 원래 포스터의 시나리오에 실제로 맞는지 모르겠습니다.

C 프로그래밍 언어로 iPhone 앱을 빌드 할 수 있습니까?

그러나 나는 일반적으로 말하기와 GUI가있는 앱의 경우 OpenGL (C 인 GUI)로 GUI를 작성해야한다고 사람들에게 동의합니다.

나는 그것이 대부분의 게임이하는 것이라고 생각합니다. C에서 iPhone의 I / O (예 : 터치 스크린)에 액세스 할 수 있는지 확실하지 않습니다.

마지막으로, 바위 위에 코드를 작성한 사람들! :)


1
요구 사항에 따라 우리는 iPhone 및 iOS 개발에서 c 코드를 사용하고 있습니다.
스포츠

objc_msgSend()initWithFrame:메소드 구현은 C 함수이기 때문에 호출하는 사실은 중요하지 않습니다.
가브리엘 페트로 넬라

objc_msgSend ()는 C 함수입니다. 그러나 Objective-C 런타임의 일부입니까?
make.it.floss

거기에 게시 된 코드에서 Obj-C 구문을 볼 수 없습니다. 그러나 여전히 "C"방식으로 obj-c 라이브러리를 호출해도 작동합니다!
techcraver
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.