[转载]25个增强iOS应用程序性能的提示和技巧 - easonoutlook - 博客园

mikel阅读(1013)

[转载]25个增强iOS应用程序性能的提示和技巧 – easonoutlook – 博客园.

在开发iOS应用程序时,让程序具有良好的性能是非常关键的。这也是用户所期望的,如果你的程序运行迟钝或缓慢,会招致用户的差评。然而由于iOS设备的局限性,有时候要想获得良好的性能,是很困难的。在开发过程中,有许多事项需要记住,并且关于性能影响很容易就忘记。

本文收集了25个关于可以提升程序性能的提示和技巧,把性能优化技巧分为3个不同的等级:初级、中级和高级

初级

在开发过程中,下面这些初级技巧需要时刻注意:

1.使用ARC进行内存管理
2.在适当的情况下使用reuseIdentifier
3.尽可能将View设置为不透明(Opaque)
4.避免臃肿的XIBs
5.不要阻塞主线程

6.让图片的大小跟UIImageView一样
7.选择正确的集合
8.使用GZIP压缩

1) 使用ARC进行内存管理

ARC是在iOS 5中发布的,它解决了最常见的内存泄露问题——也是开发者最容易健忘的。ARC的全称是“Automatic Reference Counting”——自动引用计数,它会自动的在代码中做retain/release工作,开发者不用再手动处理。

下面是创建一个View通用的一些代码块:

  1. UIView *view = [[UIView alloc] init]; 
  2. // … 
  3. [self.view addSubview:view]; 
  4. [view release]; 

在上面代码结束的地方很容易会忘记调用release。不过当使用ARC时,ARC会在后台自动的帮你调用release。

ARC除了能避免内存泄露外,还有助于程序性能的提升:当程序中的对象不再需要的时候,ARC会自动销毁对象。所以,你应该在工程中使用ARC。

下面是学习ARC的一些资源:

苹果的官方文档
Matthijs Hollemans的初级ARC
Tony Dahbura的如何在Cocos2D 2.X工程中使用ARC
如果你仍然不确定ARC带来的好处,那么看一些这篇文章:8个关于ARC的神话——这能够让你相信你应该在工程中使用ARC!
值得注意的是,ARC并不能避免所有的内存泄露。使用ARC之后,工程中可能还会有内存泄露,不过引起这些内存泄露的主要原因是:block,retain循环,对CoreFoundation对象(通常是C结构)管理不善,以及真的是代码没写好。
这里有一篇文章是介绍哪些问题是ARC不能解决的 — 以及如何处理这些问题。

2) 在适当的情况下使用reuseIdentifier

在适当的情况使用reuseIdentifier
在iOS程序开发中一个普遍性的错误就是没有正确的为UITableViewCells、UICollectionViewCells和UITableViewHeaderFooterViews设置reuseIdentifier。

为了获得最佳性能,当在tableView:cellForRowAtIndexPath:方法中返回cell时,table view的数据源一般会重用UITableViewCell对象。table view维护着UITableViewCell对象的一个队列或者列表,这些数据源已经被标记为重用了。

如果没有使用reuseIdentifier会发生什么?如果你在程序中没有使用reuseIdentifier,table view每次显示一个row时,都会配置一个全新的cell。这其实是一个非常消耗资源的操作,并且会影响程序中table view滚动的效率。

自iOS 6以来,你可能还希望header和footer views,以及UICollectionView的cell和supplementary views。

为了使用reuseIdentifiers,在table view请求一个新的cell时,在数据源中调用下面的方法:

  1. static NSString *CellIdentifier = @“Cell”
  2. UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath]; 

如果table view维护的UITableViewCell队列或列表中有可用的cell,则从队列从移除一个已经存在的cell,如果没有的话,就从之前注册的 nib文件或类中创建一个新的cell。如果没有可以重用的cell,并且没有注册nib文件或类,tableview的 dequeueReusableCellWithIdentifier:方法会返回一个nil。
3) 尽可能将View设置为不透明(Opaque)

http://s1.51cto.com/wyfs01/M01/07/01/wKioOVFs7KXCEIf9AAAs6bKr60Q336.jpg
尽量将view设置为Opaque

如果view是不透明的,那么应该将其opaque属性设置为YES。为什么要这样做呢?这样设置可以让系统以最优的方式来绘制view。opaque属性可以在Interface Builder或代码中设置。

苹果的官方文档对opaque属性有如下解释:

This property provides a hint to the drawing system as to how it should treat the view. If set to YES, the drawing system treats the view as fully opaque, which allows the drawing system to optimize some drawing operations and improve performance. If set to NO, the drawing system composites the view normally with other content. The default value of this property is YES.

(opaque属性提示绘制系统如何处理view。如果opaque设置为YES,绘图系统会将view看为完全不透明,这样绘图系统就可以优化一些绘制操作以提升性能。如果设置为NO,那么绘图系统结合其它内容来处理view。默认情况下,这个属性是YES。)

如果屏幕是静止的,那么这个opaque属性的设置与否不是一个大问题。但是,如果view是嵌入到scroll view中的,或者是复杂动画的一部分,不将设置这个属性的话肯定会影响程序的性能!可以通过模拟器的Debug\Color Blended Layers选项来查看哪些view没有设置为不透明。为了程序的性能,尽可能的将view设置为不透明!

4) 避免臃肿的XIBs

避免臃肿的XIB

在iOS 5中开始使用Storyboards,并且将替代XIBs。不过在有些情况下XIBs仍然有用。如果你的程序需要运行在装有iOS 5之前版本的设备上,或者要自定义可重用的view,那么是避免不了要使用XIBs的。

如果必须要使用XIBs的话,尽量让XIBs文件简单。并且每个view controller对于一个XIB文件,如果可以的话,把一个view controller的view不同的层次单独分到一个XIBs文件中。

(注意:当把一个XIB文件加载到内存时,XIB文件中的所有内容都将被加载到内存中,包括图片。如果有一个view还不立即使用的话,就会造成内 存的浪费。而这在storyboard中是不会发生的,因为storyboard还在需要的时候才实例化一个view controller。)

当加载XIB时,所有涉及到的图片都将被缓存,并且如果是开发的程序是针对OS X的话,声音文件也会被加载。苹果的官方文档这样说:

When you load a nib file that contains references to image or sound resources, the nib-loading code reads the actual image or sound file into memory and and caches it. In OS X, image and sound resources are stored in named caches so that you can access them later if needed. In iOS, only image resources are stored in named caches. To access images, you use the imageNamed: method of NSImage or UIImage, depending on your platform.

(当加载一个nib文件时,也会将nib文件涉及到的图片或声音资源加载到内存中,nib-loading代码会将实际的图片或声音文件读取到内存 中,并一直缓存着。在OS X中,图片和声音资源都存储在命名缓存中,这样之后如果需要的话,可以对其进行访问。在iOS中,只有图片资源被存储到命名缓存中。要访问图片的话,使用 NSImage或UIImage(根据不同的系统)的imageNamed:方法即可。)

显然,在使用storyboard时也会发生类似的缓存操作;不过我没有找到相关内容的任何资料。想要学习storyboard的更多知识吗?可以看看Matthijs Hollemans写的iOS 5中:初级Storyboard Part 1Part2

5) 不要阻塞主线程

永远都不要在主线程做繁重的任务。因为UIKit的左右任务都在主线程中进行,例如绘制、触摸管理和输入响应。

在主线程做所有任务的风险是:如果你的代码阻塞了主线程,那么程序将出现反应迟钝。这回招致用户在App Store上对程序的差评!

在执行I/O操作中,大多数情况下都会祖塞主线程,这些操作需要从读写外部资源,例如磁盘或者网络。

关于网络操作可以使用NSURLConnection的如下方法,以异步的方式来执行:

  1. + (void)sendAsynchronousRequest:(NSURLRequest *)request queue:(NSOperationQueue *)queue completionHandler:(void (^)(NSURLResponse*, NSData*, NSError*))handler 

或者使用第三方框架,例如AFNetworking

如果你需要做一些其它类型开销很大的操作(例如执行一个时间密集型的计算或者对磁盘进行读写),那么就使用GCD(Grand Central Dispatch),或NSOperations 和 NSOperationQueues。

下面的代码是使用GCD的一个模板:

  1. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 
  2.     // switch to a background thread and perform your expensive operation 
  3.     dispatch_async(dispatch_get_main_queue(), ^{ 
  4.         // switch back to the main thread to update your UI 
  5.     }); 
  6. }); 

 

如上代码,为什么在第一个dispatch_async里面还嵌套了一个dispatch_async呢?这是因为关于UIKit相关的代码需要在主线程里面执行。

可以看看Ray Wenderlich中的教程:iOS中多线程和GCD—初级,以及Soheil Azarpour的如何使用NSOperations和NSOperationQueues教程

6) 让图片的大小跟UIImageView一样

http://s2.51cto.com/wyfs01/M01/07/01/wKioOVFs7LDytOxKAAAdyOKIZVg798.jpg1
确保图片和UIImageView大小一致
如果需要将程序bundle中的图片显示到UIImageView中,请确保图片和UIImageView的大小是一样的。因为图片的缩放非常耗费资源,特别是将UIImageView嵌入到UIScrollView中。

如果是从远程服务中下载图片,有时候你控制不了图片的尺寸,或者在下载之前无法在服务器上进行图片的缩放。这种情况,当图片下载完之后,你可以手动进行图片的缩放——做好是在后台线程中!——然后再在UIImageView中使用缩放过的图片。
7) 选择正确的集合

选择正确的集合
学习使用最适合的类或对象是编写高效代码的基础。特别是在处理集合数据时,尤为重要。
苹果的官网上有一篇文章:集合编程主题(Collections Programming Topics)——详细的介绍了在集合数据中可以使用的类,以及什么情况下使用哪个类。在使用集合时,每个开发者都应该阅读一下这个文档。
太长,不想阅读(TLDR)?下面是常见集合类型的一个简介:
•数组:是一个值按顺序排列的一个列表。根据索引可以快速查找,不过根据值进行查找就比较慢,另外插入和删除也比较慢。
•字典:  存储键/值对。根据键可以快速查找。
•Sets:  是一个值无序排列的列表,根据值可以快速查找,另外插入和删除也比较快。

8) 使用GZIP压缩

使用GZIP压缩
越来越多的程序依赖于外部数据,这些数据一般来自远程服务器或者其它的外部APIs。有时候你需要开发一个程序来下载一些数据,这些数据可以是XML,JSON,HTML或者其它一些文本格式。

问题是在移动设备上的网络是不确定的。用户的设备可能在EDGE网络一分钟,然后接着又在3G网络中。不管在什么情况下,都不要让用户等待。

有一个可以优化的选择:使用GZIP对网络传输中的数据进行压缩,这样可以减小文件的大小,并加快下载的速度。压缩对于文本数据特别有用,因为文本具有很高的压缩比。

iOS中,如果使用NSURLConnection,那么默认情况下已经支持GZIP压缩了,并且基于NSURLConnection的框架页支持GZIP压缩,如AFNetworking。甚至有些云服务提供商已经提供发送经压缩过的响应内容,例如 Google App Engine

这里有一篇关于GZIP压缩很好的文章,介绍了如何在Apache活IIS服务器中开启支持GZIP压缩。

在性能优化时,当你碰到一些复杂的问题,应该注意和使用如下技巧:

9.重用和延迟加载View
10.缓存、缓存、缓存
11.考虑绘制
12.处理内存警告
13.重用花销很大的对象
14.使用Sprite Sheets
15.避免重新处理数据
16.选择正确的数据格式
17.设置适当的背景图片
18.降低Web内容的影响
19.设置阴影路径
20.优化TableView
21.选择正确的数据存储方式

中级性能提升
现在,在进行代码优化时,你已经能够完成一些初级性能优化了。但是下面还有另外一些优化方案,虽然可能不太明显(取决于程序的架构和相关代码),但是,如果能够正确的利用好这些方案,那么它们对性能的优化将非常明显!
9) 重用和延迟加载View

程序界面中包含更多的view,意味着界面在显示的时候,需要进行更多的绘制任务;也就意味着需要消耗更多的CPU和内存资源。特别是在一个UIScrollView里面加入了许多view。

这种情况的管理技巧可以参考UITableView和UICollectionView的行为:不要一次性创建所有的subview,而是在需要的时候在创建view,并且当view使用完毕时候将它们添加到重用队列中。

这样就可以仅在UIScrollView滚动的时候才配置view,以此可以避免分配创建view的带来的成本——这可能是非常耗资源的。

现在有这样的一个问题:在程序中需要显示的view在什么时机创建(比如说,当用户点击某个按钮,需要显示某个view)。这里有两种可选方法:

在屏幕第一次加载以及隐藏的时候,创建view;然后在需要的时候,再把view显示出来。
直到需要显示view的时候,才创建并显示view。
每 种方法都有各自的优点和缺点。第一种方法需要消耗更多的内容,因为创建出来的view一直占据着内存,直到view被release掉。不过,使用这种 方法,当用户点击按钮时,程序会很快的显示出view,因为只需要修改一下view的可见性即可。而第二种方法则产生相反的效果;当需要的时候猜创建 view,这会消耗更少的内存;不过,当用户点击按钮的时候,不会立即显示出view。
10) 缓存、缓存、缓存

在开发程序时,一个重要的规则就是“缓存重要的内容”——这些内容一般不会改变,并且访问的频率比较高。

可以缓存写什么内容呢?比如远程服务器的响应内容,图片,甚至是计算结果,比如UITableView的行高。

NSURLConnection根据HTTP头的处理过程,已经把一些资源缓存到磁盘和内存中了。你甚至可以手动创建一个NSURLRequest ,让其只加载缓存的值。

下面的代码片段一般用在为图片创建一个NSURLRequest:

  1. + (NSMutableURLRequest *)imageRequestWithURL:(NSURL *)url { 
  2.     NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; 
  3.     request.cachePolicy = NSURLRequestReturnCacheDataElseLoad; // this will make sure the request always returns the cached image 
  4.     request.HTTPShouldHandleCookies = NO; 
  5.     request.HTTPShouldUsePipelining = YES; 
  6.     [request addValue:@“image/*” forHTTPHeaderField:@“Accept”]; 
  7.     return request; 

注意:你可以使用NSURLConnection抓取一个URL请求,但是同样可以使用AFNetworking来抓取,这种方法不用修改所有网络相关的代码——这是一个技巧!

更多关于HTTP 缓存, NSURLCache, NSURLConnection 以及相关的内容, 那么看一下NSHipster中的the NSURLCache entry

如果你需要缓存的内容没涉及到HTTP请求,那使用NSCache。NSCache的外观和行为与NSDictionary类似, 但是,当系统需要回收内存时,NSCache会自动的里面存储的内容。Mattt Thompson 在NSHipster上写了一篇关于NSCache非常不错的文章

更多关于HTTP缓存的内容,建议读一下Google的这篇文章:best-practices document on HTTP caching
11) 考虑绘制

在iOS中制作漂亮的按钮有多种方法。可以使用全尺寸图片,可缩放图片,或使用CALayer, CoreGraphics, 甚至是OpenGL来手动测量和绘制按钮。

这些方法的复杂程度不同,性能也有区别。这篇关于iOS中图形性能的文章值得一读。其中Andy Matuschak(曾经是苹果的UIKit小组的组员)对这篇文章的评论中,对于不同的方法及其性能权衡有非常好的一个见解。

简单来说,使用预渲染图片技术是最快的,因为iOS中不用等到在屏幕上显示的时候才创建图形和对形状进行绘制(图片已经创建好了!)。这样带来的问 题是需要把所有的图片都放到程序bundle中,从而增加了程序的大小。因此使用可伸缩图片在这里将排上用场了:可以移除“浪费”空间的图片——iOS可 以重复利用。并且针对不同的元素(例如按钮)不需要创建不同的图片。

不过,使用图片的话会失去代码对图片的控制能力,进而针对不同的程序,就需要重复的生成每一个需要的图片,并反复的放到每个程序中。这个处理过程一 般会比较慢。另外一点就是如果你需要一个动画,或者许多图片都要进行轻微的调整(比如多个颜色的覆盖),那么需要在程序中加入许多图片,进而增加了程序 bundle的大小。

总的来说,要考虑一下什么才是最重要的:绘制性能还是程序大小。一般来说都重要,所以在同一个工程中,应该两种都应考虑。
12) 处理内存警告

当系统内存偏低时,iOS会通知所有在运行的程序。苹果的官方文档中介绍了如何处理低内存警告:

If your app receives this warning, it must free up as much memory as possible. The best way to do this is to remove strong references to caches, image objects, and other data objects that can be recreated later.

如果程序收到了低内存警告,在程序中必须尽量释放内存。最佳方法就是移除强引用的涉及到的缓存,图片对象,以及其它可以在之后使用时还可以重新创建的数据对象。

UIKit中提供了如下几种方法来接收低内存(low-memory)警告:

实现app delegate中的applicationDidReceiveMemoryWarning: 方法。
在UIViewController子类中重写(Override)didReceiveMemoryWarning方法。
在通知中心里面注册UIApplicationDidReceiveMemoryWarningNotificatio通知。
在收到以上任意的警告时,需要立即释放任何不需要的内存。

例如,UIViewController的默认情况是清除掉当前不可见的view;在UIViewController的子类中,可以清除一些额外的数据。程序中不没有显示在当前屏幕中的图片也可以release掉。

当收到低内存警告时,尽量释放内存是非常重要的。否则,运行中的程序有可能会被系统杀掉。

不过,在清除内存时要注意一下:确保被清除的对象之后还可以被创建出来。另外,在开发程序的时候,请使用iOS模拟器中的模拟内存警告功能对程序进行测试!

13) 重用花销很大的对象

http://s4.51cto.com/wyfs01/M01/07/02/wKioOVFs9ezTW4RpAAAwQF1sIqU235.jpg
有些对象的初始化非常慢——比如NSDateFormatter和NSCalendar。不过有时候可以避免使用这些对象,例如在解析JSON/XML中的日期时。

当使用这些对象时,为了避免性能上的瓶颈,可以尝试尽量重用这些对象——在类中添加一个属性或者创建一个静态变量。

注意,如果使用静态变量的话,对象会在程序运行的时候一直存在,就像单例一样。

下面的代码演示创建一个延迟加载的日期格式属性。第一次调用属性的时候,会创建一个新的日期格式。之后再调用的话,会返回已经创建好的实例对象:

  1. // in your .h or inside a class extension 
  2. @property (nonatomic, strong) NSDateFormatter *formatter; 
  3. // inside the implementation (.m) 
  4. // When you need, just use self.formatter 
  5. – (NSDateFormatter *)formatter { 
  6.     if (! _formatter) { 
  7.         _formatter = [[NSDateFormatter alloc] init]; 
  8.         _formatter.dateFormat = @“EEE MMM dd HH:mm:ss Z yyyy”// twitter date format 
  9.     } 
  10.     return _formatter; 

另外,还需要记住的是在设置NSDateFormatter的日期格式时,同样跟创建新的一个NSDateFormatter实例对象时一样慢!因此,在程序中如果需要频繁的处理日期格式,那么对NSDateFormatter进行重用是非常好的。

14) 使用Sprite Sheets

http://s1.51cto.com/wyfs01/M00/07/02/wKioJlFs9eyy6e-cAAA44XQc7Zg575.jpg
使用sprite sheets
你是一个游戏开发者吗?是的话那么sprite sheets是最佳选择之一。使用Sprite sheets跟常用的绘制方法比起来,绘制更快,并且消耗更少的内存。

下面是两个非常不错的sprite sheets教程:

如何在Cocos2D中使用动画和Sprite Sheets 

如何在Cocos2D中使用纹理包(Texture Packer)和像素格式来创建并优化Sprite Sheets  。(第二个教程详细的介绍了像素格式——在游戏中可以衡量性能的影响)

如果还不熟悉sprite sheets,可以看看这里的介绍:SpriteSheets – 视频, Part 1和 Part 2 。这两个视频的作者是Andreas Löw, 他是纹理包(Texture Packer)的创建者, 纹理包是创建sprite sheets的重要工具。

除了使用sprite sheets外,这里还介绍了一些用于游戏开发中的技巧,例如,如果你有很多sprite(比如射击类游戏中),那么可以重用sprite,而不用每次都创建sprite。
15) 避免重新处理数据

许多程序都需要从远程服务器中获取数据,以满足程序的需求。这些数据一般是JSON或XML格式。在请求和接收数据时,使用相同的数据结构非常重要。

为什么呢?在内存中把数据转换为适合程序的数据格式是需要付出额外代价的。

例如,如果你需要在table view中显示一些数据,那么请求和接收的数据格式最好是数组格式的,这样可以避免一些中间操作——将数据转换为适合程序使用的数据结构。

类似的,如果程序是根据键来访问具体的值,那么最好请求和接收一个键/值对字典。

在第一时间获得的数据就是所需要格式的,可以避免将数据转换为适合程序的数据格式带来的额外代价。
16) 选择正确的数据格式


选择正确的数据格式

将数据从程序传到网络服务器中有多种方法,其中使用的数据格式基本都是JSON和XML。你需要做的就是在程序中选择正确的数据格式。

JSON的解析速度非常快,并且要比XML小得多,也就意味着只需要传输更少数据。并且在iOS5之后,已经有内置的JSON反序列化API了,所以使用JSON是很容易的。

不过XML也有它自己的优势:如果使用SAX方法来解析XML,那么可以边读XML边解析,并不用等到全部的XML获取到了才开始解析,这与JSON是不同的。当处理大量数据时,这种方法可以提升性能并减少内存的消耗。
17) 设置适当的背景图片

在iOS编码中,跟别的许多东西类似,这里也有两种方法来给view设置一个背景图片:

1.可以使用UIColor的colorWithPatternImge方法来创建一个颜色,并将这个颜色设置为view的背景颜色。
2.可以给view添加一个UIImageView子视图。

如果你有一个全尺寸的背景图片,那么应该使用UIImageView,因为UIColor的colorWithPatternImge方法是用来创建小图片的——该图片会被重复使用。此时使用UIImageView会节省很多内存。

  1. // You could also achieve the same result in Interface Builder 
  2. UIImageView *backgroundView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@“background”]]; 
  3. [self.view addSubview:backgroundView]; 

不过,如果你计划用小图片当做背景,那么应该使用UIColor的colorWithPatternImge方法。这种情况下绘制速度会很快,并且不会消耗大量的内存。

  1. self.view.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@“background”]]; 

 

18)降低Web、内容的影响

UIWebView非常有用。用它可以很容易的显示web内容,甚至可以构建UIKit空间难以显示的内容。

不过,你可以能已经注意到程序中使用的UIWebView组建没有苹果的Safari程序快。这是因为JIT编译限制了WebKit的Nitro引擎的使用。

因此为了获得更加的性能,需要调整一下HTML的大小。首先就是尽量的摆脱JavaScript,并避免使用大的矿建,例如JQuery。有时候使用原始的JavaScript要比别的框架快。

另外,尽量的异步加载JavaScript文件——特别是不直接影响到页面行为时,例如分析脚本。

最后——让使用到的图片,跟实际需要的一样大小。如之前提到的,尽量使用sprite sheets,以此节省内存和提升速度。

更多相关信息,可以看一下: WWDC 2012 session #601 – 在iOS中优化UIWebView和网站中的Web内容

19) 设置阴影路径

如果需要在view活layer中添加一个阴影,该如何处理呢?大多数开发者首先将QuartzCore框架添加到工程中,然后添加如下代码:

 

  1. #import <QuartzCore/QuartzCore.h> 
  2. // Somewhere later … 
  3. UIView *view = [[UIView alloc] init]; 
  4. // Setup the shadow … 
  5. view.layer.shadowOffset = CGSizeMake(-1.0f, 1.0f); 
  6. view.layer.shadowRadius = 5.0f; 
  7. view.layer.shadowOpacity = 0.6;

上面这种方法有一个问题,Core Animation在渲染阴影效果之前,必须通过做一个离屏(offscreen)才能确定view的形状,而这个离屏操作非常耗费资源。下面方法可以更容易地让系统进行阴影渲染:设置阴影路径!

 

  1. view.layer.shadowPath = [[UIBezierPath bezierPathWithRect:view.bounds] CGPath];

通过设置阴影路径,iOS就不用总是再计算该如何绘制阴影了。只需要使用你预先计算好的路径即可。有一点不好的是,根据view的格式,自己可能很难计算出路径。另外一个问题就是当view的frame改变时,必须每次都更新一下阴影路径。

如果你想了解更多相关信息,可参看Mark Pospesel的一篇文章:shadowPath
20) 优化TableView

Table views需要快速的滚动——如果不能的话,用户会感觉到停顿。为了让table view平滑的滚动,确保遵循了如下建议:

1.设置正确的reuseIdentifer以重用cell。
2.尽量将view设置为不透明,包括cell本身。
3.避免渐变,图像缩放以及离屏绘制。
4.如果row的高度不相同,那么将其缓存下来。
5.如果cell显示的内容来此网络,那么确保这些内容是通过异步来获取的。
6.使用shadowPath来设置阴影。
7.减少subview的数量。
8.在cellForRowAtIndexPath:中尽量做更少的操作。如果需要做一些处理,那么最好做过一次之后,就将结果缓存起来。
9.使用适当的数据结构来保存需要的信息。不同的结构会带来不同的操作代价。
10.使用rowHeight, sectionFooterHeight 和 sectionHeaderHeight 来设置一个恒定 高度,而不要从delegate中获取。
21) 选择正确的数据存储方式


选择正确的数据存储方式
当需要存储和读取大量的数据时,该如何选择存储方式呢?有如下选择:

1.使用NSUserDefaults进行存储
2.保存为XML,JSON或Plist格式的文件
3.利用NSCoding进行归档
4.存储到一个本地数据库,例如SQLite。
5.使用Core Data.
使用NSUserDefaults有什么问题呢? 虽然NSUserDefaults很好并且容易,不过只只针对于存储小量数据(比如你的级别,或者声音是开或关)。如果要存储大量的数据,最好选择别的存储方式。

大量数据保存为结构化的文件也可能会带来问题。一般,在解析这些结构数据之前,需要将内容全部加载到内存中,这是很消耗资源的。虽然可以使用SAX来处理XML文件,但是这有点复杂。另外,加载到内存中的所有对象,不一定全部都需要用到。

那么使用NSCoding来保存大量数据怎么样呢?因为它同样是对文件进行读写,因此依然存在上面说的问题。

要保存大量的数据,最好使用SQLite或Core Data。通过SQLite或Core Data可以进行具体的查询——只需要获取并加载需要的数据对象——避免对数据进行不合理的搜索。在性能方面,SQLite和Core Data差不大。

SQLite和Core Data最大的区别实际上就是用法上。Core Data代表一个对象模型,而SQLite只是一个DBMS。一般,苹果建议使用Core Data,不过如果你有特殊的原因不能使用Core Data的话,可以使用低级别的SQLite。

在程序中,如果选择使用SQLite,这里有个方便的库FMDB :可以利用该库操作SQLite数据库,而不用深入使用SQLite C API。

高级
当且仅当下面这些技巧能够解决问题的时候,才使用它们:

22.加速启动时间
23.使用Autorelease Pool
24.缓存图片 — 或者不缓存
25.尽量避免Date格式化

高级性能提升

寻找一些高明的方法,让自己变为一个全代码忍者?下面这些高级的性能优化技巧可以在适当的时候让程序尽可能的高效运行!

22) 加速启动时间

能快速的启动程序非常重要,特别是在用户第一次启动程序时。第一映像对程序来说非常重要!

让程序尽量快速启动的方法就是尽量以异步方式执行任务,例如网络请求,数据访问或解析。

另外,避免使用臃肿的XIBs,因为XIB的加载是在主线程中进行的。但是记住storyboard没有这样的问题——所以如果可以的话就使用storyboard吧!

注意:在利用Xcode进行调试时,watchdog不会运行,所在设备中测试程序启动性能时,不要将设备连接到Xcode。
23) 使用Autorelease Pool

NSAutoreleasePool负责释放一个代码块中的自动释放对象。一般都是由UIKit来创建的。不过有些情况下需要手动创建NSAutoreleasePool。

例如,如果在代码中创建了大量的临时对象,你将注意到内存使用量在增加,直到这些对象被释放。问题是只有当UIKit耗尽了 autorelease pool,这些对象才会被释放,也就是说当不再需要这些对象之后,这些对象还在内存中占据着资源。

不过这个问题完全可以避免:在@autoreleasepool代码块中创建临时对象,如下代码:

 

  1. NSArray *urls = <# An array of file URLs #>; 
  2. for (NSURL *url in urls) { 
  3.     @autoreleasepool { 
  4.         NSError *error; 
  5.         NSString *fileContents = [NSString stringWithContentsOfURL:url 
  6.                                          encoding:NSUTF8StringEncoding error:&error]; 
  7.         /* Process the string, creating and autoreleasing more objects. */ 
  8.     } 

当每次迭代完之后,都会释放所有的autorelease对象。

关于NSAutoreleasePool的更多内容可以阅读苹果的官方文档
24) 缓存图片–或者不缓存

iOS中从程序bundle中加载UIImage一般有两种方法。

第一种比较常见:imageNamed。

第二种方法很少使用:imageWithContentsOfFile

为什么有两种方法完成同样的事情呢?imageNamed的优点在于可以缓存已经加载的图片。苹果的文档中有如下说法:

This method looks in the system caches for an image object with the specified name and returns that object if it exists. If a matching image object is not already in the cache, this method loads the image data from the specified file, caches it, and then returns the resulting object.

这种方法会在系统缓存中根据指定的名字寻找图片,如果找到了就返回。如果没有在缓存中找到图片,该方法会从指定的文件中加载图片数据,并将其缓存起来,然后再把结果返回。

而imageWithContentsOfFile方法只是简单的加载图片,并不会将图片缓存起来。这两个方法的使用方法如下:

  1. UIImage *img = [UIImage imageNamed:@“myImage”]; // caching 
  2. // or 
  3. UIImage *img = [UIImage imageWithContentsOfFile:@“myImage”]; // no caching 

那么该如何选择呢?

如果加载一张很大的图片,并且只使用一次,那么就不需要缓存这个图片。这种情况imageWithContentsOfFile比较合适——系统不会浪费内存来缓存图片。

然而,如果在程序中经常需要重用的图片,那么最好是选择imageNamed方法。这种方法可以节省出每次都从磁盘加载图片的时间。

25) 尽量避免Date格式化
如果有许多日期需要使用NSDateFormatter,那么需要小心对待了。如之前(重用花销很大的对象)所提到的,无论什么时候,都应该尽量重用NSDateFormatters。

 

然而,如果你需要更快的速度,那么应该使用C来直接解析日期,而不是NSDateFormatter。Sam Soffes写了一篇文章,其中提供了一些解析ISO-8601格式日期字符的串代码。你只需要简单的调整一下其中的代码就可以满足自己特殊的需求了。

这听起来不错把——不过,你相信这还有更好的一个办法吗?

如果你自己能控制处理日期的格式,那么可以选择 Unix timestamps(http://en.wikipedia.org/wiki/Unix_time)。Unix timestamps是一个简单的整数,代表了从新纪元时间(epoch)开始到现在已经过了多少秒,通常这个新纪元参考时间是00:00:00 UTC on 1 January 1970。

你可以很容易的见这个时间戳转换为NSDate,如下所示:

  1. – (NSDate*)dateFromUnixTimestamp:(NSTimeInterval)timestamp { 
  2.     return [NSDate dateWithTimeIntervalSince1970:timestamp]; 

上面这个方法比C函数还要快!

注意:许多网络APIs返回的时间戳都是毫秒,因此需要注意的是在将这个时间戳传递给dateFromUnixTimestamp之前需要除以1000。

何去何从?
强烈建议对程序性能优化感兴趣的读者看看下面列出来的WWDC视频。在看视频之前,你需要注册一个Apple ID(注册后就可以观看所有WWDC2012的视频):

#406: Adopting Automatic Reference Counting
#238: iOS App Performance: Graphics and Animations
#242: iOS App Performance: Memory
#235: iOS App Performance: Responsiveness
#409: Learning Instruments
#706: Networking Best Practices
#514: OpenGL ES Tools and Techniques
#506: Optimizing 2D Graphics and Animation Performance
#601: Optimizing Web Content in UIWebViews and Websites on iOS
#225: Up and Running: Making a Great Impression with Every Launch
下面这些视频来自WWDC 2011 ,也非常有用:

#308: Blocks and Grand Central Dispatch in Practice
#323: Introducing Automatic Reference Counting
#312: iOS Performance and Power Optimization with Instruments
#105: Polishing Your App: Tips and tricks to improve the responsiveness and performance
#121: Understanding UIKit Rendering

这里还有更多相关视频,大多数来自iOS 5技术讲座

Optimizing App Performance with Instruments
Understanding iOS View Compositing
基于 “Your iOS App Performance Hitlist” 视频,Ole Begemann写了一篇文章。苹果还提供了一篇非常好的文章:性能优化。其中提供的技巧和提示对程序性能提升很有帮助。

 

[转载]SQL Server中char、nchar、varchar、nvarchar的区别 - 无风听海 - 博客园

mikel阅读(945)

[转载]SQL Server中char、nchar、varchar、nvarchar的区别 – 无风听海 – 博客园.

对于程序中的一般字符串类型的字段,SQL Server中有char、varchar、nchar、nvarchar四种类型来对应,那么这四种类型有什么区别呢,这里做一下对比。

1.定长或变长

所谓定长就是长度固定,当要保存的数据长度不够时将自动在其后面填充英文空格,使长度达到相应的长度;有var前缀的,表示是实际存储空间是动态变化的,比如varchar,nvarchar变长字符数据则不会以空格填充。

2.Unicode或非Unicode

数据库中,英文字符只需要一个字节存储就足够了,但汉字和其他众多非英文字符,则需要两个字节存储。如果英文与汉字同时 存在,由于占用空间数不同,容易造成混乱,导致读取出来的字符串是乱码。Unicode字符集就是为了解决字符集这种不兼容的问题而产生的,它所有的字符 都用两个字节表示,即英文字符也是用两个字节表示。而前缀n就表示Unicode字符,比如nchar,nvarchar,这两种类型使用了 Unicode字符集。

3.几种数据类型的存储的最大容量

char,varchar 最多8000个英文,4000个汉字

nchar,nvarchar  最多可存储4000个字符,无论英文还是汉字

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
--创建表
CREATE TABLE TempTable(
    id INT PRIMARY KEY,
    charField CHAR(10),
    varcharField VARCHAR(10),
    nvarcharField NVARCHAR(10)
)
INSERT INTO TempTable VALUES(1,'WFTH','WFTH','WFTH')
INSERT INTO TempTable VALUES(2,'无风听海','无风听海','无风听海')
INSERT INTO TempTable VALUES(3,'','','')
INSERT INTO TempTable(id) VALUES(4)
INSERT INTO TempTable VALUES(5,'1234567890','1234567890','12345')
SELECT DATALENGTH(charField) AS charFieldLen,
       DATALENGTH(varcharField) AS varcharFieldLen,
       DATALENGTH(nvarcharField) AS nvarcharFieldLen
FROM temptable WHERE id =1
SELECT DATALENGTH(charField) AS charFieldLen,
       DATALENGTH(varcharField) AS varcharFieldLen,
       DATALENGTH(nvarcharField) AS nvarcharFieldLen
FROM temptable WHERE id =2
SELECT DATALENGTH(charField) AS charFieldLen,
       DATALENGTH(varcharField) AS varcharFieldLen,
       DATALENGTH(nvarcharField) AS nvarcharFieldLen
FROM temptable WHERE id =3
SELECT DATALENGTH(charField) AS charFieldLen,
       DATALENGTH(varcharField) AS varcharFieldLen,
       DATALENGTH(nvarcharField) AS nvarcharFieldLen
FROM temptable WHERE id =4
SELECT DATALENGTH(charField) AS charFieldLen,charField,
       DATALENGTH(varcharField) AS varcharFieldLen,varcharField,
       DATALENGTH(nvarcharField) AS nvarcharFieldLen,nvarcharField
FROM temptable WHERE id =5

[转载]ERP框架开发中的License许可验证机制设计与实现 (包含源代码下载) - James Li - 博客园

mikel阅读(1335)

[转载]ERP框架开发中的License许可验证机制设计与实现 (包含源代码下载) – James Li – 博客园.

许可机制是ERP框架中必不可少的一部分,可以有效的保护框架资源在授权范围内应用,增 加企业投资的回报。在研究了几种类型的许可机制(序列号注册码,Web服务联机验证,授权License文件)后,最后选定以Signed Xml配合RSA算法,作为许可机制的主要技术实现。

主要达到的目的如下

1  可以实现版本控制。企业版可使用所有的功能,专业版只可用部分功能,个人版免费使用,但功能集更少。

public enum Version {  Enterprise, Professional, Personal }

2  功能点的控制上,同时在线用户数量控制,帐套数量控制,硬件验证控制,试用过期控制,虚拟机控制。

  • 在线用户数量 可以控制同时在线的用户数量,超过许可数量,则无法登陆
  • 帐套数量控制 比如,只可以建立10套帐,超过此限制则无法登陆
  • 硬件验证控制 生成许可文件时,会绑定硬件信息(硬盘,CPU,内存,主板),以此硬件信息生成的许可文件,不可以在别的电脑上运行,以控制用户数量。
  • 试用过期控制 超过期限则停止进入系统,有效阻止未授权用户的继续使用,收回投资
  • 虚 拟机控制  因为虚拟机中安装与还原操作系统非常容易方便,我们常以此来试用软件,当软件试用到期后还想继续用,则只需要的还原一下虚拟机中的系统,则可以继续体验。 以此选项,控制软件不可以运行于虚拟机中。常见的虚拟机即VMware Workstation和Virtual PC。

以此理论,设计如下格式的License.lic文件,以作为要颁发的许可证文件。

image

 

在程序编写过程中,参考了CodeProject网站中的文章

Using XML Digital Signatures for Application Licensing – CodeProject
http://www.codeproject.com/Articles/4940/Using-XML-Digital-Signatures-for-Application-Licen

几乎就是对这篇文章的定制,就可以完成以上所需要达到的目的。以下分享几个遇到的实际问题,供您参考。

1 Xml序列时,元素的顺序。通过Google得知,请仔细阅读以下的几段话。

XmlSerializer takes all fields in the order that they are declared.
the order problem on the Compact Framework.Unfortunately, this is by Design.

The order of elements serialized by the NETCF xml serializer is not
guaranteed to match that of the desktop. There is nothing in the generated
schema class included in the attached project that specifies the order of
the elements.

In order to accomplish this you should add the /order option to xsd.exe and
then regenerate the schema class
(xsd.exe /order /c foo.xsd)

By doing this all the particle members will have their explicit order
identifiers and then the serializer will honor the order of the schema. The
new schema generated by with the /order switch will have the orders
property specified on the XmlElementAttribute
e.g. [System.Xml.Serialization.XmlElementAttribute(Order = 2)]

Xml序列化以元素声明的顺序,但是Compack Framework不一样,需要手动指定它们的顺序。

 

2 硬件信息的集成绑定

这应该是一个名值对,比如

CPU: Intel Pentium T440

Hard Driver: WD Elements

所以,需要设计一个List<HardwareInfo>,或是继承于CollectionBase。

public struct HardwareInfo 
{  
public string HardwareId { get;set;} 

public string Description { get;set;}

}

这个List<T>要可以序列化,它要绑定到License.lic文件中。

 

3 类型中,有些对象不需要序列化的,要加上标签以阻止序列化。

[NonSerialized]
private string _hashValue;

4  生成公钥和私匙配对,然后放到代码中去。生成的内容如下

  public static string PrivateKey
        {
            get
            {
                return "<BitStrength>1024</BitStrength><RSAKeyValue><Modulus>uCMDxXTd0bNbiAFrOYjbiGyQpqfZY2Znn70hoQZsprNoXV8tSZ6mM8VswoTNh6S+0qfYntzxpQq29mqv+8mUIuGN/30YpUq9tZFR1bIHEJnPqSRHcQa0ezimTilBN7EN7J6wnQBQqFyt3ZRnLYUsRta1Vjdn4eEc50Q4EfEOlO8=</Modulus><Exponent>AQAB</Exponent><P>99QWQo0ulkBCDyHwL3amXKahDSmcGa3bJHz23M++65jtxYp0LViGH+ngr5FYSxp7oAj37dKTiw4h6NO/+J6amw==</P><Q>vjVO29oMfKynSHZgRIeRhcInt6ReHm19of8YIsvBVYgasg9qi0lONFUvmW51fPrXdTPWz4fHmlnv3leWN7AaPQ==</Q><DP>tiyKHGvJthsQNC1/cHRogCzgsFtI6zt4no7ZrKFtt6PYDODk27x6A5WZW5Wc8MBL5e0RyxmC6bH+zTZypGB6Rw==</DP><DQ>Rr/bYkl75Y/u9TQa4MKwbVlnnpZD7/t4BJ63IpI5ipACpgK39bFBppOdDewZRXCkXdL3buApbY9QepqHpJUbXQ==</DQ><InverseQ>zZu/5jmI8PSbo1e6nXfaAtzZQiSUO0q3D1Dm30w51lukRw8OlkmrMOszLF7LontM/4kLhmri2BU5yeTChppXLQ==</InverseQ><D>q5JsrCqlmQRfEA4KY9Siga5u5epWA2liupOW5xw+VuGqJ/5MC2HZCTo2idUGURJvf4dHr1a9jgO60UY9bgW4kWOkLdZ3xzn0wqYyt/VCdvQE1qH/YnVEeLUZqjrbH14Zw8isR2Yxf33QCFfvHWTqIvwtm0ZdniH+cEIRgEwsPNk=</D></RSAKeyValue>";
            }
        }

        public static string PublicKey
        {
            get
            {
                return "<BitStrength>1024</BitStrength><RSAKeyValue><Modulus>uCMDxXTd0bNbiAFrOYjbiGyQpqfZY2Znn70hoQZsprNoXV8tSZ6mM8VswoTNh6S+0qfYntzxpQq29mqv+8mUIuGN/30YpUq9tZFR1bIHEJnPqSRHcQa0ezimTilBN7EN7J6wnQBQqFyt3ZRnLYUsRta1Vjdn4eEc50Q4EfEOlO8=</Modulus><Exponent>AQAB</Exponent></RSAKeyValue>";
            }
        }

公钥只可以拿来验证许可文件,私匙可以验证,但主要的是用来生成Xml文件签名。

 

5 请注意License文件的最后一个节,它放的是绑定的硬件信息,这一节信息是加密的。为了可以解密,这里用RSA算法。这个过程如下,用户试用软件,申 请许可文件,给用户一个EXE文件程序,用于生成hardware.id文件,然后用户将此文件发给软件公司,软件公司依据此文件,向用户发放试用许可文 件。

 string hardware = RSACryptionHelper.EncryptString(inputString, publicKey);

当用户将绑定有硬件信息的许可拿到别的电脑上运行时,会报异常

 if ((configuration.Items.Count == 0) && (this.LicenseType == LicenseType.Enterprise))
    {
        throw new LicenseException("Hardware fingerprint is missing in license file");
    }

 

6  许可类型

public enum  LicenseType
{  
   Internal,
   Enterprise,
   Professional,
   Personal
}

所有类型的license都会过期,在license中指定的ExpiredDate之后,都将无法运行。所以没有加Trial类型的许可。

我手头有个数据库工具软件,还有800多天的试用期,可还可使用2年多一点。IT这个行业既很传统,10年的技术,C#.NET WinForms技术,现在还在普及使用中,又很超前,大量的新技术,新知识注入到这个行业中。

2年的时间内可以做很多事,看很多书,走很多路,且行且看。

 

7  Signed Xml技术的要点主要是明文查看,但可以防止篡改。眼睁睁的看着2013-5-30号就过期了,你就是没有办法把它改成2100-5-30号。这一下子可以100年后,你改了,这企业再也不能从你这里收取费用,可怎么养活程序员呢。

但是,有两个软件可以做到一个,就是篡改系统的当前时间,然后注入到你的进程中。推荐软件RunAsDate。这软件的功能强大,可以设置当前时间,然后启动软件。如果你试用一个很好的软件,又不想付费,也找不到Cracker或是KEY的话,可以试下这个办法。

image


8   ExpiryDate是2013-5-30时,但是到了这一天,用户把时间又改回到2013-4-30号,又可以继续试用一个月。对于这个问题,也要处 理。对于MIS/ERP类型的软件,也可以不用处理。对数据的主要要求之一是准确,现在是5-2号了,你把时间改回到4-2号,继续使用软件,系统的日记 帐时间也是4-2号。以后查帐的时候,这是很严重的问题。5-2号的进仓单,实际对应的是系统中的4-2号的单据,很不方便与追踪问题。

如果要控制用户改时间,则需要记住用户第一次使用系统的时间,发现用户系统时间,直接报错,异常退出。

<IssuedDate>2013-04-30T10:58:52.5456701+08:00</IssuedDate>
<ExpiryDate>2013-05-30T23:59:59.997</ExpiryDate>

 

请到如下的网页中下载源代码,供您参考。

Using XML Digital Signatures for Application Licensing – CodeProject
http://www.codeproject.com/Articles/4940/Using-XML-Digital-Signatures-for-Application-Licen

[转载]通过分析内存来优化.NET程序 - smark - 博客园

mikel阅读(961)

[转载]通过分析内存来优化.NET程序 – smark – 博客园.

最近在做一个MSMQ的Agent服务,在这里分享一下这个服务在优化的一点经验,通过分析内存更准确地定位出程序中存在的性能问题,从而让程序的性能以倍数的提升.

问题的引发

由于通过.NET MSMQ的Client实现消息分布和故障转移实在测试效果并不理想..所以决定实现一个MSMQ的Agent服务,由于有网络编写的经验所以对实现的效 果还有很有信心的.可惜最终实现出来的效果实在惨不忍睹…4个连接并发消息写入只有150/秒,实在是完全坑爹的结果!在架构上的设计并不存在问题, 所以问题一定存在程序实现过程中,以往的经验告诉自己做内存分析是最直接的办法.

问题排查一Buffer没正常回收到Pool

由于在测试过程CPU使用率并不高,所以怀疑是Buffer没有回收到Pool导致

从分析的结果来说,的确是自己在写代码的时候犯错了…存在大量的buffer被Pop出来导致大量内存被创建.但从代码流程上来看找不到任何原因.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public override void MessageWrite(IMessage msg, BufferWriter writer)
        {
            HttpData hb = msg as HttpData; 
            try
            {
                using (hb.Message)
                {
                    msg.Save(writer);
                }
            }
            finally
            {
                
                if (hb != null)
                {
                    hb.Message = null;
                    HttpDataPackage.HttpDataPool.Push(hb);
                }
            }
        }

分析结果已经说明了问题所在,所以调试了一下程序,发现问题的根源是HttpData的Message为空,但using不会报错坑爹啊…

1
2
3
4
5
6
7
public void ToProtocolData(HttpData httpbase)
        {
            httpbase.Action = Action;
            httpbase.Message = this;
            OnToProtocolData(httpbase);
            ;
        }

适当地修改一处程序问题解决.

吐能力提高但CPU占用资源过高

经过上面程序的修复4连接的秒吞吐能力由原来每秒150左右,上升到每秒2100/秒.得到的效果是非常的明显的,但总的来说CPU占用资源还有点过高,为了验证上一次修复的问题又进行了一次内存分析,分析的结果如下:

分析说明了一个问题由于Assembly.GetName()导致了大量的string创建,代码如下:

1
value = string.Format("{0},{1}={2}", type.FullName, type.Assembly.GetName().Name, JsonConvert.SerializeObject(Message));

适当地修改一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
value = string.Format("{0}={1}", GetTypeName(type), JsonConvert.SerializeObject(Message));
private static System.Collections.Hashtable mNames = new System.Collections.Hashtable(1024);
private static string GetTypeName(Type type)
{
    string name = (string)mNames[type];
    if (name == null)
    {
        lock (mNames)
        {
            name = (string)mNames[type];
            if (name == null)
            {
                name = string.Format("{0},{1}", type.FullName, type.Assembly.GetName().Name);
                mNames[type] = name;
            }
        }
    }
    return name;
}

经过以上的进一步优化后,服务的效果算是比较理想,完全满足了现阶段的需要.

意外的发现

从测试分析的结果看来,Newtonsoft.Json这个组件还有针对性优化的空间

针对以上分析,Newtonsoft.Json存在的问题应该如何优化,那就要留大家思考了.

总结

其实很多朋友喜欢通过CPU计时来看程序的快慢,但得到的结果只能说明问题,但对于如何解 决这些问题,测试运行时间的结果似乎起不到什么作用.其实.NET程序有一个东西往往没有得到关注,MS在一些文档中要强调我们没有必要关心,这个东西其 实就是GC!的确我们对GC的工作直接可控性是没有,但有一点可以肯定的就是GC的工作由对象的创建导致,如果想控制GC我们最好是从设计层面上控制对象 的创建,这是最直接有效的办法.

[转载]Eclipse+PyDev+Django+Mysql搭建Python web开发环境 - 海 子 - 博客园

mikel阅读(1027)

[转载]Eclipse+PyDev+Django+Mysql搭建Python web开发环境 – 海 子 – 博客园.

Eclipse+PyDev+Django+MySQL搭建Python web开发环境

Python的web框架有很多,目前主流的有Django、Tornado、Web.py等,最流行的要属Django了,也是被大家最看好的框架之一。下面就来讲讲如何搭建Django的开发环境。

一.准备工作

需要下载的软件:

JDK:官网下载地址:http://www.oracle.com/technetwork/java/javase/downloads/jdk6downloads-1902814.html

Python安装包:我用的是python 2.6.6的安装包,官网下载地址是:http://www.python.org/download/releases/

Eclipse:我用的是Eclipse 3.7.2 for java ee developers版本的,官网下载地址是:

http://www.eclipse.org/downloads/packages/release/indigo/sr2

Pydev:http://sourceforge.net/projects/pydev/files/

Django:https://www.djangoproject.com/download/

MySQLhttp://www.mysql.com/downloads/mysql/

MySQL-Python:http://www.lfd.uci.edu/~gohlke/pythonlibs/

二.配置过程

我的环境是win xp,32位系统。

1.安装JDK

在JDK官网http://www.oracle.com/technetwork/java/javase/downloads/jdk6downloads-1902814.html选择合适的版本,注意32位和64位的区别。

注意安装好JDK好之后,要配置一下环境变量,具体如何安装JDK请参考我之前的文章

http://www.cnblogs.com/dolphin0520/archive/2012/03/08/2385309.html

2.安装Python

Python目前出了3.x版本的,个人觉得最好还是用2.6或者2.7版本的,2.x版本的第三方库比较多,也相对稳定。

安装好Python之后,需要配置一下环境变量,具体安装方法参考:

http://www.cnblogs.com/dolphin0520/archive/2013/03/05/2943747.html

3.安装Eclipse

Eclipse的安装包下载下来解压即可使用。

4.在Eclipse中配置Pydev

Pydev是一个Eclipse插件,使得可以在Eclipse中编写Python程序。下载好Pydev的安装包之后,解压可以看到 features和plugins两个文件夹。在Eclipse根目录下建立一个文件夹Pydev,把解压得到的两个文件夹放到Pydev文件夹下,然后 在Eclipse根目录下建立一个名为links的文件夹(若links文件夹存在则不用新建),在links文件夹下建立一个文件pydev.ini, 用记事本打开,将   path=Pydev 添加进去保存关闭即可。然后打开Eclipse,选择Window -> Preferences -> Pydev- > Interpreter-Python,然后在右侧的“Python Interpreters”面板中选择New,在弹出的对话框的 “Interpreter Name”写Python,在“Interpreter Executable”中定位到python.exe的地址,然后一直选择“ok”即可:

这里介绍的是离线安装Pydev的方法,当然可以在线进行安装,读者可以自行百度在线安装Pydev的方法。

5.安装Django

在官网https://www.djangoproject.com/download/下载了Django的安装包之后,先进行解压,然后在在cmd下进入解压的文件目录下,运行命令python setup.py install,安装完毕之后,需要配置一下环境变量,一般情况下,安装好的Django在Python安装目录的

Lib\site-packages下,然后将D:\Program Files\Python26\Lib\site-packages\django\bin;添加到系统环境变量path中(注意路径值根据个人安装情况 而定)。添加成功之后关闭cmd。再重新启动一个cmd,输入命令django-admin.py startproject mysite,如果没有提示错误,则表示安装成功。

6.安装Mysql

具体安装过程可参照:http://wenku.baidu.com/view/49b110c7bb4cf7ec4afed083.html

7.安装Mysql-Python

Mysql-Python是Python连接Mysql的接口,在http://www.lfd.uci.edu/~gohlke/pythonlibs/下载相应的安装文件安装完毕,打开cmd,输入python,然后输入import _mysql和import MySQLdb,若没有提示错误,则表示安装成功。

[转载]5张表格教会你优化B2C盈利 – i天下网商-最具深度的电商知识媒体

mikel阅读(881)

[转载]5张表格教会你优化B2C盈利 – i天下网商-最具深度的电商知识媒体.

文/张陈勇

 

摸清商品结构

小丁他们得到一笔投资,创建一个区域综合B2C。这个B2C主营超市商品,但到底要经营哪些品类,各品类下需要哪些商品,各品类商品所占比例,如何跟踪商品招商情况,小丁毫无头绪,问我怎么办。

“你需要做一张商品维度表。”我说。

这是一份商品分类分维度的表格,比如花生属于坚果这个品类,花生有味道、外形、规格、价格等多个维度。它能帮助我们跟踪商品招商情况,优化商品结构,甚至增加收益。

那么如何建立“商品维度表”?

第一步是罗列出准备经营的商品品类,以及每个小类下的重要维度(如表1)。准备此表格时,可以参考各大网站的分类与导航字段。

第二步是把招商到的商品填入商品维度表中(见表2)。

第三步,当我们填入足够多商品时,就可以使用“数据透视表”功能或SQL语句提取报表,了解商品引入情况(见表3)。

“我明白了。”小丁说,“通过这些表格,我就能知道各品类下有多少商品,每个品类各维度商品多少。如果某个品类和维度下商品过多,则限制引进新品;如果过少,则要重点引进。”

“是的。”我回答,“我们还可以通过此表格控制SKU总数,根据总库存资金分配每个品类下的SKU数量,对每个品类设定SKU范围指标。小品类的每 一个维度代表了一种需求,单个商品有多个维度,引进一些多维度商品,就能用较少的SKU,满足更多元的需求,这样就能在不影响用户体验的情况下降低库存资 金。”

小丁说:“这个表格是挺好的,不过工作量也很大,投入这样多人力时间是否值得?”

我说:“商品维度表还有其他用处,它伴随整个项目经营过程,在优化商品结构、提高盈利上也有大用,到时候你就知道了。”

 

维度表里挖效益

几个月后,网站上线正常运营,小丁发现,一方面顾客反映商品不够丰富,找不到所需商品,另一方面不少商品出现滞销,库存占用不少资金,于是来询问我商品维度表是否能解决这个问题。

“当然可以,你需要在报表中加入SKU占比和销售占比字段,如表4。”

“在表4中,花生SKU占比0.13%,销量占比0.21%,销量占比大于SKU占比。如果同类商品(比如瓜子)的SKU和销量占比相差没有这么大,则说明消费者欢迎花生,花生的品类太少了,应该增加。”

“从商品维度上看,0—100g规格的花生SKU占比达到80%,但销量占比才46%,说明0—100g的规格的花生太多了,应该减少,相应增加其他规格的花生单品。”

“这太好了,长此以往,商品结构会越来越优化,降低滞销库存,增加周转。”

“不仅如此,商品维度表还能帮助你提升收益。你看表5。”

小丁说:“这个表和上一个表差不多啊,只是把销量占比改成了贡献占比。”

我回答道:“是的,上一个表的目的,是优化商品结构,从而符合消费者需求,所以从商品销量维度比较。而这一个表的目的是为了提升收益,所以要从贡献度考虑。”

“贡献占比是什么,怎么算出的?”

“贡献度占比=(某段时间毛利-固定成本)/整体毛利。”我在纸上写了下来,“不同商品毛利率、周转率不同。B2C固定成本(配送包装成本)高于线 下卖场。因为线下卖场的主要成本(房租、人力、水电)随销量增加分摊降低,而B2C即使销量增加,固定成本分摊降低空间也很小。贡献率考虑了以上情况。”

小丁说:“明白了,根据贡献率占比和SKU占比数据分析,增加高贡献率商品,减少低贡献率商品,这样整体收益就会增加。”

我回答:“是的,但分析数据时,还要考虑其他情况,比如某些敏感商品,虽然毛利率很低,贡献率不高,但如果不经营,用户就会流失,因此是不能停售的。”

“一般而言,A类敏感商品(吸引商品)和维度结构商品是不能缺少的。维度结构商品的受众小,但如果欠缺,部分用户就会认为商品不够丰富。毕竟商品结构不管如何优化,2/8原则总是有效的,并不是所有销量少的商品都可以精简。”

我继续说道:“还需考虑季节因素。有些商品贡献度突然下滑,可能只是季节问题,并不代表此商品永远不受欢迎。商品维度表为我们提供数据基础,分析时要结合实际经验和行业特点。”

“商品维度表”是商品结构优化的基础表格,特别适合品类多样、SKU丰富的零售业态,线上线下都可使用。线下零售更多使用ABC分析法,一般只分析 品类,不分析品类维度。线上业态比线下有更海量商品,更全的数据基础(维度数据),更智能的分析工具,所以总体而言,商品维度表对线上业态有更大作用。

(作者微博:@张陈勇)

[转载]Jquery 操作Cookie - 飞舞的蒲公英 - 博客园

mikel阅读(1414)

[转载]Jquery 操作Cookie – 飞舞的蒲公英 – 博客园.

关于cookie的path设置需要注意,如果不设置path:’/’的话,path则会根据目录自动设置[如:http://www.xxx.com/user/,path会被设置为 ‘/user’]

$.extend({
 
/**
 1. 设置cookie的值,把name变量的值设为value  
example $.cookie(’name’, ‘value’);
 2.新建一个cookie 包括有效期 路径 域名等
example $.cookie(’name’, ‘value’, {expires: 7, path: ‘/’, domain: ‘jquery.com’, secure: true});
3.新建cookie
example $.cookie(’name’, ‘value’);
4.删除一个cookie
example $.cookie(’name’, null);
5.取一个cookie(name)值给myvar
var account= $.cookie('name');
**/
    cookieHelper: function(name, value, options) {
        if (typeof value != 'undefined') { // name and value given, set cookie
            options = options || {};
            if (value === null) {
                value = '';
                options.expires = -1;
            }
            var expires = '';
            if (options.expires && (typeof options.expires == 'number' || options.expires.toUTCString)) {
                var date;
                if (typeof options.expires == 'number') {
                    date = new Date();
                    date.setTime(date.getTime() + (options.expires * 24 * 60 * 60 * 1000));
                } else {
                    date = options.expires;
                }
                expires = '; expires=' + date.toUTCString(); // use expires attribute, max-age is not supported by IE
            }
            var path = options.path ? '; path=' + options.path : '';
            var domain = options.domain ? '; domain=' + options.domain : '';
            var secure = options.secure ? '; secure' : '';
            document.cookie = [name, '=', encodeURIComponent(value), expires, path, domain, secure].join('');
        } else { // only name given, get cookie
            var cookieValue = null;
            if (document.cookie && document.cookie != '') {
                var cookies = document.cookie.split(';');
                for (var i = 0; i < cookies.length; i++) {
                    var cookie = jQuery.trim(cookies[i]);
                    // Does this cookie string begin with the name we want?
                    if (cookie.substring(0, name.length + 1) == (name + '=')) {
                        cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                        break;
                    }
                }
            }
            return cookieValue;
        }
    }
 
}); 

[原创]谷歌浏览器Chrome下jQuery的position().left取不到值问题

mikel阅读(1203)

最近要做一个鼠标滑过显示弹出层,鼠标离开后隐藏的功能,在ie和firefox下用JQuery的position().left层的定位没有问题,但是在Chrome下的position().left无效,只能用offset().left获取坐标位置显示层,代码如下:


        $(function() {
            $('#topshop').mouseover(function() {
                var x = $(this).position();
                var markDiv = $("#babyNew_sortBody");
                if (x.left == 0) {//chrome下left=0
                    markDiv.css("left", $(this).offset().left);
                } else {
                    markDiv.css("left", $(this).position().left);
                }
                markDiv.css("top", $(this).position().top + 40);
                markDiv.css('display', 'block');
            });
            $('#babyNew_sortBody').mouseleave(function() {
                $("#babyNew_sortBody").attr('style', 'display:none;');
            });
        });

[转载]jQuery 事件 - mouseleave() 方法

mikel阅读(1036)

[转载]jQuery 事件 – mouseleave() 方法.

实例

当鼠标指针离开元素时,改变元素的背景色:

$("p").mouseleave(function(){
  $("p").css("background-color","#E9E9E4");
});

亲自试一试

定义和用法

当鼠标指针离开元素时,会发生 mouseleave 事件。

该事件大多数时候会与 mouseenter 事件一起使用。

mouseleave() 方法触发 mouseleave 事件,或规定当发生 mouseleave 事件时运行的函数。

注释:与 mouSEOut 事件不同,只有在鼠标指针离开被选元素时,才会触发 mouseleave 事件。如果鼠标指针离开任何子元素,同样会触发 mouSEOut 事件。请看下面例子的演示。

亲自试一试:mouseleave 与 mouseout 的不同

触发 mouseleave 事件

语法

$(selector).mouseleave()

亲自试一试

将函数绑定到 mouseleave 事件

语法

$(selector).mouseleave(function)
参数 描述
function 可选。规定当发生 mouseleave 事件时运行的函数。

亲自试一试

[原创]IE6下wbox弹出iframe窗口加载页面空白问题解决

mikel阅读(1058)

最近项目测试中发现在用wbox弹出iframe窗口中,ie6下页面加载失败导致空白页面,可是查看页面源码发现页面只是加载了一部分,没有加载完成就停止了,去网上搜了下关于iframe的ie6下的兼容性问题,[转载]ie6下的iframe,问题多多。 – izumi – 博客园中介绍了有8种之多,哦买噶的!ie6你让我情何以堪!于是一一测试,终于在ie6弹出层加载完成空白后,再右键-刷新页面,页面出现了!于是对照[转载]ie6下的iframe,问题多多。 – izumi – 博客园中的第8条,需要加载两次iframe的src,于是对wbox进行了修改了,加入了getframe方法,然后调用页面showbox()后根据浏览器版本调用getframe进行重复加载

wbox.js代码如下:

        this.getFrame = function() {
            return $(this).find('iframe[name=wBoxIframe]');
        }

调用页面代码:

        wbox.showBox();
        var ifr = wbox.getFrame();//获取wbox中的iframe

        if (getbrowser()) {
            ifr.load();//调用加载事件,重绘窗口的标题样式
            document.frames['wBoxIframe'].location.reload();//iframe对象重新加载url
        }