2015-03-04 1 views
0

При тестировании было бы удобно иметь простую модель данных Singleton, которая действует как массив. То есть, если у меня есть класс синглтона под названием MySingletonClass, который поддерживает переменное sharedInstance вы должны быть в состоянии изменить элемент в массиве с индексами:Создание модели данных Singleton Single Swift, которая подстрочно (легко) и присваивается массиву (жестко)

MySingletonClass.sharedInstance[ 0 ] = "New item 0" 

Кроме того, при запуске приложения, было бы замечательно, если простое назначение позволило системе, чтобы обеспечить начальные значения массива:

MySingletonClass.sharedInstance = ["Item 0", "Item 1", "Item 2"] 

я могу сделать первый, предоставляя MySingletonClass с индексом/получить/установить заявление. Тем не менее, я не нашел никаких предложений о том, как выполнять последнее. Вот мой класс, как она существует в настоящее время:

class MySingletonClass { 
    class var sharedInstance: MySingletonClass { 
     get { 
      struct Singleton { 
       static let instance = MySingletonClass() 
      } 
      return Singleton.instance 
     } 
    } 

    var toDoItems = [ String ]() 

    subscript(row: Int) -> NSString? { 
     get { 
      if isValidIndex(row) { 
       return toDoItems[ row ] 
      } else { 
       return nil 
      } 
     } 
     set { 
      assert(isValidIndex(row), "invalid row number \(row) in a ToDo array of \(toDoItems.count) items") 
      toDoItems[ row ] = newValue! 
     } 
    } 
    /** 
    Returns true if the passed index is valid in the toDoItems array 

    The array is indexed from 0 to toDoItems.count - 1 (inclusive). The "row" value is judged 
    to be valid only if it lies within this range. 

    :param: row (Int), specifies an index number (to count-1) in the toDoItems array 
    :returns: a boolean to indicate if the passed index number is valid 
    */ 
    func isValidIndex(row: Int) -> Bool { 
     if (row >= 0) && (row < toDoItems.count) { 
      return true 
     } else { 
      return false 
     } 
    } 
} 

Существует kludgey способ присвоить начальные значения для этого класса с использованием массива:

MySingletonClass.sharedInstance.toDoItems = ["zero", "one", "two"] 
println("first element: '\(MySingletonClass.sharedInstance[ 0 ]!)'") 

Однако, это заставляет пользователя быть осведомлены о " toDoItems ", который я предпочел бы оставаться скрытым. Есть предположения?

ответ

0

Чтобы получить поведение, которое вы ищете, вы можете создать закрытую переменную, доступную только в пределах вашего файла MySingletonClass, но не являющуюся частью самого класса.

private var instance: MySingletonClass! 
class MySingletonClass { 

    class var sharedInstance: MySingletonClass { 
     get { 
      return instance 
     } 

     set(newItems) { 
      println("NewItems") 
     } 
    } 

    private var toDoItems = [String]() 

    subscript(row: Int) -> String? { 
     get { 
      if isValidIndex(row) { 
       return toDoItems[ row ] 
      } else { 
       return nil 
      } 
     } 
     set { 
      //assert(isValidIndex(row), "invalid row number \(row) in a ToDo array of \(toDoItems.count) items") 
      toDoItems[ row ] = newValue! 
     } 
    } 

    private init(items: [String]) { 
     self.toDoItems = items 
    } 

    class func initialize(items: [String]) { 
     instance = MySingletonClass(items: items) 
    } 

    func isValidIndex(row: Int) -> Bool { 
     if (row >= 0) && (row < toDoItems.count) { 
      return true 
     } else { 
      return false 
     } 
    } 
} 

Теперь вы можете использовать класс FUNC initialize, чтобы создать общий экземпляр, не подвергая toDoItems вне класса.

class Controller { 

    init() { 
     MySingletonClass.initialize(["Get sack of crickets", "Pick up Lizards from daycare"]) 
     MySingletonClass.sharedInstance[ 0 ] = "Turn on Heat Lamps" 
    } 
} 
+0

Хаудите Blacksquare, – user2121934

+0

Спасибо за ваш любезный ответ. Это кажется неблагодарным, но есть ли способ сделать sharedInstance похожим на массив? Если бы он проиндексировал его, было бы здорово, если бы я мог дать ему назначение, «.delete []» или «.insert afterIndex:». -George – user2121934

+0

Вы могли бы предоставить несколько примеров, чтобы я мог точно видеть, о чем вы говорите? – kellanburket

0

Плохая новость заключается в том, что в Swift 1.1 вы не можете использовать стандартный оператор присваивания присвоить массив одноплодной модели данных. В книге «Яблоки» «Быстрый язык программирования» есть примечание:

Невозможно перегрузить оператор присваивания по умолчанию (=).

Конечно, вы могли бы создать свой собственный оператор присваивания, но это оставило бы у ваших пользователей возможность угадывать ваш уход из обычной практики массивов. Вы можете увидеть предложения о том, что одноэлементный массив можно создать с помощью «struct». Я не смог заставить это предложение работать как настоящий синглтон, см. Это link.

Хорошей новостью является то, что можно создать модель данных singleton, которая в противном случае действует как массив. То есть вы можете использовать стандартные функции массива, такие как «.append», «.remove: atIndex:» и т. Д. Вам нужно только установить пустой файл Swift и вставить код ниже. Код состоит из двух частей. Первый - это протокол под названием Item. Вторая часть - это класс, который устанавливает синглтон и инкапсулирует массив под названием «элементы». Вы можете добавлять, изменять или удалять объекты в «элементах» при условии, что объекты соответствуют протоколу Item. Пример показан ниже.

Model.data.append(myItemConformingInstance) 

Одно слово предупреждения, Xcode имеют некоторые трудности, представляющие одиночка, и этот файл делает Xcode действительно раздражительным. Вы можете изменить его, но вам нужно терпение!

import Foundation 

/** 
This module provides a simple Data Model for you to use in a Model-View-Controller setup. The class Model provides a singleton called data. You can treat the "data" singleton as if it were an array (see below for an exception related to assignments). Model.data stores objects that conform to the Item protocol. You provide your own class that conforms to this protocol, and also provides instance variablesimport Foundation 
*/ 

protocol Item { 

    /** 
    Returns a single-line description of the data stored in an instance conforming to the Item protocol. 

    Required to support plist-like description of the values stored in the items array 

    :returns: (String) a description of the item's data 
    */ 
    var description: String { get } 

    /* Make your Item-conforming class also conform to Equatable. It should have a function that determines if two instances of your class are considered equal. The function must be in the same file, but NOT INSIDE your Item-conforming class. The function looks like: 

     func​ ==(​left​: ​<NewItemClass>, ​right​: <NewItemClass>) -> ​Bool​ 

    */ 
} 

// MARK: - start of Model singleton 
/** 
The Model.data singleton should look like a Swift Array, so it supports these functions: 

ACCESSING ARRAY ELEMENTS 

subscript(_: Int) 

    gets or sets existing elements using square bracket subscripting. If the index is not valid for the current items array then an assertion is triggered. It is not possible to insert additional items into the array using subscripts larger than the current highest index for the items array (see .append). 

subscript(_: Range<Int>) 

    gets or sets a subrange of existing elements in the items array. If any of the indices in the specified range are not valid then an assertion is triggered. A subrange can be replaced (or eliminated) by a new array of objects conforming to the Item protocol, and the new array does not have to have the same number of entries as is specified by the Range. 

ADDING OR REMOVING ELEMENTS 

Model.data.append(newItem: Item) 

    Adds a new Item-conforming object as the last element in the items array 

Model.data.insertAtIndex(<Item>, atIndex: <Int>) 

    Inserts an Item-conforming object into the items array at the given index. If the index is not valid then an assertion is triggered. 

Model.data.removeAtIndex(<Int>) 

    Removes the element in the items collection at the given index and returns the removed element. If the index is invalid then an asertion is triggered 

Model.data.removeLast() 

    Removes the last element in the items collection and returns the removed element. If the items array is already empty then an assertion is triggered. 

Model.data.removeAll(keepCapacity: Bool) 

    Removes all elements from the items array and by default clears the underlying storage buffer 

Model.data.reserveCapacity(minimumCapacity: Int) 

QUERYING THE ARRAY OF ITEMS 

Model.data.count 

    Returns the number of elements in the items array. 

Model.data.isEmpty 

    Returns true if the items array is empty, otherwise returns false. 

Model.data.capacity 

    Returns an integer value representing the number of elements that the items array can store without reallocation. 

Model.data.isValidIndex(index: Int) (not standard) 

    returns true if the given index is between 0 and items.count - 1, otherwise it returns false. 

Model.data.description(optional limit: <Int>?) (not standard) 

    returns "empty" if the items array is empty, otherwise returns an enumerated list of Item descriptions. Ordinarily all items are listed, unless you provide a limit to tell the system how many items to list. 

ISSUES 

Although it would be nice to be able to assign Model.data usign the standard approach, e.g. 

    *Model.data = [ item1, item2, item3 ] 
    *THIS WON'T WORK, and the Swift 1.1 documentation says that the assign operator (=) cannot be overridden. Instead, use the underlying items array. E.G: 
    *Model.data.items = [ Item1, Item2, Item3 ] 

This approach uses the normal singleton method for Swift 1.1. Reportedly, it gets much simpler in Swift 1.2 
*/ 
class Model { 
    class var data: Model { 
     get { 
      struct Singleton { 
       static let instance = Model() 
      } 
      return Singleton.instance 
     } 
    } 

    var items = [ Item ]() 

    // MARK: - ACCESSING ARRAY ELEMENTS 

    subscript(index: Int) -> Item? { 
     get { 
      if isValidIndex(index) { 
       return items[ index ] 
      } else { 
       return nil 
      } 
     } 
     set { 
      assert(isValidIndex(index), 
       "Fatal error: could not replace an item in Model.data using index number: \(index) in an items array of: \(items.count) items") 
      items[ index ] = newValue! 
     } 
    } 

    subscript(subRange: Range<Int>) -> Slice<Item> { 
     get { 
      assert(isValidIndex(subRange.startIndex), 
       "Fatal error: could not retrieve a range of items in Model.data using low index number: \(subRange.startIndex) from an items array of: \(items.count) items") 
      // in testing, it looks as though subRange is always set up to do a test of range 
      // [ .startIndex..<.endIndex ], which means that .endIndex can be equal to the count 
      // of elements in a array. 
      assert(subRange.endIndex <= items.count, 
       "Fatal error: could not retrieve a range of items in Model.data using high index number: \(subRange.endIndex) from an items array of: \(items.count) items") 
      return self.items[ subRange ] 
     } 
     set { 
      assert(isValidIndex(subRange.startIndex), 
       "Fatal error: could not replace a range of items in Model.data using low index number: \(subRange.startIndex) in an items array of: \(items.count) items") 
      assert(subRange.endIndex <= items.count, 
       "Fatal error: could not replace a range of items in Model.data using high index number: \(subRange.endIndex) in an items array of: \(items.count) items") 

      items[ subRange ] = newValue 
     } 

    } 

    // MARK: - ADDING OR REMOVING ELEMENTS 

    /** 
    Adds a new object conforming to Item at the end of items array 

    :param: newItem (Item) an object conforming to the Item protocol 
    */ 
    func append(newItem: Item) { 
     self.items.append(newItem) 
    } 

    /** 
    Inserts a new item into the items array based on a passed index number 

    The insertion places an object conforming with the Item protocol into the items array at the position specified by atIndex. Existing items that are at that position or higher are moved up by 1. Insertions fail if the passed index number is not valid. 

    :param: newItem (Item) an object conforming with Item 
    :param: atIndex (Int) the position in the list where the new task name is to be inserted. 
    */ 
    func insert(newItem: Item, atIndex: Int) { 
     assert(isValidIndex(atIndex), 
      "Fatal error: could not insert a new item into Model.data with invalid index number: \(atIndex) in an items array of: \(items.count) items") 
     self.items.insert(newItem, atIndex: atIndex) 

    } 

    /** 
    removes an item at the items array at the specified index position 

    If the specified index is not valid then a runtime error is generated. After a successful deletion, the items that started with index numbers higher than the passed index number will end with their index numbers decremented by 1. 

    :param: atIndex (Int) the index number of the data item to be removed 
    :returns: (Item) This is the item that was removed from the items array. 
    */ 
    func removeAtIndex(atIndex: Int) -> Item? { 
     assert(isValidIndex(atIndex), 
      "Fatal error: could not remove an item from Model.data using index number: \(atIndex) in an items array of: \(items.count) items") 
     return self.items.removeAtIndex(atIndex) 
    } 

    /** 
    removes the last Item object in the items array in Model.data 

    If the items array is empty then a runtime error is generated 

    :returns: (Item) an object conforming to the Item protocol that was removed from the items array. 
    */ 
    func removeLast() -> Item { 
     assert(self.items.count > 0, "Fatal error: could not remove the 'last' item from Model.data since the items collection was already empty.") 
     return self.items.removeLast() 
    } 

    /** 
    Removes all items in the items array 

    :param: keepCapacity: Bool if true then overrides default clearing of the underlying buffer 
    */ 
    func removeAll(keepCapacity: Bool = false) { 
     self.items.removeAll(keepCapacity: keepCapacity) 
    } 

    /* 
    Ensures that the underlying storage for the items array can hold the given total number of elements 
    */ 
    func reserveCapacity(minimumCapacity: Int) { 
     self.items.reserveCapacity(minimumCapacity) 
    } 

    // MARK: - QUERYING THE ARRAY OF ITEMS 
    /** 
    Returns an integer value indicating how many items the items array in Model.data can store 

    You will have to reallocate the Model.data.items array if storage capacity is exceeded, using 
    reserveCapacity. 

    :returns: (Int) the number of items that the items array can store. 
    */ 
    var capacity: Int { 
     get { 
      return self.items.capacity 
     } 
    } 

    /** 
    Returns the number of items in the items array 
    */ 
    var count: Int { 
     get { 
      return items.count 
     } 
    } 

    /** 
    Returns true if the items array of Model.data is empty 

    :returns: (Bool) true if the items array s empty, otherwise false. 
    */ 
    var isEmpty: Bool { 
     get { 
      return self.items.isEmpty 
     } 
    } 

    /** 
    Returns true if the passed index is valid in the data array 

    The items array is indexed from 0 to items.count - 1 (inclusive). The index is judged to be valid   only if it lies within this range. 

    :param: index (Int), specifies an index number in for an object in the items array. 
    :returns: a boolean to indicate if the passed index number is valid 
    */ 
    func isValidIndex(index: Int) -> Bool { 
     if (index >= 0) && (index < items.count) { 
      return true 
     } else { 
      return false 
     } 
    } 

    /** 
    Returns a string in the form of an ennumerated list that shows a description for each item in the items array 
    :param: limit: Int? tells the system to limit the list to "limit" items. 
    :returns: (String) the ennumerated list of items 
    */ 
    func description(limit: Int? = nil, title: String? = nil) -> String { 
     var msg = "" 
     if let label = title { 
      msg = "*** \(label)\n" 
     } 
     if items.count == 0 { 
      msg += "the items array is empty\n" 
     } else { 
      var index = 0 
      for nextItem in self.items { 
       msg = msg + "[\(index)].\t\(nextItem.description)\n" 
       index++ 
      } 
     } 
     return msg 
    } 
} 

Чтобы проверить это, представьте, что вы хотите, чтобы одиночный массив представлял элементы в списке дел. Ваш класс может выглядеть так:

import Foundation 

    class ToDoItem: Item, Equatable { 

     var name: String 
     var createdAt: NSDate 
     var done: Bool 

     init(name: String, createdAt: NSDate, done: Bool = false) { 
      self.name = name 
      self.createdAt = createdAt 
      self.done = done 
     } 

     var description: String { 
      get { 
       return "Task Name:\(name), Created On:\(createdAt), Done: \(done)" 
      } 
     } 


    } 

    // External function to make equivalence between Items testable 
    func ==(left: ToDoItem, right: ToDoItem) -> Bool { 
     return 
       left.done == right.done 
      && 
       left.createdAt == right.createdAt 
      && 
       left.name == right.name 

    } 

Одноэлементный массив Model.data находится в вашем проекте, просто включив его класс. Вы можете начать добавлять к одноплодной массив Model.data по инстанцированию своего класса Item и добавления:

let task1 = ToDoItem(name: "task 1", createdAt: NSDate(), done: false) 
let task2 = ToDoItem(name: "task 2", createdAt: NSDate(), done: false) 
let task3 = ToDoItem(name: "task 3", createdAt: NSDate(), done: false) 

Model.data.append(task1) 
Model.data.append(task2) 
Model.data.append(task3) 

В дополнении к стандартным функциям, предоставляемых Swift объектов Array, есть метод, description(limit:Int?, title:String:). Метод возвращает строку, перечисляющую каждый элемент в вашем одноэлементном массиве. Предельный параметр является необязательным и сообщает системе, что вы хотите видеть только ограниченное количество элементов из вашего массива. Параметр title помещает текст в начале описания. Например:

Model.data.description(title: "First Items") 

Это приводит следующую строку:

> *** First Items: 
>[0]. Task Name:task 1, Created On:2015-03-05 22:25:14, Done: false 
>[1]. Task Name:task 2, Created On:2015-03-05 22:25:14, Done: false 
>[2]. Task Name:task 3, Created On:2015-03-05 22:25:14, Done: false 
Смежные вопросы