прошивкой Свифт 3 приложение, которое запрашивает данные из стека обмен с API


Это мой первый Свифт, так хочется улучшить все, что я могу О мой код, и знаю, что я могу сделать лучше. Приложение запрашивает 10 разработчики из API клиент StackExchange и отображает их в виде таблицы с возможностью нажать на одну из них и увидеть более подробную информацию о нем.

Меня попросили решить следующие управления кэш-памяти:

  1. Если данные находятся в оперативной памяти, получить его оттуда

  2. Еще, если это на диск, достать его оттуда

  3. Еще, сделать запрос API и сохранить после этого данные

Данные на диске должны остаться на 30 минут.

Я не уверен, если то, что я сделал с NSCache было хорошо, я имею в виду, если у меня есть данные в моей viewController класс я не думаю, что мне нужно реализовать NSCache потому что это идет в ОЗУ, верно? Я хочу видеть, что я могу улучшить в этом проекте.

ViewController код ниже, и ссылку на GitHub РЕПО:

GitHub РЕПО

import UIKit
import Alamofire
import Foundation


class ViewController: UIViewController,UITableViewDelegate,UITableViewDataSource{

@IBOutlet weak var tableView: UITableView!

private var userData = UserData()
private var clearMemoryTimer = Timer.scheduledTimer(timeInterval: 30.0*60.0, target: self, selector: #selector(clearMemory), userInfo: nil, repeats: true)
private let ramCache = NSCache<NSString,UserData>()


let param: Parameters = [
    "order": "desc",
    "max" : 10,
    "sort" : "reputation",
    "site" : "stackoverflow"
]

private var filePath:String{
    let manager = FileManager.default
    let url = manager.urls(for: .documentDirectory, in: .userDomainMask).first
    return url!.appendingPathComponent("SavedData").path
}


override func viewDidLoad() {
print("view loaded")

    super.viewDidLoad()
    self.loadData()

    tableView.delegate = self
    tableView.dataSource = self

    let nibName = UINib(nibName: "CustomTableViewCell", bundle: nil)
    tableView.register(nibName, forCellReuseIdentifier: "tableViewCell")
}


func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return self.userData.numberOfUsers()
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "tableViewCell", for: indexPath) as! CustomTableViewCell

    cell.commonInit(image: self.userData.userAtIndex(index: indexPath.item)!.ProfilePicImage!, labelText: self.userData.userAtIndex(index: indexPath.item)!.Username)

    return cell
}

func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    return 80
}

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    let detailViewController = DetailsUserViewController()
    detailViewController.commonInit(image: self.userData.userAtIndex(index: indexPath.item)!.ProfilePicImage!,
                                    name: self.userData.userAtIndex(index: indexPath.item)!.Username,
                                    location: self.userData.userAtIndex(index: indexPath.item)!.Location,
                                    bronzeBadges: self.userData.userAtIndex(index: indexPath.item)!.BronzeBadges,
                                    silverBadges: self.userData.userAtIndex(index: indexPath.item)!.SilverBadges,
                                    goldBadges: self.userData.userAtIndex(index: indexPath.item)!.GoldBadges)

    self.navigationController?.pushViewController(detailViewController, animated: true)
    self.tableView.deselectRow(at: indexPath, animated: true)
}

private func saveData(user: User) {
    self.userData.appendUser(newUser: user)
    NSKeyedArchiver.archiveRootObject(userData, toFile: filePath)  // we save on disk
    ramCache.setObject(self.userData, forKey: "Data" )
}

private func loadData() {
    if let cachedUsers = self.ramCache.object(forKey: "Data" ) {
        print("data was on ram")
        self.userData.SavedData = cachedUsers.SavedData

    }
    else if let savedUsers = NSKeyedUnarchiver.unarchiveObject(withFile: filePath) as? UserData {
        // check if its on disk
        print("data was on disk")
        self.userData.SavedData = savedUsers.SavedData
        ramCache.setObject(self.userData, forKey: "Data")
    } else {
        print("we requested the data")
        self.requestDataFromApi()
    }
}

func clearMemory()
{
    do{
        try FileManager.default.removeItem(atPath: filePath)
    }
    catch{
        print("Error in clearMemory()")
    }
}



private func requestDataFromApi() {
    // GET the data from the stackexchange api



    Alamofire.request("https://api.stackexchange.com/2.2/users", method: .get, parameters: param).responseJSON { (response) -> (Void) in

        if let json = response.result.value {
            // we got a result

            /* I know this is a bit ugly */
            let json1 = json as! [String:AnyObject]
            let usersInfoFromJSON = json1["items"] as! NSArray       // remember to cast it as NSDictionary


            for userInfo in usersInfoFromJSON {

                let userDict = userInfo as! NSDictionary

                // download user image from url

                Alamofire.request(userDict["profile_image"] as! String).responseData { (response) in
                    if response.error == nil {

                        //print(response.result)

                        if let data = response.data {

                            // save the downloaded image

                            let imageView = UIImageView()
                            imageView.image = UIImage(data: data)

                            // check if user has location set, if not display a proper message
                            var userLocation:String=""

                            if let checkLocation = (userDict["location"] as? String) {
                                userLocation = checkLocation
                            } else {
                                userLocation = "Location is not set."
                            }

                            // get every badge count from the json to use it in User constructor

                            let badgeCounts = userDict["badge_counts"] as? [String:Int]

                            var goldb = 0
                            var bronzeb = 0
                            var silverb = 0

                            if badgeCounts != nil {
                                bronzeb = badgeCounts!["bronze"]!
                                silverb = badgeCounts!["silver"]!
                                goldb   =   badgeCounts!["gold"]!
                            }


                            let newUser = User(username: userDict["display_name"] as! String,
                                               location: userLocation,
                                               bronzeBadges: bronzeb,
                                               silverBadges: silverb,
                                               goldBadges: goldb,
                                               profilePicUrl: userDict["profile_image"] as! String,
                                               profilePicImg: imageView.image)

                            self.saveData(user: newUser)

                            self.tableView.reloadData()

                        }
                    }
                } // end alamofire second request
            } // end user iteration
        }
    } // end alamofire first request
}


 }

Пользователи класса:

import Foundation
import UIKit

class User: NSObject,NSCoding {

struct Keys{
    static let Username = "name"
    static let Location = "location"
    static let BronzeBadges = "bronzeb"
    static let SilverBadges = "silverb"
    static let GoldBadges = "goldb"
    static let ProfilePicUrl = "profilePicUrl"
    static let ProfilePicImg = "profilePicImg"
}

var _username:String! = ""
var _location:String! = ""
var _bronzeBadges:Int! = 0
var _silverBadges:Int! = 0
var _goldBadges:Int! = 0
var _profilePicUrl:String! = ""
var _profilePicImage:UIImage?

override init() {}

init(username:String,location:String,bronzeBadges:Int,silverBadges:Int,goldBadges:Int,profilePicUrl:String,profilePicImg:UIImage?) {
    self._username = username
    self._location = location
    self._profilePicUrl = profilePicUrl
    self._profilePicImage = profilePicImg
    self._bronzeBadges = bronzeBadges
    self._silverBadges = silverBadges
    self._goldBadges = goldBadges
}

required init?(coder aDecoder: NSCoder) {
    if let usernameObj = aDecoder.decodeObject(forKey: Keys.Username) as? String {
        _username = usernameObj
    }

    if let locationObj = aDecoder.decodeObject(forKey: Keys.Location) as? String {
        _location = locationObj
    }

    if let bronzeBadgesObj = aDecoder.decodeObject(forKey: Keys.BronzeBadges) as? Int {
        _bronzeBadges = bronzeBadgesObj
    }

    if let silverBadgesObj = aDecoder.decodeObject(forKey: Keys.SilverBadges) as? Int {
        _silverBadges = silverBadgesObj
    }

    if let goldBadgesObj = aDecoder.decodeObject(forKey: Keys.GoldBadges) as? Int {
        _goldBadges = goldBadgesObj
    }

    if let profilePicUrlObj = aDecoder.decodeObject(forKey: Keys.ProfilePicUrl) as? String {
        _profilePicUrl = profilePicUrlObj
    }

    if let profilePicImgObj = aDecoder.decodeObject(forKey: Keys.ProfilePicImg) as? UIImage {
        _profilePicImage = profilePicImgObj
    }
}

func encode(with aCoder: NSCoder) {
    aCoder.encode(_username, forKey: Keys.Username)
    aCoder.encode(_location, forKey: Keys.Location)
    aCoder.encode(_profilePicUrl, forKey: Keys.ProfilePicUrl)
    aCoder.encode(_profilePicImage, forKey: Keys.ProfilePicImg)
    aCoder.encode(_bronzeBadges, forKey: Keys.BronzeBadges)
    aCoder.encode(_silverBadges, forKey: Keys.SilverBadges)
    aCoder.encode(_goldBadges, forKey: Keys.GoldBadges)
}

var Username: String{
    get {
        return _username
    }
    set {
        _username = newValue
    }
}

var Location: String{
    get{
        return _location
    }
    set{
        _location = newValue
    }
}

var BronzeBadges:Int{
    get{
        return _bronzeBadges
    }
    set{
        _bronzeBadges = newValue
    }
}

var SilverBadges:Int{
    get{
        return _silverBadges
    }
    set{
        _silverBadges = newValue
    }
}

var GoldBadges:Int{
    get{
        return _goldBadges
    }
    set{
        _goldBadges = newValue
    }
}

var ProfilePicImage:UIImage?{
    get{
        return _profilePicImage
    }
    set{
        _profilePicImage = newValue
    }
}

var ProfilePicUrl:String{
    get{
        return _profilePicUrl
    }
    set{
        _profilePicUrl = newValue
    }
}
}

Класс Пользователя:

import Foundation

class UserData: NSObject,NSCoding {

    struct Keys{
        static let Data = "data"
    }

    private var _users : [User] = []

    override init() {}

init(with data: [User]) {
    _users = data
}

required init?(coder aDecoder: NSCoder) {
    if let userObj = aDecoder.decodeObject(forKey: Keys.Data) as? [User] {
        _users = userObj
    }

}

func encode(with aCoder: NSCoder) {
    aCoder.encode(_users, forKey: Keys.Data)
}

var SavedData : [User] {
    get {
        return _users
    }
    set {
        _users = newValue
    }
}

func appendUser(newUser: User) {
    _users.append(newUser)
}

func numberOfUsers() -> Int {
    return _users.count
}

func userAtIndex(index: Int) -> User? {
    if(index < _users.count) {
        return _users[index]
    } else {
        print("Error in userAtINdex(() index to big")
        return nil
        }
    }
}


Комментарии
1 ответ

Аксессоры собственность

Определение методов доступа для хранимых свойств, таких как

class User: NSObject, NSCoding {

var _username:String! = ""

var Username: String{
get {
return _username
}
set {
_username = newValue
}
}

// ...
}

нет необходимости в Swift, как описано в свойствах
в Свифт справочник по языку:


Если у вас есть опыт работы с Objective-C, вы, возможно, знаете, что он предлагает два способа хранения ценностей и ссылок в рамках экземпляра класса. В дополнение к свойствам, вы можете использовать переменные экземпляра в качестве резервного хранилища для ценностей, хранящихся в собственность.

Свифт объединяет эти понятия в одно объявление. Быстрое собственность не имеет соответствующей переменной экземпляра, и резервного хранилища на собственность не обращались напрямую. Такой подход позволяет избежать путаницы о том, как ценности осуществляется в различных контекстах и упрощает декларации на имущество в единую, окончательное заявление. Вся информация о недвижимости, включая ее имя, тип и характеристики памяти управление—определяется в одном месте как часть определения типа.

Так что в Свифт-это просто

class User: NSObject, NSCoding {

var userName: String! = ""

// ...
}

где имена свойств должны быть lowerCamelCase согласно
Проектирование API руководящие принципы).

Избежать неявно развернутые опции

Как Свифт 3, “неявно развернул необязательно” - это не отдельный вид,
но атрибут на декларация обычный/сильный необязательный, см.
ЮВ-0054 отменить ImplicitlyUnwrappedOptional типа:


В ImplicitlyUnwrappedOptional ("пик") тип представляет собой ценный инструмент для импорта Objective-С Апис
где допустимость значения параметра или возвращаемого типа является неуказанным. Он также представляет
удобный механизм для работы через определенные проблемы инициализации в инициализаторы.
...
За исключением нескольких конкретных сценариев, варианты всегда надежнее, и мы хотим, чтобы поощрять людей, чтобы использовать их вместо IUOs.

В частности, неявно развернутые опции обрабатываются как обычные (сильная) варианты
во многих случаях, который может привести к путанице.

Так что вы должны объявить свойства пользователя не обязательны или как обычные варианты,
в зависимости от того, требуются ли они или нет. Возможный выбор будет

class User: NSObject, NSCoding {

let userName: String
let location: String?
let bronzeBadges: Int
let silverBadges: Int
let goldBadges: Int
let profilePicUrl: String?
let profilePicImage: UIImage?

// ...
}

Обратите внимание, что я объявил их как константы , потому что свойства не
изменить после инициализации.

Упростить дешифратор, использовать дополнительные цепочки

Это

    if let locationObj = aDecoder.decodeObject(forKey: Keys.Location) as? String {
_location = locationObj
}
if let bronzeBadgesObj = aDecoder.decodeObject(forKey: Keys.BronzeBadges) as? Int {
_bronzeBadges = bronzeBadgesObj
}
// ...

теперь может быть упрощен до

    location = aDecoder.decodeObject(forKey: Keys.location) as? String
bronzeBadges = aDecoder.decodeObject(forKey: Keys.bronzeBadges) as? Int ?? 0
// ...

Расположение отеля просто становится nil если проверка терпит неудачу, и значки собственность
по умолчанию к нулю при помощи специальной цепочки.

Подводя итоги изменений до сих пор User класс выглядит так:

class User: NSObject, NSCoding {

struct Keys{
static let username = "name"
static let location = "location"
static let bronzeBadges = "bronzeb"
static let silverBadges = "silverb"
static let goldBadges = "goldb"
static let profilePicUrl = "profilePicUrl"
static let profilePicImg = "profilePicImg"
}

let userName: String
let location: String?
let bronzeBadges: Int
let silverBadges: Int
let goldBadges: Int
let profilePicUrl: String?
let profilePicImage: UIImage?

init(userName: String, location: String?, bronzeBadges: Int, silverBadges: Int,
goldBadges: Int, profilePicUrl: String?, profilePicImage: UIImage?) {
self.userName = userName
self.location = location
self.bronzeBadges = bronzeBadges
self.silverBadges = silverBadges
self.goldBadges = goldBadges
self.profilePicUrl = profilePicUrl
self.profilePicImage = profilePicImage
}

required init?(coder aDecoder: NSCoder) {
guard let userName = aDecoder.decodeObject(forKey: Keys.username) as? String else {
return nil
}
self.userName = userName

location = aDecoder.decodeObject(forKey: Keys.location) as? String
bronzeBadges = aDecoder.decodeObject(forKey: Keys.bronzeBadges) as? Int ?? 0
silverBadges = aDecoder.decodeObject(forKey: Keys.silverBadges) as? Int ?? 0
goldBadges = aDecoder.decodeObject(forKey: Keys.goldBadges) as? Int ?? 0
profilePicUrl = aDecoder.decodeObject(forKey: Keys.profilePicUrl) as? String
profilePicImage = aDecoder.decodeObject(forKey: Keys.profilePicImg) as? UIImage
}

func encode(with aCoder: NSCoder) {
aCoder.encode(userName, forKey: Keys.username)
aCoder.encode(location, forKey: Keys.location)
aCoder.encode(bronzeBadges, forKey: Keys.bronzeBadges)
aCoder.encode(silverBadges, forKey: Keys.silverBadges)
aCoder.encode(goldBadges, forKey: Keys.goldBadges)
aCoder.encode(profilePicUrl, forKey: Keys.profilePicUrl)
aCoder.encode(profilePicImage, forKey: Keys.profilePicImg)
}
}

Подобные изменения могут быть применены к UserData класс.

Читать ответ – Ты не разверну силу!

Это относится к форс-распакуйте оператора ! и сила-литой
оператор as!как легко вызвать фатальные ошибки и неожиданные прерывании программы.

Есть много силы-кастинг в обработчик завершения в
requestDataFromApi() функции, начиная в самом начале:

        if let json = response.result.value {
/* I know this is a bit ugly */
let json1 = json as! [String:AnyObject]
let usersInfoFromJSON = json1["items"] as! NSArray
// ...

Что не безобразно, но плохо. Если по какой-либо причине (например, изменение в API)


  • ответ-это не словарь, или

  • нет никаких "предметов" ключ в ответ, или

  • стоимость "предметы" ключ не является массивом

тогда сила оператора приведения as! во время выполнения исключение.

Правильный способ заключается в использовании условных забросы и Факультативного связывания:

        if let json = response.result.value as? [String: Any],
let items = json["items"] as? [[String: Any]] {
for userDict in items {
// ...
}
}

Отметим также, что фундамент видах NSArray и NSDictionary не нужны.

Вот похожая проблема:

        let badgeCounts = userDict["badge_counts"] as? [String:Int]

var goldb = 0
var bronzeb = 0
var silverb = 0

if badgeCounts != nil {
bronzeb = badgeCounts!["bronze"]!
silverb = badgeCounts!["silver"]!
goldb = badgeCounts!["gold"]!
}

Вы проверьте, если badgeCounts не пропустит, но даже тогда badgeCounts!["bronze"]
может быть nil и силовое разворачивание причины аварии.

Безопасное и лаконичным решением является использовать дополнительные цепочки

        let badgeCounts = userDict["badge_counts"] as? [String:Int]
let goldb = badgeCounts?["gold"] ?? 0
let bronzeb = badgeCounts?["silver"] ?? 0
let silverb = badgeCounts?["bronze"] ?? 0

вместо. Теперь свойства равны нулю, если соответствующая запись отсутствует.

4
ответ дан 14 апреля 2018 в 08:04 Источник Поделиться