1.异步
昨天别人给了同事一个简单的demo,问题是UITableViewController上有内有textfield的cell,在textfield被选中,弹出键盘,界面会自动滚动,就和咱们平时作textfield输入时不要被键盘挡住那样的滚动。对方是想要不要这个自动滚动,由于那个demo滚动的位置不对,查了一下,发现不知道何时起,UITableViewController自动适配了这个需求,完成不滚动的要求只有两个作法:(1)把UITableViewController换成UIViewController;(2)重载viewWillAppear方法,但不要继承[super viewWillAppear]async
2.oop
你们都知道字典类NSDictionary和NSMutableDictionary在写入的时候,value对应的key只要实现copy协议就能够。上次碰到要把一个字典的数据存到沙盒的plist表里,发现写入失败。作了些对比后发现,由于字典类中,有一个key是number类型的,而plist表去查看source code,发现它的key都是同种类型,string的,因此写入失败。测试
3.动画
在CoreText里获取文本被点击位置的文本索引,通常用的是方法CTLineGetStringIndexForPosition,但屡次测试会发现,你点某一个字的前半部分,输出是前一个字的索引,点击后半部分才输出正确,看了下官方文档里的解释,大概意思是这个方法是将点击位置转换为最近的字符插入处(其实就是光标),因此才会形成这样。修正的代码应该是这样的:spa
CFIndex idx = CTLineGetStringIndexForPosition(line, relativePoint); CGFloat glyphStart; CTLineGetOffsetForStringIndex(line, idx, &glyphStart); if (relativePoint.x < glyphStart && idx) { --idx; }
4.线程
CALayer类有个方法,检测某个点是不是在它的区域中指针
/* Returns true if the bounds of the layer contains point 'p'. */ - (BOOL)containsPoint:(CGPoint)p;
方法上面是文档里的说明,用的是layer的bounds属性来比对点p是否在其中,咱们都知道bounds属性的坐标是原点,因此使用这个方法,须要把点p先用如下方法转换到要检测是layer坐标系中,再进行检测:调试
- (CGPoint)convertPoint:(CGPoint)p fromLayer:(CALayer *)l; - (CGPoint)convertPoint:(CGPoint)p toLayer:(CALayer *)l;
5.code
关于CALayer,网上一直能够看到这样一段描述:
UIView的layer树形在系统内部,被系统维护着三份copy(这段理解有点吃不许)。 第一份,逻辑树,就是代码里能够操纵的,例如更改layer的属性等等就在这一份。 第二份,动画树,这是一个中间层,系统正在这一层上更改属性,进行各类渲染操做。 第三份,显示树,这棵树的内容是当前正被显示在屏幕上的内容。 这三棵树的逻辑结构都是同样的,区别只有各自的属性。
或者是这样一个很简单的解释:
UIView的layer树形在系统内部,被维护着三份copy。分别是逻辑树,这里是代码能够操纵的;动画树,是一个中间层,系统就在这一层上更改属性,进行各类渲染操做;显示树,其内容就是当前正被显示在屏幕上得内容。
具体是怎样的,没明白,本身找资料,发现有这样两个属性:
- (id)presentationLayer; - (id)modelLayer;
第一个属性是显示树或者呈现树,第二个属性是模型树。叫法有不一样,有叫逻辑树,动画树和显示树,也有叫呈现树,模型树和渲染树。对这两个方法得到的layer,修改其属性是无效的。
呈现树是咱们在显示在屏幕上所看到的layer,因此若是下面的测试代码在viewDidLoad里输出这一层,会看到是空的,由于这时候尚未显示在屏幕上。
- (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view from its nib. _colorLayer = [[CALayer alloc] init]; _colorLayer.bounds = CGRectMake(0, 0, 10, 10); _colorLayer.position = CGPointMake(_centerView.bounds.size.width / 2, _centerView.bounds.size.height / 2); _colorLayer.backgroundColor = [UIColor blueColor].CGColor; [_centerView.layer addSublayer:_colorLayer]; _timer = [NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:@selector(showLogOut) userInfo:nil repeats:YES]; [_timer fire]; } - (void)showLogOut { CALayer* layer = _colorLayer.presentationLayer; CALayer* layer1 = _colorLayer.modelLayer; NSLog(@"present layer %@", layer); NSLog(@"%f-------%f", layer.position.x, layer1.position.x); } - (IBAction)doAnimation:(id)sender { CGPoint point = CGPointMake(_centerView.bounds.size.width / 2, _centerView.bounds.size.height / 2); CABasicAnimation* animation = [CABasicAnimation animationWithKeyPath:@"position"]; animation.toValue = [NSValue valueWithCGPoint:CGPointMake(point.x + 100, point.y)]; animation.duration = 10; [_colorLayer addAnimation:animation forKey:nil]; }
上面的测试代码是作了一个向右平移100的动画,输出能够看出,presentationLayer的属性跟随动画的变化而变化,记录各个时期的layer的状态,但modelLayer却一直不变。动画结束后,就是layer平移到了指定位置后,会自动返回原始的位置,这就是modelLayer所记录初始状态的做用。
还有一点须要注意,上面的定时器输出还输出了 presentationLayer ,而 presentationLayer 每次都是不同的。
上面一直没提到的渲染树(或者是动画树)是私有的,咱们没法访问,渲染树对呈现树的数据进行渲染,为了避免阻塞主线程,渲染树的行为都是在其余线程上进行的。
6.NSTimer注意事项
NSTimer会自动retain一次target和userinfo的参数,因此在NSTimer不用的时候,要先invalidate,再把NSTimer置nil,而这两步操做不能在dealloc,不然dealloc永远不会执行,由于没法释放。
NSTimer在滑动视图的时候,是中止执行的,由于runloop让给了UITrackingRunLoopMode,想要执行须要加上这个
[[NSRunLoop currentRunLoop] addTimer:_myTimer forMode:NSRunLoopCommonModes];
NSTimer在非主线程的线程是不执行的,除非加入下面的代码
[[NSRunLoop currentRunLoop] addTimer:_myTimer forMode:NSRunLoopCommonModes]; [[NSRunLoop currentRunLoop] run];
7.Block
咱们都知道,block内部是不能修改外部变量的,若是要修改,须要加上__block。
上面的描述,其实不对,block是不能修改外部的局部变量,可是对于属性和成员变量,是能够修改的。这是为何呢?
简单来讲,普通的局部外部变量,是分配到栈上的,而__block所修饰的变量,会自动复制到堆上。
参考1:http://chun.tips/blog/2014/11/13/hei-mu-bei-hou-de-blockxiu-shi-fu/
参考2:https://www.zhihu.com/question/39980914
8.引用计数相关
retainCount 是引用计数,指的是对某一块内存地址的使用状况,若是为0,表明没有使用了,能够释放。
可是好比说
MyClass* a = [[MyClass alloc] init];
a 是一个指针对象,即 a 这个内存地址里的内容,是存储另外一片内存地址,那么 retainCount 是指那一片内存地址的使用状况?
MyClass* aObj = [[MyClass alloc] init]; MyClass* bObj = [aObj copy]; MyClass* cObj = [aObj retain]; NSLog(@"count %d %d %d", aObj.retainCount, bObj.retainCount, cObj.retainCount); NSLog(@"%p %p %p", aObj, bObj, cObj); NSLog(@"%p %p %p", &aObj, &bObj, &cObj);
跑完上面的测试代码能够获得结果, &aObj, &bObj, &cObj 三者是对应三个对象的内存地址,各不相同,但 aObj, bObj, cObj 是三者的内容,a 和 c 相同, 和 b 不一样,因此 retainCount 是指内容的地址。
还有一个状况
NSString* aStr = @"1"; NSString* bStr = [aStr retain]; NSString* cStr = [aStr copy]; NSString* dStr = [aStr mutableCopy]; NSLog(@"%p %p %p %p", aStr, bStr, cStr, dStr); NSLog(@"%d %d %d %d", aStr.retainCount, bStr.retainCount, cStr.retainCount, dStr.retainCount);
输出的结果是,a, b, c 都是指向同一片内存,而 d 不一样,引用计数上,a, b, c 都是 -1,d 是 1 。
9.tableViewHeaderView的高度(tableFooterView同理)
用纯代码设置tableViewHeaderView的话,是没有啥问题的,可是从xib中加载一个view,再指定这个view做为tableHeaderView的话(代码以下),高度展现上就会不合要求。
MYHeaderView* headerView = [[[NSBundle mainBundle] loadNibNamed:@"MYHeaderView" owner:self options:nil] lastObject]; CGRect frame = headerView.frame; frame.size.height = 100; headerView.frame = frame; tableView.tableHeaderView = headerView;
结果是会挡住开头的cell。缘由未知,解决的办法有这两种:
1.在外面代码生成一个view作为容器,把从xib加载的加到这个容器中,把容器作为tableHeaderView
UIView* headerContentView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.height, 100)]; MYHeaderView* headerView = [[[NSBundle mainBundle] loadNibNamed:@"MYHeaderView" owner:self options:nil] lastObject]; [headerContentView addSubview:headerView]; tableView.tableHeaderView = headerContentView;
2.在MYHeaderView(xib对应的类)中,重写layoutSubviews方法,指定这个tableHeaderView的宽高
- (void)layoutSubviews { [super layoutSubviews]; self.bounds = CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.height, 100); } 附:这个要配合第一段代码设置好从xib加载出来的view的高度
10.textField的KVO监听输入
原本想用KVO来监听UITextField的键盘输入
[textField addObserver:self forKeyPath:@"text" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil]; - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context { NSLog(@"change"); }
却发现上面的观察,在键盘输入中是不会触发的,除非手动设置textField.text,一时不解是为何,查了资料,最终获得个提示,用runtime遍历出UITextField的全部成员变量
unsigned int outCount; Ivar *IvarArray = class_copyIvarList([UITextField class], &outCount);//获取到UITextField中的全部成员变量 for (unsigned int i = 0; i < outCount; i ++) { Ivar *ivar = &IvarArray[i]; NSLog(@"第%d个成员变量:%s,类型是:%s",i,ivar_getName(*ivar),ivar_getTypeEncoding(*ivar));// 依次获取每一个成员变量而且打印成员变量名字和类型 }
发现其中没有text对应的成员变量,可是有几个label
第30个成员变量:_displayLabel,类型是:@"UITextFieldLabel" 第31个成员变量:_placeholderLabel,类型是:@"UITextFieldLabel" 第32个成员变量:_dictationLabel,类型是:@"UITextFieldLabel" 第33个成员变量:_suffixLabel,类型是:@"UITextFieldLabel" 第34个成员变量:_prefixLabel,类型是:@"UITextFieldLabel" 第36个成员变量:_label,类型是:@"UILabel"
若是设置过textField的text或者placeholder,用object_getIvar根据成员变量进行输出,会看到_displayLabel和_placeholderLabel会有对应的值,可是若是从键盘输入的话,是不会有对应的值的。所以,在不肯定被观察者内部构造的状况下,不适宜使用KVO。
11.新线程中更新UI
通常咱们若是有某些耗时的操做,好比图片合成之类的,咱们会把这个操做新开一个线程去作,完成后异步调用主线程去赋值。但若是在不调用主线程去赋值,直接在新线程中赋值,是得不到更新的。这是由于在新线程中,没法读取到当前的图像上下文的缘由。
- (void)drawRect:(CGRect)rect { dispatch_queue_t colorQueue = dispatch_queue_create("testMyNewColor", DISPATCH_QUEUE_CONCURRENT); dispatch_async(colorQueue, ^{ CGContextRef context = UIGraphicsGetCurrentContext(); CGContextSetFillColorWithColor(context, [UIColor blueColor].CGColor); CGContextFillRect(context, self.bounds); }); }
上面这段测试代码,在运行后,控制台就会报出一些内容,告诉你invalid context,由于在这里的context是空的,去掉外层的新线程就没问题。
ps:固然,对于UI的某些更新是能够的,好比更新Button的title这样的操做,可是这彷佛没太多意义。
12.hiddenWhenPush
以前只知道这个属性,设置后,在push的时候,会隐藏tabbar,以后push的vc都是隐藏tabbar的。今天调试一个bug发现,因为我把设置hiddenWhenPush = YES的那个vc从viewControllers的堆栈中移除掉,致使后面再push的vc都显示了tabbar。
也就是说每次push出下一个vc的时候,都会去遍历堆栈中,看是否hiddenWhenPush = YES来决定是否隐藏tabbar。