How do you translate a @synthesize protocol variable in Swift?

284 views Asked by At

I have to convert some files from objective-C into Swift. The problem is that I am really confused between protocols and classes that are called the same, and the @synthetize protocol variable that I don't know how to init or how to translate into Swift. I'll try to make a simple scheme.

// Protocols.h
ProtocolA {
 - functionA
}

ProtocolB {
 - variableNameB : ProtocolA // Can a variable be of type protocol? shouldn't it be of type class and that class is the one that follows the protocol?
 - functionB
}
------------------------

// Class1.m
Class1: ProtocolB {
// This is what I don't understand at all, how is initiating itself? There are not initialisers for Protocols.
@synthesize variableNameB = _variableNameB 
}

ERRORS I GET

If I try to just write var variableNameB: ProtocolA without giving any value, I will get this in the Class1 init method:

Property 'self.variableNameB' not initialized at implicitly generated super.init call

If I try to initialise it inside the Class1 init doing self.variableNameB = ProtocolA() I get:

ProtocolA cannot be constructed because it has no accessible initializers

If I try to make variableNameB optional, so I am not force to initialize it, I get:

Objective-C method 'variableNameB' provided by getter for 'variableNameB' conflicts with optional requirement getter for 'variableNameB' in protocol ProtocolB

Objective-C method 'setVariableNameB:' provided by setter for variableNameB conflicts with optional requirement setter for 'variableNameB' in protocol ProtocolB


ALL FILES FOR EXTRA CONTEXT, OBJECTIVE-C & SWIFT

TermsAndConditionsReader.h

@class UserAccountManager;
@class TermsAndConditionsManager;
@protocol TermsAndConditionsManagerObserver;

NS_ASSUME_NONNULL_BEGIN

@interface TermsAndConditionsReader : GenericInteractor <TermsAndConditionsReader>

INTERACTOR_INIT_UNAVAILABLE;

- (instancetype)initWithUserAccountManager:(UserAccountManager*)userAccountManager
                 termsAndConditionsManager:(TermsAndConditionsManager*)termsAndConditionsManager
                                     appId:(NSUInteger)appId NS_DESIGNATED_INITIALIZER;


@property (nonatomic, strong, readonly) UserAccountManager* userAccountManager;
@property (nonatomic, strong, readonly) TermsAndConditionsManager* termsAndConditionsManager;

@end

NS_ASSUME_NONNULL_END

TermsAndConditionsReader.m

@interface TermsAndConditionsReader () <TermsAndConditionsManagerObserver>

  @property (nonatomic, assign) NSUInteger appId;

@end

@implementation TermsAndConditionsReader
  @synthesize listener = _listener;

- (instancetype)initWithUserAccountManager:(UserAccountManager*)userAccountManager
                 termsAndConditionsManager:(TermsAndConditionsManager*)termsAndConditionsManager
                                     appId:(NSUInteger)appId {
    self = [super init];

    if (self) {
        _userAccountManager = userAccountManager;
        _termsAndConditionsManager = termsAndConditionsManager;
        [termsAndConditionsManager addTermsAndConditionsManagerObserverWithObserver:self];
        _appId = appId;
    }

    return self;
}

- (BOOL)areTermsAndConditionsAccepted {
    id<UserAccount> userAccount = self.userAccountManager.userAccount;
    return [userAccount hasAcceptedTermsAndConditionsForApp:self.appId];
}

#pragma mark - TermsAndConditionsManagerObserver

- (void)termsAndConditionsManagerUserNeedsToAcceptNewTermsWithSender:(TermsAndConditionsManager * _Nonnull)sender {
    [self.listener termsAndConditionsReaderNewTermsAndConditionsHasToBeAccepted:self];
}

@end

TermsAndConditionsReader.swift --> This is probably wrong, it's how I tried to convert it

@objc public class TermsAndConditionsReader: GenericInteractor, TermsAndConditionsReaderProtocol, TermsAndConditionsManagerObserver {

let userAccountManager: UserAccountManager
let termsAndConditionsManager: TermsAndConditionsManager
let appId: UInt
public var listener: AnyObject & TermsAndConditionsListener

@objc public init(userAccountManager: UserAccountManager, termsAndConditionsManager: TermsAndConditionsManager, appId: UInt) {
    self.appId = appId
    self.userAccountManager = userAccountManager
    self.termsAndConditionsManager = termsAndConditionsManager
    termsAndConditionsManager.addTermsAndConditionsManagerObserver(observer: self) // -> It complains here because I am calling self before having initialised listener
}

// MARK: - TermsAndConditionsReaderProtocol

@objc public func areTermsAndConditionsAccepted() -> Bool {
    return self.userAccountManager.userAccount?.hasAcceptedTermsAndConditions(forApp: self.appId) ?? false
}

// MARK: - TermsAndConditionsManagerObserver

@objc public func termsAndConditionsManagerUserNeedsToAcceptNewTerms(sender: TermsAndConditionsManager) {
    // call listener here:
}
}

InteractorsTermsAndConditions.h

@protocol TermsAndConditionsListener <NSObject>

- (void)termsAndConditionsReaderNewTermsAndConditionsHasToBeAccepted:(id<TermsAndConditionsReader>)sender;

@end


@protocol TermsAndConditionsReader <NSObject>

@property (nonatomic, weak, nullable) id<TermsAndConditionsListener> listener;

- (BOOL)areTermsAndConditionsAccepted;

@end

InteractorsTermsAndConditions.swift -> This is how I translated it

@objc public protocol TermsAndConditionsListener {
    @objc func termsAndConditionsReaderNewTermsAndConditionsHasToBeAccepted(sender: TermsAndConditionsReaderProtocol)

}


@objc public protocol TermsAndConditionsReaderProtocol {

    @objc var listener: AnyObject &  TermsAndConditionsListener { get set }
    @objc func areTermsAndConditionsAccepted() -> Bool

}
1

There are 1 answers

3
Skwiggs On

You cannot use ProtocolA as a type in your Class1 implementation; you need to declare another Class2: ProtocolA and use that as your variableNameB

In the context of your code, you'd need to declare

// rename your listener protocol
protocol TermsAndConditionsListenerProtocol {
    // whatever requirements
}

// implement a concrete type that conforms to this protocol
class TermsAndConditionsListener: TermsAndConditionsListenerProtocol {
    // implement requirements 
    // as well as any init you need
    init(hello: String) { /* ... */ }
}

// adjust your reader protocol
protocol TermsAndConditionsReaderProtocol {
    var listener: TermsAndConditionsListenerProtocol { get set }
    func areTermsAndConditionsAccepted() -> Bool
}

// implement a concrete reader that conforms to reader protocol
class TermsAndConditionsReader: TermsAndConditionsReaderProtocol {
    // here you use a concrete type that conforms to the listener protocol
    var listener: TermsAndConditionsListener = .init(hello: "world")
    
    // + other requirements
}