在这篇文章中,我们将建立一个小型但却全面支持Core Data的应用。应用允许你创建嵌套的列表;每个列表的item都可以有子列表,这将允许你创建非常深层次的item。为了让大家完整的了解发生了什么,我们将通过使用手动创建堆栈的方式来代替Xcode中Core Data的模板。这个应用的代码放到了GitHub上。

我们将怎么建立?

首先,我们创建一个PersistentStack对象,为其提供一个Core Data模型和一个文件名,PersistentStack会返回一个managed object context。然后我们将要创建我们的Core Data模型。接着,我们将创建一个简单的table view controller来显示使用fetched results controller取回的item根目录,并且通过增加items, sub-items的导航,删除items,增加undo支持,一步一步增加交互。

设置堆栈

我们将为主队列创建一个managed object context,在旧代码中,你可能见到[[NSManagedObjectContext alloc] init]。而目前,你应该用initWithConcurrencyType: 初始化,明确你是使用基于队列的并发模型。

1
2
3
4
5
6
7
8
9
10
11
- (void)setupManagedObjectContext
{
  self.managedObjectContext = [[NSManagedObjectContext alloc]initWithConcurrencyType:NSMainQueueConcurrencyType];
  self.managedObjectContext.persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:self.managedObjectModel];
  NSError* error;
  [self.managedObjectContext.persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:self.storeURL options:nil error:&error];
  if (error) {
    NSLog(@"error: %@", error);
  }
  self.managedObjectContext.undoManager = [[NSUndoManager alloc] init];
}

检查错误是非常重要的,因为在开发过程中,这很有可能经常出错。当Core Data发现你改变了数据模型时,就会暂停操作。你也可以通过设置选项来告诉Core Data在遇到这种情况后怎么做。这个在Martin关于migrations的文章中彻底的解释了。注意,最后一行增加了一个undomanager;我们将在稍后用到。在iOS中,你需要明确的去增加一个undo manager,但是在Mac中,undo manager是默认有的。

这段代码建立了一个真正简单的Core Data堆栈:一个拥有持久化存储协调器的managed object context,拥有一个持久化存储的持久化存储协调器。更复杂的设置都是可能的;最常见的是拥有多个managed object context(每一个都在单独的队列中)。

创建一个模型

创建模型比较简单,我们只需要增加一个新文件到我们的项目,在Core Data选项中选择Data Model template。这个模型文件将会被编译成后缀名为.momd类型的文件,我们将会在运行时加载这个文件来为持久化存储创建需要用的NSManagedObjectModel,模型的源码是简单的XML,根据我们的经验,当你check到代码控制器中时,不会有任何合并冲突。如果你愿意,你还可以在代码中创建一个managed object model。

一旦你创建了模型,你就可以增加Item实体,这个实体有两个属性:字符串类型的title和integer类型的order。然后,增加两个关系:parent,关联一个item到它的父item,children,一个一对多的子关系。设置它们为彼此相反的关系,也就是说,你设置a的parent为b,那么b就会自动有一个children为a。

通常,你甚至可以完全抛开order属性,使用排序好的的关系。然而,他们并不能很好的和fetched results controllers(后面会用到)集成在一起工作。我们要么需要重新实现fetched results controller的一部分,要么重新实现排序,通常我们都会选择后者。

现在,从菜单中选择Editor > NSManagedObject,创建一个绑定到NSManagedObject实体的子类,这将会创建两个文件:Item.h和Item.m。在头文件中,会有一个额外的类,我们需要将其删除(这是遗留原因导致的)。

创建一个Store类

对于我们的模型,我们将创建一个根节点作为我们item树的开始。我们需要一个地方来创建这个根节点,并且方便以后容易找到。因此,我们可以通过创建一个简单的存储类来达到这个目的。存储类有一个managed object context,还有一个rootItem方法。在app delegate中,我们将会在运行时查找这个root item,并且传给了root view controller。作为一种优化,为了查找这个item变得更快,你可以将item对象的id存储到user default中:

1
2
3
4
5
6
7
8
9
10
- (Item*)rootItem {
  NSFetchRequest* request = [NSFetchRequest fetchRequestWithEntityName:@"Item"];
  request.predicate = [NSPredicate predicateWithFormat:@"parent = %@"nil];
  NSArray* objects = [self.managedObjectContext executeFetchRequest:request error:NULL];
  Item* rootItem = [objects lastObject];
  if (rootItem == nil) {
    rootItem = [Item insertItemWithTitle:nil parent:nil inManagedObjectContext:self.managedObjectContext];
  }
  return rootItem;
}

增加一个item大多数情况下都是简单的。然而,我们需要设置order属性比任何已经存在的item及其父类更大。我们将会设置第一个子节点的order为0,随后每一个子节点都会增加1.我们在Item类中创建一个自定义的方法来存放逻辑:

1
2
3
4
5
6
7
8
+ (instancetype)insertItemWithTitle:(NSString*)title parent:(Item*)parent inManagedObjectContext:(NSManagedObjectContext *)managedObjectContext {
  NSUInteger order = parent.numberOfChildren;
  Item* item = [NSEntityDescription insertNewObjectForEntityForName:self.entityName inManagedObjectContext:managedObjectContext];
  item.title = title;
  item.parent = parent;
  item.order = @(order);
  return item;
}

获得子节点数量的方法很简单:

1
2
3
- (NSUInteger)numberOfChildren {
  return self.children.count;
}

为了支持自动更新我们的table view,我们需要使用fetched results controller。Fetched results controller是一个可以管理取出大量item请求的对象,同时对table view来说,也是一个完美的Core Data指南,在下一节中我们将会看到:

1
2
3
4
5
- (NSFetchedResultsController*)childrenFetchedResultsController {
  NSFetchRequest* request = [NSFetchRequest fetchRequestWithEntityName:[self.class entityName]];  request.predicate = [NSPredicate predicateWithFormat:@"parent = %@",self];
  request.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"order" ascending:YES]];
  return [[NSFetchedResultsController alloc] initWithFetchRequest:request managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:nil];
}

增加一个支持Table-View的Fetched Results Controller

我们下一步是创建一个root view controller:一个从NSFetchedResultsController读取数据的table view。Fetched results controller管理你的读取请求,如果你为它分配一个delegate,那么在managed object context中发生的任何改变都会通知你。实际上,这意味着如果你实现了delegate方法,当数据模型中发生相关变化时,你可以自动更新你的table view。比如,你在后台线程同步,并且把变化存储到数据库中,那么你的table view将会自动更新。

创建Table View的Data Source

lighter view controllers这篇文章中,我们演示了怎么从table view中分离出data source。这里,我们将会用同样的方法创建一个fetched results controller。我们创建一个分离出的FetchedResultsControllerDataSource

类,它扮演了table view的data source,通过监听fetched results controller,自动更新table view。

我们初始化一个table view对象,初始化方法如下:

1
2
3
4
5
6
7
8
- (id)initWithTableView:(UITableView*)tableView {
  self = [super init];
  if (self) {
    self.tableView = tableView;
    self.tableView.dataSource = self;
  }
  return self;
}

当我们设置fetch results controller时,我们需要自己设置delegate,并且自己执行fetch的初始化。performFetch:方法经常容易被忘了调用,那么你将得不到结果(并且不会出错):

1
2
3
4
5
- (void)setFetchedResultsController:(NSFetchedResultsController*)fetchedResultsController{
  _fetchedResultsController = fetchedResultsController;
  fetchedResultsController.delegate = self;
  [fetchedResultsController performFetch:NULL];
}

因为我们的类实现了UITableViewDataSource协议,我们需要实现相关的方法。在这两个方法中,我们只需要向fetched results controller请求需要的信息:

1
2
3
4
5
6
7
- (NSInteger)numberOfSectionsInTableView:(UITableView*)tableView {
  return self.fetchedResultsController.sections.count;
}
- (NSInteger)tableView:(UITableView*)tableView numberOfRowsInSection:(NSInteger)sectionIndex {
  id<NSFetchedResultsSectionInfo> section = self.fetchedResultsController.sections[sectionIndex];
  return section.numberOfObjects;
}

然而,当我们需要创建cell的时候,只需要一些简单的步骤:向fetched results controller请求正确的对象,从table view出列一个cell,然后告诉delegate用相应的对象配置这个cell。作为view controller,只会关心用模型对象更新cell:

1
2
3
4
5
6
- (UITableViewCell*)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath{
  id object = [self.fetchedResultsController objectAtIndexPath:indexPath];
  id cell = [tableView dequeueReusableCellWithIdentifier:self.reuseIdentifier forIndexPath:indexPath];
  [self.delegate configureCell:cell withObject:object];
  return cell;
}

创建table view controller

现在,我们可以创建一个view controller,使用刚刚创建的类显示item。在示例程序中,我们创建一个Storyboard,并且增加一个拥有table view controller的navigation controller。这会自动设置view controller作为数据源,而这不是我们想要的效果。因此,在我们的viewDidLoad中,我们做下面的操作:

1
2
fetchedResultsControllerDataSource = [[FetchedResultsControllerDataSource alloc] initWithTableView:self.tableView];
self.fetchedResultsControllerDataSource.fetchedResultsController = self.parent.childrenFetchedResultsController;     fetchedResultsControllerDataSource.delegate = self; fetchedResultsControllerDataSource.reuseIdentifier = @"Cell";

在初始化fetched results controller data source时,table view的数据源可以被设置。reuse标识符匹配在Storyboard中相对应的对象。现在,我们需要实现delegate方法:

1
2
3
4
5
- (void)configureCell:(id)theCell withObject:(id)object {
  UITableViewCell* cell = theCell;
  Item* item = object;
  cell.textLabel.text = item.title;
}

当然,除了设置text的label外,你还可以做更多的事情,但是你应该明白了要领。现在我们已经为显示数据准备好了相当多的事情,但是却仍然没有增加数据的方法,这看起来有点空。

增加互动

我们将会增加两种和数据交互的方法。首先,我们需要实现增加items。然后我们需要实现fetched results controller的delegate方法去更新table view,并且增加删除和undo支持。

增加items

为了增加items,我们借鉴 Clear 的交互设计,这是我认为最漂亮的程序之一。我们增加一个text field作为table view的头,并修改table view的content inset,确保它默认保持隐藏,正如Joe在 scroll view article 这篇文章中解释一样。像往常一样,所有的代码都在github上,这里是插入item相关的代码,在textFieldShouldReturn中:

1
2
3
[Item insertItemWithTitle:title parent:self.parent inManagedObjectContext:self.parent.managedObjectContext];
textField.text = @"";
[textField resignFirstResponder];

监听改变

下一步是确保table view会为新创建的item插入一行。有好几种方法可以做到,但是我们将会使用fetched results controller的代理方法:

1
2
3
4
5
- (void)controller:(NSFetchedResultsController*)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath*)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath*)newIndexPath {
  if (type == NSFetchedResultsChangeInsert) {
    [self.tableView insertRowsAtIndexPaths:@[newIndexPath]withRowAnimation:UITableViewRowAnimationAutomatic];
  }
}

fetched results controller也会为删除、改变和移动调用一些方法(我们将在稍后实现)。如果你一次有很多改变,你可以多实现两个方法,那么table view将会同步的展现那些改变。对于单个item的插入和删除,这并不会有任何不同,但是如果你选择实现同时同步,那么将会变得更漂亮:

1
2
3
4
5
6
- (void)controllerWillChangeContent:(NSFetchedResultsController*)controller {
  [self.tableView beginUpdates];
}
- (void)controllerDidChangeContent:(NSFetchedResultsController*)controller {
  [self.tableView endUpdates];
}

使用Collection View

值得注意的是,fetched results controllers并非只能用于table view;你可以将它只用在任何view中。因为,他们都是index-path-based,同时它也能和collection views很好的一起工作,但是collection view并没有beginUpdates和endUpdates方法,却有一个performBatchUpdates。为了处理这种情况,你可以收集你得到的所有更新,然后在controllerDidChangeContent中,用block执行所有的更新。Ash Furrow写了一个关于 如何做的例子

实现你自己的Fetched Results Controller

你不必使用NSFetchedResultsController。实际上,在很多情况下,为你的程序创建一个类似的类将显得更有意义。你可以做的是订阅NSManagedObjectContextObjectsDidChangeNotification(即注册监听通知)。然后你就可以得到一个notification,userInfo字典将会包含改变对象,插入对象,删除对象的列表,然后你可以按你喜欢的方式执行这些操作。

现在我们可以增加并且列出itmes了,现在我们需要确定能够创建sub-lists.在Storyboard中,你可以通过拖拽一个cell到view controller中来创建一个segue。这需要为segue指定一个名字,这样,如果一个view controller中有多个segues的话,我们就可以将其区分开了。

我处理segues的模式看起来像这样:首先,你尝试识别出这个segue,对于每一个segue,你为它的目标view controller单独写一个方法:

1
2
3
4
5
6
7
8
9
10
- (void)prepareForSegue:(UIStoryboardSegue*)segue sender:(id)sender {
  [super prepareForSegue:segue sender:sender];
  if ([segue.identifier isEqualToString:selectItemSegue]) {
    [self presentSubItemViewController:segue.destinationViewController];
  }
}
- (void)presentSubItemViewController:(ItemViewController*)subItemViewController {
  Item* item = [self.fetchedResultsControllerDataSource selectedItem];
  subItemViewController.parent = item;
}

子view controller需要唯一的东西就是item。通过item,也可以得到managed object context。我们从data source中得到选中的item(通过table view选中item的index值,从fetched results controller中取出正确的item),就这么简单。

很不幸的是,在app delegate中,将managed object context作为一个属性,然后总是在任何地方访问它,这是很常见的一种模式,但这是一个坏主意,如果你想要为你view controller中的一部分使用一个不同的managed object context,这时,将很难重构,此外,你的代码将变得很难测试。

现在,尝试在sub-list中增加一个item,你很有可能得到一个crash。这是因为我们现在有两个fetched results controllers,一个是topmost view controller,还有一个是root view controller。后者尝试去更新它的table view,而它的table view是离屏的(offscreen),就这样所有的操作都crash了。解决方案是告诉我们的data source停止监听fetched results controller的代理方法:

1
2
3
4
5
6
7
8
- (void)viewWillAppear:(BOOL)animated {
  [super viewWillAppear:animated];
  self.fetchedResultsControllerDataSource.paused = NO;
}
- (void)viewWillDisappear:(BOOL)animated {
  [super viewWillDisappear:animated];
  self.fetchedResultsControllerDataSource.paused = YES;
}

一种方法就是在data source中设置fetched results controller的代理为nil,这样就再也不会收到更新通知了。当我们离开paused状态时,还需要加上去:

1
2
3
4
5
6
7
8
9
10
- (void)setPaused:(BOOL)paused {
  _paused = paused;
  if (paused) {
    self.fetchedResultsController.delegate = nil;
  else {
    self.fetchedResultsController.delegate = self;
    [self.fetchedResultsController performFetch:NULL];
    [self.tableView reloadData];
  }
}

这样performFetch就会确保你的data source保持最新的。当然,更好的实现方法并不是设置代理为nil,而是保证每一个改变都是在paused状态下进行的,相应的,离开paused状态后,更新table view。

删除

为了支持删除,我们需要花费几步操作。首先,我们需要确信我们的table view支持删除,第二,我们需要从core data中删除对象,并且保证我们的排序是正确的。

为了支持滑动删除,我们需要在data source中实现两个方法:

1
2
3
4
5
6
7
8
9
- (BOOL)tableView:(UITableView*)tableView  canEditRowAtIndexPath:(NSIndexPath*)indexPath  {
  return YES;
}
- (void)tableView:(UITableView *)tableView   commitEditingStyle:(UITableViewCellEditingStyle)editingStyle   forRowAtIndexPath:(NSIndexPath *)indexPath {
  if (editingStyle == UITableViewCellEditingStyleDelete) {
    id object = [self.fetchedResultsController objectAtIndexPath:indexPath];
    [self.delegate deleteObject:object];
  }
}

我们需要通知代理删除对象,而不是立即删除。这样,我们不需要将store object分配给data source(data source在整个项目中都必须可重用),并且保持自定义操作的灵活性。view controller只需在managed object context中简单的调用deleteObject:。

然而,还有两个重要的问题需要被解决:我们怎么处理被删除item的子item,怎么强制我们的order变化?幸运的是,传播删除是很简单的:在我们的数据模型中我们可以选择Cascade作为子关系的删除规则。

为了强制我们的order变化,我们可以重写prepareForDeletion方法,用更高一层的order更新所有兄弟姐妹。

1
2
3
4
5
6
7
8
- (void)prepareForDeletion {
  NSSet* siblings = self.parent.children;
  NSPredicate* predicate = [NSPredicate predicateWithFormat:@"order > %@"self.order];
  NSSet* siblingsAfterSelf = [siblings filteredSetUsingPredicate:predicate];
  [siblingsAfterSelf enumerateObjectsUsingBlock:^(Item* sibling, BOOL* stop){
    sibling.order = @(sibling.order.integerValue - 1);
  }];
}

现在我们几乎快完成了。我们可以和table view的cell交互,并且可以删除模型对象。最后一步是实现一旦模型对象被删除后,删除table view cell的代码。在我们data sources的控制器中:didChangeObject:….方法,我们增加另一个if分句:

1
2
3
else if (type == NSFetchedResultsChangeDelete) {
 
[self.tableView deleteRowsAtIndexPaths:@[indexPath]withRowAnimation:UITableViewRowAnimationAutomatic]; }

增加Undo支持

Core Data优点之一就是集成了undo支持。我们将为undo增加抖动的功能,第一步就是告诉程序,我们可以这样做:

1
application.applicationSupportsShakeToEdit = YES;

现在,这个功能可以被任何摇动触发,程序将会为它的undo manager请求第一响应器,并且执行一次undo操作。在上个月的文章中,我们了解了,一个view controller也在响应链中(responder chain),这也正是我们将要使用的。在我们的view controller中,我们重写来自UIResponder类中的两个方法:

1
2
3
4
5
6
- (BOOL)canBecomeFirstResponder {
  return YES;
}
- (NSUndoManager*)undoManager {
  return self.managedObjectContext.undoManager;
}

现在,当一个抖动发生时,managed object context的undo 管理器将会得到一个undo消息,并且撤销最后一次改变。记住,在iOS中,managed object context默认并没有一个undo manager,(Mac中,新建的managed object context默认是有的),所以我们需要在持久化堆栈中设置:

1
self.managedObjectContext.undoManager = [[NSUndoManager alloc] init];

基本上就是这样了。现在,当你抖动时,你将得到iOS默认有两个按钮的提醒框:一个是undo按钮,一个cancel按钮。Core Data一个非常好的特性是将改变自动分组。比如,addItem:父节点将会记录这个操作,作为一个undo处理。关于删除,也是一样。

为了让用户管理undo操作更容易一些,我们可以给操作命名,并且将textFieldShouldReturn:的第一行修改成这样:

1
2
3
4
NSString* title = textField.text;
NSString* actionName = [NSString stringWithFormat: NSLocalizedString(@"add item \"%@\""@"Undo action name of add item"), title];
[self.undoManager setActionName:actionName];
[self.store addItem:title parent:nil];

现在,当用户抖动时,除了普通的“Undo”标签外,Ta将得到更多的上下文环境。

编辑

编辑目前在示例程序中并不支持,但是这只是一个改变对象属性的问题。比如,改变一个item的title,只需要设置title属性就好了。改变foo item的parent,只需要设置parent属性为一个新值bar,所有的东西都将得到更新:bar现在有一个children为foo,因为我们使用fetched results controllers,用户界面同样也会自动更新。

重新排序

重新排序cell,在现有程序中也是不可行的,但是这实现起来很简单。但是,还有一个需要注意的地方:如果你允许用户重新排序,你将需要在model中更新order属性,并且从fetched results controller得到一个delegate call(你需要忽略这个调用,因为cell已经被移动了)。这在NSFetchedResultsControllerDelegate documentation中解释了。

保存

保存和在managed object context中调用保存一样简单。因为我们并不直接访问managed object context,而是在store中保存。唯一的困难是什么时候去保存。Apple的示例代码在applicationWillTerminate:中执行这个操作,但是这取决于你使用情况,这也有可能在applicationDidEnterBackground:中,甚至当你程序运行时调用。

讨论

在写这篇文章和示例程序时,我犯了一个处级错误:我选择了一个不为空的根item,取而代之,让所有用户创建的item在根目录级别有一个指针为空的父item。这将造成很多问题:因为view controller中的父item可能是nil,我们需要将store传给每一个子view controller。同样的,强制order重新排序也非常困难,因为我们需要查找出所有item的兄弟姐妹,这样会迫使Core Data到磁盘上读取数据。不幸的是,当写这些代码时,问题并没有立刻弄明白,一些问题只是在写测试时才变得清晰。当我重新写代码的时候,我知道了将Store类中大部分代码移到Item类中,就这样,事情变得清楚多了。

原文链接:http://answerhuang.duapp.com/index.php/2013/09/17/一个完整的core-data应用/

一个完整的Core Data应用的更多相关文章

  1. ios开发:Core Data概述

    Core Data 概述 2005年的四月份,Apple 发布了 OS X 10.4,在这个版本中 Core Data 框架发布了.Core Data本身既不是数据库也不是数据库访问框架.相反,Cor ...

  2. 【转】深受开发者喜爱的10大Core Data工具和开源库

    http://www.cocoachina.com/ios/20150902/13304.html 在iOS和OSX应用程序中存储和查询数据,Core Data是一个很好的选择.它不仅可以减少内存使用 ...

  3. 《驾驭Core Data》 第三章 数据建模

    本文由海水的味道编译整理,请勿转载,请勿用于商业用途.    当前版本号:0.1.2 第三章数据建模 Core Data栈配置好之后,接下来的工作就是设计对象图,在Core Data框架中,对象图被表 ...

  4. 《驾驭Core Data》 第二章 Core Data入门

    本文由海水的味道编译整理,请勿转载,请勿用于商业用途.    当前版本号:0.4.0 第二章 Core Data入门 本章将讲解Core Data框架中涉及的基本概念,以及一个简单的Core Data ...

  5. iOS开发中的4种数据持久化方式【二、数据库 SQLite3、Core Data 的运用】

                   在上文,我们介绍了ios开发中的其中2种数据持久化方式:属性列表.归档解档.本节将继续介绍另外2种iOS持久化数据的方法:数据库 SQLite3.Core Data 的运 ...

  6. Core Data(数据持久化)

    Core Data可能是OS X和iOS中最容易被误解的框架之一了.为了帮助大家理解,我们将快速研究Core Data,来看一下它是关于什么的.为了正确使用Core Data, 有必要理解其概念.几乎 ...

  7. 手把手教你从Core Data迁移到Realm

    来源:一缕殇流化隐半边冰霜 (@halfrost ) 链接:http://www.jianshu.com/p/d79b2b1bfa72 前言 看了这篇文章的标题,也许有些人还不知道Realm是什么,那 ...

  8. 四种数据持久化方式(下) :SQLite3 和 Core Data

    在上文,我们介绍了iOS开发中的其中2种数据持久化方式:属性列表.归档解档. 本节将继续介绍另外2种iOS持久化数据的方法:数据库 SQLite3.Core Data 的运用: 在本节,将通过对4个文 ...

  9. 手把手教你从 Core Data 迁移到 Realm

    前言 看了这篇文章的标题,也许有些人还不知道Realm是什么,那么我先简单介绍一下这个新生的数据库.号称是用来替代SQLite 和 Core Data的.Realm有以下优点: 使用方便 Realm并 ...

随机推荐

  1. leetcode 【 Search in Rotated Sorted Array II 】python 实现

    题目: 与上一道题几乎相同:不同之处在于array中允许有重复元素:但题目要求也简单了,只要返回true or false http://www.cnblogs.com/xbf9xbf/p/42545 ...

  2. 使用 Spirit 类在 XNA 中创建游戏中的基本单位精灵(十三)

    平方已经开发了一些 Windows Phone 上的一些游戏,算不上什么技术大牛.在这里分享一下经验,仅为了和各位朋友交流经验.平方会逐步将自己编写的类上传到托管项目中,没有什么好名字,就叫 WPXN ...

  3. 动态规划--找零钱 coin change

    来自http://www.geeksforgeeks.org/dynamic-programming-set-7-coin-change/ 对于整数N,找出N的所有零钱的表示.零钱可以用S={s1,s ...

  4. Spring MVC控制器方法参数类型

    HttpServletRequest Spring会自动将 Servlet API 作为参数传过来 HttpServletResponse InputStream 相当于request.getInpu ...

  5. The following signatures couldn't be verified because the public key is not available 解决方法

    今天试图把 deepin 的软件源加到我到 Ubuntu 16.04 中去. 在 deepin wiki 上看到一个教程. 在 /etc/apt/sources.list 中加上 deepin 的软件 ...

  6. BZOJ3998 [TJOI2015]弦论 【后缀自动机】

    题目 对于一个给定长度为N的字符串,求它的第K小子串是什么. 输入格式 第一行是一个仅由小写英文字母构成的字符串S 第二行为两个整数T和K,T为0则表示不同位置的相同子串算作一个.T=1则表示不同位置 ...

  7. ALICTF2014 EvilAPK4脱壳分析

    相关文件可以在下面链接中下载: http://pan.baidu.com/s/1sjpvFy9 1 简述 该apk使用libmobisec.so函数实现对dex的解密还原.真正的dex为assets目 ...

  8. sql中的like和正则的区别

    like,模糊查询,更多的是用于匹配已知的字符,比如查询该字段含有1的记录,like ‘%1%’:但是如果要匹配不确定的,但是一个系列的字符,比如数字,最好用regexpselect * from t ...

  9. 当时用vuex的时候,使用...对象展开扩展符报错的解决办法

    出现这种问题的主要原因是当前的babel不支持...对象展开扩展符,只需要安装一个插件然后再在.babelrc当中进行下配置就好了 npm i babel-plugin-transform-objec ...

  10. tomcat 日志输出

    1.找到tomcat安装目录:cd tomcat/logs 2.tail -f catalina.out 3.ctrl+c 退出