I am implementing a picture posting app by using parse.com. several days ago, I had an error on my app, which is
2015-10-22 14:36:52.501 bany[2595:874380] * Terminating app due to uncaught exception 'NSRangeException', reason: '* -[__NSArrayM objectAtIndex:]: index 0 beyond bounds for empty array’
but my array was fine. then I posted stackOverFlow, someone told me this problem occurs that I did it wrong with thread safety and UI updating.
this is the post - index 0 beyond bounds for empty array, sometime works, sometime does not
I read about thread safety from apple reference, I figured out that using Mutable array is thread unsafe class type.
here is my code.
class MainTVC: UITableViewController {
@IBOutlet weak var categorySegment: UISegmentedControl!
var postsArray : NSMutableArray = NSMutableArray()
var filterdArray : NSMutableArray = NSMutableArray()
var objectTwo : PFObject!
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(true)
bringAllDatafromParse()
}
@IBAction func segmentTapped(sender: AnyObject) {
// Empty postArray
postsArray = []
// get post's data by categories
switch categorySegment.selectedSegmentIndex {
case 0 :
bringAllDatafromParse()
case 1 :
bringCategoryDataFromParse(1)
case 2 :
bringCategoryDataFromParse(2)
case 3 :
bringCategoryDataFromParse(3)
default :
bringAllDatafromParse()
}
self.tableView.reloadData()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return postsArray.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! MainTVCE
let postObjects = self.postsArray.objectAtIndex(indexPath.row) as! PFObject
// IndexPath for comment button on tableView
cell.didRequestToShowComment = { (cell) in
let indexPath = tableView.indexPathForCell(cell)
let objectToSend = self.postsArray[indexPath!.row] as? PFObject
// Show your Comment view controller here, and set object to send here
self.objectTwo = objectToSend!
self.performSegueWithIdentifier("mainToComment", sender: self)
}
// Show sold label or not
cell.soldLabel.hidden = true
if (postObjects.objectForKey("sold") as! Bool) == true {
cell.soldLabel.hidden = false
}
// title Label of post
cell.titleLabel.text = postObjects.objectForKey("titleText") as? String
// nick name of user
if let nickNameExists = postObjects.objectForKey("nickName") as? String {
cell.nickNameLabel.text = nickNameExists
}else {
cell.nickNameLabel.text = postObjects.objectForKey("username") as? String
}
// time label for posts
let dateFormatter:NSDateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "MM /dd /yy"
cell.timeLabel.text = (dateFormatter.stringFromDate(postObjects.createdAt!))
// price label
let price = (postObjects.objectForKey("priceText") as! String)
cell.priceLable.text = " $\(price)"
// main Image for post
let mainImages = postObjects.objectForKey("front_image") as! PFFile
mainImages.getDataInBackgroundWithBlock { (imageData, error) -> Void in
let image = UIImage(data: imageData!)
cell.mainPhoto.image = image
}
//profile picture for user
if let profileImages = (postObjects.objectForKey("profile_picture") as? PFFile){
profileImages.getDataInBackgroundWithBlock { (imageData, error) -> Void in
let image = UIImage(data: imageData!)
cell.profilePhoto.image = image
}
}else{ cell.profilePhoto.image = UIImage(named: "AvatarPlaceholder")
}
circularImage(cell.profilePhoto)
return cell
}
func bringAllDatafromParse() {
//empty postArray
postsArray = []
//bring data from parse
let query = PFQuery(className: "Posts")
query.orderByAscending("createdAt")
query.findObjectsInBackgroundWithBlock { (objects: [PFObject]?, error) -> Void in
if error == nil && objects != nil{
for object : PFObject in objects! {
self.postsArray.addObject(object)
}
let array : Array = self.postsArray.reverseObjectEnumerator().allObjects
self.postsArray = array as! NSMutableArray
}
self.tableView.reloadData()
}
}
func bringCategoryDataFromParse(category : Int) {
let query = PFQuery(className: "Posts")
query.whereKey("category", equalTo: category)
query.orderByAscending("createdAt")
query.findObjectsInBackgroundWithBlock { (objects: [PFObject]?, error) -> Void in
if error == nil && objects != nil{
for object : PFObject in objects! {
self.postsArray.addObject(object)
}
let array : Array = self.postsArray.reverseObjectEnumerator().allObjects
self.postsArray = array as! NSMutableArray
self.tableView.reloadData()
}
}
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// send object to commentViewController
if (segue.identifier == "mainToComment") {
let destViewController : CommentVC = segue.destinationViewController as! CommentVC
destViewController.object = objectTwo
}
// send object to DetailViewController
if (segue.identifier == "mainToDetail") {
let selectedRowIndex = self.tableView.indexPathForSelectedRow
let destViewController : DetailVC = segue.destinationViewController as! DetailVC
destViewController.object = (postsArray[(selectedRowIndex?.row)!] as? PFObject)
}
}
func circularImage(image : UIImageView) {
image.layer.cornerRadius = image.frame.size.width / 2
image.clipsToBounds = true
image.layer.borderColor = UIColor.blackColor().CGColor
image.layer.borderWidth = 1
}
Here are thoughts about what I did wrong on my code during look into other's code. So please comment these are right or not.
I should use NSArray instead of using NSMutableArray for thread safety. Will it be okay if I use NSArray?
I will have separate data class for retrieving data includes checking nil or there is a value, instead of putting in the method "cellForRowAtIndexPath" which would have 'only' define of cell for reuse.
If I do it this, should I retrieve data from array in most ViewControllers instead of using prepareSegue method to send specific(selected) object from mainViewcontroller which has function retrievingAllData?
- should I use cache and dispatch_async in "cellForRowAtIndexPath" Is it mandatory?
4.UI part, I am really confused. Could anyone tip for this? For example, how does make UI showing progressing until data retrived done? You can tip just 'a' comment. It will be helpful a lot for me. I really appreciate that.
please give me advise.