Arrays 创建下标(简单)并可分配给数组(硬)的Swift单例数据模型

Arrays 创建下标(简单)并可分配给数组(硬)的Swift单例数据模型,arrays,swift,singleton,assign,Arrays,Swift,Singleton,Assign,在测试中,使用一个简单的、类似于数组的单例数据模型会很方便。也就是说,如果我有一个名为MySingletonClass的单例类,它支持sharedInstance变量,那么您应该能够使用下标更改数组中的元素: MySingletonClass.sharedInstance[ 0 ] = "New item 0" 此外,当应用程序打开时,如果一个简单的赋值允许系统向数组提供初始值,那就太好了: MySingletonClass.sharedInstance = ["Item 0", "Item

在测试中,使用一个简单的、类似于数组的单例数据模型会很方便。也就是说,如果我有一个名为MySingletonClass的单例类,它支持sharedInstance变量,那么您应该能够使用下标更改数组中的元素:

MySingletonClass.sharedInstance[ 0 ] = "New item 0"
此外,当应用程序打开时,如果一个简单的赋值允许系统向数组提供初始值,那就太好了:

MySingletonClass.sharedInstance = ["Item 0", "Item 1", "Item 2"]
我可以通过向MySingletonClass提供subscript/get/set语句来完成第一步。但是,我没有找到任何关于如何执行后者的建议。这是我的类,因为它目前存在:

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
        }
    }
}

但是,它强制用户注意“toDoItems”变量,我更希望保持隐藏状态。有什么想法吗?

要获得您正在寻找的行为,您可以创建一个私有变量,该变量只能在
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
        }
    }
}

坏消息是,在Swift 1.1中,不能使用标准赋值运算符将数组赋值给单例数据模型。在苹果的《Swift编程语言》一书中,有一条注释说:

无法重载默认赋值运算符(=)

当然,您可以编写自己的赋值运算符,但这只会让用户猜测您是否偏离了正常的数组实践。您可能会看到使用“struct”创建单例数组的建议。作为一个真正的单身汉,我无法让这个建议发挥作用,明白吗

好消息是,可以建立一个单独的数据模型,否则它的行为就像一个数组。也就是说,您可以使用诸如“.append”、“.remove:atIndex:”等标准数组函数。您只需设置一个空Swift文件并粘贴到下面的代码中。代码分为两部分。第一个是名为Item的协议。第二部分是一个类,用于建立单例并封装一个名为“items”的数组。如果对象符合项目协议,则可以添加、修改或删除“项目”中的对象。下面是一个例子

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
    }
}
singleton array Model.data仅通过包含其类就可以包含在项目中。通过实例化Item类并附加以下内容,可以开始向singleton array Model.data添加:

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数组对象提供的标准函数外,还有一个方法,
description(limit:Int?,title:String:)
。该方法返回一个字符串,列出单例数组中的每个项。limit参数是可选的,它告诉系统您只希望看到数组中数量有限的项。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

谢谢你的友好回复。这似乎是忘恩负义,但有没有办法让sharedInstance看起来像一个数组?订阅之后,如果我能给它赋值,“.delete[]”或“.insert afterIndex:”功能,那就太好了-乔治:你能提供几个例子吗?这样我就可以确切地了解你在说什么了。布莱克广场,再次感谢你的回复。我想我可能是错误地认为这种类结构是构成一个单子的必要条件。至少,还有另一个讨论断言结构可以完成这项工作。我正在调查。谢谢你的友好回复!乔治没有回答。我希望有类似以下工作的语句:
MySingletonClass.sharedInstance.delete[0]
MySingletonClass.sharedInstance.insert(“一个新项目”,atIndex:5)
。(即,提供数组的sharedInstance特性)
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 )
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