0%

1 copy和mutableCopy

NSObject类有两个跟拷贝相关的方法——copymutableCopy。这两个方法都是返回一个id类型的对象,那么这两者之间有什么区别呢?根据官方文档解释,copy方法,返回copyWithZone方法返回的对象(Returns the object returned by copyWithZone:)。而mutableCopy方法,返回mutableCopyWithZone方法返回的对象(Returns the object returned by mutableCopyWithZone:)。读起来有点绕,一言以蔽之,调用copy就是调用copyWithZone,调用mutableCopy就是调用mutableCopyWithZone还是不够清楚!!!接下来我们以NSString为例子,来说明copymutableCopy的区别。

1.1 NSString对象调用copy和mutableCopy

那么对NSString对象调用copy和mutableCopy究竟有什么效果呢?
在实验之前先定义如下通用宏

1
2
3
4
5
6
//打印方法名
#define LOG_METHOD_NAME {NSLog(@"%@", NSStringFromSelector(_cmd));}
//打印对象的类名,以及对象本身的地址
#define LOG_OBJ_ADDRESS(obj) {NSLog(@"%@ : %p",NSStringFromClass([obj class]), obj);}
//打印空行
#define LOG_END {NSLog(@"%@", @" ");}

测试代码如下:

1
2
3
4
5
6
7
8
9
10
11
- (void)stringCopyTest
{
LOG_METHOD_NAME
NSString *str = @"Hello";
LOG_OBJ_ADDRESS(str);
NSString *cpStr = [str copy];
LOG_OBJ_ADDRESS(cpStr);
NSMutableString *mutCpStr = [str mutableCopy];
LOG_OBJ_ADDRESS(mutCpStr);
LOG_END
}

输出结果如下:

1
2
3
4
5
2015-11-22 19:58:05.738 CopyTest[17230:1045019] stringCopyTest
2015-11-22 19:58:05.738 CopyTest[17230:1045019] __NSCFConstantString : 0x100fee0b0
2015-11-22 19:58:05.738 CopyTest[17230:1045019] __NSCFConstantString : 0x100fee0b0
2015-11-22 19:58:05.738 CopyTest[17230:1045019] __NSCFString : 0x7fabbaa00190
2015-11-22 19:58:05.738 CopyTest[17230:1045019]

由以上输出可知,对一个NSString对象调用copy返回的还是该对象本身,因为str的地址和cpStr的地址是同一个。而调用mutableCopy,返回的是一个NSMutableString对象(注:__NSCFConstantString是常量串即NSString,而__NSCFString是可变串即NSMutableString)。

1.2 NSMutableString对象调用copy和mutableCopy

同理,引申到NSMutableString对象呢?
测试代码如下:

1
2
3
4
5
6
7
8
9
10
11
- (void)mutableStringCopyTest
{
LOG_METHOD_NAME
NSMutableString *mutStr = [@"OC" mutableCopy];
LOG_OBJ_ADDRESS(mutStr);
NSMutableString *cpMutStr = [mutStr copy];
LOG_OBJ_ADDRESS(cpMutStr);
NSMutableString *mutCpMutStr = [mutStr mutableCopy];
LOG_OBJ_ADDRESS(mutCpMutStr);
LOG_END
}

输出结果如下:

1
2
3
4
5
2015-11-22 19:58:05.738 CopyTest[17230:1045019] mutableStringCopyTest
2015-11-22 19:58:05.738 CopyTest[17230:1045019] __NSCFString : 0x7fabba9012c0
2015-11-22 19:58:05.739 CopyTest[17230:1045019] NSTaggedPointerString : 0xa0000000000434f2
2015-11-22 19:58:05.739 CopyTest[17230:1045019] __NSCFString : 0x7fabb840a860
2015-11-22 19:58:05.739 CopyTest[17230:1045019]

由以上输出可知,对一个NSMutableString对象调用copy返回的是一个NSTaggedPointerString对象,该对象可认为是一个常量串。而调用mutableCopy返回的是另外一个可变对象__NSCFString,即NSMutableString(原NSMutableString对象的地址是0x7fabba9012c0,新NSMutableString对象地址是0x7fabb840a860)。

针对NSArray、NSDictionary、NSSet等具有Mutable版本的类进行试验出现跟NSString类似的现象,不一一列举,有兴趣可以自己去试验。

1.3 copy和mutableCopy调用小结

  • 针对不可变对象调用copy返回该对象本身,调用mutableCopy返回一个可变对象(新的);
  • 针对可变对象调用copy返回一个不可变对象(新的),调用mutableCopy返回另外一个可变对象(新的)。
class copy mutableCopy
不可变(如,NSString) 返回本身(其他什么都不干) 创建新的可变对象(如,创建一个NSMutableString对象,地址跟原对象不同)
可变(如,NSMutableString) 创建新的不可变对象(如,创建一个NSTaggedPointerString对象,地址跟原对象不同 ) 创建新的可变对象(如,创建一个NSMutableString对象,地址跟原对象不同 )

再进一步从是否新建返回对象,返回对象是否可变两个角度总结如下:

  • 只有不可变的copy是返回本身(其他什么都不干),其他都是创建一个新对象;
  • copy返回的是不可变对象,mutableCopy返回的是可变对象。

2 属性copy还是strong?

假设有两个id类型的属性如下:

1
2
@property (nonatomic, copy) id cpID;
@property (nonatomic, strong) id stID;

那么编译器把以上两属性分别实现为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- (void)setCpID:(id)cpID {
_cpID = [cpID copy];
}

- (id)cpID {
return _cpID;
}

- (void)setStID:(id)stID {
_stID = stID;
}

- (id)stID {
return _stID;
}

从以上实现可以看出,strong和copy的属性主要是set方法有区别,strong的set是直接设置指定值,而copy的set是设置指定值的copy版本。接下来探索一下NSString、NSMutableString的copy和strong属性。

2.1 NSString属性copy还是strong?

测试代码如下:

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
@property (nonatomic, copy) NSString *cpStr;
@property (nonatomic, strong) NSString *stStr;

- (void)stringPropertyTest
{
LOG_METHOD_NAME
NSMutableString *mutStr = [@"123" mutableCopy];
LOG_OBJ_ADDRESS(mutStr);
self.cpStr = mutStr;
LOG_OBJ_ADDRESS(self.cpStr);
self.stStr = mutStr;
LOG_OBJ_ADDRESS(self.stStr);

NSLog(@"修改前");
NSLog(@"mutStr:%@", mutStr);
NSLog(@"copy:%@", self.cpStr);
NSLog(@"strong:%@", self.stStr);

[mutStr appendString:@"456"];
NSLog(@"修改后");
NSLog(@"mutStr:%@", mutStr);
NSLog(@"copy:%@", self.cpStr);
NSLog(@"strong:%@", self.stStr);

LOG_END
}

输出结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
2015-11-22 21:11:29.458 CopyTest[17977:1317284] stringPropertyTest
2015-11-22 21:11:29.458 CopyTest[17977:1317284] __NSCFString : 0x7fad23409b80
2015-11-22 21:11:29.458 CopyTest[17977:1317284] NSTaggedPointerString : 0xa000000003332313
2015-11-22 21:11:29.458 CopyTest[17977:1317284] __NSCFString : 0x7fad23409b80
2015-11-22 21:11:29.458 CopyTest[17977:1317284] 修改前
2015-11-22 21:11:29.458 CopyTest[17977:1317284] mutStr:123
2015-11-22 21:11:29.458 CopyTest[17977:1317284] copy:123
2015-11-22 21:11:29.458 CopyTest[17977:1317284] strong:123
2015-11-22 21:11:29.458 CopyTest[17977:1317284] 修改后
2015-11-22 21:11:29.458 CopyTest[17977:1317284] mutStr:123456
2015-11-22 21:11:29.459 CopyTest[17977:1317284] copy:123
2015-11-22 21:11:29.459 CopyTest[17977:1317284] strong:123456
2015-11-22 21:11:29.459 CopyTest[17977:1317284]

由以上输出可知,假设两个NSString属性实际上指向的都是一个NSMutableString对象,那么在原NSMutableString对象修改后,strong版本的NSString属性跟着修改,而copy版本属性保持原状。self.cpStr实际上是一个NSTaggedPointerString对象,该对象正是NSMutableString对象执行copy的返回值。

2.2 NSMutableString属性copy还是strong

测试代码如下:

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
@property (nonatomic, copy) NSMutableString *cpMutStr;
@property (nonatomic, strong) NSMutableString *stMutStr;

- (void)mutableStringPropertyTest
{
LOG_METHOD_NAME
NSMutableString *mutStr = [@"123" mutableCopy];
LOG_OBJ_ADDRESS(mutStr);
self.cpMutStr = mutStr;
LOG_OBJ_ADDRESS(self.cpMutStr);
self.stMutStr = mutStr;
LOG_OBJ_ADDRESS(self.stMutStr);

NSLog(@"修改前");
NSLog(@"mutStr:%@", mutStr);
NSLog(@"copy:%@", self.cpMutStr);
NSLog(@"strong:%@", self.stMutStr);

[mutStr appendString:@"456"];
NSLog(@"修改后");
NSLog(@"mutStr:%@", mutStr);
NSLog(@"copy:%@", self.cpMutStr);
NSLog(@"strong:%@", self.stMutStr);
LOG_END
}

输出结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
2015-11-22 21:48:21.774 CopyTest[18508:1539502] mutableStringPropertyTest
2015-11-22 21:48:21.774 CopyTest[18508:1539502] __NSCFString : 0x7f806879b620
2015-11-22 21:48:21.774 CopyTest[18508:1539502] NSTaggedPointerString : 0xa000000003332313
2015-11-22 21:48:21.774 CopyTest[18508:1539502] __NSCFString : 0x7f806879b620
2015-11-22 21:48:21.774 CopyTest[18508:1539502] 修改前
2015-11-22 21:48:21.774 CopyTest[18508:1539502] mutStr:123
2015-11-22 21:48:21.774 CopyTest[18508:1539502] copy:123
2015-11-22 21:48:21.774 CopyTest[18508:1539502] strong:123
2015-11-22 21:48:21.775 CopyTest[18508:1539502] 修改后
2015-11-22 21:48:21.775 CopyTest[18508:1539502] mutStr:123456
2015-11-22 21:48:21.775 CopyTest[18508:1539502] copy:123
2015-11-22 21:48:21.775 CopyTest[18508:1539502] strong:123456
2015-11-22 21:48:21.775 CopyTest[18508:1539502]

看起来没啥问题,strong版本的属性跟随原对象的变化而变化,copy版本的属性不变。但是,假设调用

1
[self.cpMutStr appendString:@"789"];

程序会崩溃。崩溃信息如下:

1
2
2015-11-22 21:51:37.282 CopyTest[18542:1545579] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[NSTaggedPointerString appendString:]: unrecognized selector sent to instance 0xa000000003332313'
*** First throw call stack:

原因很明显,是朝NSTaggedPointerString对象发了一个它不能识别的selector。原因是copy版本的NSMutableString属性本质上不是一个NSMutableString对象,而是一个NSTaggedPointerString对象,它是一个不可变对象。该对象是NSMutableString对象执行copy得来的,还记得我们上一节的结论吗?对一个对象执行copy得到的用于是一个不可变的对象。

针对NSArray、NSDictionary、NSSet等具有Mutable版本的类进行试验出现跟NSString类似的现象。

2.3 结论

  • 不可变类型属性,推荐使用copy,因为假设该对象实际上指向的是一个mutable的对象,mutable对象的改变不会导致该对象的改变;假设指向的不是mutable的对象,那么copy和strong是等价,因为对一个不可变对象调用一次copy什么事情都不发生,_cpID = [cpID copy] 等价于_cpID = cpID。
  • 可变类型属性,不能使用copy,因为copy产生的对象是一个不可变对象,跟属性描述是冲突的。

1UIViewController生命周期函数勿忘调用相应的super函数

背景

当我们创建一个UIViewController类的对象时,通常系统会生成几个默认的方法,这些方法大多与视图的调用有关。

通常上述方法包括如下几种,这些方法都是UIViewController类的方法:

1
2
3
4
5
6
- (void)viewDidLoad;
- (void)viewDidUnload;
- (void)viewWillAppear:(BOOL)animated;
- (void)viewDidAppear:(BOOL)animated;
- (void)viewWillDisappear:(BOOL)animated;
- (void)viewDidDisappear:(BOOL)animated;

详见UIViewController的生命周期及iOS程序执行顺序

iOS工程有一种常见的工程实践方法——项目中做一个base UIViewController,假设为BaseViewController。之后,所有页面控制器不要直接继承UIViewController,而是继承BaseViewController。BaseViewController针对所有的页面控制器做一些通用设置。比如:设置navigation bar的颜色,设置navigation bar的默认按钮,通用事件统计(如,某个页面控制器的展示次数),通用全局手势,通用内存警告处理等。

现象

某次调试发现自定义的页面控制器并未正确设置navigation bar的颜色。代码如下:

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
@interface BaseViewController : UIViewController
@end

@implementation BaseViewController

- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];

//其他代码...

//设置导航条外观
[self setNavigationBarAppearance];

//其他代码...
}

- (void) hellWorld {
[super helloWo];
}

@end

@interface MyViewController : BaseViewController
@end

@implementation MyViewController

- (void)viewWillAppear:(BOOL)animated {
//[super viewWillAppear:animated];

//其他代码...
}

@end

解释

BaseViewController在**- (void)viewWillAppear:(BOOL)animated函数做了导航条外观的通用设置。而,MyViewController并未在- (void)viewWillAppear:(BOOL)animated**的时候调用其父类的相应函数,因此navigation bar的外观设置会出错。

解法

如果你自己实现了UIViewController生命周期相关函数,那么需要在该函数最开始调用其父类的相应函数。

2NSNotification问题

背景

NSNotification是iOS上多种事件通知机制的一种。常用于一对多的通知。

BaseViewController在处理非当前展示控制器收到的内存警告的时候总会把self.view设置为nil。代码如下:

1
2
3
4
5
6
7
8
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
if ([[UIDevice currentDevice].systemVersion floatValue] >= 6.0) {
if (self.isViewLoaded && !self.view.window) {
self.view = nil;
}
}
}

现象

某次发现,MyViewController收到@”FooNotification”通知时会多次调用*- (void)p_onFooNotification:(NSNotification )notify函数。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@interface MyViewController : BaseViewController
@end

@implementation MyViewController

- (void)viewDidLoad {
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(p_onFooNotification:)
name:@"FooNotification"
object:nil];
//其他代码...
}

- (void)p_onFooNotification:(NSNotification *)notify {
//其他代码
}

@end

解释

某次收到了内存警告,MyViewController正好不在界面上,(是底部controller)。BaseViewController的*- (void)didReceiveMemoryWarning会将self.view设置为nil。下次MyViewController显示的时候会再调用一次- (void)viewDidLoad。这时又添加了一次@”FooNotification”通知的观察。所以通知一旦发出,- (void)p_onFooNotification:(NSNotification )notify函数会被调用多次。

解法

1)了解**- (void)viewDidLoad**函数可能会被调用多次,只要self.view 是nil。在controller显示出来的时候就会调用该函数;

2)在controller中添加对通知的观察最好不要放在**- (void)viewDidLoad函数中;推荐做法是在- (void)viewDidAppear:(BOOL)animated中添加对通知的观察,- (void)viewDidDisappear:(BOOL)animated**中移除对通知的观察。例如NSNotification errors所作的解答。

1数字溢出

背景

写iOS代码经常需要在某处计算时间戳。通常时间戳是格林威治时间(即零时区时间)1970年1月1日0点至当前的毫秒数。例如北京时间(东八区)2015-7-4 10:35:6,的时间戳是1435977306000。就目前来说它是个13位数字。

现象

某种时间戳转为string的方式如下。

1
2
long st = [[NSDate date] timeIntervalSince1970] * 1000;
NSString *strSt = [@(st) stringValue];

用5s及以上的模拟器跑都能得到正确的结果。而用5s以下的模拟器跑都输出错误的值。

解释

产生该问题的原因如下:对于iOS的64位应用,long是8byte整数,涵盖-9223372036854775808 到 +9223372036854775807。而32位的iOS应用,long是4byte整数,涵盖-2147483648 到 +2147483647。32位下long最多只能表征10位正数,而我们的时间戳是13位的,显然数字溢出了。而对于64位的iOS应用long能表示19位正数,这是毫无压力的。详见iOS上应用如何兼容32位系统和64位系统

解法

我们观察到不管是IPL32规范还是IPL64规范,long long都是以8byte存储,能表征时间戳。所以此问题的正确姿势是用long long来存储时间戳。正确代码如下:

1
2
long long st = [[NSDate date] timeIntervalSince1970] * 1000;
NSString *strSt = [@(st) stringValue];

2NSDictionary使用不当

背景

iOS客户端跟服务器进行通信通常采用json格式。客户端从服务器上获取数据后,使用NSJSONSerialization把json串变为NSDictionary。早期,在不使用Mantle等第三方库做NSDictionary到Model转换的时候,我们需要自己去把Dic模型化。NSDictionary中的key是一个符合NSCopying协议的objc对象,通常我们用NSString作为Dic的key。NSDictionary中的value需要是一个objc对象。

现象

某次Dic模型化转换如下。其中,AppData是需要转换的模型。webType是一个BOOL量。发现该BOOL一直为YES。不受wap_type的控制。

1
2
3
4
5
6
7
AppData *appData = [[AppData alloc] initWithAppId:[dic[@"app_id"] integerValue]
iconURL:dic[@"app_icon"]
appName:dic[@"app_name"]
linkUrl:urlStr
webType:(BOOL)dic[@"wap_type"]
appTip:dic[@"app_desc"]
andAppMarks:appMarks];

解释

(BOOL)dic[@”wap_type”]

这种写法 ,是取出dic中@”wap_type”这个key的value,该value是一个objc对象,把该对象强制转换为BOOL类型。因此,只要dic中含有 @”wap_type”这个key对应的value的时候会是YES,否则是NO。它不会考虑这个value具体是YES还是NO。

解法

[dic[@”wap_type”] integerValue]

取出dic中@”wap_type”这个key的value,获取该value的integerValue。

3NSArray NSMutableArray相关问题

背景

NSArray和NSMutableArray是常用的objc容器,该容器常用于存储objc对象。

现象

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
//例1

NSArray *testAry = [[NSArray alloc] init] ;
id firstObj = [testAry objectAtIndex:0];
id lastObj = [testAry objectAtIndex:testAry.cout - 1];

//例2

@interface TestClass : NSObject

- (id)createObj;

@end

@implementation TestClass

- (id)createObj {
id obj = [[NSObject alloc] init];
return obj;
}

@end

// {外部调用代码
TestClass * __weak weakTestObj = someStrongTestObj;
[self foo:^{
NSMutableArray *ary = [[NSMutableArray alloc] init];
[ary addObject:[weakTestObj createObj]];
}];
// 外部调用代码}

例1

对一个空NSArray(注意空NSArray!!!不是nil NSArray,两者不是一样的东西),访问第一个和最后一个对象,都会抛出 NSRangeException。如果不抓异常,程序会崩溃。

例2

**[ary addObject:[weakTestObj createObj]]**这函数调用有时候会抛出NSInvalidArgumentException。如果不抓异常,程序会崩溃。

解释

对于例1,NSArray通过index访问对象,如果index超限那么会抛出异常。这种函数的函数名一般都有一个AtIndex。这一点是要记住的!!!

对于例2,weakTestObj是一个weak对象,在其指向的对象被销毁后,weakTestObj可能是nil。那么,[weakTestObj createObj]函数会返回nil,在此种情况下[ary addObject:[weakTestObj createObj]]可能会抛出异常。

解法

这里给出一些常用的解决方案:

1)index访问对象通过条件语句判断index是否在合理范围内。NSArray的第一个对象访问推荐用firstObject属性,最后一个对象访问推荐用lastObject属性。

2)给NSMutableArray添加对象要做nil判断;

3)用try catch包一下,设置异常处理逻辑。(终极杀招)

4NSString NSMutableString相关问题

背景

在项目中经常需要比较两个string是否相等,取某个string的sub string,对一个mutable string添加string。

现象

1
2
3
4
5
6
7
8
9
10
11
12
//例1
NSString *test = [@"123" mutableCopy] ;
NSLog(@"%@", test == @"123" ? @"相等" : @"不等");
NSLog(@"%@", [test isEqualToString:@"123"] ? @"相等" : @"不等");

//例2
NSMutableString *str = [@"123" mutableCopy];
[str appendString:nil];

//例3
NSString *str = [@"123" mutableCopy];
NSString *subStr = [str substringFromIndex:str.length + 1];

例1

输出:不等、相等

例2

抛出非法参数异常

例3

抛出范围异常

解释

例1,两个string比较其内容是否相同要用*- (BOOL)isEqualToString:(NSString )aString方法。不要直接用”==”操作符比较。”==”操作符比较的是操作符两边的oc对象是否是同一个oc对象,这个问题可以从c语言指针的角度来思考。而*- (BOOL)isEqualToString:(NSString )aString是类似于c语言的库函数**int strcmp(const char str1, const char str2)

例2和例3,其实跟上述NSArray NSMutableArray相关问题类似。访问之前判断范围,添加内容判断nil。

解法

1)NSString判等用*- (BOOL)isEqualToString:(NSString )aString

2)NSString和NSMutableString操作的时候要注意异常处理,一般含有Index的这种要注意范围异常,append这种要注意传参异常;当然终极杀招依旧是try catch

5表里不一,看起来是个NSString对象,实际上是其他对象

背景

由于项目原因,针对id号这种参数,早期服务器传参可能使用的是string。而服务器端代码重构后,是使用整数传参。

现象

1
2
3
4
5
6
7
8
9
10
NSString *topicId = dic[@"tId"]; //dic是NSJSONSerialization解析json串得到的字典

//判断topicId是否为空
if (![topicId length]) {
//对于一个NSString对象调用length判空可以判断两种情况,一种是该对象为nil,另一种是该对象是空串@""

} else {

}

调用上述代码后,程序会崩溃。

解释

由于服务端重构,把json中的string变成了int,所以*NSString topicId = dic[@”tId”]此处实际得到的是一个NSNumber对象,对NSNumber调用length方法会出现未知选择子崩溃。

解法

1)树立信念,不要相信服务器会100%传好数据给你,即使正确率有99.9%那剩下的0.1%就是异常;

2)从服务器上得到的数据总应该进行类型判断,具体做法是用**- (BOOL)isKindOfClass:(Class)aClass- (BOOL)isMemberOfClass:(Class)aClass**这两种方法;

3)用try catch包,做异常处理(终极解法)。

1引言

众所周知Block已被广泛用于iOS编程。它们通常被用作可并发执行的逻辑单元的封装,或者作为事件触发的回调。Block比传统回调函数有2点优势:

  1. 允许在调用点上下文书写执行逻辑,不用分离函数
  2. Block可以使用local variables.

基于以上种种优点Cocoa Touch越发支持Block式编程,这点从UIView的各种动画效果可用Block实现就可见一斑。而BlocksKit是对Cocoa Touch Block编程更进一步的支持,它简化了Block编程,发挥Block的相关优势,让更多UIKit类支持Block式编程。

2BlocksKit目录结构

BlocksKit代码存放在4个目录中分别是Core、DynamicDelegate、MessageUI、UIKit。其中:

  • Core 存放Foundation Kit相关的Block category
  • DynamicDelegate 动态代理(一种事件转发机制)相关代码
  • MessageUI 存放MessageUI相关的Block category
  • UIKit 存放UIKit相关的Block category

本文尝试去梳理BlocksKit的实现方式并列举BlocksKit使用的例子。

3Core相关代码分析和举例

Core文件夹下面的代码可以分为如下几个部分:

  1. 容器相关(NSArray、NSDictionary、NSSet、NSIndexSet、NSMutableArray、NSMutableDictionary、NSMutableSet、NSMutableIndexSet)
  2. 关联对象相关
  3. 逻辑执行相关
  4. KVO相关
  5. 定时器相关

3.1容器相关的BlocksKit

不管是可变容器还是不可变容器,容器相关的BlocksKit代码总体上说是对容器原生block相关函数的封装。容器相关的BlocksKit函数更加接近自然语义,有一种函数式编程和语义编程的感觉。

以下给出部分不可变容器的BlocksKit声明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//串行遍历容器中所有元素
- (void)bk_each:(void (^)(id obj))block;
//并发遍历容器中所有元素(不要求容器中元素顺次遍历的时候可以使用此种遍历方式来提高遍历速度)
- (void)bk_apply:(void (^)(id obj))block;
//返回第一个符合block条件(让block返回YES)的对象
- (id)bk_match:(BOOL (^)(id obj))block;
//返回所有符合block条件(让block返回YES)的对象
- (NSArray *)bk_select:(BOOL (^)(id obj))block;
//返回所有!!!不符合block条件(让block返回YES)的对象
- (NSArray *)bk_reject:(BOOL (^)(id obj))block;
//返回对象的block映射数组
- (NSArray *)bk_map:(id (^)(id obj))block;

//查看容器是否有符合block条件的对象
//判断是否容器中至少有一个元素符合block条件
- (BOOL)bk_any:(BOOL (^)(id obj))block;
//判断是否容器中所有元素都!!!不符合block条件
- (BOOL)bk_none:(BOOL (^)(id obj))block;
//判断是否容器中所有元素都符合block条件
- (BOOL)bk_all:(BOOL (^)(id obj))block;

其中bk_all函数的实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- (BOOL)bk_all:(BOOL (^)(id obj))block
{
NSParameterAssert(block != nil);

__block BOOL result = YES;

[self enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
if (!block(obj)) {
result = NO;
*stop = YES;
}
}];

return result;
}

从以上各种函数声明以及bk_all函数的定义我们就可以看出,容器相关的BlocksKit函数确实更加接近自然语义,简化编程。

再给出部分不可变容器的BlocksKit声明:

1
2
3
4
5
6
7
8
9
10
11
12
13
/** Filters a mutable array to the objects matching the block.

@param block A single-argument, BOOL-returning code block.
@see <NSArray(BlocksKit)>bk_reject:
*/
//删除容器中!!!不符合block条件的对象,即只保留符合block条件的对象
- (void)bk_performSelect:(BOOL (^)(id obj))block;

//删除容器中符合block条件的对象
- (void)bk_performReject:(BOOL (^)(id obj))block;

//容器中的对象变换为自己的block映射对象
- (void)bk_performMap:(id (^)(id obj))block;

3.2关联对象相关的BlocksKit

关联对象的作用如下:
在类的定义之外为类增加额外的存储空间。使用关联,我们可以不用修改类的定义而为其对象增加存储空间。这在我们无法访问到类的源码的时候或者是考虑到二进制兼容性的时候是非常有用。关联是基于关键字的,因此,我们可以为任何对象增加任意多的关联,每个都使用不同的关键字即可。关联是可以保证被关联的对象在关联对象的整个生命周期都是可用的(ARC下也不会导致资源不可回收)。关联对象的例子

在我们的实际项目中的常见用法一般有category中用关联对象定义property,或者使用关联对象绑定一个block。

关联对象相关的BlocksKit是对objc_setAssociatedObject、objc_getAssociatedObject、objc_removeAssociatedObjects这几个原生关联对象函数的封装。主要是封装其其内存管理语义。

部分函数声明如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//@interface NSObject (BKAssociatedObjects)

//以OBJC_ASSOCIATION_RETAIN_NONATOMIC方式绑定关联对象
- (void)bk_associateValue:(id)value withKey:(const void *)key;
//以OBJC_ASSOCIATION_COPY_NONATOMIC方式绑定关联对象
- (void)bk_associateCopyOfValue:(id)value withKey:(const void *)key;
//以OBJC_ASSOCIATION_RETAIN方式绑定关联对象
- (void)bk_atomicallyAssociateValue:(id)value withKey:(const void *)key;
//以OBJC_ASSOCIATION_COPY方式绑定关联对象
- (void)bk_atomicallyAssociateCopyOfValue:(id)value withKey:(const void *)key;
//弱绑定
- (void)bk_weaklyAssociateValue:(__autoreleasing id)value withKey:(const void *)key;
//删除所有绑定的关联对象
- (void)bk_removeAllAssociatedObjects;

除了弱绑定以外,其它BlocksKit函数都是简单封装。只有弱绑定看起来挺牛B,有点扩展关联对象原生语义的感觉。

弱绑定实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@interface _BKWeakAssociatedObject : NSObject

@property (nonatomic, weak) id value;

@end

- (void)bk_weaklyAssociateValue:(__autoreleasing id)value withKey:(const void *)key
{
_BKWeakAssociatedObject *assoc = objc_getAssociatedObject(self, key);
if (!assoc) {
assoc = [_BKWeakAssociatedObject new];
//做一个_BKWeakAssociatedObject对象中间层
//以非原子持有的方式绑定一个_BKWeakAssociatedObject对象
//_BKWeakAssociatedObject该对象又有真实对象的弱引用
[self bk_associateValue:assoc withKey:key];
}
//_BKWeakAssociatedObject的weak property设置为真正应该关联的对象
assoc.value = value;
}

从以上实现代码可以看出,所谓弱绑定实际上是在关联对象之间做了一个中间层,让本对象以OBJC_ASSOCIATION_RETAIN_NONATOMIC的形式去关联中间层(_BKWeakAssociatedObject),而中间层又以weak property的形式去存储真正关联对象的指针。

3.3逻辑执行相关的BlocksKit

所谓逻辑执行,就是Block块执行。逻辑执行相关的BlocksKit是对dispatch_after函数的封装。使其更加符合语义。

主要函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
//@interface NSObject (BKBlockExecution)

//主线程执行block方法,延迟时间可选
- (id)bk_performBlock:(void (^)(id obj))block afterDelay:(NSTimeInterval)delay
//后台线程执行block方法,延迟时间可选
- (id)bk_performBlockInBackground:(void (^)(id obj))block afterDelay:(NSTimeInterval)delay

//所有执行block相关的方法都是此方法的简化版,该函数在指定的block队列上以指定的时间延迟执行block
- (id)bk_performBlock:(void (^)(id obj))block onQueue:(dispatch_queue_t)queue afterDelay:(NSTimeInterval)delay;

//取消block,非常牛逼!!!一般来说一个block加到block queue上是没法取消的,此方法实现了block的取消操作(必须是用BlocksKit投放的block)
+ (void)bk_cancelBlock:(id)block;
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
- (id)bk_performBlock:(void (^)(id obj))block onQueue:(dispatch_queue_t)queue afterDelay:(NSTimeInterval)delay
{
NSParameterAssert(block != nil);
//cancelled是个__block变量,使得该block在加入queue后能够逻辑上取消。注意,仅仅是逻辑上取消,不能把block从queue中剔除。
__block BOOL cancelled = NO;

//在外部block之上加一层能够逻辑取消的代码,使其变为一个wrapper block
//当调用wrapper(YES)的时候就让__block BOOL cancelled = YES,使得以后每次block主体都被跳过。
void (^wrapper)(BOOL) = ^(BOOL cancel) {
//cancel参数是为了在外部能够控制cancelled _block变量
if (cancel) {
cancelled = YES;
return;
}
if (!cancelled) block(self);
};

//每个投入queue中的block实际上是wraper版的block
dispatch_after(BKTimeDelay(delay), queue, ^{
//把cancel设置为NO,block能够逻辑执行
wrapper(NO);
});

//返回wraper block,以便bk_cancelBlock的时候使用
return [wrapper copy];
}

+ (void)bk_cancelBlock:(id)block
{
NSParameterAssert(block != nil);
void (^wrapper)(BOOL) = block;
//把cancel设置为YES,修改block中_block cancelled变量,如果此时block未执行则,block在执行的时候其逻辑主体会被跳过
wrapper(YES);
}

以上代码给出了bk_performBlock和bk_cancelBlock的实现。其中bk_performBlock对参数block进行一层封装后再用dispatch_after投入相应的queue。注意该函数会返回的wrapper block,以供cancel。

3.4KVO相关BlocksKit

KVO主要涉及两类对象,即“被观察对象“和“观察者“。

与“被观察对象”相关的函数主要有如下两个:

1
2
3
4
//添加观察者
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context;
//删除观察者
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(void *)context;

与“观察者“相关的函数如下:

1
2
//观察到对象发生变化后的回调函数(观察回调)
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context;

通常的KVO做法是先对“被观察对象”添加“观察者”,同时在“观察者”中实现观察回调。这样每当“被观察对象”的指定property改变时,“观察者”就会调用观察回调。

KVO相关BlocksKit弱化了“观察者”这种对象,使得每当“被观察对象”的指定property改变时,就会调起一个block。具体实现方式是定义一个_BKObserver类,让该类实现观察回调、对被观察对象添加观察者和删除观察者。

_BKObserver类定义如下:

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
@interface _BKObserver : NSObject {
BOOL _isObserving;
}

//存储“被观察的对象”
@property (nonatomic, readonly, unsafe_unretained) id observee;
@property (nonatomic, readonly) NSMutableArray *keyPaths;
//存储回调block
@property (nonatomic, readonly) id task;
@property (nonatomic, readonly) BKObserverContext context;

- (id)initWithObservee:(id)observee keyPaths:(NSArray *)keyPaths context:(BKObserverContext)context task:(id)task;

@end

static void *BKObserverBlocksKey = &BKObserverBlocksKey;
static void *BKBlockObservationContext = &BKBlockObservationContext;

@implementation _BKObserver

- (id)initWithObservee:(id)observee keyPaths:(NSArray *)keyPaths context:(BKObserverContext)context task:(id)task
{
if ((self = [super init])) {
_observee = observee;
_keyPaths = [keyPaths mutableCopy];
_context = context;
_task = [task copy];
}
return self;
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
//观察者回调,在KV改变的时候调用相关block
if (context != BKBlockObservationContext) return;

@synchronized(self) {
switch (self.context) {
case BKObserverContextKey: {
void (^task)(id) = self.task;
task(object);
break;
}
case BKObserverContextKeyWithChange: {
void (^task)(id, NSDictionary *) = self.task;
task(object, change);
break;
}
case BKObserverContextManyKeys: {
void (^task)(id, NSString *) = self.task;
task(object, keyPath);
break;
}
case BKObserverContextManyKeysWithChange: {
void (^task)(id, NSString *, NSDictionary *) = self.task;
task(object, keyPath, change);
break;
}
}
}
}

//开启KV观察
- (void)startObservingWithOptions:(NSKeyValueObservingOptions)options
{
@synchronized(self) {
if (_isObserving) return;

[self.keyPaths bk_each:^(NSString *keyPath) {
//observee的被观察对象,observer是自己,
[self.observee addObserver:self forKeyPath:keyPath options:options context:BKBlockObservationContext];
}];

_isObserving = YES;
}
}

//停止KV观察
- (void)stopObservingKeyPath:(NSString *)keyPath
{
NSParameterAssert(keyPath);

@synchronized (self) {
if (!_isObserving) return;
if (![self.keyPaths containsObject:keyPath]) return;

NSObject *observee = self.observee;
if (!observee) return;

[self.keyPaths removeObject: keyPath];
keyPath = [keyPath copy];

if (!self.keyPaths.count) {
_task = nil;
_observee = nil;
_keyPaths = nil;
}

[observee removeObserver:self forKeyPath:keyPath context:BKBlockObservationContext];
}
}

具体添加删除KVO block的函数实现如下:

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
//添加观察block
- (void)bk_addObserverForKeyPaths:(NSArray *)keyPaths identifier:(NSString *)identifier options:(NSKeyValueObservingOptions)options context:(BKObserverContext)context task:(id)task
{
NSParameterAssert(keyPaths.count);
NSParameterAssert(identifier.length);
NSParameterAssert(task);

Class classToSwizzle = self.class;
NSMutableSet *classes = self.class.bk_observedClassesHash;
@synchronized (classes) {
NSString *className = NSStringFromClass(classToSwizzle);
if (![classes containsObject:className]) {
SEL deallocSelector = sel_registerName("dealloc");

__block void (*originalDealloc)(__unsafe_unretained id, SEL) = NULL;

//勾住类的dealloc方法,并且在其中添加bk_removeAllBlockObservers方法
id newDealloc = ^(__unsafe_unretained id objSelf) {
[objSelf bk_removeAllBlockObservers];

if (originalDealloc == NULL) {
struct objc_super superInfo = {
.receiver = objSelf,
.super_class = class_getSuperclass(classToSwizzle)
};

void (*msgSend)(struct objc_super *, SEL) = (__typeof__(msgSend))objc_msgSendSuper;
msgSend(&superInfo, deallocSelector);
} else {
originalDealloc(objSelf, deallocSelector);
}
};

IMP newDeallocIMP = imp_implementationWithBlock(newDealloc);

if (!class_addMethod(classToSwizzle, deallocSelector, newDeallocIMP, "v@:")) {
// The class already contains a method implementation.
Method deallocMethod = class_getInstanceMethod(classToSwizzle, deallocSelector);

// We need to store original implementation before setting new implementation
// in case method is called at the time of setting.
originalDealloc = (void(*)(__unsafe_unretained id, SEL))method_getImplementation(deallocMethod);

// We need to store original implementation again, in case it just changed.
originalDealloc = (void(*)(__unsafe_unretained id, SEL))method_setImplementation(deallocMethod, newDeallocIMP);
}

[classes addObject:className];
}
}

//生成_BKObserver对象(真正的KV Observer)
NSMutableDictionary *dict;
_BKObserver *observer = [[_BKObserver alloc] initWithObservee:self keyPaths:keyPaths context:context task:task];
//启动观察
[observer startObservingWithOptions:options];

//将新生成的_BKObserver对象添加到dic中
@synchronized (self) {
dict = [self bk_observerBlocks];

if (dict == nil) {
dict = [NSMutableDictionary dictionary];
[self bk_setObserverBlocks:dict];
}
}

dict[identifier] = observer;
}

//删除观察block
- (void)bk_removeAllBlockObservers
{
NSDictionary *dict;

@synchronized (self) {
dict = [[self bk_observerBlocks] copy];
[self bk_setObserverBlocks:nil];
}

[dict.allValues bk_each:^(_BKObserver *trampoline) {
[trampoline stopObserving];
}];
}

3.5定时器相关BlocksKit

NSTimer有个比较恶心的特性,它会持有它的target。比如在一个controller中使用了timer,并且timer的target设置为该controller本身,那么想在controller的dealloc中fire掉timer是做不到的,必须要在其他的地方fire。这会让编码很难受。具体参考《Effective Objective C》的最后一条。 BlocksKit解除这种恶心,其方式是把timer的target设置为timer 的class对象。把要执行的block保存在timer的userInfo中执行。因为timer 的class对象一直存在,所以是否被持有其实无所谓。

实现代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
+ (id)bk_scheduledTimerWithTimeInterval:(NSTimeInterval)inTimeInterval block:(void (^)(NSTimer *timer))block repeats:(BOOL)inRepeats
{
NSParameterAssert(block != nil);
return [self scheduledTimerWithTimeInterval:inTimeInterval target:self selector:@selector(bk_executeBlockFromTimer:) userInfo:[block copy] repeats:inRepeats];
}

+ (id)bk_timerWithTimeInterval:(NSTimeInterval)inTimeInterval block:(void (^)(NSTimer *timer))block repeats:(BOOL)inRepeats
{
NSParameterAssert(block != nil);
return [self timerWithTimeInterval:inTimeInterval target:self selector:@selector(bk_executeBlockFromTimer:) userInfo:[block copy] repeats:inRepeats];
}

+ (void)bk_executeBlockFromTimer:(NSTimer *)aTimer {
void (^block)(NSTimer *) = [aTimer userInfo];
if (block) block(aTimer);
}

4BlocksKit源码分析(一)小结

本文介绍了BlocksKit源码的组织结构:Core、DynamicDelegate、MessageUI、UIKit。并简要分析了Core的源码。该部分代码由5个小部分组成:容器、关联对象、逻辑执行、KVO、定时器。由源码可以看出BlocksKit扩展更加符合自然语义,如容器相关代码;BlocksKit一般通过中间层来承载回调函数,并在中间层回调的时候调用相应block,如KVO的BlocksKit实现。总体来说,Core代码是这些代码的最基础部分,具有很强的通用性。

1引言

《BlocksKit源码分析(一)》中我们分析了BlocksKit源码组织结构以及第一部分Core的源码。在这里我们接着分析BlocksKit第二部分——DynamicDelegate(动态代理)。所谓动态代理,听起来挺玄乎。实际一言以蔽之,就是把delegate转为block的手段。

2动态代理样例

我们先从一个例子来看看动态代理的使用方式:

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
- (IBAction) annoyUser
{
// 创建一个alert view
UIAlertView *alertView = [[UIAlertView alloc]
initWithTitle:@"Hello World!"
message:@"This alert's delegate is implemented using blocks. That's so cool!"
delegate:nil
cancelButtonTitle:@"Meh."
otherButtonTitles:@"Woo!", nil];

// 获取该alert view的动态代理对象(什么是动态代理对象稍后会说)
A2DynamicDelegate *dd = alertView.bk_dynamicDelegate;

// 调用动态代理对象的 - (void)implementMethod:(SEL)selector withBlock:(id)block;方法,使得SEL映射一个block对象(假设叫做block1)
[dd implementMethod:@selector(alertViewShouldEnableFirstOtherButton:) withBlock:^(UIAlertView *alertView) {
NSLog(@"Message: %@", alertView.message);
return YES;
}];

// 同上,让映射-alertView:willDismissWithButtonIndex:的SEL到另外一个block对象(假设叫做block2)
[dd implementMethod:@selector(alertView:willDismissWithButtonIndex:) withBlock:^(UIAlertView *alertView, NSInteger buttonIndex) {
NSLog(@"You pushed button #%d (%@)", buttonIndex, [alertView buttonTitleAtIndex:buttonIndex]);
}];

// 把alertView的delegate设置为动态代理
alertView.delegate = dd;

[alertView show];
}
// 那么,alert view在显示的时候收到alertViewShouldEnableFirstOtherButton:消息调用block1;alert view在消失的时候收到alertView:willDismissWithButtonIndex:消息,调用block2

从上面的代码我们可以直观地看到:dd(动态代理对象)直接被设置为alert view的delegate对象,那么该alert view的UIAlertViewDelegate消息直接传递向给了dd。然后dd又通过某种方式把对应的SEL调用转为对应的block调用。我们又可以作出如下猜测:

  1. dd内部可能有个dic一样的数据结构,key可能是SEL,value可能是与之对应的block,通过implementMethod:withBlock:这个方法把SEL和block以键值对的形式建立起dic映射
  2. Host对象(本例是alertView)向dd发delegate消息的时候传递了SEL,dd在内部的dic数据结构查找对应的block,找到后,调用该block。

基本思路是这样的,but还有两个问题需要解决:

  1. dd(动态代理对象)是如何创建的?
  2. 如UIAlertViewDelegate这样的各种delegate有不同的消息,任意一个消息都可能发给dd,dd是如何能接受任意消息的,显然实现每个消息是不现实的。

3消息转发机制

我们先来回答第二个问题,即动态代理是如何能接受任意消息的。
当一个object收到它没实现的消息的时候,通常会发生如下的情况。

  1. 首先看是否为该 selector 提供了动态方法决议机制,如果提供了则转到2;如果没有提供则转到3;

  2. 如果动态方法决议真正为该 selector 提供了实现,那么就调用该实现,完成消息发送流程,消息转发就不会进行了;如果没有提供,则转到 3;

  3. 其次看是否为该 selector 提供了消息转发机制,如果提供了消息了则进行消息转发,此时,无论消息 转发是怎样实现的,程序均不会 crash。(因为消息调用的控制权完全交给消息转发机制处理,即使消息转发并没有做任何事情,运行也不会有错误,编译器更不会有错误提示。);如果没提供消息转发机制, 则转到 4;

  4. 运行报错:无法识别的 selector,程序 crash;

    通过以上描述我们可以知道。dd(A2DynamicDelegate类)支持任意消息发送,不是用了消息决议就是用了消息转发。实际上通过代码我们可以看出 A2DynamicDelegate是继承自NSProxy类,该类是专门做消息转发的。继承自该类的类必须要实现- (void)forwardInvocation:(NSInvocation *)outerInv方法。因为该方法是当该类的对象收到无法识别的消息的时候调用的方法。所以dd消息映射流程大概是这样的:当dd收到无法识别的消息时会调用forwardInvocation,该函数的参数outerInv可以拿到对应消息的SEL,再通过SEL在再内部的dic里面找到对应的block进行调用。(简要思路)

4动态代理对象的创建

A2DynamicDelegate这种对象是如何创建的?为什么从代码里面看dd(A2DynamicDelegate)好像是alertView的一个property。
实际上dd是通过NSObject的(A2DynamicDelegate)分类来实现的。该分类用关联对象的方法,把protocal和A2DynamicDelegate对象关联起来。使得相应的protocal有对应的A2DynamicDelegate对象。某个类的bk_dynamicDelegate实际上是查找该类对应delegate的protocal,再根据这个protocal找到对应的A2DynamicDelegate对象。如果找不到A2DynamicDelegate对象就创建一个关联起来。比如alertView.bk_dynamicDelegate;会查找alertView的delegate UIAlertViewDelegate protocal所关联的对象,如果没有找到就创建一个,并且做关联。
相关代码如下:

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
 // File:NSObject+A2DynamicDelegate.m
- (id)bk_dynamicDelegateWithClass:(Class)cls forProtocol:(Protocol *)protocol
{
/**
* Storing the dynamic delegate as an associated object of the delegating
* object not only allows us to later retrieve the delegate, but it also
* creates a strong relationship to the delegate. Since delegates are weak
* references on the part of the delegating object, a dynamic delegate
* would be deallocated immediately after its declaring scope ends.
* Therefore, this strong relationship is required to ensure that the
* delegate's lifetime is at least as long as that of the delegating object.
**/

__block A2DynamicDelegate *dynamicDelegate;

dispatch_sync(a2_backgroundQueue(), ^{
dynamicDelegate = objc_getAssociatedObject(self, (__bridge const void *)protocol);

if (!dynamicDelegate)
{
dynamicDelegate = [[cls alloc] initWithProtocol:protocol];
objc_setAssociatedObject(self, (__bridge const void *)protocol, dynamicDelegate, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
});

return dynamicDelegate;
}

5代码设计

A2DynamicDelegate是动态代理类,NSObject_A2DynamicDelegate是NSObject分类用来创建动态代理对象。A2BlockInvocation这个类我们之前没有说过,它是block的一层封装。A2DynamicDelegate里面的字典数据结构装得不是纯粹的block,而是A2BlockInvocation对象。

相关类图如下:
类图

相关序列图如下:
序列图

一、引言

项目中很早就引入了Nimbus框架,一直在搞天气动画,没时间看这个东西。最近要做新的feature,跟table相关。于是花了一下午读了读相关部分的源码。Git上是这么介绍Nimbus的——The iOS framework that grows only as fast as its documentation.一看文档和注释,果然不错,读起来一气呵成,很舒服。

二、整体思路

众所周知,生成一个UITableView,一般要设置两个东西:dataSource和delegate。其中,dataSource来控制tableview上展示的内容,而delegate用来通知tableview上发生的各种事件,如cell被点击等等。通常我们把这两个委托设置为UITableViewController,让UITableViewController同时实现UITableViewDataSource和UITableViewDelegate protocol。而 Nimbus根据这两个特性造了两个类分别是NITableViewModel(实现UITableViewDataSource,用来设置数据源dataSource)和NITableViewActions(实现UITableViewDelegate,触发tableview上的各种事件)。

1
2
3
4
5
//testTableView is a UITableView
//_model is a NITableViewModel instance
//_actions is a NITableViewActions instance
self.testTableView.delegate = _actions;
self.testTableView.dataSource = _model;

三、从数据源到具体Cell

我们先来看看怎么从数据源生成具体的Cell。Nimbus把该过程分为2部分:数据管理和Cell生成。其中数据管理由NITableViewModel实现,而Cell生成是实现NITableViewModelDelegate protocol,可能有多种方式。

1数据管理

首先来看看数据管理,NITableViewModel定义。

1
2
3
4
5
@interface NITableViewModel : NSObject <UITableViewDataSource>
- (id)initWithDelegate:(id<NITableViewModelDelegate>)delegate;
- (id)initWithListArray:(NSArray *)sectionedArray delegate:(id<NITableViewModelDelegate>)delegate;
- (id)initWithSectionedArray:(NSArray *)sectionedArray delegate:(id<NITableViewModelDelegate>)delegate;
@end

其中NITableViewModelDelegate,是指定Cell的生成委托,对应第二步,我们先忽略。
其中sectionedArray是指定的数据内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/*
* @code
* NSArray* contents =
* [NSArray arrayWithObjects:
* @"Section 1",
* [NSDictionary dictionaryWithObject:@"Row 1" forKey:@"title"],
* [NSDictionary dictionaryWithObject:@"Row 2" forKey:@"title"],
* @"Section 2",
* // This section is empty.
* @"Section 3",
* [NSDictionary dictionaryWithObject:@"Row 3" forKey:@"title"],
* [NITableViewModelFooter footerWithTitle:@"Footer"],
* nil];
* [[NIStaticTableViewModel alloc] initWithSectionedArray:contents delegate:self];
*/

其中调用方式可能如上所示,如果sectionedArray里面的一项是NSString则认为这是一个section头部(标题),如果是一个NITableViewModelFooter对象则认为是一个section的尾部。其余每一项则认为是一个Cell上的内容。如上例,该sectionedArray展示了3个section,第一个section的标题是@”Section 1”,内部有两个cell,具体内容分别由[NSDictionary dictionaryWithObject:@”Row 1” forKey:@”title”]和[NSDictionary dictionaryWithObject:@”Row 2” forKey:@”title”]表示。如此跟table视觉上的排布一致。得到这个sectionedArray后,NITableViewModel对该数组进行一顿解析,在内部用一些更加友好的数据管理起来。我们可以先把NITableViewModel简单的理解为:解析sectionedArray数组,了解里面有哪些section哪些cell,在UITableViewDataSource相应的函数里面返回解析过后的数值。

2Cell生成

通过上文我们知道,NITableViewModel实现了UITableViewDataSource protocol,该protocol有个一函数用来生成cell的

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;

NITableViewModel职责有限,只能管理数据源不管你生成Cell,所以他把这个东西抛给他的委托,NITableViewModelDelegate,让这个委托来生成Cell。我们前文提到生成Cell有多种方式,这些方式都是通过实现NITableViewModelDelegate得来的。
UITableViewController实现NITableViewModelDelegate

1
2
3
4
5
6
7
8
9
10
11
- (UITableViewCell *)tableViewModel: (NITableViewModel *)tableViewModel
cellForTableView: (UITableView *)tableView
atIndexPath: (NSIndexPath *)indexPath
withObject: (id)object
{
id object = [tableViewModel objectAtIndexPath:indexPath];
//从数据源获取cell上的数据
//object就是该cell对应的数据,也就是你在model的sectionedArray里面设置的东西
//如上例子,若indexPath为section0 row0则object是[NSDictionary dictionaryWithObject:@"Row 1" forKey:@"title"]
//得到这些信息后,我们再通过平时的策略来生成cell
}

利用NICellFactory实现NITableViewModelDelegate

若利用此种方式实现Cell生成,则必须要求第一步sectionedArray表示cell内容的对象是一个NICellObject对象。其中NICellObject类定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@protocol NICellObject <NSObject>
@required
/** The class of cell to be created when this object is passed to the cell factory. */
- (Class)cellClass;
@optional
/** The style of UITableViewCell to be used when initializing the cell for the first time. */
- (UITableViewCellStyle)cellStyle;
@end

@interface NICellObject : NSObject <NICellObject>
// Designated initializer.
- (id)initWithCellClass:(Class)cellClass userInfo:(id)userInfo;
- (id)initWithCellClass:(Class)cellClass;
+ (id)objectWithCellClass:(Class)cellClass userInfo:(id)userInfo;
+ (id)objectWithCellClass:(Class)cellClass;
@property (nonatomic, strong) id userInfo;
@end

通过类定义,我们不难发现,NICellObject实际就是一个带cellClass信息的类,也就是说NICellFactory要求我们传到model的sectionedArray里面的cell内容对象都是带cell class信息的。通过改造,我们可以把indexPath为section0 row0的object变为[NICellObject objectWithCellClass:[UITableViewCell class] userInfo:[NSDictionary dictionaryWithObject:@”Row 1” forKey:@”title”] ];
具体生成函数如下所示:

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
+ (UITableViewCell *)tableViewModel:(NITableViewModel *)tableViewModel
cellForTableView:(UITableView *)tableView
atIndexPath:(NSIndexPath *)indexPath
withObject:(id)object {
UITableViewCell* cell = nil;

// Only NICellObject-conformant objects may pass.
if ([object respondsToSelector:@selector(cellClass)]) {
//1拿cellClass
Class cellClass = [object cellClass];
//2根据cellClass来生成cell
cell = [self cellWithClass:cellClass tableView:tableView object:object];

} else if ([object respondsToSelector:@selector(cellNib)]) {
UINib* nib = [object cellNib];
cell = [self cellWithNib:nib tableView:tableView indexPath:indexPath object:object];
}

// If this assertion fires then your app is about to crash. You need to either add an explicit
// binding in a NICellFactory object or implement the NICellObject protocol on this object and
// return a cell class.
NIDASSERT(nil != cell);

return cell;
}

+ (UITableViewCell *)cellWithClass:(Class)cellClass
tableView:(UITableView *)tableView
object:(id)object {
UITableViewCell* cell = nil;

NSString* identifier = NSStringFromClass(cellClass);

if ([cellClass respondsToSelector:@selector(shouldAppendObjectClassToReuseIdentifier)]
&& [cellClass shouldAppendObjectClassToReuseIdentifier]) {
identifier = [identifier stringByAppendingFormat:@".%@", NSStringFromClass([object class])];
}

cell = [tableView dequeueReusableCellWithIdentifier:identifier];

if (nil == cell) {
UITableViewCellStyle style = UITableViewCellStyleDefault;
if ([object respondsToSelector:@selector(cellStyle)]) {
style = [object cellStyle];
}
cell = [[cellClass alloc] initWithStyle:style reuseIdentifier:identifier];
}

// Allow the cell to configure itself with the object's information.
// 调用cell类的shouldAppendObjectClassToReuseIdentifier函数,给自定义的cell一个改变自己的机会
if ([cell respondsToSelector:@selector(shouldUpdateCellWithObject:)]) {
[(id<NICell>)cell shouldUpdateCellWithObject:object];
}

return cell;
}

Cell一气呵成

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
//自定义的cell
@interface TestCell : UITableViewCell <NICell>
@end

@interface TestCell ()
@property (nonatomic, weak) UILabel* lbl;
@end

@implementation TestCell
- (BOOL)shouldUpdateCellWithObject:(id)object
{
if (!_lbl) {
UILabel* tmp = [[UILabel alloc] initWithFrame:self.bounds];
self.lbl = tmp;
[self.contentView addSubview:_lbl];
}
NSDictionary* data = ((NICellObject*)object).userInfo;
_lbl.text = data[@"title"];
return YES;
}
@end

//一气呵成生成cell的相关代码
UITableView* tmpView = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStyleGrouped];
self.testView = tmpView;
[self.view addSubview:_testView];
NSArray* contentAry = @[ [NICellObject objectWithCellClass:[TestCell class] userInfo:@{ @"title" : @"hello" }],
[NICellObject objectWithCellClass:[TestCell class] userInfo:@{ @"title" : @"nimbus" }],
[NICellObject objectWithCellClass:[TestCell class] userInfo:@{ @"title" : @"what" }],
[NICellObject objectWithCellClass:[TestCell class] userInfo:@{ @"title" : @"are" }] ];

self.model = [[NITableViewModel alloc] initWithSectionedArray:contentAry
delegate:(id)[NICellFactory class]];
self.testView.dataSource = _model;

四、tableview事件处理

说完了数据源展示,我们在来探索一下table事件处理。Nimbus用到了NITableViewActions类,该类接口定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@interface NITableViewActions : NIActions <UITableViewDelegate>

#pragma mark Forwarding

- (id<UITableViewDelegate>)forwardingTo:(id<UITableViewDelegate>)forwardDelegate;
- (void)removeForwarding:(id<UITableViewDelegate>)forwardDelegate;

#pragma mark Object state

- (UITableViewCellAccessoryType)accessoryTypeForObject:(id)object;
- (UITableViewCellSelectionStyle)selectionStyleForObject:(id)object;

#pragma mark Configurable Properties

@property (nonatomic, assign) UITableViewCellSelectionStyle tableViewCellSelectionStyle;

@end

可以看出该类实现了UITableViewDelegate protocol,因此可以当作tableview的事件处理类。NITableViewActions继承自NIActions类,该类又定义如下

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
@interface NIActions : NSObject

// Designated initializer.
- (id)initWithTarget:(id)target;

#pragma mark Mapping Objects

- (id)attachToObject:(id<NSObject>)object tapBlock:(NIActionBlock)action;
- (id)attachToObject:(id<NSObject>)object detailBlock:(NIActionBlock)action;
- (id)attachToObject:(id<NSObject>)object navigationBlock:(NIActionBlock)action;

- (id)attachToObject:(id<NSObject>)object tapSelector:(SEL)selector;
- (id)attachToObject:(id<NSObject>)object detailSelector:(SEL)selector;
- (id)attachToObject:(id<NSObject>)object navigationSelector:(SEL)selector;

#pragma mark Mapping Classes

- (void)attachToClass:(Class)aClass tapBlock:(NIActionBlock)action;
- (void)attachToClass:(Class)aClass detailBlock:(NIActionBlock)action;
- (void)attachToClass:(Class)aClass navigationBlock:(NIActionBlock)action;

- (void)attachToClass:(Class)aClass tapSelector:(SEL)selector;
- (void)attachToClass:(Class)aClass detailSelector:(SEL)selector;
- (void)attachToClass:(Class)aClass navigationSelector:(SEL)selector;

#pragma mark Object State

- (BOOL)isObjectActionable:(id<NSObject>)object;

+ (id)objectFromKeyClass:(Class)keyClass map:(NSMutableDictionary *)map;

@end

从类接口可以直观的看出,该类是把block块或者selector绑定到一个对象上。应该是在内部实际维护了从对象到block块或者selector的关系。

1
2
3
4
5
6
7
@interface NIActions ()

@property (nonatomic, strong) NSMutableDictionary* objectToAction;
@property (nonatomic, strong) NSMutableDictionary* classToAction;
@property (nonatomic, strong) NSMutableSet* objectSet;

@end

再从这个类的extention,果断看出了端倪,证明我们的猜测不错。
我们来看一个attach方法

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
- (id)attachToObject:(id<NSObject>)object tapBlock:(NIActionBlock)action {
[self.objectSet addObject:object];
//获取该object对应的action数据结构,并且设置该数据结构对应项的值
[self actionForObject:object].tapAction = action;
return object;
}

- (NIObjectActions *)actionForObject:(id<NSObject>)object {
//获取object的hash key
id key = [self keyForObject:object];
//通过object hash key在内部dic中查询NIObjectActions对象,如果没有找到,则新建一个
NIObjectActions* action = [self.objectToAction objectForKey:key];
if (nil == action) {
action = [[NIObjectActions alloc] init];
[self.objectToAction setObject:action forKey:key];
}
return action;
}

@interface NIObjectActions : NSObject

@property (nonatomic, copy) NIActionBlock tapAction;
@property (nonatomic, copy) NIActionBlock detailAction;
@property (nonatomic, copy) NIActionBlock navigateAction;

@property (nonatomic) SEL tapSelector;
@property (nonatomic) SEL detailSelector;
@property (nonatomic) SEL navigateSelector;

@end

通过以上函数可以看出NIActions内部用到了NIObjectActions这个数据结构来表示各种action,而NIActions维护了一个从object的hash key到NIObjectActions对象的关系。
我再回过头来看NITableViewActions类的UITableViewDelegate实现,具体函数如下:

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NIDASSERT([tableView.dataSource isKindOfClass:[NITableViewModel class]]);
if ([tableView.dataSource isKindOfClass:[NITableViewModel class]]) {
NITableViewModel* model = (NITableViewModel *)tableView.dataSource;
//从model中获取cell内容对象
id object = [model objectAtIndexPath:indexPath];

if ([self isObjectActionable:object]) {
//查询cell内容对象的action对象(NIObjectActions)
NIObjectActions* action = [self actionForObjectOrClassOfObject:object];

//如果action对象中设置了tapAction block ,调用之

BOOL shouldDeselect = NO;
if (action.tapAction) {
// Tap actions can deselect the row if they return YES.
shouldDeselect = action.tapAction(object, self.target, indexPath);
}

//如果action对象中设置了tapSelector,并且目标对象(由NITableViewActions对象保存)能响应tapSelector,调用之
if (action.tapSelector && [self.target respondsToSelector:action.tapSelector]) {
//多参数不能直接performSelector
NSMethodSignature *methodSignature = [self.target methodSignatureForSelector:action.tapSelector];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature];
invocation.selector = action.tapSelector;
if (methodSignature.numberOfArguments >= 3) {
[invocation setArgument:&object atIndex:2];
}
if (methodSignature.numberOfArguments >= 4) {
[invocation setArgument:&indexPath atIndex:3];
}
[invocation invokeWithTarget:self.target];

NSUInteger length = invocation.methodSignature.methodReturnLength;
if (length > 0) {
char *buffer = (void *)malloc(length);
memset(buffer, 0, sizeof(char) * length);
[invocation getReturnValue:buffer];
for (NSUInteger index = 0; index < length; ++index) {
if (buffer[index]) {
shouldDeselect = YES;
break;
}
}
free(buffer);
}
}
if (shouldDeselect) {
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}

if (action.navigateAction) {
action.navigateAction(object, self.target, indexPath);
}
//直接performSelector
if (action.navigateSelector && [self.target respondsToSelector:action.navigateSelector]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[self.target performSelector:action.navigateSelector withObject:object withObject:indexPath];
#pragma clang diagnostic pop
}
}
}

// Forward the invocation along.
for (id<UITableViewDelegate> delegate in self.forwardDelegates) {
if ([delegate respondsToSelector:_cmd]) {
[delegate tableView:tableView didSelectRowAtIndexPath:indexPath];
}
}
}

所以一个table的action可能是这样的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
self.actions = [[NITableViewActions alloc] initWithTarget:self];
self.testView.delegate = _actions;

NSArray* contentAry = @[
[_actions attachToObject:[NICellObject objectWithCellClass:[TestCell class] userInfo:@{ @"title" : @"hello" }]
tapBlock:^BOOL(id object, id target, NSIndexPath* indexPath) {
NSLog(@"%@", indexPath);
return YES;
}],
[NICellObject objectWithCellClass:[TestCell class] userInfo:@{ @"title" : @"nimbus" }],
[NICellObject objectWithCellClass:[TestCell class] userInfo:@{ @"title" : @"what" }],
[NICellObject objectWithCellClass:[TestCell class] userInfo:@{ @"title" : @"are" }]
];

self.model = [[NITableViewModel alloc] initWithSectionedArray:contentAry
delegate:(id)[NICellFactory class]];
self.testView.dataSource = _model;

model里面表示cell内容的对象可以被attach到actions对象上。

1引言

你点击了桌面上的Chrome图标,一个浏览器窗口出现了,输入网址就可以在Internet世界愉快玩耍。这一切是怎么实现的呢?Chromium这个多进程的程序是如何启动各个进程的呢?浏览器主进程(界面进程)启动了哪些线程?如何启动的呢?这些问题一直萦绕在心头,一起来看看源代码吧。本文主要针对Chromium for Mac的源代码,其它操作系统大同小异。

2背景知识

浏览器作为一个应用程序,是以进程的形式运行在操作系统上的。首先,Chromium是一个多进程的应用程序,我们需要了解Chromium的多进程框架。其次,Chromium中的每个进程又是多线程的,因而又需要了解Chromium的线程设计。接下来我们就从进程和线程入手介绍相关背景知识。

2.1多进程框架

Chromium是个多进程程序,每当你点开Chromium图标,默认会启动多个进程。Google之所以这样设计,是怕你打开某个挫B网站而把它的整个浏览器全部弄崩掉。所以它利用现代操作系统多进程不同地址空间这个设计,把它整个浏览器分为多进程,这样即使一个进程崩掉了,也不一定会让整个浏览器崩掉。Chromium进程分为一般分为两类:第一类,Browser进程,它管理tabs和插件进程以及运行主界面UI。第二类,Renderer进程,它利用WebKit布局引擎来解释和布局HTML。Browser是老大,只有一个;Renderer是小弟可以有很多个,每个小弟都能跟老大相互通信,但是小弟之间不能相互通信。老大和小弟们之间的关系如下。

1

从上图可以看出每个Renderer进程都有一个RenderProcess对象,这个对象是用来管理整个Renderer进程的;与之对应的,Browser进程有许多RenderProcessHost对象,每个对象对应一个RenderProcess对象,也即是对应一个Renderer进程。

2.2多线程框架

我们平时写程序也用多线程,其中一个常见的场景是把UI和费时处理过程分离,让他们拥有各自的线程。Chromium中的多线程基本是同理的,为了保证UI的快速响应,防止IO阻塞和费时计算而利用了多线程。但是我们在处理多线程共享资源的时候通常是利用锁,这容易引发各种莫名奇妙的问题,如死锁、优先级反转等。Chromium为了避免这些问题就少用锁,它有一些简单的约定:

  1. 线程之间通过传递消息来交流(确切来说是传递任务);
  2. 对象只存在在一个线程上。

BrowserProcess类是Browser进程的服务管理者,它控制着大多数的线程。通常程序运行在UI线程,但是我们把某些类型的处理放到其他线程。这些线程如下:

ui_thread:应用程序启动时的主线程。

io_thread:这个线程的名字有点令人费解。它是处理browser进程和所有子进程之间通信的调度线程。它也负责所有的资源请求(网页加载)的分发(见多进程架构)。

file_thread:文件操作的通用线程。当你想执行阻塞文件系统的操作(例如,请求某种文件一个图标,把下载文件写到磁盘),会调用该线程。

db_thread:数据库操作的线程。例如,Cookie服务在这个线程上使用SQLite操作。注意,历史数据库不使用该线程。

本文也主要是讨论Browser上的线程何时启动的。更多关于Chromium进程和线程框架请参考:
http://www.chromium.org/developers/design-documents/multi-process-architecture

http://www.chromium.org/developers/design-documents/threading

3Browser多线程的创建和启动

双击浏览器图标,我们启动了一个Chromium进程,这就是Browser进程。用C++编写的程序启动函数是main,Chromium当然也不例外。具体流程如下图所示:

2

其中,RunNamedProcessTypeMain是个很重要的函数
该函数定义于”content/public/app/content_main_runner.cc”文件。

函数原型如下:

1
2
3
4
int RunNamedProcessTypeMain(
const std::string& process_type,
const MainFunctionParams& main_function_params,
ContentMainDelegate* delegate)

该函数内部定义了个静态结构体数组:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
  static const MainFunction kMainFunctions[] = {
#if !defined(CHROME_MULTIPLE_DLL_CHILD)
{ "", BrowserMain },
#endif
#if !defined(CHROME_MULTIPLE_DLL_BROWSER)
#if defined(ENABLE_PLUGINS)
{ switches::kPluginProcess, PluginMain },
{ switches::kWorkerProcess, WorkerMain },
{ switches::kPpapiPluginProcess, PpapiPluginMain },
{ switches::kPpapiBrokerProcess, PpapiBrokerMain },
#endif // ENABLE_PLUGINS
{ switches::kUtilityProcess, UtilityMain },
{ switches::kRendererProcess, RendererMain },
{ switches::kGpuProcess, GpuMain },
#endif // !CHROME_MULTIPLE_DLL_BROWSER
};

该结构体有两个成员,一个是const char process_type[]表示进程类型,它从启动进程传入的参数解析得到;另一个是函数指针,表示该进程对应的主函数。对于Browser进程而言,它的启动参数是空的,解析出来process_type也是空的。我们从上面的结构体数组可以看到,空参数表示该进程的主函数是BrowserMain。我们再来看看BrowserMain中做了啥。主要代码如下所示:

1
2
3
4
5
6
7
scoped_ptr<BrowserMainRunner> main_runner(BrowserMainRunner::Create());

int exit_code = main_runner->Initialize(parameters);
if (exit_code >= 0)
return exit_code;

exit_code = main_runner->Run();

我们可以看出该函数主要是创建了一个BrowserMainRunner对象,然后Initialize,紧接着Run。BrowserMainRunner的相关类图如下所示。

3

从类图可以看出BrowserMainRunner是个接口类,该类包含3个接口方法:Initialize、Run、Shutdown。其实现类是BrowserMainRunnerImpl。该类管理了一个BrowserMainLoop对象,通过控制BrowserMainLoop来控制该进程。我们可以发现BrowserMainLoop对象管理了许多线程对象,如:main­­_threaddb_threadfile_thread等。这就是Browser进程中的主要线程对象。相关序列图如下图所示。

4

main_runner在Initialize的时候创造了BrowserMainLoop对象,并调用了该对象的Initialize函数和CreateStartupTasks函数。而CreateStartupTask调用的时候创建了一个StartupTaskRunner对象,利用该对象来管理Startup Tasks,比如添加任务、执行任务。在此函数中就利用了AddTask来添加任务,其中一个重要的任务是CreateThreads,即把所有他管理的线程对象创建起来。该函数具体如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void BrowserMainLoop::CreateStartupTasks() {
//other code
StartupTask pre_create_threads =
base::Bind(&BrowserMainLoop::PreCreateThreads, base::Unretained(this));
startup_task_runner_->AddTask(pre_create_threads);

StartupTask create_threads =
base::Bind(&BrowserMainLoop::CreateThreads, base::Unretained(this));
startup_task_runner_->AddTask(create_threads);

StartupTask browser_thread_started = base::Bind(
&BrowserMainLoop::BrowserThreadsStarted, base::Unretained(this));
startup_task_runner_->AddTask(browser_thread_started);

StartupTask pre_main_message_loop_run = base::Bind(
&BrowserMainLoop::PreMainMessageLoopRun, base::Unretained(this));
startup_task_runner_->AddTask(pre_main_message_loop_run);

startup_task_runner_->RunAllTasksNow();
}

以上就是Browser各种主要线程创建的时机。

4多进程的创建和启动

上文我们已经提到,Chromium是一种多进程的程序,那除了双击启动的Browser进程,其它的小弟进程Renderer在哪里启动的呢?通过上文,我们知道每个Renderer都有个RenderProcess对象,相应的Browser上有于之对应的RenderProcessHost对象。追踪源码我们可以发现,在RenderProcessHostImpl类的Init函数中,有这样几行代码:

1
2
3
4
5
6
7
8
9
10
11
child_process_launcher_.reset(new ChildProcessLauncher(
#if defined(OS_WIN)
new RendererSandboxedProcessLauncherDelegate,
#elif defined(OS_POSIX)
renderer_prefix.empty(),
base::EnvironmentMap(),
channel_->TakeClientFileDescriptor(),
#endif
cmd_line,
GetID(),
this));

该函数new了一个ChildProcessLauncher对象,再看看对应的构造函数,里面有如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
context_ = new Context();
context_->Launch(
#if defined(OS_WIN)
delegate,
#elif defined(OS_ANDROID)
ipcfd,
#elif defined(OS_POSIX)
use_zygote,
environ,
ipcfd,
#endif
cmd_line,
child_process_id,
client);

Launch函数的内部又做了什么呢?代码如下:

1
2
3
4
5
6
7
8
9
10
11
BrowserThread::PostTask(
BrowserThread::PROCESS_LAUNCHER, FROM_HERE,
base::Bind(
&Context::LaunchInternal,
make_scoped_refptr(this),
client_thread_id_,
child_process_id,
use_zygote,
environ,
ipcfd,
cmd_line));

可以看出它PostTask到BrowserThread::PROCESS_LAUNCHER,让其执行Context ::LaunchInternal任务。再来看看这个函数,里面有一行:

1
bool launched = base::LaunchProcess(*cmd_line, options, &handle);

这个函数调用fork()然后execvp ()启动了子进程。子进程会执行上文的RunNamedProcessTypeMain函数,但是这次它是带执行参数的,所以不会进入BrowserMain函数,而是进入其它函数,如:RendererMain等。以上就是子进程的启动流程了。

5总结

Browser多线程启动

  1. Browser主函数是BrowserMain,该函数创建了一个BrowserMainRunnerImpl对象,同时执行了该对象的Initialize函数;
  2. BrowserMainRunnerImpl的Initialize创建了一个BrowserMainLoop对象,同时执行了该对象的Init和CreateStartupTasks函数;
  3. BrowserMainLoop的CreateStartupTasks添加了一堆任务,其中一个任务就是CreateThreads函数;
  4. 在CreateThreads函数中会初始化各种线程对象,并且StartWithOptions。

Chromium多进程

  1. 双击Chromium图标启动第一个进程,即Browser进程;
  2. Browser进程中RenderProcessHostImpl对象的Init函数会new一个ChildProcessLauncher对象,该对象来管理子进程的启动;
  3. ChildProcessLauncher构造过程中会借助其Context对象,Post一个Task到PROCESS_LAUNCHER线程;
  4. Task会执行base::LaunchProcess,该函数调用fork()然后execvp ()启动了子进程;
  5. 启动的子进程是带参数的,这次在执行RunNamedProcessTypeMain的时候不会进入BrowserMain,而是进入其对应的主函数。

1头文件导入与新的基本类型

OC的头文件跟C和CPP一样都是.h,而OC的源文件是.m

导入头文件是用#import,这比#include好的地方在于默认设置了头文件保护,不需要在头文件中设置保护符

#import <Foundation/Foundation.h>

前面的Foundation是框架名,后面的Foundation.h是头文件名

OC引入了新的基本类型BOOL,类似于CPP中的bool,但是BOOL的值是YES /NO

语言 C CPP ObjC
源文件 .c .cpp .m
.h导入符号 #include #include #import
.h导入方式 导入文件名 导入文件名 导入框架/文件名
Log输出 printf cout NSLog

2面向对象

创建一个OC类的关键字是@interface,实现一个类的关键词是@implementation

而CPP创建类是class,实现类没有关键词,只是函数前面都要加”类名::”。

所有的OC类必须要直接或者间接继承NSObject类。而CPP没有限制

OC在类的.h文件中不定义方法名字,直接在.m文件中写这些方法是没问题的。

而CPP必须要在头文件的class块中定义方法名字,才能在.CPP中实现。

对象

OC的对象全部是new出来的,都存在heap上,而CPP的对象可能存在stack上,也可能在heap上

消息

OC对对象的调用是基于消息的。这样即使在类的中没有明确指出该消息,也能调用该消息。编译是能过的。

CPP类要是没有写出成员函数,调用该成员函数是肯定编译不过的。

类对象与OC对象模型

OC中存在类对象,每个OC对象中都有一个指针指向其类对象,以此来标识这个对象所从属的类。类对象也是一种对象。OC的面向对象模型是基于这种类对象,查表找对应的函数。

CPP中无类对象,面向对象的多态是基于vtbl

对象模型 CPP ObjC
文档 《深度探索C++对象模型》 《深入浅出Cocoa教程》
作者 侯捷 白云飘飘

OC多了类对象一层,程序多了一层间接性,因此效率比CPP要低一些,但是正因为这种间接性,所用其编程的灵活性应该比CPP要强,更加容易设计出易于扩展和维护的程序。因此OC中的设计模式可能更加灵活。

id

OC中的id类型泛指某个对象的指针,其实可以看作NSObject*
对应的CPP中相当于void*

虚函数

OC中每个成员函数都可以当作虚函数,而CPP中标记为virtual的成员函数才是虚函数。

继承

OC只支持单继承,而且没有继承方式
CPP支持多继承,继承方式有public、protected、private

Big3

构造

OC中以init开头的函数是构造函数,但是它不会像CPP的构造函数那样先帮你调用父类的构造函数。在OC中需要手动调用[super init];

析构

OC对象的析构函数统一为-(void) dealloc; 析构本类成员后需要手动调用[super dealloc];

复制构造

OC对象的复制构造以copy 或者 mutableCopy开头

3内存管理

OC的对象全部是alloc后init的存在heap上,stack上只是保存OC对象的指针。

不同的指针指向同一个heap对象的时候是以引用计数的方式进行统计的。

对象初创的时候,其计数是1, retain消息会把对象引用计数+1,release消息会把对象引用计数-1。当引用计数减到0的时候,自动调用对象的dealloc函数,对对象进行析构。

autorelease消息会把对象注册到距离最近的AutoreleasePool上去,一但这个AutoreleasePool被析构,它会向注册到其上的每个对象发一个release消息,使他们的对象引用计数-1。

4property(属性)

在类声明的时候以@property的方式来声明一个属性,

在类实现的时候以@synthesize来实现属性的存取方法

MRC模式下属性的参数

1复制
assign(默认),retain,copy

2读写
readwrite(默认),readonly

3原子性
atomic(默认)nonatomic

5category(类别)

1
2
3
4
5
@interface 宿主类名(类别名)
声明方式如下:
@interface HostClass(MyCategory)
-(void) categoryFunc;
@end

作用:

  1. 分散类的实现,一个大类分拆成小的category
  2. 为类添加前向声明(消除编译器的warning)
  3. 当作非正式的协议,category中的方法不必每个都实现

6protocol(协议)

用@protocol来声明协议,若某个类需要实现协议必须把协议放在他父类的尖括号列表中,并且该类必须实现protocol中的所有声明的方法
如:

1
2
3
4
5
6
7
//protocol definition
@protocol Myprotocol
-(void) protocolFun;
@end
//use protocol
@interface MyClass : NSObject<Myprotocol>
@end

7delegate(委托)

委托是一种设计模式,就是把本类需要实现的东西,交给其委托类去实现,一般来说委托是基于protocol的,用protocol是声明某种接口,比如

1
2
3
4
5
6
7
8
@protocol MyDelegate
-(void) delegateFunc;
@end
@interface HostClass
{
NSObject<MyDelegate> *delegate;
}
@end

实现MyDelegate协议的所有类都能当作HostClass的委托对象。

8 常用的Foundation Kit类

字符串

NSString:类似于string,但是!!!不能直接用==比较两个字符是否相等,因为OC没有操作符重载,应该使用isEqualToString

集合

NSArray\NSMutableArray:类似于vector
NSDictionary\NSMutableDictionary: 类似于map!!!objectForKey和KVC的valueForKey;setObject:forKey:和KVC的setValue:forKey:不要混淆

数值

NSNumber:标量的类封装,如int,float等都可以使用NSNumber封装
NSValue:任意类型的类封装,以byte方式实现
NSNull:nil的类封装

9KVC

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- (void)setValue:(id)value forKey:(NSString *)key
来设置对象中的属性
- (id)valueForKey:(NSString *)key
来获取对象中的属性
内部调用的是key名称的property,如果没有property,就就直接查找key名称或者_key名称的成员变量
路径
- (id) valueForKeyPath:(NSString *)key
获取路径上的属性值
整体操作
如果向NSArray请求一个键值,它实际上会查询数组中的每个对象来查找这个值,然后讲查询结果打包到另外一个数组并且返回给你
运算符
用- (id) valueForKeyPath:(NSString *)key可以引用一些运算符来进行运算
@count计算左侧的结果,用于计数
@sum计算总和,如:@"cars.@sum.mileage" @sum将结果拆成两部分
@agv计算平均值,如:@"cars.@avg.mileage"
@min取最小值
@max取最大值

1.百度-数据分析部门

公司:百度
转正:实习生转正率70%~80%,9月份转正
2013年3月5日14:00
数据分析部门

一面:

  1. 笔写链表判环算法
  2. 10G数求前K个
  3. 给出以p概率生成0,1-p概率生成1的函数,设计以等概率生成0和1的函数。进一步设计随机数生成器。
  4. 想法,如何搜集用户信息

一面通过

二面:

  1. 一个log文件含有大小10G的query,求前K个出现次数最多的query
  2. 给出一个数据库设计,分析不足之处
  3. 随便写一个排序算法
  4. 代码量多少
  5. 想法,如何判断刘德华和郭富城的关系

二面挂了

不足之处:笔写函数的熟练度,对常见排序算法的理解,数据库的常见知识点,索引之类。

2.百度-基础平台部

公司:百度
2013年03月11日 14:00
基础平台部的C/C++实习研发工程师

一面:

面试官:研发工程师

  1. 笔写c++实现一个栈
  2. IPC的几种方式
  3. 笔写链表逆序
  4. 智力题,100小球在管道对碰问题

一面通过

二面:

面试官:资深研发工程师

  1. 给1000万个北京电话号码排序,给出算法,分析时间复杂度
  2. 二叉树遍历时间空间复杂度
  3. KMP算法
  4. c++实现不能被继承的类
  5. auto_ptr如何管理vector,auto_ptr的作用
  6. N个数前M大
  7. iostream和iostream.h的区别
  8. vector排序
  9. 进程和线程区别
  10. 笔写c++实现string类,实现big3,以及赋值操作符

二面通过

三面:

面试官:经理

首先给简历,没有固定题目,随便了解

  1. 问对网络编程的了解,四层协议,分别解释一下
  2. 问数据库范式的作用,分别解释
  3. 数据库容量瓶颈在哪里,为什么不能把数据库设为无限大
  4. 多线程的项目经历
  5. 自身最大优点是什么,怎么证明
  6. 为什么在简历中说善于发现bug,如果一个函数10次中有7次正确,3次错误,问题可能出现在哪里。你怎么解决问题,找到bug
  7. 所用过的互联网产品,人人和微信各有什么特点
  8. 职业规划如何,为什么不去研究所

三面挂了

不足之处:STL底层,算法时间复杂度分析,数据库的常见知识点,操作系统进程线程,及其通信同步。

3.SYMTEC

公司:SYMTEC

转正:完成实习任务即可转正,概率很高

研发环境:linux,unix,shell,perl,jquery

软件:Storge Foundation相关内部工具

户口:每年有名额,由FESCO管理

2013.03...**
电话面试
面试官:**技术人员

  1. 数组逆序
  2. 字符拷贝
  3. SQL查找取前100个
  4. 英语自我介绍,
  5. 英文对话

2013.03.15.16.30
面试
面试官:技术员1,技术员2,hr。三人同时

技术员1:

  1. 基类析构函数设置为virtual的作用,在实际工程项目中是否遇到过memory leak
  2. C++多继承出现钻石环,孙子类调用祖父类的函数会有什么情况
  3. 介绍STL,vector的底层实现,sizeof(vector)是多少,map底层实现
  4. MFC中所有窗口都继承的类,MFC中所有类都是从什么类派生的(注:面试官出的题目有错误,有些类不是从CObject继承的,比如CPoint)
  5. CString和string的相互转化
  6. 引用和指针的区别,是否可以有const引用
  7. C语言中的malloc和C++中new的区别

技术员2:

  1. SQL查找取前100个
  2. 黑板上手写一个二分查找(注:写下了严书中的算法,技术员1指出了严书上二分查找的不足之处)
  3. 能不能耐住寂寞,面对50%的重复性劳动会怎样
  4. 英语问答环节
  5. Self Introduction
  6. Why don`t you go abroad
  7. Short term and long term goals
  8. Why do you choose automation as you major
  9. Are you a popular person,how can you prove it
  10. I think you are too confident , how do you think so
  11. Who is always disappointed on you?
    Hr:
    一直在观察面试者,基本上没问问题
    不足之处:STL底层,数据库查询语句。

4.酷我音乐

公司:KUWO
转正:概率很高

2013.03.19

一面:

  1. 多线程同步
  2. 快速排序时间复杂度,最好时间复杂度,以排序的数组,再进行快速排序,时间复杂度是多少
  3. 设计模式,写个单例模式,说说了解的设计模式
  4. 为什么控制跑来做软件开发

二面:

  1. 虚函数的作用,静态成员函数的作用
  2. 类对象的内存布局
  3. 在dll里面干掉一个线程会出现什么情况

三面:

  1. 如何快速从文件中读取数据,比如数据记录是姓名,年龄,性别,数据记录是顺序排列。查询的输入是第x条记录。(只回答对了建立文件映射,关键是不理解题目)
  2. 如何设计一个通用的单例模式
  3. 简历中为什么说善于发现bug,最近一次发现bug是什么时候,怎么解决的。有没有发现运行时期的bug

四面:

1
2
3
4
5
6
7
8
9
10
11
12
13
int a[] = {1,2,3,4,5};  *(&a+1)和*(&a[0] + 1)分别是多少
struct Test
{
static int a;
int m;
}
Test test;
&test的值和 &(test.m) 和 &(test.a)的值的关系
解释一下堆栈 和 堆的特点
解释一下static_castdynamic_cast的区别
vector<int> a;如何获得a内部数组地址
主线程等到子线程干完才能干事,主线程怎么知道子线程干完了?不能用内核同步方式。
多维数组存储模型

五面:
一个服务号码比如123456 对应的是下载音乐服务。有很多服务号码,分别对应不同的服务。这些号码不会超过13位。设计一个数据结构根据输入的码找到相应服务。
开始回答用map,查找的复杂度是O(nlogn);
后来说如果输入的服务码多了会怎样,怎么容错?比如一个服务码是22221对应听故事服务,用户输入了222211111,如何任然定位到听故事服务。内存不限。
想到了基数排序,最后设计了一个算法,面试官说我设计的叫做字典树,我表示瞎猫碰死耗子

六面:
基本聊天,没问技术问题。

七面:
HR
不足之处:算法的分析,数据结构的了解。

5.搜狗

职位:Windows客户端浏览器开发

时间:2013.03.27

一面:

  1. 虚析构函数的作用
  2. void memcpy (void* dest, const void* src, size_t len);实现函数;有什么需要注意的?
  3. list<int> iList;删除所用等于某个特定值的元素
  4. 多进程通信用过哪些?
  5. 多线程同步方式?用过哪些?
  6. WTL和COM的了解
  7. 是否用过模版?解释模版特化和偏特化
  8. dll中new的对象在exe中删除是否合适?
  9. exeA和exeB都载入dllC,问dllC是否在A和B中各有一份?
  10. 你是如何在不同工程中实现同种子功能的?
  11. DC,客户区和非客户区,贴图图形闪烁的原因
  12. 1000万个人的身高,找出前20
  13. txtA和txtB每行都是文本,求A和B内容相同的行

不足之处:Win32中dll的了解。

时间:2013.04.03

二面:

  1. 如何获得当前exe运行的目录
  2. vetor容器不停push_back导致容器的capacity增长,描述一下capacity增长的过程
  3. 双线程(一个读线程、一个写线程)同时对一块内存操作,比如int g_iNum;是否需要加锁?如果把整数换成list呢?
  4. SendMessage 和 PostMessage的区别,如果窗口A和窗口B相互SendMessage会出现什么情况?
  5. 多线程同步,列举你用过的方式。Event 和 Mutex有什么区别?
  6. 自动重置的Event和获得Event后立即手动重置有什么区别?
  7. dll载入后,怎么调用函数?
  8. 最近看了什么书?
  9. 为什么看了那么多书?不去实践一下?
  10. 如果老板交给你一个活,你怎么处理?
  11. 有没有女朋友?工作还是上学?如果她不能在北京找到工作怎么办?
  12. 找工作中是否户口很重要?如果一个国企有户口,和一个你喜欢干的工作没有户口,怎么选择?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class A
{
public :
void func() {cout<<"Hello World"<<endl;}
};
int main()
{
A* p = NULL;
p->func();//会出问题吗?
return 0;
}

class A
{
public :
void func() { cout<<m_iTest<<endl;cout<<"Hello World"<<endl;}
int m_iTest;
};
这样呢?调用p->func()会出问题吗?

如果把 func函数设置为virtual呢? 问题出在哪一步?
1
2
3
4
5
6
7
8
9
10
A tmpA,tmpB;
tmpA.func();
tmpB.func();
编译器怎知道是tmpA还是tmpB调用func呢?

类的成员函数可以作为线程函数吗?

为什么static成员函数可以作为线程函数?

static成员函数和普通的成员函数的区别在哪里?针对这个问题。。。

不足之处:Win32中dll的了解。

6.豌豆荚

一面:

实现大整数乘法

二面:

实现字符串匹配算法

7.腾讯

职位:Windows客户端浏览器开发

时间:2013.04.24

一面:

  1. MFC怎么实现消息映射
  2. Windows程序的基本流程,创建的窗口怎么跟窗口过程绑定起来
  3. C++看过什么书
  4. 说一下《Effective C++》中印象最深的一个条款
  5. 编译器为类默认实现的函数有哪些
  6. C++怎么实现虚函数机制
  7. A、B、C三个类,A是C的基类,同时C中包含有B的对象,A、B、C构造函数执行顺序
  8. Vptr在哪里初始化的
  9. Windows中多线程同步机制
  10. Windows中有没有共享内存? 文件映射的作用
  11. 用两个队列模拟一个堆栈
  12. 设计一个不能被继承的类
  13. Heap和Stack的区别

二面:

  1. C++看过什么书
  2. Windows看过什么书
  3. 说一下Windows内存模型
  4. Windows哪块比较熟悉?IO、内存管理、多线程?
  5. 怎么把单线程函数拆为多线程函数?比如用单线程对一张位图进行灰度化。怎么把这种单线程任务拆分为多线程
  6. 1G内存,两个exe。说一下内存占用情况

三面:
HR

8.阿里巴巴

时间:2013.05.21

一面:

  1. 解释重载和多态
  2. 堆和栈的区别?应用场景分别是什么?
  3. 用过的类库?vector和list的区别。是否多线程安全的?
  4. 设计模式了解哪些?写一个单例模式
  5. 适配器模式是否了解?
  6. 是否做过多进程通讯?跨机器的进程通讯的方式有哪些?
  7. 说一下OSI七层模型
  8. 网络拥塞发生在哪一层?
  9. tcp和udp区别
  10. 数据库了解多少?
  11. 有没有看过常用算法的工业级源码实现?如快速排序等
  12. 聊项目,做的东西
  13. 引申我做的项目,同一反射内存地址 怎么在多机器环境下保持读写不冲突?

二面:(后记)

  1. 感觉写过最好的代码
  2. 一道算法题
  3. 聊了一下dota喜欢什么英雄

三面:
HR