Как я могу обновить переменную swift при срабатывании функции обратного вызова c

#c #ios #objective-c #swift #objective-c

#c #iOS #objective-c #swift #objective-c

Вопрос:

Я хотел бы уведомлять swift code всякий раз, когда что-то меняется или событие запускается в моих функциях .cpp.

В моем приложении я использую следующую структуру.

(.cpp) [.hpp] -> (.mm) [.h] -> .swift

Я могу обработать changeThisString из кода swift через :

 let swiftString = CPPWrapper().MyMethodWrapper()
 

это нормально для нажатия кнопки / viewDidLoad, но я хотел бы обновлять это значение всякий раз, когда я устанавливаю его с c .

Если C передает новую строку в swift, она не должна ждать нажатия кнопки, она должна работать как слушатель.

Я буду очень рад любой помощи, спасибо.

Пример:

my.cpp:

 std::string changeThisString = "";

...

virtual void myCallBack(MyCallBackParam amp;paramter){
    changeThisString = "I WANT TO SEE THIS MESSAGE ON MY APP!"
}

std::string MyClass::MyMethod() {
    return changeThisString;
}
 

my.hpp:

 #include <string>

class MyClass{
public:
    std::string MyMethod();
};
 

wrapper.mm

 #import "wrapper.h"
#import "my.hpp"

    @implementation CPPWrapper
    
    MyClass myClass;
    
    - (NSString*) MyMethodWrapper {
        NSString* result = [NSString stringWithUTF8String:myClass.MyMethod().c_str()];
        return resu<
    }
    @end
 

Wrapper.h

 #import <Foundation/Foundation.h>

@interface CPPWrapper : NSObject
-(NSString*) MyMethodWrapper;
@end
 

.swift

 let swiftString = CPPWrapper().MyMethodWrapper()
 

Ответ №1:

Это пример обратного вызова C, который запускает уведомление об объединении.

(Перенесено на GitHub прямо здесь: https://github.com/moosefactory/C-callback-to-Swift )

Этот пример изменяет значение строки C в библиотеке C и отображает его в поле в представлении SwiftUI.

введите описание изображения здесь

Вам не нужно проходить Objective-C.

  • Первая часть — это библиотека C (несколько изменений, чтобы заставить ее работать с C )
  • Вторая часть — это класс C<->Swift

Я думаю, что неплохо иметь объект, который создает мост между вашим кодом C и вашим приложением swift, чтобы удалить эзотерический синтаксис из кода приложения. В этом примере файл MyCLibraryInterface выполняет эту работу.

Этот класс является наблюдаемым объектом, который будет публиковать изменение значения с помощью combine, так что это немного выходит за рамки вопроса — вы можете остановиться на этом и делать то, что хотите, как только окажетесь в блоке обратного вызова. Обратите внимание, что мы не можем перехватить контекст swift в вызовах c (нет вызовов self или переменных, объявленных в куче)

  • Третья часть — это простое приложение SwiftUI, которое получает изменения и обновляет интерфейс

Библиотека C

  • MyCLibrary.h
 #ifndef MyCLibrary_h
#define MyCLibrary_h

#include <stdio.h>
#include <dispatch/dispatch.h>
#include <stdlib.h>


/// The callback to call when the string is changed
typedef void callback_t(const char* string);

void setCallBack(callback_t _callback);

/// A function that will change the string
void setString(const char* string);

void startTimer(void);

void cancelTimer(void);

#endif /* MyCLibrary_h */
 
  • MyCLibrary.c
 #include "MyCLibrary.h"

const char* myString;

dispatch_queue_t queue;
dispatch_source_t timer;
bool running;

callback_t* callback;

void setCallBack(callback_t _callback) {
    callback = _callback;
}

void setString(const char* string) {
    myString = string;
    callback(myString);
}

/// A function that will start a timer that changes string

int ticks = 0;

void idle(dispatch_source_t timer)
{
    ticks  ;
    char ticksStr[32];
    sprintf(ticksStr, "Time : %ds", ticks);
    setString(ticksStr);
}

void startTimer() {
    if (running) { cancelTimer(); sleep(1); }
    
    queue = dispatch_queue_create("timerQueue", 0);

    // Create dispatch timer source
    timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);

    dispatch_source_set_event_handler(timer, ^{idle(timer);});

    dispatch_source_set_cancel_handler(timer, ^{
        dispatch_release(timer);
        dispatch_release(queue);
    });

    dispatch_time_t start = dispatch_time(DISPATCH_TIME_NOW, 0);

    // Set timer
    dispatch_source_set_timer(timer, start, NSEC_PER_SEC, 0);

    ticks = 0;
    running = true;
    dispatch_resume(timer);
}

void cancelTimer() {
    running = false;
    dispatch_source_cancel(timer);
    char ticksStr[32];
    sprintf(ticksStr, "Canceled after %ds", ticks);
    setString(ticksStr);
}
 

Часть C<>Swift

  • MyApp-Bridging-Header.h
 #import "MyCLibrary.h"
 
  • MyCLibraryInterface.swift
 import Foundation

class MyCLibraryInterface: ObservableObject {
    @Published var string: String = "This is a string"
    
    static let shared = MyCLibraryInterface()
    
    init() {
        setCallBack { stringPtr in
            let newString = CFStringCreateWithCString(kCFAllocatorDefault, stringPtr, kCFStringEncodingASCII) ?? "" as CFString
            DispatchQueue.main.async {
                MyCLibraryInterface.shared.string = newString as String
            }
        }
    }
    
    func setLibString(string: String) {
        string.withCString { stringPointer in
            setString(stringPointer)
        }
    }
    
    func startLibTimer() {
        startTimer()
    }

    func cancelLibTimer() {
        cancelTimer()
    }
}
 

Пример SwiftUI

В этом примере приложения представлены начальная строка и кнопка. При щелчке или касании setString функция вызывается в CLibrary, вызывается быстрый обратный вызов, и представление обновляется после ObservableObject изменения

 import SwiftUI

struct ContentView: View {
    @ObservedObject var myCLibInterface: MyCLibraryInterface = MyCLibraryInterface.shared
    
    var body: some View {
        VStack {
            Text(myCLibInterface.string).frame(width:150).padding()
            Button("Reset") {
                myCLibInterface.setLibString(string: "C Timer Example")
            }.padding()
            Button("Start Timer") {
                myCLibInterface.startLibTimer()
            }.padding()
            Button("Cancel") {
                myCLibInterface.cancelLibTimer()
            }.padding()
        }.padding(20)
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
 

Комментарии:

1. Спасибо, но я уже могу использовать этот способ с тем, чем я поделился, я хочу что-то вроде observer вместо ожидания нажатия кнопки, это должно измениться напрямую. setString — это не то, что я хочу, и использовать поток обратного вызова в c через swift непросто. В любом случае спасибо.

2. Кнопка приведена здесь только в качестве примера для запуска функции C. Извините, я должен был дать более конкретный ответ. Если вы используете таймер, который увеличивает счетчик в библиотеке c, ваш обратный вызов будет вызван в swift. Вам всегда придется передавать обратный вызов в любом случае. Что-то вроде myCLibInterface.startTimer() { // Your swift callback } . А также вам не нужна часть obj-C. Я заменю пример ‘setString’ на ‘startTimer’, когда найду время. Приветствия

3. Я изменил, чтобы добавить таймер, который обновляет строку без действий пользователя. Если вы действительно хотите наблюдать строковое значение C без вызова setString , вам следует создать таймер, который отслеживает строковое значение. Но я не уверен, что это хороший вариант.

4. Большое вам спасибо, я действительно ценю ваши усилия. Это действительно ясно и понятно. Мне нужна некоторая модификация, чтобы иметь возможность адаптировать ее для c , тогда в соответствии с результатом я одобрю ваш ответ. Еще раз большое вам спасибо @Moose!

5. Добро пожаловать, Эмре :). Ошибка странная. Вы пробовали использовать проект, который я загрузил на github? Это похоже на проблему с соединительным заголовком.