大牛说这是不可错过的iOS开发技巧.docx
- 文档编号:12305302
- 上传时间:2023-04-18
- 格式:DOCX
- 页数:19
- 大小:25.13KB
大牛说这是不可错过的iOS开发技巧.docx
《大牛说这是不可错过的iOS开发技巧.docx》由会员分享,可在线阅读,更多相关《大牛说这是不可错过的iOS开发技巧.docx(19页珍藏版)》请在冰豆网上搜索。
大牛说这是不可错过的iOS开发技巧
大牛说:
这是不可错过的iOS开发技巧
一直想做这样一个小册子,来记录自己平时开发、阅读博客、看书、代码分析和与人交流中遇到的各种问题。
之前有过这样的尝试,但都是无疾而终。
不过,每天接触的东西多,有些东西不记下来,忘得也是很快,第二次遇到同样的问题时,还得再查一遍。
好记性不如烂笔头,所以又决定重拾此事,时不时回头看看,温故而知新。
这里面的每个问题,不会太长。
或是读书笔记,或是摘抄,亦或是验证,每个问题的篇幅争取在六七百字的样子。
笔记和摘抄的出处会详细标明。
问题的个数不限,凑齐3500字左右就发一篇。
争取每月至少发两篇吧,权当是对自己学习的一个整理。
本期主要记录了以下几个问题:
NSString属性什么时候用copy,什么时候用strong?
Foundation中的断言处理
IBOutletCollection
NSRecursiveLock递归锁的使用
NSHashTable
NSString属性什么时候用copy,什么时候用strong?
我们在声明一个NSString属性时,对于其内存相关特性,通常有两种选择(基于ARC环境):
strong与copy。
那这两者有什么区别呢?
什么时候该用strong,什么时候该用copy呢?
让我们先来看个例子。
示例
我们定义一个类,并为其声明两个字符串属性,如下所示:
1.@interface TestStringClass ()
2.
3.@property (nonatomic, strong) NSString *strongString;
4.@property (nonatomic, copy) NSString *copyedString;
5.
6.@end
上面的代码声明了两个字符串属性,其中一个内存特性是strong,一个是copy。
下面我们来看看它们的区别。
首先,我们用一个不可变字符串来为这两个属性赋值,
1.- (void)test {
2.
3.NSString *string = [NSString stringWithFormat:
@"abc"];
4.self.strongString = string;
5.self.copyedString = string;
6.
7.NSLog(@"origin string:
%p, %p", string, &string);
8.NSLog(@"strong string:
%p, %p", _strongString, &_strongString);
9.NSLog(@"copy string:
%p, %p", _copyedString, &_copyedString);
10.}
其输出结果是:
1.origin string:
0x7fe441592e20, 0x7fff57519a48
2.strong string:
0x7fe441592e20, 0x7fe44159e1f8
3.copy string:
0x7fe441592e20, 0x7fe44159e200
我们要以看到,这种情况下,不管是strong还是copy属性的对象,其指向的地址都是同一个,即为string指向的地址。
如果我们换作MRC环境,打印string的引用计数的话,会看到其引用计数值是3,即strong操作和copy操作都使原字符串对象的引用计数值加了1。
接下来,我们把string由不可变改为可变对象,看看会是什么结果。
即将下面这一句
1.NSString *string = [NSString stringWithFormat:
@"abc"];
改成:
1.NSMutableString *string = [NSMutableString stringWithFormat:
@"abc"];
其输出结果是:
1.origin string:
0x7ff5f2e33c90, 0x7fff59937a48
2.strong string:
0x7ff5f2e33c90, 0x7ff5f2e2aec8
3.copy string:
0x7ff5f2e2aee0, 0x7ff5f2e2aed0
可以发现,此时copy属性字符串已不再指向string字符串对象,而是深拷贝了string字符串,并让_copyedString对象指向这个字符串。
在MRC环境下,打印两者的引用计数,可以看到string对象的引用计数是2,而_copyedString对象的引用计数是1。
此时,我们如果去修改string字符串的话,可以看到:
因为_strongString与string是指向同一对象,所以_strongString的值也会跟随着改变(需要注意的是,此时_strongString的类型实际上是NSMutableString,而不是NSString);而_copyedString是指向另一个对象的,所以并不会改变。
结论
由于NSMutableString是NSString的子类,所以一个NSString指针可以指向NSMutableString对象,让我们的strongString指针指向一个可变字符串是OK的。
而上面的例子可以看出,当源字符串是NSString时,由于字符串是不可变的,所以,不管是strong还是copy属性的对象,都是指向源对象,copy操作只是做了次浅拷贝。
当源字符串是NSMutableString时,strong属性只是增加了源字符串的引用计数,而copy属性则是对源字符串做了次深拷贝,产生一个新的对象,且copy属性对象指向这个新的对象。
另外需要注意的是,这个copy属性对象的类型始终是NSString,而不是NSMutableString,因此其是不可变的。
这里还有一个性能问题,即在源字符串是NSMutableString,strong是单纯的增加对象的引用计数,而copy操作是执行了一次深拷贝,所以性能上会有所差异。
而如果源字符串是NSString时,则没有这个问题。
所以,在声明NSString属性时,到底是选择strong还是copy,可以根据实际情况来定。
不过,一般我们将对象声明为NSString时,都不希望它改变,所以大多数情况下,我们建议用copy,以免因可变字符串的修改导致的一些非预期问题。
关于字符串的内存管理,还有些有意思的东西,可以参考NSString特性分析学习。
参考
NSStringcopynotcopying?
NSString特性分析学习
NSString什么时候用copy,什么时候用strong
Foundation中的断言处理
经常在看一些第三方库的代码时,或者自己在写一些基础类时,都会用到断言。
所以在此总结一下Objective-C中关于断言的一些问题。
Foundation中定义了两组断言相关的宏,分别是:
1.NSAssert / NSCAssert
2.NSParameterAssert / NSCParameterAssert
这两组宏主要在功能和语义上有所差别,这些区别主要有以下两点:
如果我们需要确保方法或函数的输入参数的正确性,则应该在方法(函数)的顶部使用NSParameterAssert/NSCParameterAssert;而在其它情况下,使用NSAssert/NSCAssert。
另一个不同是介于C和Objective-C之间。
NSAssert/NSParameterAssert应该用于Objective-C的上下文(方法)中,而NSCAssert/NSCParameterAssert应该用于C的上下文(函数)中。
当断言失败时,通常是会抛出一个如下所示的异常:
1.*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason:
'true is not equal to false'
Foundation为了处理断言,专门定义了一个NSAssertionHandler来处理断言的失败情况。
NSAssertionHandler对象是自动创建的,用于处理失败的断言。
当断言失败时,会传递一个字符串给NSAssertionHandler对象来描述失败的原因。
每个线程都有自己的NSAssertionHandler对象。
当调用时,一个断言处理器会打印包含方法和类(或函数)的错误消息,并引发一个NSInternalInconsistencyException异常。
就像上面所看到的一样。
我们很少直接去调用NSAssertionHandler的断言处理方法,通常都是自动调用的。
NSAssertionHandler提供的方法并不多,就三个,如下所示:
1.// 返回与当前线程的NSAssertionHandler对象。
2.// 如果当前线程没有相关的断言处理器,则该方法会创建一个并指定给当前线程
3.+ (NSAssertionHandler *)currentHandler
4.
5.// 当NSCAssert或NSCParameterAssert断言失败时,会调用这个方法
6.- (void)handleFailureInFunction:
(NSString *)functionName file:
(NSString *)object lineNumber:
(NSInteger)fileName description:
(NSString *)line, format,...
7.
8.// 当NSAssert或NSParameterAssert断言失败时,会调用这个方法
9.- (void)handleFailureInMethod:
(SEL)selector object:
(id)object file:
(NSString *)fileName lineNumber:
(NSInteger)line description:
(NSString *)format, ...
另外,还定义了一个常量字符串,
1.NSString * const NSAssertionHandlerKey;
主要是用于在线程的threadDictionary字典中获取或设置断言处理器。
关于断言,还需要注意的一点是在Xcode4.2以后,在release版本中断言是默认关闭的,这是由宏NS_BLOCK_ASSERTIONS来处理的。
也就是说,当编译release版本时,所有的断言调用都是无效的。
我们可以自定义一个继承自NSAssertionHandler的断言处理类,来实现一些我们自己的需求。
如MatttThompson的NSAssertionHandler实例一样:
1.@interface LoggingAssertionHandler :
NSAssertionHandler
2.@end
3.
4.@implementation LoggingAssertionHandler
5.
6.- (void)handleFailureInMethod:
(SEL)selector
7.object:
(id)object
8.file:
(NSString *)fileName
9.lineNumber:
(NSInteger)line
10.description:
(NSString *)format, ...
11.{
12.NSLog(@"NSAssert Failure:
Method %@ for object %@ in %@#%i", NSStringFromSelector(selector), object, fileName, line);
13.}
14.
15.- (void)handleFailureInFunction:
(NSString *)functionName
16.file:
(NSString *)fileName
17.lineNumber:
(NSInteger)line
18.description:
(NSString *)format, ...
19.{
20.NSLog(@"NSCAssert Failure:
Function (%@) in %@#%i", functionName, fileName, line);
21.}
22.
23.@end
上面说过,每个线程都有自己的断言处理器。
我们可以通过为线程的threadDictionary字典中的NSAssertionHandlerKey指定一个新值,来改变线程的断言处理器。
如下代码所示:
1.- (BOOL)application:
(UIApplication *)application
2.didFinishLaunchingWithOptions:
(NSDictionary *)launchOptions
3.{
4.NSAssertionHandler *assertionHandler = [[LoggingAssertionHandler alloc] init];
5.[[[NSThread currentThread] threadDictionary] setValue:
assertionHandler
6.forKey:
NSAssertionHandlerKey];
7.// ...
8.
9.return YES;
10.}
而什么时候应该使用断言呢?
通常我们期望程序按照我们的预期去运行时,如调用的参数为空时流程就无法继续下去时,可以使用断言。
但另一方面,我们也需要考虑,在这加断言确实是需要的么?
我们是否可以通过更多的容错处理来使程序正常运行呢?
MatttThompson在NSAssertionHandler中的倒数第二段说得挺有意思,在此摘抄一下:
ButifwelookdeeperintoNSAssertionHandler—andindeed,intoourownhearts,therearelessonstobelearnedaboutourcapacityforkindnessandcompassion;aboutourabilitytoforgiveothers,andtorecoverfromourownmissteps.Wecan'tberightallofthetime.Weallmakemistakes.Byacceptinglimitationsinourselvesandothers,onlythenareweabletogrowasindividuals.
参考
NSAssertionHandler
NSAssertionHandlerClassReference
IBOutletCollection
在IB与相关文件做连接时,我们经常会用到两个关键字:
IBOutlet和IBAction。
经常用xib或storyboard的童鞋应该用这两上关键字非常熟悉了。
不过UIKit还提供了另一个伪关键字IBOutletCollection,我们使用这个关键字,可以将界面上一组相同的控件连接到同一个数组中。
我们先来看看这个伪关键字的定义,可以从UIKit.framework的头文件UINibDeclarations.h找到如下定义:
1.#ifndef IBOutletCollection
2.#define IBOutletCollection(ClassName)
3.#endif
另外,在Clang源码中,有更安全的定义方式,如下所示:
1.#define IBOutletCollection(ClassName) __attribute__((iboutletcollection(ClassName)))
从上面的定义可以看到,与IBOutlet不同的是,IBOutletCollection带有一个参数,该参数是一个类名。
通常情况下,我们使用一个IBOutletCollection属性时,属性必须是strong的,且类型是NSArray,如下所示:
1.@property (strong, nonatomic) IBOutletCollection(UIScrollView) NSArray *scrollViews;
假定我们的xib文件中有三个横向的scrollView,我们便可以将这三个scrollView都连接至scrollViews属性,然后在我们的代码中便可以做一些统一处理,如下所示:
1.- (void)setupScrollViewImages
2.{
3.for (UIScrollView *scrollView in self.scrollViews) {
4.[self.imagesData enumerateObjectsUsingBlock:
^(NSString *imageName, NSUInteger idx, BOOL *stop) {
5.UIImageView *imageView = [[UIImageView alloc] initWithFrame:
CGRectMake(CGRectGetWidth(scrollView.frame) * idx, 0, CGRectGetWidth(scrollView.frame), CGRectGetHeight(scrollView.frame))];
6.imageView.contentMode = UIViewContentModeScaleAspectFill;
7.imageView.image = [UIImage imageNamed:
imageName];
8.[scrollView addSubview:
imageView];
9.}];
10.}
11.}
这段代码会影响到三个scrollView。
这样做的好处是我们不需要手动通过addObject:
方法将scrollView添加到scrollViews中。
不过在使用IBOutletCollection时,需要注意两点:
IBOutletCollection集合中对象的顺序是不确定的。
我们通过调试方法可以看到集合中对象的顺序跟我们连接的顺序是一样的。
但是这个顺序可能会因为不同版本的Xcode而有所不同。
所以我们不应该试图在代码中去假定这种顺序。
不管IBOutletCollection(ClassName)中的控件是什么,属性的类型始终是NSArray。
实际上,我们可以声明是任何类型,如NSSet,NSMutableArray,甚至可以是UIColor,但不管我们在此设置的是什么类,IBOutletCollection属性总是指向一个NSArray数组。
关于第二点,我们以上面的scrollViews为例,作如下修改:
1.@property (strong, nonatomic) IBOutletCollection(UIScrollView) NSSet *scrollViews;
实际上我们在控制台打印这个scrollViews时,结果如下所示:
1.(lldb) po self.scrollViews
2.<__NSArrayI 0x1740573d0>(
3. 0x12d60d770; frame = (0 0; 320 162); clipsToBounds = YES; autoresize = W+H; gestureRecognizers = 0x1740574f0>; layer = 0x174229480>; contentOffset: {0, 0}; contentSize: {0, 0}>, 4. 0x12d60dee0; frame = (0 0; 320 161); clipsToBounds = YES; autoresize = W+H; gestureRecognizers = 0x174057790>; layer = 0x1742297c0>; contentOffset: {0, 0}; contentSize: {0, 0}>, 5. 0x12d60e650; frame = (0 0; 320 163); clipsToBounds = YES; autoresize = W+H; gestureRecognizers = 0x1740579a0>; layer = 0x1742298e0>; contentOffset: {0, 0}; contentSize: {0, 0}> 6.) 可以看到,它指向的是一个NSArray数组。 另外,IBOutletCollection实际上在iOS4版本中就有了。 不过,现在的Objective-C已经支持objectliterals了,所以定义数组可以直接用@[],方便了许多。 而且objectliterals方式可以添加不在xib中的用代码定义的视图,所以显得更加灵活。 当然,两种方式选择哪一种,就看我们自己的实际需要和喜好了。 参考 IBAction/IBOutlet/IBOutletCollection IBOutletCollection.m NSRecursiveLock递归锁的使用 NSRecursiveLock实际上定义的是一个递归锁,这个锁可以被同一线程多次请求,而不会引起死锁。 这主要是用在循环或递归操作中。 我们先来看一个示例: 1.NSLock *lock = [[NSLock alloc] init]; 2. 3.dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 4. 5.static void (^RecursiveMethod)(int); 6. 7.RecursiveMethod = ^(int value) { 8. 9.[lock lock]; 10.if (value > 0) { 11. 12.NSLog(@"value = %d", value); 13.sleep (2); 14.RecursiveMethod(value - 1); 15.} 16.[lock unlock]; 17.}; 18. 19.RecursiveMethod(5); 20.}); 这段代码是一个典型的死锁情况。 在我们的线程中,RecursiveMethod是递归调用的。 所以每次进入这个block时,都会去加一次锁,而从第二次开始,由于锁已经被使用了且没有解锁,所以它需要等待锁被
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 大牛说这是 不可 错过 iOS 开发 技巧