Blacktea's Life

A programmer who love music and football


  • 首页

  • 分类

  • 关于

  • 归档

  • 标签

  • 公益404

iOS里synchronized的死锁、性能、实效

发表于 2017-10-19 | 分类于 iOS | 阅读次数

synchronized的死锁

我们已经习惯了使用synchronized来做同步锁,但在实际开发中发现,写得不好容易出现死锁的坑。比如像下面这样的代码有两个个锁被公用,在多线程的情况下就容易导致死锁。

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
#import "BTSynchronizedObjectA.h"
@implementation BTSynchronizedObjectA
- (void)dosomethingInA {
@synchronized(self){
NSLog(@"dosomethingInA");
}
}
@end
#import "BTSynchronizedObjectB.h"
@implementation BTSynchronizedObjectB
- (void)callA {
NSLog(@"call A begin");
[self.shareLock lock];
[self.synchronizedObjectA dosomethingInA];
[self.shareLock unlock];
NSLog(@"call A end");
}
- (void)dosomethingInB {
NSLog(@"dosomethingInB begin");
@synchronized(self.synchronizedObjectA) {
[self.shareLock lock];
NSLog(@"dosomethingInB");
[self.shareLock unlock];
}
NSLog(@"dosomethingInB begin");
}
@end

怎么写可以避免这种情况出现呢?改写的方法是在用到synchronized时传入一个内部NSObject成员变量,外部不能获取到这个变量,就不会形成交替死锁。

1
2
3
4
5
6
7
8
9
#import "BTSynchronizedObjectA.h"
@implementation BTSynchronizedObjectA
- (void)dosomethingInA {
@synchronized(self.tokenA){
NSLog(@"dosomethingInA");
}
}
@end

synchronized的性能

synchronized锁本身性能是会比其他锁要慢,有人对此做过测试。但是在实际使用中,锁本身的性能差异几乎可以忽略不计。
那为什么有人会抱怨用synchronized性能不好呢?

1
2
3
4
5
6
- (void)testPerformance {
@synchronized(self.token) {
// do some thing
[self dosomethingelse];
}
}

像上面这段被加锁的代码里,通过[self dosomethingelse]再调用别的方法。dosomethingelse方法的开发者,很可能并不知道自己的方法被锁同步的,他又可能调用别的函数,这样一层一层调用就可能变得更慢。
所以使用synchronized时一定要注意,尽量减少锁的范围和粒度。

不同数据使用不同锁,控制最小的粒度

1
2
3
4
5
6
7
@synchronized (tokenA) {
// do some thing
}
@synchronized (tokenB) {
// do some thing
}

减小加锁的范围,不必要加锁的代码放到外面

1
2
3
4
5
6
@synchronized (tokenA) {
// do some thing need locked
}
// do some thing else
[self dosomethingelse];
...

synchronized的失效

用synchronized时需要传入一个object,你有没有想过如果你传入的是nil会怎么样呢?
根据汇编代码可以发现,synchronized实现里会调用objc_sync_enter和objc_sync_exit

1
2
3
4
5
{
@synchronized(self) {
return [[myString retain] autorelease];
}
}

转换为

1
2
3
4
5
6
{
objc_sync_enter(self)
id retVal = [[myString retain] autorelease];
objc_sync_exit(self);
return retVal;
}

objc_sync_enter和objc_sync_exit函数定义在
其中objc_sync_enter函数的实现里,正常的情况obj不为nil时,会根据obj内存地址的哈希值查找合适的SyncData然后使用递归mutex加锁recursive_mutex_lock。到了objc_sync_exit时同样通过obj的内存地址的哈希值查找合适的SyncData,然后将其解锁recursive_mutex_unlock。

But,当执行objc_sync_enter函数时,如果传入的obj为nil,那并不会加锁,直接走到objc_sync_nil。也就是说,如果使用@synchronized时传入的obj为nil,那将不会加锁也就是失去了同步的效果。所以一定要保证obj不在执行期间被设置被nil。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
int objc_sync_enter(id obj)
{
int result = OBJC_SYNC_SUCCESS;
if (obj) {
SyncData* data = id2data(obj, ACQUIRE);
require_action_string(data != NULL, done, result = OBJC_SYNC_NOT_INITIALIZED, "id2data failed");
result = recursive_mutex_lock(&data->mutex);
require_noerr_string(result, done, "mutex_lock failed");
} else {
// @synchronized(nil) does nothing
if (DebugNilSync) {
_objc_inform("NIL SYNC DEBUG: @synchronized(nil); set a breakpoint on objc_sync_nil to debug");
}
objc_sync_nil();
}
done:
return result;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int objc_sync_exit(id obj)
{
int result = OBJC_SYNC_SUCCESS;
if (obj) {
SyncData* data = id2data(obj, RELEASE);
require_action_string(data != NULL, done, result = OBJC_SYNC_NOT_OWNING_THREAD_ERROR, "id2data failed");
result = recursive_mutex_unlock(&data->mutex);
require_noerr_string(result, done, "mutex_unlock failed");
} else {
// @synchronized(nil) does nothing
}
done:
if ( result == RECURSIVE_MUTEX_NOT_LOCKED )
result = OBJC_SYNC_NOT_OWNING_THREAD_ERROR;
return result;
}

在工程里,将obj设置为nil,给objc_sync_nil加个断点,会看到确实走到objc_sync_nil函数

参考

synchronized的实现
objc-sync源码
关于 @synchronized,这儿比你想知道的还要多
正确使用多线程同步锁@synchronized()

明确的目标的反效果

发表于 2017-09-28 | 分类于 iOS | 阅读次数

最近听到一个毁三观的观点,”明确的目标是有害的“。为什么是有害的呢?
第一个理由是,明确的目标违背经济学原理“看不见的手”。
比如今年宝洁定了1000亿的业务目标,如果提前完成了,大家就会放松了。但今年行情可能就是很好,应该乘胜追击才不浪费机会。而如果行情不好,很难完成这个数字,就可能会做各种不合理的促销,消耗第二年的购买力。
第二个理由是,目标太明确,可能会让人不惜一切代价,忽视风险。
例如像我定了每天跑3.5公里的目标,但由于最近工作比较忙,回去已经10点多了。如果按目标继续跑,因为身体状况不好,跑步得到的可能达不到锻炼的效果,反而增加了身体的负担。

这两个理由听起来还是很符合逻辑的。那我们难道就不定明确的目标吗?
也不是。
制定目标的过程,能使我们梳理清楚自己对未来的合理期望。
我们大部份人对自己未来的期望都很模糊。因为模糊,很难衡量期望的合理性、意义、价值和难度。
这个时候思考目标的过程就很有意义了,这让我们从混沌到清晰。
有点挑战的目标,并让一定范围内的社交关系知道,能让自己形成一定的焦虑,从而提升自己的执行力。

目标制定好,在执行的过程中我们应该尽量遵循2个原则。
1 合理投入,理性调整
在执行的过程中,我们要投入预期的精力。遇到特殊情况应该适当地调整目标。
2 自我觉察,克服人性弱点。
我们常常因为懒惰、拖延、逃避等人性的弱点导致不能投入足够的精力在目标的执行中。这关键在于不能及时觉察到自己在拖延。
最典型例子就是我们平时会无意识地刷微信朋友圈,网易新闻。事后我们可能会懊恼,又浪费了时间。避免这种窘境的方法是,提升觉察能力,在打开微信前觉察到自己的行为不理性,也可以像查理.芒格一样思考一下,“我即将做的是一个理性的抉择吗?”

以上主要针对个人目标来考虑,而针对团队的目标,其实还会有更多的意义…

iOS开发性能最佳实践之——容易出现耗时阻塞的检查清单(持续更新)

发表于 2017-09-22 | 分类于 iOS | 阅读次数

剪贴板读取

1
2
3
4
5
6
7
8
9
10
11
12
13
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
if (pasteboard.string.length > 0) {//这个方法会阻塞线程
NSString *text = [pasteboard.string copy];
[pasteboard setValue:@"" forPasteboardType:UIPasteboardNameGeneral];
if (text == nil || [text isEqualToString:@""]) {
return ;
}
dispatch_async(dispatch_get_main_queue(), ^{
[self processShareCode:text];
});
}
});

日期格式化

1
2
3
4
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateStyle:NSDateFormatterShortStyle];
[formatter setDateFormat:@"yyyy#MM#dd#HH:mm#ss"];
NSString *result = [formatter stringFromDate:date];

文件读取

我们通常会使用NSFileManager来读取文件,但是NSFileManager读取文件并不是性能最好的,推荐使用stat.h里的’int stat(const char , struct stat ) __DARWIN_INODE64(stat);’方法。

NSFileManager

1
2
3
4
5
6
7
8
9
10
11
12
mach_timebase_info_data_t info;
if (mach_timebase_info(&info) != KERN_SUCCESS) return -1.0;
__darwin_off_t fileSize1 = 0;
uint64_t start = mach_absolute_time();
for (NSInteger i = 0; i <= 10000; i++) {
NSDictionary<NSFileAttributeKey, id>* attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:imagePath error:nil];
fileSize1= [attributes fileSize];
}
uint64_t end = mach_absolute_time();
uint64_t elapsed = end - start;
uint64_t nanos1 = elapsed * info.numer / info.denom;
return (CGFloat)nanos / NSEC_PER_SEC;

stat

1
2
3
4
5
6
7
8
9
10
11
12
mach_timebase_info_data_t info;
if (mach_timebase_info(&info) != KERN_SUCCESS) return -1.0;
__darwin_off_t fileSize = 0;
uint64_t start = mach_absolute_time();
for (NSInteger i = 0; i <= 10000; i++) {
int rs = stat(filePath, &statBuf);
fileSize= statBuf.st_size;
}
uint64_t end = mach_absolute_time();
uint64_t elapsed = end - start;
uint64_t nanos1 = elapsed * info.numer / info.denom;
return (CGFloat)nanos / NSEC_PER_SEC;

在iPhone6S设备10000次循环的测试条件下,stat是性能是NSFileManager的4倍左右

如何编写安全的iOS代码

发表于 2017-09-20 | 分类于 iOS | 阅读次数

遍历集合先Copy

如果你正在遍历的集合是mutable的,在你遍历的同时,可能另外一个线程对这个集合做了修改,一个Crash将出现.

1
*** Terminating app due to uncaught exception 'NSGenericException', reason: '*** Collection was mutated while being enumerated.'

如何避免呢?
拿到NSArray/NSMutableArray、NSDictionary/NSMutableDictionary都copy一下。

集合操作要预判
提前做好判断,保证key/value不为nil,数组不会越界。

1
2
3
4
5
6
addObject:
removeObjectAtIndex:
insertObjectAtIndex:
setObject:forKey:
@[]
@{}

NSKeyedArchiver别常用
在iOS8上会抛exception,非要用加上@try{}@catch{}

KVO成对出现

有add必要有remove,否则极有可能Crash,可以使用FaceBook的FBKVOController

tableView的delegate要置空
viewController的dealloc把tableView的delegate置空

iOS8及以前,delegate属性不是weak,不会自动设为nil。否则做异步网络请求,退出页面就容易出现野指针。

不要在Category覆盖系统方法

不要坑别人

Api需要特定线程,记得加断言
如果一个api需要在特定线程执行,记得加上断言判断,否则调用方容易在不合适的线程调用,导致Crash

多线程环境下懒加载变量要加锁

1
2
3
4
5
6
7
8
9
10
11
12
- (id *)pen
{
if (!_pen) {
@synchronized(self) {
if (!_pen) {
_pen = [[BTPen alloc] init];
_pen = @"tea";
}
}
}
return _pen;
}

NSNotificationCenter要防止Self为空
NSNotificationCenter在iOS 8及更老系统上存在多线程bug,selector执行到一半时可能会因为self销毁而触发crash.

1
2
3
4
5
6
7
8
9
- (void)receiveTesNotify:(NSNotification *)notify
{
__weak typeof(self) weakSelf = self;
__strong typeof(self) strongSelf = wself;
if (! weakSelf) {
return;
}
[strongSelf action];
}

使用dispatch_sync要小心

容易死锁。下面将一个Block同步到主线程的队列里。因为你用同步,必须先让你执行,但你放到主线程队列里,却要等待主线程原来的任务执行完。

1
2
3
4
5
6
7
- (void)test {
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_block_t block = ^() {
NSLog(@"Hello");
};
dispatch_sync(mainQueue, block);
}

cancelPreviousPerformRequestsWithTarget要防御

对receiver使用cancelPreviousPerformRequestsWithTarget,会导致receiver引用计数减1。如果调用前receiver的引用计数就是1,调用完receiver就会被释放。

1
2
3
4
5
6
7
8
9
__weak typeof(self) weakSelf = self;
[NSObject cancelPreviousPerformRequestsWithTarget:self];
if (!weakSelf)
{
//self已经被销毁
return;
}
// 再调用会Crash
[self action];

在dealloc中,不要将self作为参数传递

如果self在此时被retain住,到下个runloop周期再次释放,则会造成多次释放crash。

1
2
3
-(void)dealloc{
[self doSome:self];
}

因为当前已经在self所指向对象的销毁阶段,如果在doSome:中将self放到了autorelease pool中,那么self会被retain住,计划下个runloop周期再进行销毁;但是dealloc运行结束后,self对象的内存空间就直接被回收了,self变成了野指针
当到了下个runloop周期,self指向的对象实际上已经被销毁,会因为非法访问造成crash问题

使用锁要避免出现特殊逻辑锁没有被回收

如果某个方法里锁未被回收,下次调用该方法会造成线程死锁

可能提前出现return,要在return前回收

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- (void) doSomething{
[self.lock lock];
if (type == 1){
.....
[self.lock unlock];
return;
}
else if(type == 2){
.....
[self.lock unlock];
return;
}
.....
[self.lock unlock];
}

可能出现异常,要在finally回收

1
2
3
4
5
6
7
8
9
10
- (void) doSomething{
[self.lock lock];
@try{
...
}@catch(NSException* ex){
}@finally{
......
[self.lock unlock];
}
}

iOS最容易违反的代码规范

发表于 2017-09-15 | 分类于 iOS | 阅读次数

命名

常量和枚举添加3个(2个是预留给Apple的)大写字符做为前缀

如果方法返回接收者的某个属性,则以属性名称作为方法名。如果方法没有间接地返回 一个或多个值,您也无须使用”get“这样的单词。

反例:

1
- (CGFloat)itemHeight;

正例:

1
- (CGFloat)getitemHeight;

不要使用”and“来连接两个表示接受者属性的关键字。
虽然下面的例子使用”and“这个词感觉还不错,但是随着创建的方法所带有的关键字越来 越多,这种方式会引起问题

正例:

1
- (void)getColor:(UIColor *)color count:(int*)count name:(NSString *)name;

反例:

1
- (void)getColor:(UIColor *)color andCount:(int*)count andName:(NSString *)name;

只有当方法间接地返回对象或者数值,您才需要在方法名称中使用 get”。这种格式只适 用于需要返回多个数据项的方法。

1
- (void)getColor:(UIColor *)color count:(int*)count name:(NSString *)name;

方法名称的开头应标识出发送消息的对象所属的类

1
2
3
- (BOOL)tableView:(NSTableView *)tableView didSelectRow:(int)row;
- (BOOL)application:(NSApplication *)sender openUrl:(NSString
*)url;

要求委托代表其他对象执行某件事,使用“should”

1
- (BOOL)controllerShouldFetch:(id)sender;

常量

NS_ENUM枚举类型定义一群相互关联的整数值常量。枚举项以枚举类型为前缀

1
2
3
4
typedef NS_ENUM(NSInteger,BTPlay) {
BTPlayBall = 0,
BTPlayGame = 1
} ;

NS_OPTIONS定义一组相互关联的位移枚举常量。位移枚举常量是可以组合使用的。枚举项以枚举类型为前缀

1
2
3
4
typedef NS_OPTIONS(NSInteger,BTPlay) {
BTPlayEveryData = 1 << 0,
BTPlayMusic = 1 << 1
} ;

使用 const 来创建浮点值常量。如果某个整数值常量和其他的常量不相关,您也可以使用 const 来创建,否则,则应使用枚举类型。

1
const float BTLabelHeight;

大写字符表示预处理字符

1
#ifdef DEBUG

使用常量来代替字符串字面值和数字。常量应该用 static 声明为静态常量,而不要用 #define,除非它明确的作为一个宏来使用。

正例:

1
2
static NSString * const BTBarcelonaNotification = @"BTBarcelonaNotification";
static const CGFloat BTBarcelonaHeaderHeight = 20.0f;

反例:

1
2
#define BTBarcelonaNotification @"BTBarcelonaNotification"
#define BTBarcelonaHeaderHeight 50

在头文件将常量暴露,在实现文件中为它赋值

1
extern NSString *const BTBarcelonaNotification;

Notification消息使用全局的 NSString 对象进行标识

[Name of associated class] + [Did | Will] + [UniquePartOfName] + Notification

1
BTWindwowWillBecomeBlackNotification

异常使用全局的 NSString 对象来标识
[Prefix] + [UniquePartOfName] + Exception

1
BTImageCornerDrawException

+(void)initialize必须判断class类型或使用dispatch_once防止执行多次

零基础如何做Weex开发

发表于 2017-09-14 | 分类于 iOS | 阅读次数

核心概念

组件(Components)
可以复用的UI元素,相当于在iOS里自己写了一个UI控件

属性(Props)
Props 就是组件的属性,相当于iOS里的Property

状态(State)

组件内部维持的状态数据称为 state ,它是组件的当前状态。可以把组件简单看成一个”状态机”,根据 state 呈现不同的 UI 展示。一旦 state 被更改,组件就会自动调用自身的 render 函数重新渲染 UI,这个更改 state 的动作会通过 this.setState方法来触发

事件(Events)
使用驼峰式命名指定要绑定的事件属性为组件定义的一个方法

1
<TextInput onInput={ (event) => this.setState({ text: event.value }) } />

JSX
JSX 是一个看起来很像 XML 的 JavaScript 语法扩展。 它只是一种语法糖。Rax 的 DSL 语法是基于 React JSX 语法而创造。

Flexbox 布局
使用flexbox 规则来描述组件

样式
使用对象的方式来描述 CSS 中的样式,并传递给组件的 style

1
<View style={{ width: 100, height: 100, backgroundColor: 'skyblue' }} />

组件的生命周期

渲染阶段

getDefaultPops
用于返回组件实例的默认props值。对于组件类来说,这个方法只会被调用一次.

getInitialState
用来初始化每个实例的state。对于每个组件实例来讲,这个方法只会调用一次

componentWillMount
该方法在首次渲染之前调用

render
该方法会创建一个虚拟 DOM,用来表示组件的输出。

componentDidMount
该方法被调用时,页面中已经渲染出真实的节点。

存在阶段

componentWillReceiveProps
父组件可以在外部更改子组件的props,更改后会调用此方法

销毁阶段

componentWillUnmount

在 componentDidMount 中添加的任务都需要再该方法中撤销,如创建的定时器或事件监听器

Flexbox 和样式

style属性用来定义组件的样式,并支持一个数组.

1
2
3
4
5
6
7
8
9
10
11
12
13
<View style={[styles.container, styles.custom]}>
<Text>hello world</Text>
</View>
const styles = {
container: {
background: 'grey',
width: '750rem'
},
custom: {
height: '100rem'
}
};

设置单位时,推荐使用不加单位的写法

1
2
3
4
5
6
7
8
9
10
11
12
<View style={styles.container}>
<Text>hello world</Text>
</View>
const styles = {
container: {
background: 'grey',
width: 375
}
};
1 个单位的大小为屏幕宽度的 1/750,这样做的好处是当你拿到一份 750px 宽的视觉稿,你再也不需要去做人工换算。

JSX语法

Rax中使用CSS书写样式

详细文档:Rax-CSS

事件处理

简单的点击事件、Appear事件、滚动事件、用户输入事件、复杂手势事件
详细文档:Rax事件处理

网络请求

Rax 实现了 Fetch API,并推荐使用 fetch 来发起异步网络请求
详细文档:Fetch

调试工具

使用 weex-toolkit。
详细文档:weex-toolkit

1
npm install -g weex-toolkit

ES6语法

ECMAScript 6 入门

React 新手
Flexbox布局教程
CSS3的border-radius属性详解
Rax官方文档
Weex官方文档

iOS性能最佳实践之——圆角和阴影的正确使用姿势

发表于 2017-09-11 | 分类于 iOS | 阅读次数

图片使用阴影的正确姿势

错误示范:会导致离屏渲染

imageView.layer.shadowOpacity = 0.5;

正确做法:通过shadowPath,告诉CoreAnimation你要渲染的形状,减少离屏渲染

1
2
imageView.layer.shadowOpacity = 0.5;
imageView.layer.shadowPath = [[UIBezierPath bezierPathWithRect:imageView.bounds] CGPath];

对图片做圆角处理的正确姿势

错误示范:
给imageView加cornerRadius后用masksToBounds切。同时加上cornerRadius和masksToBounds后,会触发离屏幕渲染,如果一个Cell里有多个这样的图片,滑动的时候GPU压力会很大,从而导致严重的掉帧。

1
2
3
4
UIImage *image = [UIImage imageNamed:@"IMG_6083"];
imageView.image = image;
imageView.layer.cornerRadius = CGRectGetWidth(imageView.frame)/ 4.0;
imageView.layer.masksToBounds = YES;

正确做法:
通过Core Graphics创建一块位图画布,将图片切成需要的形状

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
+ (id)createRoundedRectImage:(UIImage *)image size:(CGSize)size radius:(int)radius {
size = CGSizeMake(size.width * image.scale, size.height * image.scale);
radius = radius * image.scale;
UIImage *img = image;
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef context = CGBitmapContextCreate(NULL, size.width, size.height, CGImageGetBitsPerComponent(image.CGImage), 4 * size.width, CGImageGetColorSpace(image.CGImage), kCGImageAlphaPremultipliedFirst);
CGRect rect = CGRectMake(0, 0, size.width, size.height);
CGContextBeginPath(context);
addRoundedRectToPath(context, rect, radius, radius);
CGContextClosePath(context);
CGContextClip(context);
CGContextDrawImage(context, CGRectMake(0, 0, size.width, size.height), img.CGImage);
CGImageRef imageMasked = CGBitmapContextCreateImage(context);
img = [UIImage imageWithCGImage:imageMasked];
CGContextRelease(context);
CGColorSpaceRelease(colorSpace);
CGImageRelease(imageMasked);
return img;
}

给UILabel设置圆角属性

不需要背景颜色时
不需要设置label的backgroundColor,只设置borderWidth、borderColor的label,直接设置cornerRadius,不需要设置masksToBounds = YES,就可以实现圆角功能。

需要背景色
设置label.layer.backgroundColor

1
2
3
4
view.layer.cornerRadius = 20;
view.layer.backgroundColor = [UIColor purpleColor].CGColor;
view.layer.borderColor = [UIColor blackColor].CGColor;
view.layer.borderWidth = 0.5;

cornerRadius属性:
它是影响layer显示的backgroundColor和border,对layer的contents不起作用。
如果UILabel设置backgroundColor,是为contents设置背景色,二不是设定layer的背景色,此时cornerRadius不能实现圆角效果。解决的方法是直接设置label.layer.backgroundColor。

在Cell里,需要响应cell的高亮效果

给UIButton或UITextView等视图做圆角处理

视图里如果不需要呈现图片,可以直接在后台线程画一个带圆角的背景图片,做为子视图。

1
2
3
UIImage *image = [UIImage rounedCornerImage:20 borderWidth:0.5 backgroundColor:[UIColor purpleColor] borderColor:[UIColor clearColor] size:CGSizeMake(CGRectGetWidth(view.frame), CGRectGetHeight(view.frame))];
UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
[button insertSubview:imageView atIndex:0];
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
+ (UIImage *)rounedCornerImage:(CGFloat)radius
borderWidth:(CGFloat)borderWidth
backgroundColor:(UIColor *)backgroundColor
borderColor:(UIColor *)borderColor
size:(CGSize)size{
CGSize sizeToFit = CGSizeMake(floor(size.width), floor(size.height));
CGFloat halfBorderWidth = (CGFloat)borderWidth / 2.0;
UIGraphicsBeginImageContextWithOptions(sizeToFit, false, [[UIScreen mainScreen] scale]);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(context, borderWidth);
CGContextSetStrokeColorWithColor(context, borderColor.CGColor);
CGContextSetFillColorWithColor(context, backgroundColor.CGColor);
CGFloat width = sizeToFit.width, height = sizeToFit.height;
CGContextMoveToPoint(context, width - halfBorderWidth, radius + halfBorderWidth); // 开始坐标右边开始
CGContextAddArcToPoint(context, width - halfBorderWidth, height - halfBorderWidth, width - radius - halfBorderWidth, height - halfBorderWidth, radius); // 右下角角度
CGContextAddArcToPoint(context, halfBorderWidth, height - halfBorderWidth, halfBorderWidth, height - radius - halfBorderWidth, radius); // 左下角角度
CGContextAddArcToPoint(context, halfBorderWidth, halfBorderWidth, width - halfBorderWidth, halfBorderWidth, radius); // 左上角
CGContextAddArcToPoint(context, width - halfBorderWidth, halfBorderWidth, width - halfBorderWidth, radius + halfBorderWidth, radius); // 右上角
CGContextDrawPath(UIGraphicsGetCurrentContext(), kCGPathFillStroke);
UIImage *output = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return output;
}

iOS开发性能最佳实践系列之——集合遍历

发表于 2017-09-09 | 分类于 iOS | 阅读次数

场景一

场景条件

  • 需要按顺序
  • 同步执行

使用建议:

  • for..in (NSFastEnumeration)
    * enumerateObjectsUsingBlock
    * 经典for循环

推荐原因:
在集合元素“1000000”量级上,for in效率比经典for循环要高50倍。集合内元素较少时,经典for循环效率比forin要高1倍。但for in代码可读性和安全性更高,综合考虑建议使用用for in.

场景二

场景条件

  • 需要按顺序
  • 同时需要key和value

按顺序优先考虑下列方法
* enumerateKeysAndObjectsUsingBlock
* 经典for循环

推荐原因:
在集合元素”100“量级和“1000000”量级时,enumerateKeysAndObjectsUsingBlock的效率都比“经典for循环”要高,而且不会出现“off-by-one errors”错误。建议需要key、vlaue时使用enumerateKeysAndObjectsUsingBlock

场景三

场景条件

  • 处理比较耗时
  • 不需要按顺序

按顺序优先考虑下列方法

  • enumerateObjectsWithOptions (NSEnumerationConcurrent)
    * dispatch_apply(Concurrent)

推荐原因:
enumerateObjectsWithOptions和dispatch_apply(Concurrent)各种场景的效率都差不多,但是enumerateObjectsWithOptions可读性更强,特性也更多。建议使用enumerateObjectsWithOptions。

1
2
3
[array enumerateObjectsWithOptions:NSEnumerationConcurrent usingBlock:^(Sark *sark, NSUInteger idx, BOOL *stop) {
[sark doSomethingSlow];
}];

场景四

场景条件

  • 倒序遍历

按顺序优先考虑下列方法

  • enumerateObjectsWithOptions:NSEnumerationReverse
  • reverseObjectEnumerator
1
2
3
4
NSArray *strings = @[@"1", @"2", @"3"];
for (NSString *string in [strings reverseObjectEnumerator]) {
NSLog(@"%@", string);
}
1
2
3
[array enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(Sark *sark, NSUInteger idx, BOOL *stop) {
[sark doSomething];
}];

推荐原因:
enumerateObjectsWithOptions和reverseObjectEnumerator在各量级的效率表现相似,两个方法均可使用

参考

  • iOS遍历方法实验数据
  • Enumerators

iOS性能最佳实践之--保证页面流畅性的检查清单

发表于 2017-09-05 | 分类于 iOS | 阅读次数

避免滑动时加工数据

网络接口回来的数据有时需要进行字符串拼接、时间格式转换等销毁CPU的操作。这种场景可以在后台线程进行数据加工,然后将加工后的数据存放到ViewModel。当列表滑动的时候,不需要在单独做计算。

计算高度耗时较长时,将高度进行缓存

建议使用”UITableView+FDTemplateLayoutCell“。

使用Cell的复用机制

此处不需要解释

复用Cell内的子视图

有些开发者确实是使用了cell的复用机制,但是每次复用时由于子视图有一些差异,会把Cell里子视图释放掉,再重新初始化需要的子视图。这种做法是非常消耗CPU的,子视图也要尽量复用。

使用隐藏而不是移除

移除一个子视图也是需要开销的。如果有一些子视图不需要显示,将其隐藏而不是移除。

到后台线程销毁视图

有大量视图需要销毁时,放到后台线程,减少主线程的资源消耗。

图片尺寸不要大于需要显示的布局

到后台线程图片解码

加载图片时,图片的解码需要消耗CPU的资源,而这部分是可以放到子线程处理的。(SDWebImage默认是在子线程解码的,除非你关了)

到后台线程图片绘制

CoreGraphic是线程安全的,使用CoreGraphic绘制图片可以在子线程做,绘制完再回主线程加载到要呈现的控件里

减少透明视图的混合

设置视图的opaque属性为True,减少视图的混合消耗GPU资源

手动计算布局,并缓存

特别复杂的Cell,Autolayout的布局消耗大量CPU资源。可以根据数据源手动计算frame并进行缓存,从而避免每次都重新计算布局。

结构化思考——完美应对电梯30秒的思考模型

发表于 2017-09-03 | 分类于 thought | 阅读次数

为什么你的想法总是不能很好地表达?

如果你的工作内容是技术型的(程序员),你很可能对自己日常工作中的语言表达不太满意。你可能专业知识很丰满,对事情的看法也很独到,但就是讲不好。

你也许还不觉得这是个很大的问题,因为大家会给某种职业贴标签:“XXX就是不善于表达”。这样的说法你听多了,可能会真的接受这种观点,但事实并不是如此。

讲得不好主要有三个因素影响,思考能力、语言能力、心态。
思考能力决定你的表达内容的质量,思路是否清晰、重点是否突出、观点是否独到。
语言能力决定你的表达的内容形式是否丰富、有没有文采,有没有类比、举例帮组听众理解更深刻等。
心态决定你能不能正常发挥自己的水平。

也许你已经可以看出来,思考能力决定你表达的核心内容。因为在日常工作中,工作中的交流是趋于理性的,大家更看重的是你的思路和观点。

为什么是结构力思考?

在工作中我们遇到各种问题时,大脑会习惯性依据经验来思考。这样思考的结果,观点通常是散状的,类似现在微信文章的清单体。散状的观点,重点不突出,各个观点间没有清晰的逻辑联系。这样不能形成严密的逻辑关系,不利于让听众了解你表达的重点,观点之间有可能相互重复、甚至冲突。而在工作中,我们讨论解决方案时,最终是需要团队协作去执行的。这需要方案重点突出、逻辑严密,既能够统一思想又具有指导意义。

因此,我们需要结构化思考的意识。

4个策略助你进行结构化思考

结论先行

将结论(核心观点/内容)作为树形结构的父节点。

以下呼上

树形结构里,子节点要呼应/支持父节点的内容。

合理归类

通过合理地归纳分类,让一个复杂地问题逐步细化,成为可理解、可执行的观点/指南。

逻辑递进

如果能形成时间、重要程度、结构顺序,这几种逻辑顺序,能让人更容易理解和记忆。

先看一个清单体:“如何帮孩子缓解入园焦虑、快速融入幼儿园生活”。

1
2
3
4
5
6
7
8
9
10
11
12
13
1. 最开始上幼儿园时,大部分孩子会感到压力和不适应。作为家长,不要觉得孩子小题大做,而要认真对待。
2. 提前在家帮孩子调整作息,尽量跟幼儿园保持一致。比如早晨7:30起床吃早饭、中午12点左右午睡。同步作息时间,能让孩子快速适应幼儿园的节奏。
3. 不要在平时借幼儿园抱怨孩子。“还不会自己吃饭,上了幼儿园怎么办”……这只会增加孩子对它的恐惧。
4. 提前带孩子熟悉幼儿园环境。试着坐坐小椅子,上个卫生间,在校园里散散步。熟悉了环境,孩子会更有安全感。
5. 尽快让孩子和老师熟悉起来——和孩子一起与老师聊聊天、当孩子的面夸夸老师,都会增加他对老师的信任。如果有机会提前让孩子和老师接触,那就更好了。
6. 让孩子挑选一件熟悉的物件陪他上学,比如一块手绢、一辆玩具车或一块午睡毯。一件熟悉的物品,能缓解孩子对陌生环境的不适。
7. 不要用欺骗的方式把孩子骗到幼儿园,更不要偷偷溜走,鼓励孩子主动跟你说“再见”。
8. 跟孩子约定时间时,要说他能理解的话。不要说“妈妈5点来接你”,孩子没有“5点”的概念,应该说“你在幼儿园乖乖吃完晚餐,妈妈就来接你”。且一定要说话算数,否则会失去孩子的信任。
9. 让孩子练习表达自己的感受,鼓励他说出需求。不妨引导他多说说学校的事情。习惯于表达需求后,孩子更敢于向老师求助。
10. 分离焦虑,很大程度上是父母的焦虑。你的情绪会传染给孩子,一定要控制好。孩子哭闹时,不要跟他一起难过;一旦约定分别,就安心上班,不要在边上偷看,被孩子看到反而会激起他刚平复的心情。
11. 孩子哭诉时,告诉他幼儿园的好是没用的,要用“共情”的方式沟通——“宝宝,妈妈也想念你”。向孩子表达你和他有同样的感受,这样孩子才会觉得你和他是一个“阵营”的。
12. 不要觉得孩子在幼儿园受了委屈,就在家里加倍补偿。那反而会让孩子觉得“家里这么舒服,还是不要去幼儿园了”。
13. 最后,从入园第一天,你的态度就要一以贯之,不要昨天去了,今天孩子哭得厉害就不去了。相信幼儿园和老师,他们比你有经验。

下面看看,如果使用结构化进行思考会怎么样。

合理选择结构化思考的表达层次

在工作中我们以下有几种场景会发表观点:

临时汇报(电梯30秒)
只有一句话时间,就说第一层。

会议发言(1分钟)
说到第二层。

工作总结(3到5分钟)
讲到第三层。

主题分享/培训 (30分钟)
抓住一个故事/案例展开来,深入浅出地阐述

根据不同场景和时间限制,合理地把握表达的层次和深度,无论是电梯30秒、会议发表观点、主题分享、培训、还是述职答辩都能应对自如。

1234
Blacktea

Blacktea

iOS music footbood barcelona 歌手、程序员、爵士乐、巴萨罗那、自由主义者、学生

38 日志
14 分类
35 标签
RSS
GitHub 微博 豆瓣 知乎
© 2017 - 2019 Blacktea
由 Hexo 强力驱动
主题 - NexT.Pisces