测试已测试 | ✗ |
语言语言 | SwiftSwift |
许可证 | MIT |
发布最后发布 | 2016年7月 |
SPM支持 SPM | ✗ |
由 Dimitar Dimitrov 维护。
依赖 | |
Alamofire | ~> 3.0 |
EVReflection | ~> 2.6 |
SwiftyJSON | ~> 2.3.1 |
此 SDK 旨在与 Progress 后端服务 一起使用,以便使用 Swift 语言创建本地 iOS 应用程序。
此 SDK 旨在与 Telerik 后端服务一起使用,以便使用 Swift 语言创建本地 iOS 应用程序。
第一版中涵盖的功能
这里有一个示例应用程序,演示了在真实案例场景下的所有功能。
SDK 本身没有特定要求,所以所需的所有版本都来自其中使用的库。
只需将以下内容添加到您的 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,它存储了每个项目的根本属性。
下一步是扩展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)
下面的示例将使用以下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")
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或删除所有项目来实现删除项目。通过过滤条件删除太危险了,目前不支持(只需通过传递不正确的过滤条件,您可能会丢失所有数据)
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和内容类型。通过使用FilesHandler的download函数,明确下载文件数据。
everliveApp.Files().download("file-id").execute { (fileResult:File?, err: EverliveError?) in
// all properties are setm including the Data
}
我的建议是不要使用此函数,因为它只是执行始终下载文件内容的GET请求,没有缓存并且可能较慢。您最好使用类似Kingfisher或AlamofireImage的库,这样只需提供文件的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
}
目前没有针对云功能的特定处理器,但您可以通过使用EverliveApp的connection属性手动发出任何请求。
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对象表示的错误。它有两个属性
如果回调中的错误对象不是 nil,这意味着操作未成功,您希望在接收到预期的结果。
已经有很多测试,但包含测试的项目将会很快被添加到这个库中。