2

I made an array in a singleton to write objects into it from multiple parts of my code. Here's how:

// in singleton.h
#import <UIKit/UIKit.h>

// make globally accessible array
@interface MyManager : NSObject {
    NSMutableArray *imgArray;
}
@property (nonatomic, retain) NSMutableArray *imgArray;
+ (id)sharedManager;
@end

// in singleton.m
#import "singleton.h"

For my .m file :

@implementation MyManager

@synthesize imgArray;

#pragma mark Singleton Methods

+ (id)sharedManager {
    static MyManager *sharedMyManager = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedMyManager = [[self alloc] init];
    });
    return sharedMyManager;
}

- (id) init {
    if (self = [super init]) {
        self.imgArray = [NSMutableArray new];
        }
    NSLog(@"initialized");
    return self;
}

@end

I can access my array called imgArray it from my objective C code. However, In swift I get an error when I do this:

let array  = MyManager.sharedManager()

array.imgArray.add("hello world") .    (!!!) Value of type 'Any?' has no member 'imgArray'

I can access MyManager.sharedManager(), but Why can't I access imgArray the same way as in objective C?

Rob
  • 415,655
  • 72
  • 787
  • 1,044
SumakuTension
  • 542
  • 1
  • 9
  • 26

2 Answers2

3

You should declare it as instancetype or MyManager *. E.g.

+ (MyManager *)sharedManager;

or

+ (instancetype)sharedManager;

A couple of suggestions:

  1. The Swift convention for singleton’s is to use a class property name of shared, not a class method of sharedManager(). When you declare it in Objective-C, you might want to explicitly say that it’s a class property:

    @property (class, readonly, strong) MyManager *sharedManager NS_SWIFT_NAME(shared);
    

    This won’t change any of the Objective-C behavior, but in Swift, you can just do:

    let manager = MyManager.shared
    manager.images.add(image)
    

    This results in more concise and idiomatic Swift code.

  2. I’d suggest that you audit your Objective-C for nullability. I.e., confirm what can be nil and what can’t. Since both imgArray (which I might just call images) and sharedManager can never be nil, I would just use the NS_ASSUME_NONNULL_BEGIN/END macros which tells the compiler “unless I tell you otherwise, assuming this property cannot be nil”:

    //  MyManager.h
    
    @import UIKit;
    
    NS_ASSUME_NONNULL_BEGIN
    
    @interface MyManager : NSObject
    
    @property (nonatomic, strong) NSMutableArray <UIImage *> *images;
    @property (class, readonly, strong) MyManager *sharedManager NS_SWIFT_NAME(shared);
    
    @end
    
    NS_ASSUME_NONNULL_END
    

    By telling the compiler that these two cannot be nil, that means that you’ll have to do less unnecessary unwrapping of optionals in your Swift code.

  3. As an aside, notice that I didn't declare an instance variable. (And if you did need one, I wouldn’t advise declaring it in the public interface.) Objective-C will now synthesize the ivars backing our properties automatically for us. (So my property images will have an ivar called _images that will be synthesized for me.) And you don’t need/want the @synthesize line, either:

    //  MyManager.m
    
    #import "MyManager.h"
    
    @implementation MyManager
    
    + (instancetype)sharedManager {
        static MyManager *sharedMyManager = nil;
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            sharedMyManager = [[self alloc] init];
        });
        return sharedMyManager;
    }
    
    - (instancetype)init {
        if (self = [super init]) {
            self.images = [NSMutableArray new];
        }
        NSLog(@"initialized");
        return self;
    }
    
    @end
    
Rob
  • 415,655
  • 72
  • 787
  • 1,044
2

Change + (id)sharedManager; to + (MyManager *)sharedManager;. Otherwise Swift doesn't know what kind of object sharedManager is and it will assume it's Any.

EmilioPelaez
  • 18,758
  • 6
  • 46
  • 50