0

My app have a e-store purchase function which use billing address. I also have a Billing Address List and tap on the address cell in the list will go inside to the edit address page. But after I run the purchase function and edit the address, and go back to the Billing Address List page, the app will stop running. (If I edit address without make purchase everything would be fine)

The error show:

Terminating app due to uncaught exception 'RLMException', reason: 'Object has been deleted or invalidated.'

And:

libc++abi.dylib: terminating with uncaught exception of type NSException

And also in AppDelegate.swift,

"Thread 1: signal SIGABRT" 

error show in this line:

class AppDelegate: UIResponder, UIApplicationDelegate {

Here's the Billing Address List code (which contain many billing address cells, tap on cell will go inside to the edit address function:

import Foundation
import PKHUD

class ManageBillingAddressVC: _BaseViewController {

    @objc var billingAddress: [UserBillingAddress] = [] { //= Address.getAllAddressLists()
        didSet {
            emptyView.isHidden = billingAddress.count != 0
        }
    }

    let viewModel = BillingAddressViewModel()

    @IBOutlet weak var tblBillingAddress: UITableView!
    @IBOutlet weak var emptyView: UIView!

    override func viewDidLoad() {
        super.viewDidLoad()

        setUpNavBarWithAttributes(navtitle: "Manage Billing Address", setStatusBarStyle: .default, isTransparent: false, tintColor: .white, titleColor: Color.Tuna.instance(), titleFont: FontBook.AdobePro.navSize(), isBackHidden: false, isShadowIncluded: true, rightBarButton: [showGoldPlusBtn()])

        viewModelCallBack()
        initTableViews()
        observeAdressUpdateNotification()
    }


    @objc func viewModelCallBack() {

        viewModel.beforeApiCall = {
            HUD.show(.systemActivity)
        }

        viewModel.afterApiCall = {
            HUD.hide()
        }
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)

        self.navigationController?.setNavigationBarHidden(false, animated: true)

        fetchBillingAddress()
    }

    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        self.navigationController?.setNavigationBarHidden(true, animated: true)
    }

    @objc func updateDeliveryAddressTable() {
        fetchBillingAddress()
    }

    @objc func fetchBillingAddress() {
        viewModel.getBillingAddress(success: {
            self.billingAddress.removeAll()
            for address in $0{
                if address.crm_address_order != "postal_address"{
                    self.billingAddress.append(address)
                }
            }
            self.tblBillingAddress.reloadData()
        }) {
            self.showAlert($0)
        }
    }

    @objc func observeAdressUpdateNotification() {
        NotificationCenter.default.addObserver(self, selector: #selector(updateDeliveryAddressTable), name: Constants.billingAddress, object: nil)
    }

    override func rightBarButtonClicked() {

        viewModel.getBillingAddress(success: {
            self.billingAddress.removeAll()
            for address in $0{
                if address.crm_address_order != "postal_address"{
                    self.billingAddress.append(address)
                }
            }

            if self.billingAddress.count >= 3{
                self.showAlert("Addresses are limited to a maximum of 3")
            }else{
                self.pushViewControllerWithSlideUpEffect(vc: UIStoryboard.AddBillingAddress())
            }

            self.tblBillingAddress.reloadData()
        }) {
            self.showAlert($0)
        }
    }

    @objc func initTableViews() {

        tblBillingAddress.register(UINib(nibName : "BillingAddressCell", bundle : nil), forCellReuseIdentifier: "BillingAddressCell")

        tblBillingAddress.dataSource = self
        tblBillingAddress.delegate = self
        tblBillingAddress.separatorStyle = .none
        tblBillingAddress.contentInset = UIEdgeInsetsMake(20, 0, 20, 0)

        tblBillingAddress.rowHeight = UITableViewAutomaticDimension
        tblBillingAddress.estimatedRowHeight = 165 //108

    }

    deinit {
        NotificationCenter.default.removeObserver(self)
    }
}

extension ManageBillingAddressVC: DeliveryAddressCellDelegate {

    @objc func didSelectBillingAddress(address: UserBillingAddress, index: Int) {
        guard let vc = UIStoryboard.AddBillingAddress() as? AddBillingAddressVC else { return }
        vc.billingAddress = address
        pushViewControllerWithSlideUpEffect(vc: vc)
    }
}


extension ManageBillingAddressVC: UITableViewDataSource, UITableViewDelegate {

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return billingAddress.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

        let cell = tableView.dequeueReusableCell(withIdentifier: "BillingAddressCell" ) as! BillingAddressCell
        cell.delegate = self
        cell.billingAddress = billingAddress[indexPath.row]
        //cell.address = billingAddress[indexPath.row]
        return cell

    }

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        tableView.deselectRow(at: indexPath, animated: false)
    }

}

Here's my billing address cell in the address list code:

import UIKit

class BillingAddressCell: DeliveryAddresCell {

    @objc var billingAddress: UserBillingAddress? {
        didSet {
            billingAddress.flatMap {
                lblName.text = "\($0.salutation ?? ""). \($0.firstname ?? "") \($0.lastname ?? "")"
                lblAddress.text = $0.street?.first
                lblPhoneNo.text = $0.telephone
                lblUnitNo.text = $0.unit_number
                lblStateCountry.text = "\($0.region?.first?.region ?? ""), \($0.postcode ?? "")"

                if let id = $0.country_id, let countryID = Int(id) {
                    lblCountry.text = CountryList.getCountryName(id: countryID)
                }
            }
        }
    }

    override func btnEditTapped(_ sender: UIButton) {
        delegate?.didSelectBillingAddress(address: billingAddress!, index: tag)
    }

}

Here's the function to fetch/updating billing address:

class UserBillingAddress: UserBillingAddressInformation {

    @objc var id : String?
    @objc var customerID: String?

    required convenience init?(map: Map){
        self.init()
    }

    override func mapping(map: Map){
        super.mapping(map: map)
        id <- map["id"]
        customerID <- map["customer_id"]
    }
}

Here's the code for what happened after tap the "checkout" button in the purchase process:

@IBAction func btnCheckoutTapped(_ sender: UIButton) {

        guard let card = defaultCreditCard, let address = defaultAddress else { return }

        baseViewModel.checkCartLimit(success: { checkLimit in

            if let limitErrors = checkLimit.limit_errors {

                if limitErrors.count > 0 {
                    self.baseViewModel.handleCheckLimitError?(checkLimit)
                } else {

                    guard let processingVC = UIStoryboard.ProcessingOrderVC() as? ProcessingOrderVC else { return }
                    // present processing view
                    processingVC.modalPresentationStyle = .overCurrentContext
                    processingVC.defaultAddress = address
                    processingVC.defaultCreditCard = card
                    processingVC.isSkyDollarPurchaseForMembership = self.isSkyDollarPurchaseForMembership
                    processingVC.isMembershipProduct = self.isMembershipProduct
                    processingVC.isFullSkyDollarPurchase = self.isFullSkyDollarPurchase
                    self.present(processingVC, animated: true)
                }

            } else {

                guard let processingVC = UIStoryboard.ProcessingOrderVC() as? ProcessingOrderVC else { return }
                // present processing view
                processingVC.modalPresentationStyle = .overCurrentContext
                processingVC.defaultAddress = address
                processingVC.defaultCreditCard = card
                processingVC.isSkyDollarPurchaseForMembership = self.isSkyDollarPurchaseForMembership
                processingVC.isMembershipProduct = self.isMembershipProduct
                self.present(processingVC, animated: true)
            }

        }, failure: { (errorMessage) in
            self.showAlert(errorMessage)
        })
    }

And here's the code for the purchase processing after tap on checkout button:

import UIKit

class ProcessingOrderVC: UIViewController {

    @IBOutlet weak var orderProcessingStackView: UIStackView!
    @IBOutlet weak var orderProcessErrorStackView: UIStackView!

    @IBOutlet weak var btnOkay: UIButton!
    @IBOutlet weak var lblError: UILabel!

    @objc var isMembershipProduct : Bool = false
    @objc var isSkyDollarPurchaseForMembership : Bool = false
    @objc var isFullSkyDollarPurchase: Bool = false

    let checkoutViewModel = CheckoutStep2ViewModel()
    @objc var defaultAddress = Address()
    @objc var defaultCreditCard = CreditCardData()

    override func viewDidLoad() {
        super.viewDidLoad()
        let attributedTitle = NSAttributedString(string: "TRY AGAIN", attributes: [NSAttributedStringKey.kern: 1.3, NSAttributedStringKey.font : FontBook.Bold.of(size: 13.0), NSAttributedStringKey.foregroundColor: UIColor.white])
        btnOkay?.setAttributedTitle(attributedTitle, for: .normal)
    }

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)

        if isSkyDollarPurchaseForMembership{
            self.checkoutViewModel.placeOrderWithSkyDollarForMembership(address: defaultAddress, success: { orderID in
                (UIStoryboard.CheckoutStep3VC() as? CheckoutStep3VC).flatMap {
                    $0.orderID = orderID
                    $0.hero.modalAnimationType = .fade
                    self.present($0, animated: false, completion: nil)
                }
            }) { (error) in
                self.showError()
            }
        }else{
            self.checkoutViewModel.placeOrder(address: defaultAddress, card: defaultCreditCard, isFullSkyDollarPurchase: isFullSkyDollarPurchase, success: { orderID in
                (UIStoryboard.CheckoutStep3VC() as? CheckoutStep3VC).flatMap {
                    $0.orderID = orderID
                    $0.hero.modalAnimationType = .fade
                    self.present($0, animated: false, completion: nil)
                }
            }) { (error) in
                self.showError()
            }
        }


    }

    @IBAction func btnCloseTapped(_ sender: UIButton) {
        self.dismiss(animated: true, completion: nil)
    }

    @IBAction func btnOkayTapped(_ sender: UIButton) {
        self.dismiss(animated: true, completion: nil)
    }

    @objc func showError() {
        orderProcessErrorStackView.isHidden = false
        orderProcessingStackView.isHidden = true
    }

}

Here's the defaultAddress variable and class Address:

@objc var defaultAddress = Address()

class Address : Object, Mappable{

    @objc dynamic var id: Int = 0
    @objc dynamic var customerId: Int = 0
    @objc dynamic var region: Region? = nil
    @objc dynamic var region_id: Int = 0
    @objc dynamic var countryId: String = ""
    let streets = List<Street>()
    @objc dynamic var telephone: String = ""
    @objc dynamic var postcode: String = ""
    @objc dynamic var city: String = ""
    @objc dynamic var prefix: String = ""
    @objc dynamic var company: String = ""
    @objc dynamic var firstname: String = ""
    @objc dynamic var lastname: String = ""
    @objc dynamic var defaultShipping: Bool = false
    @objc dynamic var defaultBilling: Bool = false
    var addressAttributes = List<AddressAttributes>()


    required convenience init?(map: Map){
        self.init()
    }

By right the purchase process will not update the address, but if I don't make purchase, then editing the address have no error. The error only happens after making purchase process. I really don't know what happened. Can anyone suggest what might be the problem?

I'm quite new in iOS so please be more precise on how to solve the issue. Thank you very much!

Bhavesh Nayi
  • 3,626
  • 1
  • 27
  • 42
darkfruitmilk
  • 79
  • 1
  • 8
  • Set an exception breakpoint to see where the error actually occurs. Also, the error messages are usually helpful, so make sure your object neither gets deleted nor invalidated. – Gereon Apr 26 '19 at 07:10
  • It simply occurs when try access Realm object/entity after deletion of Realm object/entity. – Vijay Sanghavi Apr 26 '19 at 07:55
  • I set breakpoints but when crash, it suddenly jump to --> class AppDelegate: UIResponder, UIApplicationDelegate { And there should now be realm object/entity deletion when I do purchase, except this line: @objc func gotoHome() { viewModel.removeAllCartData() showHomeScreen(selectIndex: 2, expediaRequired: true) } But it is just remove data from the cart – darkfruitmilk Apr 26 '19 at 08:32
  • Hi @Vijay Sanghavi, I researched but cannot find where I deleted the object. Could you suggest which line might be causing the delete realm object issue? Actually it works fine in swift3 and xcode9, but after I upgrade to swift4 and xcode10.1, it appears this bug. Thanks – darkfruitmilk Apr 26 '19 at 10:32
  • @darkfruitmilk could you help me with the link of your github repo, so that I can see through the possibilities. – Vijay Sanghavi Apr 26 '19 at 10:58
  • @vijaySanghavi, sorry but the code repo is belongs to the company and I don't have access to it. The code might cause the problem I already posted – darkfruitmilk Apr 28 '19 at 19:22

0 Answers0