In my custom swift framework I have a singleton class which end user will be using. The singleton init() does lots of work for which I want to throw error if anything goes wrong during initialisation.
mySingleton.swift
public class mySingleton: NSObject {
.......
public static let sharedInstance = mySingleton(singleton : true)
....
}
private override convenience init() {
assertionFailure( "Please use sharedInstance" )
self.init(singleton : false)
}
private init(singleton: true) {
super.init()
//Other class objects init() etc.
//Some other processing etc.
}
Framework users:
//Initialise framework
mySingleton.sharedInstance
What would be the best way to throw error/exception to the end user during mySingleTon init process.
You should really, really think about what your intentions are here.
A singleton is created once and stays alive for the rest of the application. What is your application going to do if creating the singleton fails? Where I use singletons, they must succeed. If they don't, my app cannot run.
If creating the singleton fails, then the best is to produce an assertion that will kill the app. On the other hand, in many problematic situations (missing network connection, for example) creation of the singleton should not fail.
UPDATED
Variant with Implicitly Unwrapped value:
public class MySingleton: NSObject {
private static var _sharedInstance: MySingleton!
public static var sharedInstance: MySingleton! {
if _sharedInstance == nil {
let instance = MySingleton()
if let error = instance.initializationError {
//log an error
print("An error occured during singleton object instantiation: \(error)")
return nil
}
_sharedInstance = instance
}
return _sharedInstance
}
var initializationError: ErrorType?
private override init() {
super.init()
//Other class objects init() etc.
//Some other processing etc.
do {
try initialize()
} catch {
initializationError = error
}
}
private func initialize() throws {
throw NSError(domain: "my.error", code: -1, userInfo: nil)
}
}
If you want to throw an error or make an init fail pertaining to certain conditions ,use failable initialisers
struct Animal {
let species: String
init?(species: String) {
if species.isEmpty { return nil }
self.species = species
}
}
Hope this should help
Related
I am trying to unit test a class that has different modes of setup.
class Controller{
enum Mode{
case listing
case pages(String?)
}
private (set) var mode : Mode = .listing
private (set) var models = [Model]()
init() {
...
}
init(id : String) {
mode = .pages(id)
}
func fetchInfo(){
switch mode{
case .listing:
ApiManager.firstNetworkCall(){ (json, error) in ...
setupModel()
}
case .pages(let id):
ApiManager.secondNetworkCall(id : id){ (json, error) in ...
setupModel()
}
}
}
}
Both of these will update the models array with different quantity of data.
What I have right now:
var controller : Controller!
override func setUpWithError() throws {
// Put setup code here. This method is called before the invocation of each test method in the class.
try super.setUpWithError()
controller = Controller()
}
override func tearDownWithError() throws {
// Put teardown code here. This method is called after the invocation of each test method in the class.
controller = nil
try super.tearDownWithError()
}
func testDefaultListingMode() throws {
switch controller.mode{
case .listing:
XCTAssertTrue(true)
default:
XCTAssertFalse(false)
}
}
func testAPISetup() throws {
controller.fetchInfo()
//now what?
}
This checks if the mode is correct but I am trying to go one step further and check if the correct number of items is setup depending on the mode. And want to call the fetchInfo() method directly from the XCTestCase and just validate the model count.
All the tutorials and guides I have seen just talk about faking the behaviour with a URLSession. But the API call is dependent on the mode that happens as an internal check inside the fetchInfo method and is the only method exposed to other classes. I would simply like to test the method (in case something breaks inside that method causing a bug).
How do I go about doing that? I can't figure out how to complete the testAPISetup() method.
What I had for networking:
class NetworkingManager{
static var alamoFireManager = Session.default
static func POST(...., completion : ()->()) {
sendRequest(....., completion : completion)
}
private static func sendRequest(...., completion : ()->()) {
let request = alamoFireManager.request(.....)
request.responseJSON{
completion()
}
}
}
class APIManager{
static func firstNetworkCall(completion : ()->()){
NetworkingManager.POST(..., completion : completion)
}
}
I had to change the above and removed all mentions of static and singletons. I decided to go ahead with using class inheritance. I tried to avoid it and use protocols but it frankly was quite easier to use classes!
class NetworkingManager{
private (set) var sessionManager: Session
init(config : URLSessionConfiguration = .default){
config.timeoutIntervalForResource = 8.0
config.timeoutIntervalForRequest = 8.0
self.sessionManager = Session(configuration: config)
}
func request(...) {
//hit alamofire
}
}
class APIManager : NetworkingManager{
override init(config: URLSessionConfiguration = .default) {
super.init(config: config)
}
//other methods
...
}
class Controller{
private let apiManager : APIManager
init(manager : APIManager = APIManager()){
self.apiManager = manager
}
}
And in my test class:
override func setUpWithError() throws {
// Put setup code here. This method is called before the invocation of each test method in the class.
try super.setUpWithError()
let config = URLSessionConfiguration.ephemeral
apiManager = APIManager(config : config)
controller = Controller(manager : apiManager)
}
func testApiCalled() throws{
controller.fetchNecessaryInfo()
//had to add one second delay as alamofire adds the request on another queue. Wasn't able to put it on main queue.
sleep(1)
let promise = expectation(description: "Check request called")
apiManager.sessionManager.session.getAllTasks { (taskArray) in
if taskArray.count > 1{
XCTFail("Multiple requests when there should be only one")
}
if let task = taskArray.first, let request = task.currentRequest{
if let string = request.url?.absoluteString{
XCTAssert(...)
}else{
XCTFail("Incorrect URL")
}
}else{
XCTFail("Somehow no task exists. So this is an error")
}
promise.fulfill()
}
wait(for: [promise], timeout: 1.0)
}
I couldn't figure out any other way without having to instantiate an object for APIManager, so had to refactor!
I'm trying to implement SharedInstanceDelegate in App class. I have no idea why the functions under the protocol are not being called.
This is my Protocol and class.
class App {
let sharedInstance = SharedInstance.shared
init() {
self.sharedInstance.delegate = self
}
}
extension App: SharedInstanceDelegate {
func1() { } // this is not executed
func2() { }
}
protocol SharedInstanceDelegate: class {
func1()
func2()
}
class SharedInstance {
static let shared = SharedInstance()
weak var delegate: SharedInstanceDelegate?
private init() { }
func method1() {
self.delegate?.func1() // this is executed
}
}
I believe you meant to make SharedInstanceDelegate a protocol, but you've made it a class. In either case, App does not conform/inherit SharedInstanceDelegate, so it's not clear how this would even compile.
Here is how I would implement your code to work with the delegate:
class App {
let sharedInstance = SharedInstance.shared
init() {
self.sharedInstance.delegate = self
}
}
extension App: SharedInstanceDelegate {
func func1() { } // this will run now
func func2() { }
}
protocol SharedInstanceDelegate {
func func1()
func func2()
}
class SharedInstance {
static let shared = SharedInstance()
var delegate: SharedInstanceDelegate?
private init() { }
func method1() {
self.delegate?.func1() // this is executed
}
}
Still no idea why this was happening, but cleaning the project fixed this. This is very strange. I have other delegates that call successfully.
Your code could work but it depends on how you are calling func1(). Calling it like this:
let testinstance = App().sharedInstance
testinstance.delegate?.func1()
will not work because you are not holding on to the App object. In this case the App object is the delegate, but because its a weak member and no one is retaining it, it gets released right away.
If you call it like this:
let testapp = App()
testapp.sharedInstance.delegate?.func1()
it works. In this case the App object is being retained and is still around when func1() is called.
Either way the way these classes are related is confusing to me. Why have a separate SharedInstance class at all?
I'm creating a singleton instance like this
static let currentUser = User()
private override init() {
super.init()
// custom initialisation
}
How can I reset this instance or set back to nil?
I create all my Singletons with an optional Singleton instance.
However I also make this private and use a function to fetch it.
If the Singleton is nil it creates a new instance.
This is actually the only good way to set up a Singleton. If you have a regular object that you can't deinitialize it's a memory problem. Singletons are no different, except that you have to write a function to do it.
Singletons have to be completely self managed. This means from init to deinit.
I have a couple of templates on github for Singeltons, one of them with a fully implemented read/write lock.
class Singleton {
private static var privateShared : Singleton?
class func shared() -> Singleton { // change class to final to prevent override
guard let uwShared = privateShared else {
privateShared = Singleton()
return privateShared!
}
return uwShared
}
class func destroy() {
privateShared = nil
}
private init() {
print("init singleton")
}
deinit {
print("deinit singleton")
}
}
You can not do that if you declare currentUser as let. It should be var instead, or better still private (set) var. Also you can not assign currentUser with nil if its type is User (inferred from the way you assign it at the moment). Instead, it should be User?.
For example, something like this:
/// ...
static private (set) var currentUser: User? = User()
static func resetCurrentUser() {
currentUser = nil
}
// ...
private (set) allows changes to the property only within the scope of current file, and for the rest of your code it will be seen as let. Then method resetCurrentUser() can be used to put it to nil.
Or even this:
// ...
private static var _currentUser: User?
static var currentUser: User {
if _currentUser == nil { _currentUser = User() }
return _currentUser!
}
static func resetCurrentUser() {
_currentUser = nil
}
// ...
You can have currentUser as computed property that guarantees to return a value. So you can reset the user to nil, yes. But if later you will try to read from there again a new instance will be created automatically.
Be careful with multithreaded access, though.
The accepted answer is working, but if you want to not deal with optionals you can create a private setter:
class Singleton {
static private(set) var shared: Singleton = Singleton()
static func reset() {
shared = Singleton()
}
private init() {
print("init singleton")
}
deinit {
print("deinit singleton")
}
}
All you want is possible, but highly unrecommended :) Because singletons by design should not fall back to nil.
First, if you want to change currentUser, it must be var. Then if you want it to be nil, it must by optional type and you should unwrap it when using.
static var currentUser: User? = User()
I would propose to not change currentUser or make it non-static (for example, a property of some UsersManager.
Also you can change properties of currentUser (like name, loggedIn). At last, take a look at this answer: https://stackoverflow.com/a/28398974/326017 - it describes your situation.
I have following crash with sharedInstance return Static.instance! line:
EXC_BREAKPOINT 0x0000000100da42d8
Crash happened in Ad Hoc release build where there is no debug breakpoints.
According to call stack of the crash sharedInstance should already exist at the time of crash (it is first called on app launch, crash occurred on button tap).
Crash occurred on iPad Air 2 with iOS 8.4.0, build is compiled with Swift 2.1 compiler in Xcode 7.1.1
App itself (which calls sharedInstance on launch) and DataSource class are located in different modules. Could it be that for classes from framework with DataSource class static struct is different?
#objc public final class DataSource : NSObject
{
public class var sharedInstance: DataSource
{
struct Static
{
static var instance: DataSource?
static var token: dispatch_once_t = 0
}
dispatch_once(&Static.token)
{() -> Void in
Static.instance = DataSource()
}
return Static.instance!
}
override init()
{
super.init()
...
}
...
}
A static singleton is backed by GCD anyway so this is the recommended syntax
#objc public final class DataSource : NSObject
{
class var sharedInstance : DataSource {
struct Static {
static let sharedInstance = DataSource()
}
return Static.sharedInstance
}
override init()
{
super.init()
...
}
...
}
I am a Android developer, and I'm very new to Swift so please bear with me. I am trying to implement callback functions with Protocol in Swift. In Java I can create an Interface and make it an instance without linking it to any implementing class so that I can pass it around, for example:
public interface SomeListener {
void done();
}
SomeListener listener = new SomeListener() {
#Override
public void done() {
// do something
}
}
listener.done();
How can I do it with Protocol in Swift? Or can it actually be done?
That`s a way you can implement a protocol. Its like the delegate pattern in ObjC
protocol DoneProtocol {
func done()
}
class SomeClass {
var delegate:DoneProtocol?
func someFunction() {
let a = 5 + 3
delegate?.done()
}
}
class Listener : DoneProtocol {
let someClass = SomeClass()
init() {
someClass.delegate = self
someClass.someFunction()
}
// will be called after someFunction() is ready
func done() {
println("Done")
}
}