Category Archives: Codes

终结了3月

上篇博客是在2月底, 现在俨然已经到了4月初了. 时间过的简直是太TMD快. 北京外面下了小雨, 我还是不喜欢下雨我发现, 弄的满身很湿很难受, 不过听别人说南方的夏天貌似只要在外面就是湿的, 我就在想, 那得是一种什么状态啊… 还好我暂时在北方.

最近发现iOS上的网络应用程序的架构越来越清晰了, 也是这几天经常半夜想代码想出来的, 再加上高人指点, 于是这个整体的异步下载层架构就被我这么想明白了, 而且程序走的流程很清晰, 不会像原来搞的那么乱, NSNotification还是要善用的, 虽然用多了会产生一些恶心的结果. 最终还是把Model变成了很胖, Controller却轻松了不少. 原来可能1000+行的Controller现在也就不过300行. 不过今天着实被NSPredicate的语法恶心了一样, 虽说很久不用SQL语句了, 但是在Core Data中使用这种每个类库语法都不一样的查询语句着实是很麻烦, 而且我看官方文档上明确写着, SQL是不可能转换成NSPredicate的, 至少官方不提供这种方法. 我前些日子在Github上发现了一个Core Data + ActiveRecords的东西, 甚是好用, 虽说作者说是灵感来自Ruby, 不过我记得我很久之前写.NET的时候也见过类似的类库.

最近每个周六都要加班, 但是明天算是正常上班, 我终于迎来了一个小长假了… 我这些日子的感觉就是, 我快被逼死了, 我快崩溃了, 不过还好, 就这么就过来了. 最近貌似所有人的心情都不是很好, 估计也就这些日子, 这个波折的3月, 终将终结这一堆浮云的事情.

How to Make a Very Fast TableView on Mac with Variant Cell Height

I recently started my project to build my own Sina Weibo (a Chinese Twitter clone) app for the Mac. I started with NSTableView just like how I usually deal with UITableView on iOS programming, but I finally found that NSTableView is very complex and it’s completely different with UITableView, especially for custom cells and variant cell height. I have to use NSCell subclass instead of NSView subclass in NSTableView. Luckily, I then found an article Making Cocoa List Views Really Fast which talks about the performance issue about NSTableView. The author of the article wrote his own implementation of NSTableView, just like a UITableViewPXListView.

The benefit of using PXListView is that I can finally use NSViews as my cell instead of NSCells, so that you can construct your cells very easily just like how you make a custom UITableViewCell on iOS. However, I still have many issues when using this framework. Here is the thing:

My app is a Twitter-like app, so I need to show the Tweet and the Retweet in one cell, and the height of my cell is some paddings + Tweet NSTextView height + Retweet NSTextView height, which is based on the width of my PXListView, that is, when I changed the width of my PXListView, the height of each cell will be changed.

I then found the NS(Attributed)String+Geometrics category by Sheep Systems, which solved some of my problems. But it is way too slow because I have to calculate the height of NSTextView twice – in PXListView delegate listView:heightOfRow and my custom cell’s drawRect method.

After I looked closely through the category by Sheep Systems, I found that it used NSTextContainer, NSTextStorage, NSLayoutManager to calculate the height of the NSTextView with a fixed width. Every time the method was called, I have to alloc, init, and finally release them. It was just a waste of memory and time. I then suddenly found that all the three necessary classes to calculate the text height are just right in an NSTextView instance. So I rewrote the category to this:

@implementation NSTextView (Geometrics)

- (CGFloat)heightForWidth {

    if ([[self textStorage] length] > 0) {

		// Checking for empty string is necessary since Layout Manager will give the nominal
		// height of one line if length is 0.  Our API specifies 0.0 for an empty string.

		// NSLayoutManager is lazy, so we need the following kludge to force layout:
		[self.layoutManager glyphRangeForTextContainer:self.textContainer];

		return [self.layoutManager usedRectForTextContainer:self.textContainer].size.height;
	}

	return 0;
}

@end

It’s very clean now and have a great performance. Another thing is that I still have to call it 2 times when the cell is scrolling. To solve this, I wrote a TextViewHeightCache, which will cache the height of the given text with a fixed width. Next time PXListView asked for the height, the TextViewHeightCache first checked wether or not the height of the provided width is available in the cache. If we found it, just return the corresponding height, otherwise, recalculate it.

#import "NSTextView+Geometrics.h"

@implementation SNTextViewHeightCache

- (id)init {

	if ((self = [super init])) {
		textView = [[NSTextView alloc] init];
		textViewSizes = [[NSMutableDictionary alloc] init];
	}

	return self;
}

- (CGFloat)heightForText:(NSAttributedString *)text withWidth:(CGFloat)width {

	NSValue *sizeValue = [textViewSizes objectForKey:text];
	if (sizeValue) {
		NSSize size = [sizeValue sizeValue];
		if (size.width == width) {
			return size.height;
		}
	}

	[[textView textStorage] setAttributedString:text];
	[textView setFrameSize:CGSizeMake(width, 0)];

	CGFloat height = [textView heightForWidth];

	[textViewSizes setObject:[NSValue valueWithSize:CGSizeMake(width, height)] forKey:text];

	return height;
}

- (void)dealloc {

	[textView release];
	[textViewSizes release];

	[super dealloc];
}

@end

Now we have a very fast table view with variant cell height, completely without any knowledge of NSTableView.

C# Reflection

这两天弄竞赛的事情, 于是就遇到一件非常恶心人的事. 就是我们有20+张表, 但是我们每一个表都要有查询, 有的还要有增加和修改. 每个表又有N张字段, 最悲剧的事情是比赛时不能带任何工具, 于是每个表的增删改查都变成了非常繁琐的工作. 于是我就想到了Cocoa里的Key-Value Coding, 我想C#里应该也有差不多的东西, 对那就是和Java一个名字的Reflection!

现在只需要在实体类里把属性的名字和数据库的字段的名字一一对应, 就可以直接用一个函数全都读出来, 省了原来那些没必要的构造函数和从数据库里每读出来一个字段就要写一行代码的过程.

原来要写这样的代码:

string sql = "select * from contacts where u_id=@ContactID";

SqlParameter[] sqlParameters = new SqlParameter[1] {
	new SqlParameter("@ContactID", contactID),
};

DataSet ds = DBManager.DBQuery.ExcuteQuery(sql, sqlParameters);

if (ds == null) {
	return null;
}

int count = ds.Tables[0].Rows.Count;

if (count > 0) {

	Contact contact = new Contact(ds.Tables[0].Rows[0]["u_id"].ToString(),
								ds.Tables[0].Rows[0]["u_name"].ToString(),
								ds.Tables[0].Rows[0]["u_tel"].ToString(),
								ds.Tables[0].Rows[0]["u_email"].ToString(),
								ds.Tables[0].Rows[0]["u_photo"].ToString(),
								ds.Tables[0].Rows[0]["u_accountID"].ToString());

	return contact;
}

return null;

现在只需要这样一个函数, 就能把传进去的类型对应的全部字段读取出来:

public static void CopyProperties(Object obj, DataSet ds)
{
	Type type = obj.GetType();
	PropertyInfo[] pi = type.GetProperties();

	foreach (PropertyInfo item in pi)
	{
		Object value = ds.Tables[0].Rows[0][item.Name.ToLower()];
		if (!string.IsNullOrEmpty(value.ToString()))
		{
			item.SetValue(obj, value, null);
		}
	}
}

调用时只需要这样:

public static StudentsContact StudentsContactByID(string studentID)
{
	string sql = "select * from StudentsContacts where s_id=@StudentID";

	SqlParameter[] sqlParameters = new SqlParameter[1] {
		new SqlParameter("@StudentID", studentID),
	};

	DataSet ds = DBManager.DBQuery.ExcuteQuery(sql, sqlParameters);

	int count = ds.Tables[0].Rows.Count;

	if (count > 0)
	{
		StudentsContact studentContact = new StudentsContact();

		Tools.CopyProperties(studentContact, ds);

		return studentContact;
	}

	return null;
}