【问题标题】:Compiling Objective-C on Debian 8 GNU/Linux with Clang and GNUstep make使用 Clang 和 GNUstep make 在 Debian 8 GNU/Linux 上编译 Objective-C
【发布时间】:2016-11-19 23:03:24
【问题描述】:

在 Debian 8 GNU/Linux 上,我正在尝试使用 Clang 和 GNUstep make 从 this tutorial 编译一些 Objective-C 代码。我运行了以下命令来安装构建管道:

sudo apt-get install build-essential clang gnustep-devel

这些是我正在使用的文件:

GNUmake 文件:

CC = clang

include $(GNUSTEP_MAKEFILES)/common.make

TOOL_NAME = Main
Main_OBJC_FILES = main.m Car.m

include $(GNUSTEP_MAKEFILES)/tool.make

main.m:

#import <Foundation/Foundation.h>
#import "Car.h"

int main () {
  @autoreleasepool {
    Car *toyota = [[Car alloc] init];

    [toyota setModel:@"Toyota Corolla"];
    NSLog(@"Created a %@", [toyota model]);

    toyota.model = @"Toyota Camry";
    NSLog(@"Changed the car to a %@", toyota.model);

    [toyota drive];
  }
  return 0;
}

汽车.h:

#import <Foundation/Foundation.h>

@interface Car : NSObject {}

@property (copy) NSString *model;

- (void)drive;

@end

汽车.m:

#import "Car.h"

@implementation Car {
  double _odometer;
}

@synthesize model = _model;

- (void)drive {
  NSLog(@"Driving a %@. Vrooom!", self.model);
}

@end

我正在编译:

. /usr/share/GNUstep/Makefiles/GNUstep.sh && make -k

编译时出现以下错误:

Car.m:4:10: error: inconsistent number of instance variables specified
  double _odometer;
         ^
Car.m:7:13: error: synthesized property 'model' must either be named the same as a compatible instance variable or must
      explicitly name an instance variable
@synthesize model = _model;

要修复错误,我需要将 Car 界面更新为:

@interface Car : NSObject {
  NSString * _model;
  double _odometer;
}

Car 实现:

@implementation Car {
  NSString * _model;
  double _odometer;
}

似乎我当前的编译设置迫使我编写的代码比教程建议的编译所需的代码多。事实上,在教程中,作者特别建议不要在接口中包含“受保护的实例变量”:

@interface Car : NSObject {
    // Protected instance variables (not recommended)
}

我觉得我使用的是过时/错误配置的编译设置。我是吗?如果是这样,我该如何解决它,以便我可以编写参考教程中的代码? (如果可能的话,我更喜欢使用 GNUstep make 而不是 Clang CLI。)

【问题讨论】:

    标签: objective-c linux debian clang gnustep


    【解决方案1】:

    来自this answer,我发现我的问题可能是我没有使用“现代”Objective-C 运行时。

    @implementation { } 块内的实例变量声明是 一个相对较新的 Objective-C 特性。 . . .你还需要 符合“现代”Objective-C 运行时的条件。 . .

    this thread我找到了,

    提供 -fobjc-nonfragile-abi 也会对 运行。在 Darwin 上,它将使用 Apple 的“现代”运行时,而不是 指定它将为“旧版”运行时生成代码。如果你 指定 -fobjc-nonfragile-abi 和 -fgnu-runtime,然后 clang 将 为 GNUstep 运行时生成代码,您可以在 GNUstep 中找到它 svn 作为 libobjc2。这提供了 ObjC 2 的所有功能,除了 GC,以及其他一些在 Mac 运行时中找不到的内容。

    所以,我尝试将以下内容添加到我的 GNUmakefile:

    Main_OBJCFLAGS = -fobjc-nonfragile-abi
    

    但是当我尝试编译时,我得到了错误:

    fatal error: 'objc/blocks_runtime.h' file not found
    

    从那里,我找到了this thread,并得出结论说我可能需要“libobjc2”。

    令人失望的是,我找不到 Debian 的“libobjc2”包,所以我尝试从源代码编译它。首先我必须安装一些构建依赖项:

    sudo apt-get install cmake llvm-dev
    

    然后我不得不将this patch 应用于 llvm-3.5-dev 包,因为它导致 libobjc2 编译失败:

    wget -qO- https://launchpadlibrarian.net/221945609/fix-llvm-3.5-dev-debian.bash | bash
    

    然后我就可以使用以下命令编译 libobjc2:

    wget http://download.gna.org/gnustep/libobjc2-1.7.tar.bz2
    tar jxf libobjc2-1.7.tar.bz2
    cd libobjc2-1.7
    mkdir Build
    cd Build
    CC=clang CXX=clang++ cmake -DCMAKE_INSTALL_LIBDIR=lib ..
    make -j8
    sudo -E make install
    

    之后,我再次尝试编译教程中的代码。编译成功(没有做任何我不想做的改变)!但是,make 抱怨我的一个“.so”文件之间存在“潜在冲突”(我不记得确切的错误),当我运行我的程序(使用./obj/Main)时,我在程序中看到了以下内容输出:

    $ ./obj/Main
    Loading two versions of Protocol.  The class that will be used is undefined
    Loading two versions of Object.  The class that will be used is undefined
    2016-11-19 16:43:24.012 Main[10166] Created a Toyota Corolla
    2016-11-19 16:43:24.017 Main[10166] Changed the car to a Toyota Camry
    2016-11-19 16:43:24.017 Main[10166] Driving a Toyota Camry. Vrooom!
    

    显然,编译 libobjc2 并让它与 Debian GNUstep 软件包一起运行可能不是“好”。因此,我听取了各种在线资源的建议并着手从源代码编译 GNUstep。

    首先我卸载了 Debian 软件包:

    sudo apt-get uninstall gnustep-devel && sudo apt-get autoremove
    

    然后我安装了以下构建依赖项:

    sudo apt-get install libffi-dev libicu-dev libgnutls28-dev libxml2-dev libxslt1-dev libtiff5-dev libjpeg-dev libpng-dev libgif-dev libaspell-dev libcups2-dev libaudiofile-dev portaudio19-dev libdispatch-dev
    

    (我不能保证这是完整的依赖项列表,只有那些我能找到记录的依赖项;由于编译不相关的程序,我的计算机上可能已经安装了一些更多必要的库。)

    使用以下两个教程(1)(2),稍微调整一下(可能是不必要的),我设法编译了 GNUstep。

    wget -qO- ftp://ftp.gnustep.org/pub/gnustep/core/gnustep-make-2.6.8.tar.gz | tar xz
    wget -qO- ftp://ftp.gnustep.org/pub/gnustep/core/gnustep-base-1.24.9.tar.gz | tar xz
    wget -qO- ftp://ftp.gnustep.org/pub/gnustep/core/gnustep-gui-0.25.0.tar.gz | tar xz
    wget -qO- ftp://ftp.gnustep.org/pub/gnustep/core/gnustep-back-0.25.0.tar.gz | tar xz
    
    cd gnustep-make-2.6.8
    CC=clang ./configure --enable-objc-nonfragile-abi
    sudo make install
    cd ..
    
    cd gnustep-base-1.24.9
    CC=clang OBJCFLAGS="-fblocks -fobjc-nonfragile-abi" ./configure
    CC=clang OBJCFLAGS="-fblocks -fobjc-nonfragile-abi" make
    sudo make install
    cd ..
    
    cd gnustep-gui-0.25.0
    CC=clang OBJCFLAGS="-fblocks -fobjc-nonfragile-abi" ./configure
    CC=clang OBJCFLAGS="-fblocks -fobjc-nonfragile-abi" make
    sudo make install
    cd ..
    
    cd gnustep-back-0.25.0
    CC=clang OBJCFLAGS="-fblocks -fobjc-nonfragile-abi" ./configure
    CC=clang OBJCFLAGS="-fblocks -fobjc-nonfragile-abi" make
    sudo make install
    cd ..
    

    在那之后,我再次编译了我的教程文件,这次是:

    . /usr/local/share/GNUstep/Makefiles/GNUstep.sh && make -k
    

    最后,我不再收到关于任何“冲突”的错误,并且当我运行程序时,我不再看到“正在加载两个版本的...”表单的错误

    $ ./obj/Main
    2016-11-19 19:56:07.450 Main[10822:10822] Created a Toyota Corolla
    2016-11-19 19:56:07.451 Main[10822:10822] Changed the car to a Toyota Camry
    2016-11-19 19:56:07.451 Main[10822:10822] Driving a Toyota Camry. Vrooom!
    

    成功了!

    我还稍微将此解决方案重构为a script。这可能会更“干净”且更易于使用。我将在此处粘贴最新版本“以防万一:”

    #!/usr/bin/env bash
    
    # It seems that many modern Objective-C features aren't available without
    # libobjc2, which doesn't seem to be available as a Debian package.  Also,
    # compiling and using it alongside the Debian GNUstep packages doesn't work too
    # well (it seems like they may each provide their own definition of the Protocol
    # and Object classes).  Basically, to get a fully-functioning Objective-C
    # compilation environment on Debian 8, run this script.
    
    # Please ensure any Debian GNUstep packages are uninstalled before running this
    # script.
    
    # Slightly adapted from
    # http://wiki.gnustep.org/index.php/GNUstep_under_Ubuntu_Linux for Debian 8.
    # Also, uses the latest stable versions of source packages as of this writing
    # (hopefully to improve reproducability of success; but feel free to upgrade
    # them if you want).
    
    # If this script is successful, you should be able to compile a "main.m" program
    # by running "make" in a directory with a "GNUmakefile" with these contents:
    #
    #     include $(GNUSTEP_MAKEFILES)/common.make
    #     TOOL_NAME = Main
    #     Main_OBJC_FILES = main.m
    #     include $(GNUSTEP_MAKEFILES)/tool.make
    
    # Show prompt function
    function showPrompt()
    {
        if [ "$PROMPT" = true ] ; then
            echo -e "\n\n"
            read -p "${GREEN}Press enter to continue...${NC}"
        fi
    }
    
    # Set colors
    GREEN=`tput setaf 2`
    NC=`tput sgr0` # No Color
    
    # Set to true to pause after each build to verify successful build and installation
    PROMPT=true
    
    # Install Requirements
    sudo apt update
    
    echo -e "\n\n${GREEN}Installing dependencies...${NC}"
    sudo apt -y install clang ninja cmake libffi-dev libxml2-dev \
         libgnutls28-dev libicu-dev libblocksruntime-dev libkqueue-dev libpthread-workqueue-dev autoconf libtool \
         libjpeg-dev libtiff5-dev libffi-dev libcairo2-dev libx11-dev libxt-dev libxft-dev \
         llvm-dev libdispatch-dev
    
    # https://bugs.launchpad.net/ubuntu/+source/llvm/+bug/1387011/comments/17
    wget -qO- https://launchpadlibrarian.net/221945609/fix-llvm-3.5-dev-debian.bash | bash
    
    # Create build directory
    mkdir GNUstep-build
    cd GNUstep-build
    
    # Set clang as compiler
    export CC=clang
    export CXX=clang++
    
    # Checkout sources
    echo -e "\n\n${GREEN}Downloading sources...${NC}"
    mkdir -p libobjc2 && wget -qO- https://github.com/gnustep/libobjc2/archive/v1.8.1.tar.gz | tar xz -C libobjc2 --strip-components=1
    mkdir -p make && wget -qO- ftp://ftp.gnustep.org/pub/gnustep/core/gnustep-make-2.6.8.tar.gz | tar xz -C make --strip-components=1
    mkdir -p base && wget -qO- ftp://ftp.gnustep.org/pub/gnustep/core/gnustep-base-1.24.9.tar.gz | tar xz -C base --strip-components=1
    mkdir -p gui && wget -qO- ftp://ftp.gnustep.org/pub/gnustep/core/gnustep-gui-0.25.0.tar.gz | tar xz -C gui --strip-components=1
    mkdir -p back && wget -qO- ftp://ftp.gnustep.org/pub/gnustep/core/gnustep-back-0.25.0.tar.gz | tar xz -C back --strip-components=1
    
    showPrompt
    
    # Build GNUstep make first time
    echo -e "\n\n"
    echo -e "${GREEN}Building GNUstep-make for the first time...${NC}"
    cd make
    ./configure --enable-debug-by-default --with-layout=gnustep --enable-objc-nonfragile-abi --enable-objc-arc
    make -j8
    sudo -E make install
    
    . /usr/GNUstep/System/Library/Makefiles/GNUstep.sh
    echo ". /usr/GNUstep/System/Library/Makefiles/GNUstep.sh" >> ~/.bashrc
    
    # showPrompt
    
    # Build libobjc2
    echo -e "\n\n"
    echo -e "${GREEN}Building libobjc2...${NC}"
    cd ../libobjc2
    rm -Rf build
    mkdir build && cd build
    cmake ../ -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang -DCMAKE_ASM_COMPILER=clang -DTESTS=OFF
    cmake --build .
    sudo -E make install
    sudo ldconfig
    
    export LDFLAGS=-ldispatch
    
    showPrompt
    
    OBJCFLAGS="-fblocks -fobjc-runtime=gnustep-1.8.1"
    
    # Build GNUstep make second time
    echo -e "\n\n"
    echo -e "${GREEN}Building GNUstep-make for the second time...${NC}"
    cd ../../make
    ./configure --enable-debug-by-default --with-layout=gnustep --enable-objc-nonfragile-abi --enable-objc-arc
    make -j8
    sudo -E make install
    
    . /usr/GNUstep/System/Library/Makefiles/GNUstep.sh
    
    showPrompt
    
    # Build GNUstep base
    echo -e "\n\n"
    echo -e "${GREEN}Building GNUstep-base...${NC}"
    cd ../base/
    ./configure
    make -j8
    sudo -E make install
    
    showPrompt
    
    # Build GNUstep GUI
    echo -e "\n\n"
    echo -e "${GREEN} Building GNUstep-gui...${NC}"
    cd ../gui
    ./configure
    make -j8
    sudo -E make install
    
    showPrompt
    
    # Build GNUstep back
    echo -e "\n\n"
    echo -e "${GREEN}Building GNUstep-back...${NC}"
    cd ../back
    ./configure
    make -j8
    sudo -E make install
    
    showPrompt
    
    . /usr/GNUstep/System/Library/Makefiles/GNUstep.sh
    
    echo -e "\n\n"
    echo -e "${GREEN}Install is done. Open a new terminal to start using.${NC}"
    

    【讨论】:

    • 很高兴看到您已经解决了大部分问题——拥有现代运行时是一件好事。但请记住,这里实际上没有理由首先添加您自己的实例变量。除非您有一些特定的 ABI 要求或明确的理由来指定您自己的实例变量(不想使用属性,需要真正的私有存储等),否则属性就足够了。这些问题大多是正交的。
    【解决方案2】:

    您阅读的教程中有一个错误(或至少缺少信息)——为了使用@synthesize model = _model;,您确实需要定义一个名为_model的实例变量;当你这样做时,错误就会消失。

    不过,让我们退后一步。很久以前,Objective-C 没有@property 语法来定义属性。本教程展示了@property (copy) NSString *model;,但没有立即了解这意味着什么。 (阅读链接的“properties module”以了解作者关于此主题的更多信息。)

    @property 语法之前,您手动定义了所有实例变量:

    // In MyClass.h
    @interface MyClass : NSObject {
        // Visible instance variables. Can be marked @public, @private, and @protected to prevent access.
    }
    ...
    @end
    

    为了允许用户访问您的实例变量,编写 setter 和 getter(例如 -model-setModel:)并让您的实例变量不可访问是一种很好的做法。这遵循 OOP 模型,保护对您的内部状态的访问,并且只允许通过可呈现的界面进行访问。

    这可能是一个相当乏味的过程,并且有很多样板文件(尤其是手动内存管理、KVO 合规性等)。 @property 语法可以帮助解决这个问题。

    当您声明 @property 时,它会为您定义一个实例变量,并隐式创建 getter 和 setter 方法。您可以指定要在这些 getter 和 setter 中使用的内存管理模型(例如 strongweakassigncopy 等)、原子性等。如果你有一个名为model 的属性,@property 将生成一个_model 实例变量,以及-model-setModel:(除非你只定义了一个getter)。

    然而,@property 过去没有为您创建实例变量,只有 getter 和 setter。如果你想要一个实例变量,你必须自己创建它,或者明确要求编译器来做。这就是@synthesize 行的来源。@synthesize model = _model; 告诉编译器您自己创建了一个_model 实例变量,并且model 属性应该使用_model 作为存储。属性的前下划线只是为了帮助区分属性本身(setter/getter 的别名)和底层存储的约定。

    无论如何,这些都是过去的细节。使用现代编译器,您将不需要@synthesize 行,因为编译器将为您生成实例变量。要么自己添加变量并保留@synthesize model = _model; 行(不推荐),要么不使用该行。

    还请注意,如果您确实声明了自己的实例变量,它们确实不需要在您的@interface@implementation 之间重复——选择一个位置并将它们放在那里。我推荐@implementation,因为它们不需要可见,但这取决于你。

    【讨论】:

    • 关于“@property 语法之前”和代码块,我对此的记忆并不完美,但是在实现部分中的 ivars 声明与声明的属性几乎同时添加,我相信它对应于/取决于非脆弱的 ivars 变化。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-05-28
    • 1970-01-01
    • 2012-01-08
    • 1970-01-01
    相关资源
    最近更新 更多