EverliveSDK 1.2.0

EverliveSDK 1.2.0

测试已测试
语言语言 SwiftSwift
许可证 MIT
发布最后发布2016年7月
SPM支持 SPM

Dimitar Dimitrov 维护。



 
依赖
Alamofire~> 3.0
EVReflection~> 2.6
SwiftyJSON~> 2.3.1
 

  • [Dimitar Dimitrov]

进度后端服务(Everlive)iOS Swift SDK

此 SDK 旨在与 Progress 后端服务 一起使用,以便使用 Swift 语言创建本地 iOS 应用程序。

此 SDK 旨在与 Telerik 后端服务一起使用,以便使用 Swift 语言创建本地 iOS 应用程序。

功能

第一版中涵盖的功能

  • 设置您自己的数据模型
  • 与数据项工作
  • 与用户工作
  • 与文件工作

这里有一个示例应用程序,演示了在真实案例场景下的所有功能。

要求

SDK 本身没有特定要求,所以所需的所有版本都来自其中使用的库。

  • iOS 9.0+ / Mac OS X 10.9+ / tvOS 9.0+ / watchOS 2.0+
  • Xcode 7.3+

安装

只需将以下内容添加到您的 Podfile 中。

source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '9.0'
use_frameworks!

target '<Your Target Name>' do
    pod 'EverliveSDK', '~> 1.0'
end

问题

请注意,我在业余时间进行这项工作,可能存在一些错误。如果您发现任何问题,请在 git 存档中提交它们作为问题。我会尽快回复。另外,如果您遇到问题或需要更多有关如何实现某些事情的信息,您可以在 示例应用程序存档 或我的电子邮件中留言。

许可证

EverliveSDK 以 MIT 许可证发布。有关详细信息,请参阅 LICENSE。

任何 MR/建议都非常欢迎。

基本用法

您需要做的就是导入 EverliveSwift 库并设置应用程序对象。它以您的应用程序标识符为参数。为了更容易使用,您可以为 SDK 创建一个单例实例,该实例可以在整个应用程序中使用。

import EverliveSDK
public class EverliveSwiftApp {
    static let sharedInstance = EverliveApp(appId: "app-id-here")
    private init() {}
}

数据模型

在实际开始使用云中存储的数据之前,您需要为每个您希望与之交互的内容类型创建一个Swift类。这样,SDK将自动使用Everlive中存储的值填充该对象的属性。有一个基类DataItem,它存储了每个项目的根本属性。

  • Id - 字符串类型 - 对象的ID
  • CreatedAt - NSDate - 项目创建的时间
  • ModifiedAt - NSDate - 项目最后一次修改的时间
  • CreatedBy - String - 该项目的所有者的ID
  • ModifiedBy - String - 最后修改该项目的用户的ID

下一步是扩展DataItem,通过创建一个以您的内容类型命名的类。对于后端服务中存储的每一列,您将为您的类创建一个适当数据类型的属性。以下是一个示例

import EverliveSDK

public class Activity : DataItem {
    var Text: String? {
        didSet {
            super.propertyChanged.raise("Text")
        }
    }

    override public func getTypeName() -> String {
        return "Activities"
    }
}

通常您的内容类型的名称将是复数形式,而Swift类将是单数,因此您必须重写getTypeName函数并返回服务器上看到的您的内容类型的名称。

当您有想要更新的数据对象时,需要didSet函数来跟踪更改的属性。

每个DataItem对象都支持自定义属性。如果您有一个自定义类并且一些属性没有被映射,您仍然可以获取它们的值。您必须指定返回类型和一个默认值,如果属性没有被设置或缺失。

let nonMappedValue:Int = item.getValue("NonMappedProp", defaultValue: 0)

CRUD操作

下面的示例将使用以下Swift类,该类代表一本书。

public class Book: DataItem {   
    public var Likes: Int = 0 {
        didSet {
            super.propertyChanged.raise("Likes")
        }
    }
    public var PublishedAt: NSDate? {
        didSet {
            super.propertyChanged.raise("PublishedAt")
        }
    }

    public var Title: String? {
        didSet {
            super.propertyChanged.raise("Title")
        }
    }
}

let everliveApp = EverliveApp("app-id-here")

读取条目

通过ID读取

everliveApp.Data().getById("item-id").execute { (result: Book?, error: EverliveError?) -> Void in
    // result as a Book class
    // error if the item was not found for this Id
}

全部读取

everliveApp.Data().getAll().execute { (result: [Book]?, error: EverliveError?) -> Void in
    // result as an array of Books
    // empty array if there are no items
}

通过过滤器读取

let query = EverliveQuery()
query.filter("Likes", greaterThan: 20.0, orEqual: true)

everliveApp.Data().getByFilter(query).execute { (result: [Book]?, error: EverliveError?) -> Void in
    // result as an array of Books
    // empty array if no items found
}

更详细的过滤示例请参考“过滤器”部分

获取条目数量

let dataHandler: DataHandler<Book> = everliveApp.Data()
dataHandler.getCount().execute { (result:Int?, err:EverliveError?) -> Void in
    // there might be an error getting the count, so the result is Optional
}

创建条目

创建单个条目

let book = Book()
book.Title = "Crime and Punishment"
book.Likes = 30
book.PublishedAt = NSDate.init()
everliveApp.Data().create(book).execute { (success:Bool, error: EverliveError?) -> Void in
    // success - if the item was created, otherwise there will an error object
}

创建多个条目

let book1 = Book()
book1.Title = "Frankenstein"
book1.Likes = 40
book1.PublishedAt = NSDate.init()

let book2 = Book()
book2.Title = "Game of Thrones"
book2.Likes = 50
book2.PublishedAt = NSDate.init()

everliveApp.Data().create([book1, book2]).execute { ( result: [Book], error: EverliveError?) -> Void in
    // result array will contain the items with populated server properties - Id, CreatedAt etc..
}

更新条目

条目可以通过两种方式更新 - 使用默认的属性更改通知器或指定更新对象。

通过设置条目的属性值来更新

每个DataItem的子类都应该像上面展示的示例Book类一样设置。通过触发属性更改事件,在执行更新查询时传递整个对象,属性将被更新。

everliveApp.Data().getById("item-id").execute { (result: Book?, error: EverliveError?) -> Void in
    result!.Title = "New Title"
    result!.Likes = 100
    let updateHandler: DataHandler<Book> = everliveApp.Data()
    updateHandler.updateById("item-id", updateObject: result!).execute { (result:UpdateResult, error:EverliveError?) -> Void in
        // update result will contain the ModifiedAt date and the number of updated items
    }
}

它也可以用以下方式使用:

let book = Book()
book.Likes = 17

let updateHandler: DataHandler<Book> = everliveApp.Data()
updateHandler.updateById("itme-id", updateObject: book).execute { (result:UpdateResult, error:EverliveError?) -> Void in
    // only the Likes property will be updated
}

更新时可以应用过滤器

let book = Book()
book.Title = "Changed Title"

let query = EverliveQuery()
query.filter("Likes", greaterThan: 25, orEqual: false)

let updateHandler: DataHandler<Book> = everliveApp.Data()
updateHandler.updateByFilter(query, updateObject: book).execute { (result:UpdateResult, error:EverliveError?) -> Void in
    // the update result will contain the number of updated items 
}

使用更新对象来更新

对于更具体的更新,您可以使用 UpdateObject。 单个对象的定义包括要更新的字段名称、应应用于该字段的修改器和新的值。

let updateObject = UpdateObject(updateFields: [])
updateObject.UpdatedFields.append(UpdateField(fieldName: "Likes", modifier: UpdateModifier.Increment, value: 5))

let updateHandler: DataHandler<Book> = everliveApp.Data()
updateHandler.updateById("item-id", updateObject: updateObject).execute { (result:UpdateResult, error:EverliveError?) -> Void in
    // update result count should be one
}

也可以与过滤条件一同使用。

let updateObject = UpdateObject(updateFields: [])
updateObject.UpdatedFields.append(UpdateField(fieldName: "Likes", modifier: UpdateModifier.Increment, value: 2))

let query = EverliveQuery()
query.filter("Likes", greaterThan: 25, orEqual: false)

let updateHandler: DataHandler<Book> = everliveApp.Data()
updateHandler.updateByFilter(query, updateObject: updateObject).execute { (result:UpdateResult, error:EverliveError?) -> Void in
    // update result will contain the number of updated items 
}

当前支持的修改器有:$set/$unset/$inc。

删除项目

可以通过指定ID或删除所有项目来实现删除项目。通过过滤条件删除太危险了,目前不支持(只需通过传递不正确的过滤条件,您可能会丢失所有数据)

按ID删除

let deleteHandler: DataHandler<Book> = everliveApp.Data()
deleteHandler.deleteById("item-id").execute { (deletedItems: Int?, error: EverliveError?) -> Void in
    // if there was no error, deletedItems will be equal to 1
}

删除所有

let deleteHandler: DataHandler<Book> = everliveApp.Data()
deleteHandler.deleteAll().execute { (deletedItems: Int?, error: EverliveError?) -> Void in
    // deleted items count will returned
}

展开

Item的展开支持所有读取操作,使用ExpandDefinition类实现。注意展开项的数量,因为目前返回的最长项数限制为50。为了使用展开,您的类中应有两个属性 - 在内容类型中定义的作为关系的字段以及将持有展开结果的属性。以下是一个示例,包含示例应用中用户的定义。

class SampleUser: User {
    var Picture: String?
    var ProfilePicture: File?
}

Picture属性定义为字符串,将保存用户个人资料的ID。ProfilePicture属性为类型File,因为关系是到文件内容类型。

单个

let picExpand = ExpandDefinition(relationField: "Picture", returnAs: "ProfilePicture")
picExpand.TargetTypeName = "System.Files"
everliveApp.Users().getById("user-id").expand(picExpand).execute { (result: SampleUser?, err:EverliveError?) in
    if let profilePic = result?.ProfilePicture?.Uri {
        // set profile picture
    }
}

多个和嵌套

您还可以对多个字段进行展开,同时也有嵌套展开。示例来自示例后端服务数据 - 每个活动都有一个作者,这是一个用户,这个用户有一个个人资料图片,这是一个文件。以下是结果展开定义。

let pictureExpand = ExpandDefinition(relationField: "Picture", returnAs: "ActivityPic")
pictureExpand.TargetTypeName = "System.Files"
let userExpand = ExpandDefinition(relationField: "UserId", returnAs: "UserProfile")
userExpand.TargetTypeName = "Users"
let profilePicExpand = ExpandDefinition(relationField: "Picture", returnAs: "ProfilePicture")
profilePicExpand.TargetTypeName = "System.Files"
userExpand.ChildExpand = profilePicExpand

let multipleExpand = MultipleExpandDefinition(expandDefinitions: [pictureExpand, userExpand])
EverliveSwiftApp.sharedInstance.Data().getAll().expand(multipleExpand).execute { (activities:[Activity]?, err: EverliveError?) in         
    // each activity from the array will have its Picture expanded and will contain a User author with expanded profile picture.
}

过滤/排序/跳过/取走

过滤

过滤对象使用EverliveQuery类指定。所有基本的查询运算符都是查询类的独立的函数。以下是一些示例。

let query = EverliveQuery()
query.filter("Title", equalTo: "Lolita")
query.filter("Likes", equalTo: 20)
query.filter("Likes", notEqualTo: 20)
query.filter("Likes", greaterThan: 20, orEqual: false)
query.filter("Likes", lessThan: 20, orEqual: true)
query.filter("Title", startsWith: "lol", caseSensitive: true)
query.filter("Title", contains: "lol", caseSensitive: true) 

所有上述基本过滤器可以被串联在一起形成一个复杂查询。您只需在最后一个函数调用中指定不同过滤器之间应使用的运算符 - 和 / 或

let query = EverliveQuery()
query.filter("Likes", lessThan: 30, orEqual: false).filter("Likes", greaterThan: 10, orEqual: false).and()
query.filter("Title", notEqualTo: "Game of Thrones").filter("Likes", lessThan: 30, orEqual: true).or()

使用基本的EverliveQuery类,您可以创建大多数基本过滤器。接下来是EverliveCompoundQuery,基本上是一组基本过滤器的组合。

let query = EverliveQuery()
query.filter("Likes", lessThan: 40, orEqual: false).filter("Likes", greaterThan: 20, orEqual: false).and()
let query2 = EverliveQuery()
query2.filter("Title", startsWith: "lol", caseSensitive: false)
let compoundQuery = EverliveCompoundQuery()
compoundQuery.and([query2, query])

let query = EverliveQuery()
query.filter("Likes", lessThan: 40, orEqual: false).filter("Likes", greaterThan: 20, orEqual: false).and()
let query2 = EverliveQuery()
query2.filter("Title", contains: "lol", caseSensitive: true)
let compoundQuery = EverliveCompoundQuery()
compoundQuery.or([query2, query])

这两种类型的查询都可以用作过滤更新或读取的项目。

排序

排序表达式是用Sorting类定义的。

let sortDef = Sorting(fieldName: "Likes", orderDirection: OrderDirection.Descending)
everliveApp.Data().getAll().sort(sortDef).execute { (result: [Book]?, error: EverliveError?) -> Void in
    // the result array will be ordered by the Likes
}

跳过/取走

对于分页,您可以使用跳过和取走选项。这些功能直接应用于读取操作。通常与排序一起使用跳过/取走选项。

let sortDef = Sorting(fieldName: "Likes", orderDirection: OrderDirection.Ascending) 
everliveApp.Data().getAll().skip(2).take(1).sort(sortDef).execute { (result: [Book]?, error: EverliveError?) -> Void in
    // the resulted array will contain only one element
}

以下是一个包含排序、跳过和取走的组合过滤示例。

let query = EverliveQuery()
query.filter("Likes", greaterThan: 20.0, orEqual: true)
let sortDef = Sorting(fieldName: "Likes", orderDirection: OrderDirection.Ascending)

everliveApp.Data().getByFilter(query).skip(1).take(2).sort(sortDef).execute { (result: [Book]?, error: EverliveError?) -> Void in
    // the result array should contain two items
}

用户

SDK附带一个预定义的User类,代表后端中的用户。该类简单基于DataItem类并进行了一些属性添加。因此所有CRUD操作都以相同的方式有效,但只能使用User()处理程序。

用户注册

新用户的注册就是创建一个新对象。

let newUser = User()
newUser.Username = "username"
newUser.Password = "password"
newUser.Email = "[email protected]"
newUser.DisplayName = "display name"
everliveApp.Users().create(newUser).execute { (success:Bool, err:EverliveError?) in
    // success is true if everything was ok
}

登录

登录/登出操作由身份验证处理器提供。要登录用户,您必须传递用户名/电子邮件和密码。结果是访问令牌,您不需要使用。SDK将当前登录用户保存在当前应用程序的NSUserDefaults中,因此您不需要在每次打开应用程序时都登录用户。此外,令牌还保存在EverliveApp实例中,因此所有请求都将令牌作为授权头传递。

everliveApp.Authentication().login("username", password: "password").execute { (_:AccessToken?, err:EverliveError?) -> Void in
    // error if the credentials were invalid
}

登出

当您调用登出时,当前用户将从NSUserDefaults中删除,并且访问令牌也将从当前EverliveApp实例中删除。

everliveApp.Authentication().logout().execute { (success:Bool, err:EverliveError?) in

}

获取“我”信息

如果您想检查当前登录的用户,请使用getMe()函数。

everliveApp.Users().getMe().execute({ (currentUser: User?, err: EverliveError?) -> Void in
    if err == nil {
        // there is a user currently logged in
    }
})

自定义用户字段

您可以选择向用户的定义中添加自定义属性。相应地,您可以创建用户类的子类。

@objc(SampleUser)
class SampleUser: User {
    var Picture: String?
    var ProfilePicture: File?
}

@objc在成功设置所有属性方面非常重要。

文件

存在一个预定义的类,用于处理文件,该类对应于文件内容类型。添加的属性包括:文件名Uri内容类型数据。通常,文件用作其他内容类型的关联,并且通过使用扩展定义,您可以获取文件元数据 - 文件名、Uri和内容类型。通过使用FilesHandlerdownload函数,明确下载文件数据。

下载

everliveApp.Files().download("file-id").execute { (fileResult:File?, err: EverliveError?) in
    // all properties are setm including the Data
}

我的建议是不要使用此函数,因为它只是执行始终下载文件内容的GET请求,没有缓存并且可能较慢。您最好使用类似KingfisherAlamofireImage的库,这样只需提供文件的Uri即可为您下载文件。这在示例应用程序中得到了演示。

上传

有关使用ImagePicker完全演示上传功能的示例,请参阅示例应用程序。但是,一旦您有了文件数据,这里就是相应的代码。

let newFile = File()
newFile.Filename = "newfile.jpeg"
newFile.ContentType = "image/jpeg"
newFile.Data = some-data-here
EverliveSwiftApp.sharedInstance.Files().upload(newFile).execute { (success: Bool, err: EverliveError?) in
    // newFile properties are populated, including the Id, which is needed for relations
}   

云功能

目前没有针对云功能的特定处理器,但您可以通过使用EverliveAppconnection属性手动发出任何请求。

let likeRequest = EverliveRequest(httpMethod: "GET", url: "Functions/likeActivity?activityId=\(activity-id)")
everliveApp.connection.executeRequest(likeRequest, completionHandler: { (response:Response<AnyObject, NSError>) in
    switch response.result {
    case .Success( _):
        // succesffully called the cloud function
        // the cloud function response object is currently custom
    case .Failure(let error):
        print("Request failed with error: \(error)")
    }
})

如果需要以正文形式进行POST请求或传递自定义头,EverliveRequest对象有相应的函数。

错误处理

所有函数调用都可能返回由EverliveError对象表示的错误。它有两个属性

  • message - 这是服务器返回的错误消息
  • errorCode - 服务器返回的自定义错误代码。此代码对每个错误都是特定的,可以在Everlive的官方文档中进行检查。

如果回调中的错误对象不是 nil,这意味着操作未成功,您希望在接收到预期的结果。

测试

已经有很多测试,但包含测试的项目将会很快被添加到这个库中。

使用库