why do we need the service class using singleton in Swift? - ios

I watch realm tutorial from youtube, the instructor using singleton in the service class like the code below :
import Foundation
import RealmSwift
class RealmService {
// singleton
private init() {}
static let shared = RealmService()
var realm = try! Realm()
func save<T: Object>(object: T) {
do {
try realm.write {
realm.add(object)
}
} catch {
post(error)
}
}
func update<T: Object>(object: T, for dictionary: [String: Any?]) {
do {
try realm.write {
for (key,value) in dictionary {
object.setValue(value, forKey: key)
}
}
} catch {
post(error)
}
}
func delete<T: Object>(object: T) {
do {
try realm.write {
realm.delete(object)
}
} catch {
post(error)
}
}
func post(_ error: Error) {
NotificationCenter.default.post(name: Notification.Name.realmError, object: error)
}
func observerRealmErrors(in vc: UIViewController, completion: #escaping(Error?) -> Void ) {
NotificationCenter.default.addObserver(forName: Notification.Name.realmError, object: nil, queue: nil) { (notification) in
completion(notification.object as? Error)
}
}
func stopObservingErrors(in vc: UIViewController) {
NotificationCenter.default.removeObserver(vc, name: NSNotification.Name.realmError, object: nil)
}
}
I understand that A singleton is an object which is instantiated exactly once. but why it needs to use singleton for this purpose? if the code s like below, I understand, because it doesn't make any sense if there is more than one "bobby" as the ID, then it makes sense to create a singleton class
but how about that realm service like the code above? why it needs to use singleton? I have seen some instructor using singleton for the service class
class AccountManager {
static let sharedInstance = AccountManager()
var userInfo = (ID: "Bobby", Password: 01036343984)
// Networking: communicating server
func network() {
// get everything
}
private init() { }
}

You gave an example of a shared property (userInfo) in your second block , but in the first block the instructor uses a singleton to prevent himself from duplicating code everywhere inside the project , and only to have one single unit to do the crud ( create , read , update , delete) operations of the Realm database and it's another use of a singleton class other than sharing a value

It's simple. If you don't try to using a singleton in realm case like you have wrote, you should call realm = try! Realm() method every time when you trying to use realm objects or functions(saving deleting etc..). It will make your code too boring and also can causes many miss point. If you using realm with the singleton code like you have wrote(it wrote with generic functions), you can save your object in realm database with just single line of code like RealmService.save(yourobject).

Related

Is it possible to observe iOS NSObject value changes with Kotlin/Native

I am trying to implement an observer for changes to a value for a give key in UserDefaults from the ios native part of a multiplatform project written in Kotlin/Native. Here is the code that I wrote:
fun subscribeForDataChange(storeName: String, callback: () -> Unit) {
NSUserDefaults(storeName).addObserver(
object : NSObject() {
fun observeValue(
observer: NSObject,
forKeyPath: String,
options: NSKeyValueObservingOptions,
context: COpaquePointer?
) {
callback()
print("Data Changed!!!")
}
},
options = NSKeyValueObservingOptionNew,
forKeyPath = DATA_KEY,
context = null
)
}
The problem is that I never get a notification, most probably because the observeValue is not defined in NSObject, but what else should I do to achieve that?
Are you looking for an NSObject change, or you want to observe NSUserDefaults? If the latter, check out Multiplatform Settings. Here's the code that wires up observers.
https://github.com/russhwolf/multiplatform-settings/blob/master/multiplatform-settings/src/appleMain/kotlin/com/russhwolf/settings/AppleSettings.kt
Here is the solution for 2 apps in the same group sharing UserDefaults. I share SQLite database between two processes and I need to know when one process writes somethink to db. Classical flows are not triggered so I wrote a flow helper, which emit values in Kotlin when NSUserDefaults changes.
Implement NSObject as a part of the Swift codebase (Swift code inspiration). Swift calls a Kotlin method when NSUserDefaults changes. Firstly define interfaces.
interface NSUserDefaultsKotlinHelper {
fun userDefaultsChanged()
}
interface SwiftInjector {
fun injectIntoSwift(nsUserDefaultsKotlinHelper: NSUserDefaultsKotlinHelper?)
}
Let that interface inject listener into Swift code :
class InterprocessObserver: NSObject, SwiftInjector {
let key: String = "interprocess_communication"
private var nsUserDefaultsKotlinHelper : NSUserDefaultsKotlinHelper?
private let userDefaults = UserDefaults.init(suiteName: "group.your.group.id")
override init() {
super.init()
userDefaults?.addObserver(self, forKeyPath: key, options: [.old, .new], context: nil)
}
func injectIntoSwift(nsUserDefaultsKotlinHelper: NSUserDefaultsKotlinHelper?) {
self.nsUserDefaultsKotlinHelper = nsUserDefaultsKotlinHelper
}
func dataChangedFromAnotherProcess(data : [AnyHashable : Any]) {
userDefaults?.set(data, forKey: key)
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) {
guard let _ = change, object != nil, keyPath == key else { return }
nsUserDefaultsKotlinHelper?.userDefaultsChanged()
}
deinit {
userDefaults?.removeObserver(self, forKeyPath: key, context: nil)
}
}
Inject listener in Kotlin - I will inject when a flow starts to collect:
class InterProcessCommunication(val interPlatformInjector: InterplatformInjector) : InterplatformInjector by interplatformInjector {
val testFlow: Flow<Emitter> = flow {
val channel = Channel<Emitter>(CONFLATED)
channel.trySend(Emitter.STAY_CALM)
val listener = object : IInterprocessCommunication {
override fun interProcessChanged() {
channel.trySend(Emitter.EMIT)
}
}
interPlatformInjector.injectListener(listener)
try {
for (item in channel) {
emit(item)
}
} finally {
interPlatformInjector.injectListener(null)
}
}
}
Objects creation with Koin would be:
//Swift
func initObservers() {
let interplatformInjector = InterprocessObserver()
initKoin(interplatformInjector : interplatformInjector)
}
//Kotlin
fun initKoin(interplatformInjector : InterplatformInjector){
startKoin {
module {
single {InterProcessCommunication(interplatformInjector)}
}
}
}
//Swift Second process (for example NotificationService)
func dataChanged(interprocessObserver : InterprocessObserver) {
interprocessObserver.dataChangedFromAnotherProcess(data) //data could be anythink - for example a string
}
The method dataChenged() will trigger a Kotlin flow. Is this what you are looking for?

query regarding mocking singleton in swift ,ios using xctest?

this is not a question regarding that should we use singleton or not. but rather mocking singleton related.
this is just a sample example, as i was reading about mocking singleton is tough. so i thought let me give a try.
i am able to mock it but not sure is this a correct approach ?
protocol APIManagerProtocol {
static var sharedManager: APIManagerProtocol {get set}
func doThis()
}
class APIManager: APIManagerProtocol {
static var sharedManager: APIManagerProtocol = APIManager()
private init() {
}
func doThis() {
}
}
class ViewController: UIViewController {
private var apiManager: APIManagerProtocol?
override func viewDidLoad() {
}
convenience init(_ apimanager: APIManagerProtocol){
self.init()
apiManager = apimanager
}
func DoSomeRandomStuff(){
apiManager?.doThis()
}
}
import Foundation
#testable import SingleTonUnitTesting
class MockAPIManager: APIManagerProtocol {
static var sharedManager: APIManagerProtocol = MockAPIManager()
var isdoThisCalled = false
func doThis(){
isdoThisCalled = true
}
private init(){
}
}
class ViewControllerTests: XCTestCase {
var sut: ViewController?
var mockAPIManager: MockAPIManager?
override func setUp() {
mockAPIManager = MockAPIManager.sharedManager as? MockAPIManager
sut = ViewController(mockAPIManager!)
}
func test_viewController_doSomeRandomStuffs(){
sut?.DoSomeRandomStuff()
XCTAssertTrue(mockAPIManager!.isdoThisCalled)
}
override func tearDown() {
sut = nil
mockAPIManager = nil
}
}
The basic idea is right: Avoid repeated references to the singleton directly throughout the code, but rather inject object that conforms to the protocol.
What’s not quite right is that you are testing something internal to the MockAPIManager class. The mock is only there to serve a broader goal, namely to test your business logic (without external dependencies). So, ideally, you should be testing something that is exposed by APIManagerProtocol (or some logical result of that).
So, let’s make this concrete: For example, let’s assume your API had some method to retrieve the age of a user from a web service:
public protocol APIManagerProtocol {
func fetchAge(for userid: String, completion: #escaping (Result<Int, Error>) -> Void)
}
(Note, by the way, that the static singleton method doesn’t belong in the protocol. It’s an implementation detail of the API manager, not part of the protocol. No controllers that get a manager injected will ever need to call shared/sharedManager themselves.)
And lets assume that your view controller (or perhaps better, its view model/presenter) had a method to retrieve the age and create an appropriate message to be shown in the UI:
func buildAgeMessage(for userid: String, completion: #escaping (String) -> Void) {
apiManager?.fetchAge(for: userid) { result in
switch result {
case .failure:
completion("Error retrieving age.")
case .success(let age):
completion("The user is \(age) years old.")
}
}
}
The API manager mock would then implement the method:
class MockAPIManager: APIManagerProtocol {
func fetchAge(for userid: String, completion: #escaping (Result<Int, Error>) -> Void) {
switch userid {
case "123":
completion(.success(42))
default:
completion(.failure(APIManagerError.notFound))
}
}
}
Then you could test the logic of building this string to be shown in your UI, using the mocked API rather than the actual network service:
class ViewControllerTests: XCTestCase {
var viewController: ViewController?
override func setUp() {
viewController = ViewController(MockAPIManager())
}
func testSuccessfulAgeMessage() {
let e = expectation(description: "testSuccessfulAgeMessage")
viewController?.buildAgeMessage(for: "123") { string in
XCTAssertEqual(string, "The user is 42 years old.")
e.fulfill()
}
waitForExpectations(timeout: 1)
}
func testFailureAgeMessage() {
let e = expectation(description: "testFailureAgeMessage")
viewController?.buildAgeMessage(for: "xyz") { string in
XCTAssertEqual(string, "Error retrieving age.")
e.fulfill()
}
waitForExpectations(timeout: 1)
}
}
i was reading about mocking singleton is tough
The notion is that if you have these APIManager.shared references sprinkled throughout your code, it’s harder to swap them out with the mock object. Injecting solves this problem.
Then, again, if you’ve now injected this APIManager instance everywhere to facilitate mocking and have eliminate all of these shared references, it begs the question that you wanted to avoid, namely why use a singleton anymore?

iOS RxSwift how to connect Core bluetooth to Rx sequences?

I'm trying to create an observable sequence to indicate the status of Bluetooth on device. I'm using ReplaySubject<CBManagerState>, but am curious if there is something better, as I hear bad things about using onNext()
What is the appropriate way to connect callback delegates to the RxSwift observable domain?
class BluetoothStatusMonitor: NSObject, CBPeripheralManagerDelegate {
let bluetoothStatusSequence = ReplaySubject<CBManagerState>.create(bufferSize: 1)
var bluetoothPeripheralManager: CBPeripheralManager?
func checkBluetoothStatus()
{
//silently check permissions, without alert
let options = [CBCentralManagerOptionShowPowerAlertKey:0]
bluetoothPeripheralManager = CBPeripheralManager(delegate: self, queue: nil, options: options)
}
func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) {
bluetoothStatusSequence.onNext(peripheral.state)
}
}
This is exactly the kind of things that Subjects are good for. They exist primarily to convert non-Rx code into Rx code. That said, RxCocoa has the DelegateProxy type that is designed to handle a lot of the work necessary to do delegates right. It's still hard to figure out exactly how to implement one, but once you get the hang of it they are quite useful...
I have to admit that most of the code is black magic to me, but it does work. I try to explain as much as I can in comments below.
import RxSwift
import RxCocoa
import CoreBluetooth
// The HasDelegate protocol is an associated type for the DelegateProxyType
extension CBPeripheralManager: HasDelegate {
public typealias Delegate = CBPeripheralManagerDelegate
}
class CBPeripheralManagerDelegateProxy
: DelegateProxy<CBPeripheralManager, CBPeripheralManagerDelegate>
, DelegateProxyType
, CBPeripheralManagerDelegate {
init(parentObject: CBPeripheralManager) {
super.init(parentObject: parentObject, delegateProxy: CBPeripheralManagerDelegateProxy.self)
}
deinit {
_didUpdateState.onCompleted()
}
static func registerKnownImplementations() {
register { CBPeripheralManagerDelegateProxy(parentObject: $0) }
}
// a couple of static functions for getting and setting a delegate on the object.
static func currentDelegate(for object: CBPeripheralManager) -> CBPeripheralManagerDelegate? {
return object.delegate
}
static func setCurrentDelegate(_ delegate: CBPeripheralManagerDelegate?, to object: CBPeripheralManager) {
object.delegate = delegate
}
// required delegate functions must be implemented in the class. This is where Subjects come in.
func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) {
_didUpdateState.onNext(peripheral.state)
}
fileprivate let _didUpdateState = PublishSubject<CBManagerState>()
}
extension Reactive where Base: CBPeripheralManager {
var delegate: CBPeripheralManagerDelegateProxy {
return CBPeripheralManagerDelegateProxy.proxy(for: base)
}
var state: Observable<CBManagerState> {
return delegate._didUpdateState
}
var didUpdateState: Observable<Void> {
return delegate._didUpdateState.map { _ in }
}
// optional methods are setup using the `methodInvoked` function on the delegate
var willRestoreState: Observable<[String: Any]> {
return delegate.methodInvoked(#selector(CBPeripheralManagerDelegate.peripheralManager(_:willRestoreState:)))
.map { $0[1] as! [String: Any] }
}
var didStartAdvertising: Observable<Error?> {
return delegate.methodInvoked(#selector(CBPeripheralManagerDelegate.peripheralManagerDidStartAdvertising(_:error:)))
.map { $0[1] as? Error }
}
// I didn't implement all of the optionals. Use the above as a template to implement the rest.
}
As far as I can tell, the methodInvoked function performs some meta-programming magic on the object to install the method at runtime. This is done because many of the iOS classes that have delegates actually behave differently depending on whether or not the method was defined on the delegate (regardless of what the method does,) so we don't want to simply give the proxy every method in the protocol.
Of course, once you have the above in place. You can do all the standard RX stuff with your peripheral manager:
bluetoothManager.rx.state
.subscribe(onNext: { state in print("current state:", state) })
.disposed(by: disposeBag)
bluetoothManager.rx.didStartAdvertising
.subscribe(onNext: { error in
if let error = error {
print("there was an error:", error)
}
}
.disposed(by: disposeBag)

How to call function from another ViewController

I am trying to call a function reloadTable in my HomeViewController from my Task class. But I keep being thrown an
Use of instance member 'reloadTable' on type 'HomeViewController'; did you mean to use a value of type 'HomeViewController' instead?
This is my HomeViewController code:
import UIKit
import Alamofire
import SwiftyJSON
class HomeViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
func reloadTable() {
self.displayTask.reloadData()
}
}
This is my Tasks class:
import Foundation
import Alamofire
import SwiftyJSON
class Tasks {
static let sharedInstance = Tasks()
var datas: [JSON] = []
func getTaskDetails(){
Alamofire.request(.GET, Data.weeklyEndpoint).validate().responseJSON { response in
switch response.result {
case .Success(let data):
let json = JSON(data)
if let buildings = json.array {
for building in buildings {
if let startDate = building["start_date"].string{
print(startDate)
}
if let tasks = building["tasks"].array{
Tasks.sharedInstance.datas = tasks
HomeViewController.reloadTable()
for task in tasks {
if let taskName = task["task_name"].string {
print(taskName)
}
}
}
}
}
case .Failure(let error):
print("Request failed with error: \(error)")
}
}
}
// for prevent from creating this class object
private init() { }
}
In this case reloadTable() is an instance method. You can't call it by class name, you have to create object for HomeViewController and then you have to call that method by using that object.
But in this situation no need to call the method directly by using HomeViewController object. You can do this in another way by using NSNotification
Using NSNotification :
Add a notification observer for your HomeViewController
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(HomeViewController.reloadTable), name:"ReloadHomeTable", object: nil)
Add the above lines in your viewDidLoad method of HomeViewController
Now replace this line HomeViewController.reloadTable() by NSNotificationCenter.defaultCenter().postNotificationName("ReloadHomeTable", object: nil) in your Task class
As many of the other answers states, you are trying to access a non-static function on a static reference.
I would suggest the use of closures, consider the following;
Create a closure for .Success and the .Failure, this enables you to act accordingly to the data request result. The onError closure is defined as an optional, this means you don't have to implement the onError in the ´getTaskDetails` function call, implement it where you feel you need the error information.
func getTaskDetails(onCompletion: ([Task]) -> (), onError: ((NSError) -> ())? = nil) {
Alamofire.request(.GET, Data.weeklyEndpoint).validate().responseJSON {
response in
switch response.result {
case .Success(let data):
let json = JSON(data)
if let buildings = json.array {
for building in buildings {
if let startDate = building["start_date"].string{
print(startDate)
}
if let tasks = building["tasks"].array{
Tasks.sharedInstance.datas = tasks
onCompletion(tasks)
}
}
}
case .Failure(let error):
print("Request failed with error: \(error)")
onError?(error)
}
}
}
From your HomeViewController:
// Call from wherever you want to reload data.
func loadTasks(){
// With error handling.
// Fetch the tasks and reload data.
Tasks.sharedInstance.getTaskDetails({
tasks in
self.displayTask.reloadData()
}, onError: {
error in
// Handle error, display a message or something.
print(error)
})
// Without error handling.
// Fetch the tasks and reload data.
Tasks.sharedInstance.getTaskDetails({
tasks in
self.displayTask.reloadData()
})
}
Basics
Closures
Make class Task as Struct, make function getTaskDetails as static and try to call function getTaskDetails() in HomeViewController. In result this of this function use realoadTable()
In HomeViewController you have defined reloadTable() as instance method not the class function.You should call reloadTable() by making instance of HomeViewController as HomeViewController(). Replace
HomeViewController.showLeaderboard()
HomeViewController().showLeaderboard()
Hope it helps. Happy Coding.

Writing API requests with completion blocks using Swift generics

I am experimenting with generics in Swift and I am attempting to push it to its limits.
In my application I have a super simple API wrapper around Alamofire. The structure is like so:
API -> Request -> Alamofire request
Here is some generic code that I threw into a playground to test some concepts. Here is what I have so far:
protocol SomeProtocol {
var cheese: String { get }
init()
}
class Something: SomeProtocol {
required init() { }
var cheese: String {
return "wiz"
}
}
class API {
class func performRequest<T: SomeProtocol>(completion: (T?, NSError) -> Void) {
// This code is irrelevant, just satisfying the completion param
let test = T()
let error = NSError(domain: "Pizza", code: 1, userInfo: nil)
completion(test, error)
}
}
func test() {
API.performRequest<Something> { item, error in
}
}
Calling the function gives the error:
"Cannot explicitly specialize a generic function"
****** UPDATE ******
As per the answer below, removing the typical <> generic type specifier and instead adding the expected type to the completion params solves the issue. Just a quick example:
func test() {
API.performRequest { (item: Something?, error) in
}
}
Additionally, I have discovered that making the API wrapper class a generic class solves the issue like so:
protocol SomeProtocol {
var pizza: String { get }
}
class SomeObject: SomeProtocol {
var pizza: String { return "pie" }
}
class API<T: SomeProtocol> {
class func performRequest(completion: (T?, NSError?) -> Void) {
}
}
func test() {
API<SomeObject>.performRequest { item, error in
// Do something with item, which has a type of SomeObject
}
}
Either way, the end goal is accomplished. We have a single generic method that will perform a set of tasks and return, via completion closure, the object based on the type passed in with each use.
The way generics work is they allow a function to use unspecialized variables inside of its implementation. One can add functionality to these variables by specifying that the variables must conform to a given protocol (this is done within the declaration). The result is a function that can be used as a template for many types. However, when the function is called in the code itself, the compiler must be able to specialize and apply types to the generics.
In your code above, try replacing
func test() {
API.performRequest<Something> { item, error in
}
}
with
func test() {
API.performRequest { (item: Something?, error) in
}
}
this lets the compiler know which type it must apply to the function without explicitly specifying. The error message you received should now make more sense.
Here is what i did using alamofire and alamofire object mapper:
Step 1: Create modal classes that conforms to Mappable protocols.
class StoreListingModal: Mappable {
var store: [StoreModal]?
var status: String?
required init?(_ map: Map){
}
func mapping(map: Map) {
store <- map["result"]
status <- map["status"]
}
}
Step 2: Create a fetch request using the generic types:
func getDataFromNetwork<T:Mappable>(urlString: String, completion: (T?, NSError?) -> Void) {
Alamofire.request(.GET, urlString).responseObject { (response: Response<T, NSError>) in
guard response.result.isSuccess else{
print("Error while fetching: \(response.result.error)")
completion(nil, response.result.error)
return
}
if let responseObject = response.result.value{
print(responseObject)
completion(responseObject, nil)
}
}
}
Step 3: Now all you need is to call this fetch function. This can be done like this:
self.getDataFromNetwork("your url string") { (userResponse:StoreListingModal?, error) in
}
You will not only get your response object but it will also be mapped to your modal class.

Resources