非理性繁荣

史诗人物,第3部分:浏览,缓存,指示

8月25日,2008。申请下pythonos x可可pyobjc

欢迎来到PyObjC系列教程的第三部分,它的目的是全面介绍OS X Leopard上的pyobjc和cocoa。在处理Cocoa Bindings和网页.py前面的部分,这次我们将讨论应用程序开发中的一些常见任务:从磁盘读写,处理双击,并使用一个NSProgressIndicator提高用户体验。

如果你到目前为止还没有跟上这个项目,你可以在此处下载项目的当前状态

双击可在浏览器中查看

我们要做的第一件事是在浏览器中双击NSTableView。来完成,我们需要iboutlet到tableview和数组控制器。我们需要tableview调用它setDoubleAction_setTarget_方法,我们需要数组控制器来计算当前选中的行。

但是,等等,你可能会问,这个MWController已将搜索结果存储在其结果字段。如果我们只是问TableView它突出显示了什么行索引,然后我们不需要查询数组控制器!不幸的是,我会带着忧郁的微笑回答——直觉恰恰相反——这在我们的情况下是行不通的。如果你点击表格视图中的一个列标题,你可以按字母顺序和反字母顺序重新排列列,但是数据结果field幸运地没有意识到这些变化。相反,它们都由数组控制器处理,因此只有数组控制器知道数据在哪里。

向代码前进!

在顶部mw控制器.py浏览器,所以导入看起来是这样的:

进口objc金属网浏览器基础进口*

现在我们要创建一个方法MWController这将为当前选中行的条目打开一个web浏览器。

defOpenX(自我发件人):selectedObjs=自我arrayController所选对象()如果伦恩(selectedObjs==NSLog(你没有选定行!”返回=selectedObjs(]如果has_key(“ID”id==没有NSLog(“Row没有id!”返回url=u“http://www.freebase.com/view% s%id浏览器开放(url

虽然这个方法看起来有点长,这主要是因为处理两个错误情况的代码:未选择行时调用方法,所选行不完整。


如果你没有做太多的可可开发,可能感觉这些方法名是凭空而来的,但实际上很容易算出来。在Xcode中转到帮助菜单,然后去文档(转换选项Apple-?)并在右侧的搜索字段中进行搜索。按你使用的类搜索总是最容易的,在这里NSArrayControllerNSTableView

有了可靠的苹果开发者文档,大多数时候,你甚至不需要互联网连接来开发。除非您在Xcode文档搜索中单击某个项目,并且发现它在本地不存在…这可能会在你喜欢的时候发生。


现在我们只需要设置表视图来调用OpenX当点击的两倍。在可可中发育时,一次性设置的最佳时间是在类中。awakeFromNib方法。awakeFromNib在实例化NIB中序列化的对象时调用。因为我们有一个MWController在我们的MainMenu.xib文件(这是我们应用程序的主nib,因此在启动时加载),该实例将具有awakeFromNib在应用程序启动时调用的函数。

awakeFromNib方法MWController将如下所示:

defawakeFromNib(自我):如果自我表格视图自我表格视图setTarget_(自我自我表格视图setDoubleAction_(“开放:“

你不必检查那个self.tableView已经初始化(几乎可以肯定已经初始化),但正如我叔叔常说的:乐观地生活,但代码要谨慎1

现在保存所有东西构建和去XCode中的应用程序。一旦发射,搜索内容并双击其中一行。它应该在默认的Web浏览器中打开文章。

想象一下在metawindow后面打开的Web浏览器。

很简单,嗯?也许太容易了,你想知道我为什么要在这里详述吗?啊,好问题。本教程的这一部分试图复制真实的应用程序开发周期:

  1. 有个主意。
  2. 将其添加到应用程序中。
  3. 重复。

希望让它对双击做出响应——以及接下来的两个相当快速的示例——能够让您了解典型的PyobJC工作流(与上面的三个步骤相同,但是第1.5步:检查Apple文档,找出您需要的方法和类)。

将结果缓存到磁盘

接下来,我们将为结果添加一个简单的缓存层网页.py。它有两个相当简单的案例,我们现在要考虑的是:处理带有缓存结果的搜索,以及处理具有未缓存结果的搜索。

  1. 处理缓存的结果。

    单击搜索按钮时,我们希望检查缓存项的清单(以搜索项为键的字典和文件名和时间戳的2元组)。

    对于缓存结果,清单将包含密钥,因此我们将返回一个2元组。首先,我们将验证时间戳不超过一天。如果它那么老我们将丢弃缓存值,并继续执行,就像没有缓存值一样。

    否则,如果它不是比一天还老,然后更新。的值MWController结果包含缓存数据的字段,我们将使用2元组中的文件名从磁盘加载它。

  2. 处理未处理的结果。

    当我们搜索未缓存的结果时,我们首先确认它不在缓存中,然后用网页.py,然后缓存新检索到的结果。之后,我们将其视为缓存的结果。

为了支持这些情况,我们需要创建一个持久缓存清单,该清单在程序关闭时保存到磁盘,并从程序打开时从磁盘加载。你还记得吗应用程序已完成启动_应用程序将终止_方法存根MetaWindowAppDelegate?好吧,我们最终会用到这些。


一个简短的旁注PyObjC中的序列化选项。

Python的泡菜模块可能是您遇到的最简单的数据序列化。肯定的是,您可以通过实现NSCoder类方法,但要让它工作,代码要多得多。

然而,泡菜不知道如何序列化子类对象NSObject。这意味着它不能序列化任何可可或目标C类。这很不方便,但你可以经常绕过它。

最简单的修复方法是将对象转换为原生Python类。例如,如果你处理的是称为unaryoperations,只需将其转换为python dict并将其序列化(然后将其转换回可变异的当您反序列化它时)。

最后,不过,如果您发现自己使用的是继承自一个特别大的或复杂的类NSObject,您可能想继续并简单地使用NSCoder。最终,你的棘手的解决方案会变得足够复杂,以便于使用。NSCoder不管怎样,所以你最好给自己留点时间。

请记住,对于持久性数据还有其他解决方案,例如核心数据数据库它提供了序列化的非传统替代方案。


打开MetaWindowAppDelegate.py在XCode中。在顶部添加导入for泡菜操作系统

进口泡菜操作系统基础进口*AppKit进口*

现在我们要换掉存根了应用程序已完成启动_用这个方法:

def应用程序已完成启动_(自我发件人):试一试路径=自我路径文件(“cache.serialized”文件=开放(路径“R”自我缓存=泡菜负载(文件文件关闭()除了IOError自我缓存={}

现在什么时候MetaWindow它将尝试加载已序列化cache.serialized文件从MetaWindow目录~/库/应用程序支持/文件夹中。如果它存在,它将使用该文件的内容作为缓存,否则它将使用一个空字典作为缓存。

我们还需要确保它能保存下来self.cache当应用程序关闭时。为此,我们修改了应用程序将终止_方法。

def应用程序将终止_(自我发件人):路径=自我路径文件(“cache.serialized”文件=开放(路径“W”泡菜转储(自我缓存文件文件关闭()

现在,我们的缓存将在应用程序启动和关闭期间保持。保存并关闭MetaWindowAppDelegate.py,开放mw控制器.py

我们要做的第一件事MWController,是创建用于从应用程序委托检索缓存的属性。发射时的事情会以奇怪的顺序发生,因此,等到需要缓存从应用程序委托中检索它时再进行请求,而不是在应用程序启动时进行请求,这样做更安全。

在…的前面mw控制器.py我们要增加进口泡菜MD5日期时间,而且还想从AppKit,它看起来是这样的:

进口objc金属网浏览器泡菜MD5日期时间AppKit进口*基础进口*

添加字段_cacheMWController与价值没有看起来是这样的:

MWController(NSObject):表格视图=objcIBOutlet()文本框=objcIBOutlet()arrayController=objcIBOutlet()结果=[]_cache=没有

然后,我们需要通过添加此代码来创建属性MWController

def获取缓存(自我):如果_cache没有_cache=净面积代表()缓存返回_cache缓存=财产(获取缓存没有没有“搜索缓存。”

这是我们第一次使用净面积,这是一条可可捷径并引用当前应用程序。它相当于nsApplication.SharedApplication()。在python或[NSApplication sharedApplication];在目标C中。

我们刚刚编写的代码段创建了一个名为缓存。Python属性是使用访问器和变量的方便语法糖,在这里使用一个可以让我们从应用委托中惰性地检索缓存,不会打乱使用。


关于目标C中的变元和访问器的简短旁注。当您查看(或创建)Objective C代码时,您将会遇到比您在Python代码生命周期中所看到的更多的变量和访问器。这可能是一个非常方便的模式,特别是在促进惰性初始化或动态值。

除了图案的一般性之外,它也经常出现在目标C中,因为一个对象可以有一个同名的字段和方法(即字段和方法存在于不同的名称空间中)。这是受Smalltalk启发的方法调用语法的一个副作用,它可以让你清楚地知道你是在访问一个字段还是一个方法:

(myObject名称];myObject名称;

不幸的是,随着目标C 2.0的出现,这种区别有些模糊。使用ObjC 2.0,苹果以与python非常相似的方式添加了属性,现在已经无法确定代码是否myObject.name是对字段的引用,或者是对访问器/变量对的伪装。

新语法的支持者认为,如果正确地封装了类,而不依赖于实现细节,那么这应该是一个无关紧要的区别,但是老卫兵仍然为失去优雅而哀悼,直到今天,他们还用鲜花装饰着Objective C 1.0的坟墓。

或者至少是博客文章。


现在让我们创建一个从缓存中检索对象的方法。如果缓存的数据太旧,它应该假装没有任何存储的数据,以避免数据过时。

defgetCachedSearch(自我searchString):如果自我缓存has_key(searchString):文件名时间戳=自我缓存(searchString]年龄=日期时间日期时间现在()-时间戳如果年龄>日期时间时间增量(=1):返回没有filepath=净面积代表()路径文件(文件名文件=开放(filepath“R”数据=泡菜负载(文件文件关闭()返回数据返回没有

的功能日期时间模块验证年龄非常简单。现在我们需要创建一个方法来缓存新结果。

def缓存结果搜索(自我searchString结果):文件名=U”% s.cached”%MD5MD5(searchStringhexdigest()(十二:]filepath=净面积代表()路径文件(文件名文件=开放(filepath“W”泡菜转储(结果文件文件关闭()自我缓存(searchString]=(文件名日期时间日期时间现在())

注意,我们将结果保存到单独的文件中,然后将该文件的名称(以及创建时)存储在缓存清单中。我们可以简化它,直接把结果存储在缓存中,但是,我们需要将所有结果始终存储在内存中。

修改的最后一步MetaWindow是更新搜索_方法。

@objcIBActiondef搜索_(自我发件人):search_value=自我文本框stringValue()缓存=自我getCachedSearch(search_value如果缓存没有缓存=金属网搜索(search_value自我缓存结果搜索(search_value缓存自我结果=(字典带字典的字典_(xx缓存]自我arrayControllerrearrangeObjects()

记住,我们不能对Objective C对象使用pickle,所以我们小心地缓存Python数据,然后把它转换成字典对象,然后在tableview中使用它。

现在,在完成缓存之前,我们还有最后一点要处理。

事实上,缓存的文件永远不会被删除,即使我们不想让他们呆的时间超过一天。让我们回到MetaWindowAppDelegate.py并改进应用程序将终止_方法。

首先添加导入for日期时间在文件的开头。

进口操作系统泡菜日期时间基础进口*AppKit进口*

然后去应用程序将终止_方法。我们要剔除所有超过一天的结果。摆弄一下解释器,我们很快就能找到一个解决方案。

>>>今天=日期时间日期时间现在()>>>昨天=今天-日期时间时间增量(=1>>>今天<昨天>>>two_days_ago=日期时间日期时间现在()-日期时间时间增量(=2>>>two_days_ago<昨天真正的

所以我们会改变应用程序将终止_看起来像这样:

def应用程序将终止_(自我发件人):昨天=日期时间日期时间现在()-日期时间时间增量(=1钥匙自我缓存文件名createdTime=自我缓存(钥匙]如果createdTime<昨天filepath=自我路径文件(文件名操作系统删除(filepath自我缓存(钥匙]路径=自我路径文件(“cache.serialized”文件=开放(路径“W”泡菜转储(自我缓存文件文件关闭()

现在我们自己打扫卫生MetaWindow是个好公民。杰出的。

动画一个NSProgressIndicator搜索时

在这一节中,我们要做的最后一件事就是让搜索体验更灵敏。事实上,当你搜索未搜索到的结果时,可能需要几秒钟才能完成,搜索按钮一直处于关闭状态,这让它看起来有点像程序冻结了。

相反,如果有迹象表明确实正在取得进展,那就好得多了。

继续添加另一个IBOutletMWController,这个叫指示器

MWController(NSObject):表格视图=objcIBOutlet()文本框=objcIBOutlet()arrayController=objcIBOutlet()指示器=objcIBOutlet()结果=[]_cache=没有

现在打开MainMenu.xib在InterfaceBuilder。从图书馆窗口查找循环进度指示器然后把它加到窗口(窗口)窗口。调整textfield的大小,以便progress idicator可以放在左上角。

在InterfaceBuilder中添加一个NSProgressIdicator。

然后按一下控制器对象主菜单.xib(英语)窗口,按住控制,和阻力控制器指向我们刚刚创建的新循环进度指示器。释放然后选择指示器从下拉菜单连接IBOutlet。

最后,保存并返回MWController班级。现在我们要修改搜索_方法。具体地说,我们会让它在我们开始从Metaweb检索数据之前启动指示器的动画,并在检索到数据时停止它。

@objcIBActiondef搜索_(自我发件人):search_value=自我文本框stringValue()缓存=自我getCachedSearch(search_value如果缓存没有自我指示器startAnimation_(自我缓存=金属网搜索(search_value自我缓存结果搜索(search_value缓存自我指示器stopAnimation_(自我自我结果=(字典带字典的字典_(xx缓存]自我arrayControllerrearrangeObjects()

保存,构建并运行应用程序。它现在应该在搜索的时候动画指示器,用户将不再被迫考虑应用程序是否已冻结。

总结第三部分

你可以下载项目的当前zip,或者从在GitHub库

至此,我们已经完成了本教程的第三部分。第四部分(也是最后一部分)将介绍一些拖放功能的实现,以及对其他资源提出一些建议,以便继续使用和享受pyobjc。

第四段现已完成,你可以继续。


  1. 不一定是真实的故事。