Может ли реализация Objective- C быть определена в файле заголовка, а также импортирована из нескольких исходных файлов? - PullRequest
0 голосов
/ 14 июля 2020

Я знаю, что это нестандартно и не принято, пожалуйста, прочтите. У меня есть файл заголовка, который определяет интерфейс и реализацию класса Objective- C.

Person.h

#ifndef Person_h
#define Person_h

@interface Person : NSObject
-(void)speak;
@end

@implementation Person
-(void)speak
{
    // Say something
}
@end

#endif /* Person_h */

У меня также есть два исходных файла которые оба включают файл заголовка.

Main.mm

@import Foundation;
#import "Person.h"

int main(int argc, const char * argv[])
{
    // Do nothing
}

Test.mm

@import Foundation;
#import "Person.h"

Когда при создании проекта я получаю повторяющиеся ошибки символов.

duplicate symbol '_OBJC_CLASS_$_Person' in:
        /Debug/TestBox.build/Objects-normal/x86_64/main.o
        /Debug/TestBox.build/Objects-normal/x86_64/test.o
duplicate symbol '_OBJC_METACLASS_$_Person' in:
        /Debug/TestBox.build/Objects-normal/x86_64/main.o
        /Debug/TestBox.build/Objects-normal/x86_64/test.o
ld: 2 duplicate symbols for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

Я хочу иметь возможность включать один и тот же заголовочный файл в несколько исходных файлов. В C ++ я могу встроить реализацию в файл заголовка следующим образом:

#ifndef Person_h
#define Person_h

class Person
{
public:
    void speak()
    {
        // Say something
    }
};

#endif /* Person_h */

Однако я не смог найти способ сделать это с Objective- C. Я использую Objective- C, поэтому могу создавать подклассы событий из NSWindowDelegate и NSResponder.

Я пробовал искать решения. Большинство из них заявили, что нужно разделить реализацию в исходный файл, но это нарушит архитектуру с одним заголовком. Одно из предложений - использовать Objective- C библиотеку времени выполнения и создавать мои классы во время выполнения. Кажется, это дает мне результаты, которые я ищу, но мне интересно, есть ли более простой способ.

Есть ли способ реализовать класс Objective- C в файле заголовка, чтобы он может быть включено несколько исходных файлов? Или лучший вариант - динамическое создание классов во время выполнения?

Обновление

Я ищу решение, которое позволяет избежать использования файлов .m или .mm для написания реализации Objective- C. Даже если это не обычный способ сделать это. Файл с одним заголовком требуется архитектурой проекта, над которым я работаю. Проект является кроссплатформенным, и дизайн с одним заголовком не является проблемой для C ++ на Windows и Linux. Добавление исходного файла для определения объектов Objective- C нарушит существующую архитектуру.

Ответы [ 3 ]

2 голосов
/ 14 июля 2020

Это не предназначение файла заголовка. В файле заголовка вы определите свойства и методы, которые вы хотите сделать доступными там, где это необходимо. Реализация всегда находится в файле .m в случае цели C.

1 голос
/ 14 июля 2020

РЕДАКТИРОВАТЬ

Конечно, вы можете ... Я удалил предыдущую неудачную попытку удалить беспорядок.

// main.m

#import <Foundation/Foundation.h>

#import "A.h"
#import "B.h"
#import "C.h"

int main(int argc, const char * argv[])
{
    @autoreleasepool
    {
        NSLog(@"Hello, World!");

        [[A alloc] init];
        [[B alloc] init];
        [[C alloc] init];
    }

    return 0;
}
// A.h, repeat for B.h and C.h

#import <Foundation/Foundation.h>

@interface A : NSObject

@end
// A.m
// B.m and C.m are quite similar
// BUT drop the line below from B and C
#define ZIMP

#import "A.h"
#import "Header.h"

@implementation A

- ( id ) init
{
    self = super.init;

    [[[Z alloc] init] msg:@"A"];

    return self;
}

@end

А теперь грандиозный финал

// Header.h

#import <Foundation/Foundation.h>

@interface Z : NSObject

- ( void ) msg:( NSString * ) src;

@end

#ifdef ZIMP

@implementation Z

- ( void ) msg:( NSString * ) src
{
    NSLog( @"%@ says hi", src );
}

@end

#endif

Доказательство пудинга ... вот результат

2020-07-15 07:29:52.134413+0200 HdrImp[26901:700649] Hello, World!
2020-07-15 07:29:52.135121+0200 HdrImp[26901:700649] A says hi
2020-07-15 07:29:52.135297+0200 HdrImp[26901:700649] B says hi
2020-07-15 07:29:52.135389+0200 HdrImp[26901:700649] C says hi
Program ended with exit code: 0

Уловка, конечно же, состоит в том, чтобы заставить компилятор увидеть реализация только один раз, хотя файл заголовка включается несколько раз. Это достигается с помощью всего лишь одного #define.

Теперь мы можем сказать ребятам из Swift, что Objective- C можно сделать с помощью всего одного исходного файла.

ОБНОВЛЕНИЕ

Это работает, потому что я точно знаю, какие файлы будут включены, и могу установить защиту в одном из них. Если вы заранее не знаете, какой из файлов будет частью компиляции, у вас есть проблема, но вы можете решить ее с помощью дополнительных определений и кода в вашем файле make, где вы «знаете», какой из файлов будет включен. и можем принять меры на основании этого.

0 голосов
/ 14 июля 2020

Вам необходимо понимать различное назначение файлов заголовков (.h) и модулей (.m):

Правильное использование:

A Заголовочный файл просто объявляет типы и из чего они состоят. Это как сказать компилятору: смотри, где-то есть класс с именем Person, и вы можете вызвать для него speak. Он не сообщает компилятору внутреннее устройство функции speak, а просто сообщает, что этот тип существует где-то . Это называется объявлением типа .

Когда компилятор переводит файл Main.mm, он создает вызов еще неизвестной внешней функции speak. Он делает то же самое для Test.mm.

Затем, после компиляции вашего кода, компоновщик запускается для разрешения всех неизвестных адресов, например, проверяет, есть ли один и ровно одно определение класса Person с функцией speak. Если такого определения нет, вы получите ошибку неразрешенный символ . Если существует несколько определений, вы получите ошибку повторяющийся символ .

Следовательно, вам необходимо создать файл модуля * от до определить все ваши классы - ровно один раз. В вашем случае вам понадобится файл Person.m, который содержит @implementation класса (и функций).

Типы могут быть объявлены несколько раз, но могут быть определены только один раз.

Что вы сделали не так: Когда вы помещаете определение типа в файл заголовка, компилятор создает две реализации вашего класса (при переводе Main.mm и другую при переводе Test.mm), что затем сбивает с толку компоновщик, потому что компоновщик ожидает ровно один.

Вы можете иметь центральный файл заголовка, который объединяет все типы и субвключаемые файлы, но вам нужен один (или несколько) отдельных файлов модулей - и вы не должны включать их, потому что компоновщик разрешает ссылки для вас.

...