Im getting this error "Class declaration cannot close over value 'viewcontainer' defined in outer scope"
I created a procotol called NetworkResponse which have two methods on sucessResponse and onErrorResponse.
Then I have a class called Callback that extends from NetworkResponse and forced to implement that methods.
Here is my function :
public func login (callback : Callback, viewController : UIViewController) {
let callbackInstance: NetworkResponse = {
class callback : Callback {
override func onSucessResponse(response : NSDictionary){
viewController.dismiss(animated: true, completion: nil)
}
override func onErrorResponse(message : String, code : Int){
print("error")
}
}
return callback()
}()
postPath(callback: callbackInstance as? Callback)
}
I want to dismiss the controller from the anonymous class.
Any recomendation ?
No need to define a protocol and Callback class. Closure is just what you need.
import UIKit
public class TestInnerClass: UIViewController {
public func login(successCallback: ((response: NSDictionary) -> Void), errorCallback: ((message: String, code: Int) -> Void)) {
let success = false
let response = NSDictionary()
//
// Make your login request here, and change the `success` value depends on your response
// let response = ...
//
// If you are making a async request to login, then put the following codes inside your request callback closure.
//
if success {
successCallback(response: response)
} else {
errorCallback(message: "error occurred", code: -1)
}
}
override public func viewDidLoad() {
super.viewDidLoad()
login({
(response) in
// Get Called when success
self.dismissViewControllerAnimated(true, completion: nil)
}, errorCallback: ({
// Get called when failed
(message, code) in
print(message)
}))
}
}
I have written some sample codes for your case in my Github repo, and this example is using Alamofire to make network request, Just for your reference.
PS: Since I still using Xcode 7.3.1, so you may need to do some changes to the above code to adopt the swift 3 syntax requirement.
Related
For the given event handler attribute methods:
class AudioServer {
var didReceiveData: ((AudioBufferData) -> Void)?
func websocketCallback(event: WebSocketEvent, client: WebSocket) {
switch event {
..
}
func handleMicrophoneData(_ data: AudioBufferData) {
..
}
How can those be set up /initialized? They are not available yet in the init() method so the following most straightforward implementation does not work:
init() {
didReceiveData = { bufferData in
self.handleMicrophoneData(bufferData)
}
self.websocketClient = WebsocketClient(url: Self.WebsocketUrl, callback: self.websocketCallback)
}
We get:
'self' used in method call 'websocketCallback' before all stored properties are initialized
So what is a typical mechanism for these initializations?
As I understand, it is best to only test public methods of a class.
Let's have a look at this example. I have a view model for the view controller.
protocol MyViewModelProtocol {
var items: [SomeItem] { get }
var onInsertItemsAtIndexPaths: (([IndexPath]) -> Void)? { get set }
func viewLoaded()
}
class MyViewModel: MyViewModelProtocol {
func viewLoaded() {
let items = createDetailsCellModels()
updateCellModels(with: items)
requestDetails()
}
}
I want to test class viewLoaded(). This class calls two other methods - updateItems() and requestDetails()
One of the methods sets up the items and the other one call API to retrieve data and update those items. Items array us updated two times and onInsertItemsAtIndexPaths are called two times - when setting up those items and when updating with new data.
I can test whether after calling viewLoaded() expected items are set up and that onInsertItemsAtIndexPaths is called.
However, the test method will become rather complex.
What is your view, should I test those two methods separately or just write this one huge test?
By testing only viewLoaded(), my idea is that the implementation can change and I only care that results are what I expect.
I think the same thing, only public functions should be tested, since public ones use private ones, and your view on MVVM is correct. You can improve it by adding a DataSource and a Mapper that allows you to improve testing.
However, yes, the test seems huge to me, the tests should test simple units and ensure that small parts of the code work well, with the example you show is difficult, you need to divide by layers (clean code).
In the example you load the data into the viewModel and make it difficult to mockup the data. But if you have a Domain layer you can pass the UseCase mock to the viewModel and control the result. If you run a test on your example, the result will also depend on what the endpoint returns. (404, 200, empty array, data with error ...). So it is important, for testing purposes, to have a good separation by layers. (Presentation, Domain and Data) to be able to test each one separately.
I give you an example of how I would test a view mode, sure there are better and cooler examples, but it's an approach.
Here you can see a viewModel
protocol BeersListViewModel: BeersListViewModelInput, BeersListViewModelOutput {}
protocol BeersListViewModelInput {
func viewDidLoad()
func updateView()
func image(url: String?, index: Int) -> Cancellable?
}
protocol BeersListViewModelOutput {
var items: Box<BeersListModel?> { get }
var loadingStatus: Box<LoadingStatus?> { get }
var error: Box<Error?> { get }
}
final class DefaultBeersListViewModel {
private let beersListUseCase: BeersListUseCase
private var beersLoadTask: Cancellable? { willSet { beersLoadTask?.cancel() }}
var items: Box<BeersListModel?> = Box(nil)
var loadingStatus: Box<LoadingStatus?> = Box(.stop)
var error: Box<Error?> = Box(nil)
#discardableResult
init(beersListUseCase: BeersListUseCase) {
self.beersListUseCase = beersListUseCase
}
func viewDidLoad() {
updateView()
}
}
// MARK: Update View
extension DefaultBeersListViewModel: BeersListViewModel {
func updateView() {
self.loadingStatus.value = .start
beersLoadTask = beersListUseCase.execute(completion: { (result) in
switch result {
case .success(let beers):
let beers = beers.map { DefaultBeerModel(beer: $0) }
self.items.value = DefaultBeersListModel(beers: beers)
case .failure(let error):
self.error.value = error
}
self.loadingStatus.value = .stop
})
}
}
// MARK: - Images
extension DefaultBeersListViewModel {
func image(url: String?, index: Int) -> Cancellable? {
guard let url = url else { return nil }
return beersListUseCase.image(with: url, completion: { (result) in
switch result {
case .success(let imageData):
self.items.value?.items?[index].image.value = imageData
case .failure(let error ):
print("image error: \(error)")
}
})
}
}
Here you can see the viewModel test using mocks for the data and view.
class BeerListViewModelTest: XCTestCase {
private enum ErrorMock: Error {
case error
}
class BeersListUseCaseMock: BeersListUseCase {
var error: Error?
var expt: XCTestExpectation?
func execute(completion: #escaping (Result<[BeerEntity], Error>) -> Void) -> Cancellable? {
let beersMock = BeersMock.makeBeerListEntityMock()
if let error = error {
completion(.failure(error))
} else {
completion(.success(beersMock))
}
expt?.fulfill()
return nil
}
func image(with imageUrl: String, completion: #escaping (Result<Data, Error>) -> Void) -> Cancellable? {
return nil
}
}
func testWhenAPIReturnAllData() {
let beersListUseCaseMock = BeersListUseCaseMock()
beersListUseCaseMock.expt = self.expectation(description: "All OK")
beersListUseCaseMock.error = nil
let viewModel = DefaultBeersListViewModel(beersListUseCase: beersListUseCaseMock)
viewModel.items.bind { (_) in}
viewModel.updateView()
waitForExpectations(timeout: 10, handler: nil)
XCTAssertNotNil(viewModel.items.value)
XCTAssertNil(viewModel.error.value)
XCTAssert(viewModel.loadingStatus.value == .stop)
}
func testWhenDataReturnsError() {
let beersListUseCaseMock = BeersListUseCaseMock()
beersListUseCaseMock.expt = self.expectation(description: "Error")
beersListUseCaseMock.error = ErrorMock.error
let viewModel = DefaultBeersListViewModel(beersListUseCase: beersListUseCaseMock)
viewModel.updateView()
waitForExpectations(timeout: 10, handler: nil)
XCTAssertNil(viewModel.items.value)
XCTAssertNotNil(viewModel.error.value)
XCTAssert(viewModel.loadingStatus.value == .stop)
}
}
in this way you can test the view, the business logic and the data separately, in addition to being a code that is very reusable.
Hope this helps you, I have it posted on github in case you need it.
https://github.com/cardona/MVVM
I have a func getData(completed: #escaping ()->()) that creates alamofire request with completion handler in one class. When alamofire ends it job, inside of this function i call completed() to notify that function ended its work. This func is called in other viewcontroller after button tap, but without completion handler, and then inside of this call i reload tableview with downloaded data as below.
Now i want to add to this func additional parameters to allow user modify URL of alamofire request, and get custom response. Parameters will be setted in other textfields. But now when i call downloadRepositories() i can't omit calling completion parameter.
How can i avoid calling completion handler in it or what other
completion handler should i implement?
Current alamofire request
class DataClass {
func getData(completed: #escaping () -> ()){
//alamofire request
Alamofire.request(url).responseJSON{
//reponse
completed()
}
}
And it's implementation
class OtherVC {
var dataClass = DataClass()
#objc func searchBtnTapped(sender: UIButton!){
dataclass.getData(){
self.TableView.reloadData()
}
}
}
What i would like to do
class DataClass {
func downloadRepositories(completed: #escaping () -> (), parameter1: String, parameter2: String) {
let parameters: Parameters = [ "parameterA": parameter, "parameterB": parameter2 ]
Alamofire.request(url, parameters: parameters).responseJSON{
//response
completed()
}
Implementation of modified func
class OtherVC {
var dataClass = DataClass()
#objc func searchBtnTapped(sender: UIButton!){
dataclass.getData(parameter1: someTextField.text, parameter2: someTextField2.text){
self.TableView.reloadData()
}
}
}
Of course I know that is not possible to pass parameters in func call like this, but how can do this other way?
Use like this:
func downloadRepositories(parameter1: String, parameter2: String, completed: #escaping () -> ()) {
let parameters: Parameters = [ "parameterA": parameter, "parameterB": parameter2 ]
Alamofire.request(url, parameters: parameters).responseJSON {
//response
completed()
}
You are on the right track)
If you want call func like this:
dataclass.getData(parameter1: someTextField.text, parameter2: someTextField2.text){
self.TableView.reloadData()
}
You just need change parameters order like this:
func downloadRepositories(parameter1: String, parameter2: String, completed: #escaping () -> ())
If i understand right and you want use Trailing Closures, then that's all
I have gone through Swift Closures and ARC in Swift and I got little confused.
I have simple scenario of calling web service and using response data.
Here is my basic implementation:
class WebServices: NSObject {
func requestDataFromServer(completion: #escaping (_ data: Data?) -> Void) {
//web service call here
completion(Data())
}
deinit {
print("WebServices deinitializer...")
}
}
class Controller: NSObject {
private let webService = WebServices()
private func useResponseData(_ data: Data) {
print("Response Data: \(data)")
}
func fetchData() {
webService.requestDataFromServer { (data) in
if let responseData = data {
self.useResponseData(responseData)//direct use of self
}
}
}
deinit {
print("Controller deinitializer...")
}
}
var controller: Controller? = Controller()
controller!.fetchData()
controller = nil
Console output is:
Response Data: 0 bytes
Controller deinitializer...
WebServices deinitializer...
My question is even I'm using selfdirectly inside closure why this implementation is not causing Reference Retain Cycle?
If I use unowned or weak then also same behavior.
And what can cause reference retain cycle in above scenario?(I don't want to cause, rather want to be aware of mistakes)
There is no issue in your code as requestDataFromServercall the completion handler directly (no async). So the caller can't have been released during you call.
But when you will implement your webservice's real call, it will be async. So user can switch page before your webservice answer. In that case, you will retain a strong reference to your controller and it will never be released. You should use a [weak self]in your closure (and so call self?.useResponseData(responseData) with self as optional).
unowned is used in case that you are sure your reference will not be nil (it's not an optional)
Ok, I guess the problem is what you are doing here is not actually async, so it executes one after another, this code causes memory leak:
import Foundation
class WebServices {
func requestDataFromServer(completion: #escaping (_ data: Data?) -> Void) {
//web service call here
print("called")
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1, execute: {
print("called async")
completion(Data())
})
}
deinit {
print("WebServices deinitializer...")
}
}
class Controller: UIViewController {
private let webService = WebServices()
private func useResponseData(_ data: Data) {
print("Response Data: \(data)")
}
func fetchData() {
webService.requestDataFromServer { (data) in
print("called")
if let responseData = data {
self.useResponseData(responseData)//direct use of self
}
}
self.dismiss(animated: true, completion: nil)
}
deinit {
print("Controller deinitializer...")
}
}
var controller: Controller? = Controller()
controller!.fetchData()
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
isSuccess(true, success: { (name) -> String in
return "My name is \(name)"
})
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
func isSuccess(val:Bool, success: (name: String) -> String) {
if val {
success(name: "Jacky")
}
}
}
I expect it to return string "My name is Jacky",but it didn't .But if I change the isSuccess to :
func isSuccess(val:Bool, success: (name: String) -> String) {
if val {
print(success(name: "Jacky"))
}
}
}
Then it worked properly, why is that? Thanks in advance!
Your completion block returns a String.
When you invoke it by calling
success(name: "Jacky")
the completion block returns the String My name is Jacky. But you do nothing with that string. You just returned it and never used it.
In your second example, you actually used it - you took the string from the completion block, and printed it.
For example, instead the print, you could also write
let stringFromCompletionBlock = success(name: "Jacky")
That way you can see that it indeed returned a value.
And another thing is that the completion block should be call as the last thing in the function - this way you "notify" that the function has finished it's purpose, so it's not reasonable to use the value returned from a completion block inside the same function which called that completion block
First of all the closure in the function isSuccess should be declared this way. The closure should not return a String, it should just accept a String as param.
func isSuccess(val:Bool, success: (name: String) -> ()) {
if val {
success(name: "Jacky")
}
}
Next you could use that value to update the UI like follow
class ViewController: UIViewController {
weak var label:UILabel!
override func viewDidLoad() {
isSuccess(true) { (name) -> () in
dispatch_async(dispatch_get_main_queue()) {
self.label.text = "My name is \(name)"
}
}
super.viewDidLoad()
}
func isSuccess(val:Bool, success: (name: String) -> ()) {
if val {
success(name: "Jacky")
}
}
}