开发随笔记录

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。

相关文章
相关标签/搜索