WILLING's BLOG

never say never


  • 首页

  • 归档

GCD 小手册

发表于 2016-12-23

ObjC 中的循环引用

发表于 2016-12-09

iOS App 国际化的那些事儿

发表于 2016-11-25

iOS 多线程基础知识

发表于 2016-09-22

多线程注意点: 不要同时开太多线程,耗时操作不要再主线程执行,放到子线程执行

线程概念

a. 主线程:UI线程,显示、刷新UI界面,处理UI控件的事件
b. 子线程:后台线程,异步线程

一、NSThread

  • 1.1 创建和启动线程的三种方式
1
2
3
4
5
6
7
8
9
// 先创建后启动
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
[thread start];
// 创建完自动启动
[NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil];
// 隐式创建自动启动
[self performSelectorInBackground:@selector(run) withObject:nil];
  • 1.2 常见用法
1
2
3
4
5
6
7
8
9
+ (NSThread *)currentThread;
+ (NSThread *)mainThread;
// 睡眠、暂停当前线程
+ (void)sleepUntilDate:(NSDate *)date;
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;
// 线程的名字
@property (copy) NSString *name

二、线程同步

  • 本质: 防止多个线程访问同一个资源造成数据安全问题
  • 实现: 加一个互斥锁(同步锁)
1
2
3
@synchronized(self) {
// code
}

三、GCD(Grand Central Dispatch)

    1. 队列和任务

      • 1.1 任务: 需要执行的操作block
      • 1.2 队列: 存放任务

        • 1.2.1 全局并发队列(系统提供)

          1
          dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        • 1.2.2 串行队列(手动创建)

          1
          dispatch_queue_t queue = dispatch_queue_create("aQueue”, NULL);
        • 1.2.3 主队列(主线程中执行)

          1
          dispatch_queue_t queue = dispatch_get_main_queue();
  • 2 执行任务的函数

dispatch_sync… 同步执行: 不具备开启新线程的能力
dispatch_async… 异步执行: 具备开启新线程的能力

  • 3 常用组合

dispatch_async… + 并发队列
dispatch_async… + 串行队列

  • 4 线程间通信
1
2
3
4
5
6
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 执行耗时的操作...
dispatch_async(dispatch_get_main_queue(), ^{
// 回到主线程刷新UI
});
});
  • 5 GCD的所有API都在 libdispatch.dylib, Xcode会自动导入主头文件:

#import \

  • 6 延迟执行
1
2
3
4
5
6
7
8
perform..
[self performSelector:@selector(download:) withObject:@"http://555.jpg" afterDelay:3];
dispatch_after..
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), queue, ^{
延迟执行的代码
});
  • 7 只执行一次
1
2
3
4
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 此处的代码程序整个运行过程只执行一次
});
  • 8 队列组

dispatch_group_async \ dispatch_group_notify

四、单例模式

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
// 懒汉式
// GCD方式
@interface HMDataTool : NSObject
+ (instancetype)sharedDataTool;
+
@end
#import "HMDataTool.h"
@implementation HMDataTool
static id instace;
+ (id)allocWithZone:(struct _NSZone *)zone {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instace = [super allocWithZone:zone];
});
return instace;
}
+ (instancetype)sharedDataTool {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instace = [[self alloc] init];
});
return instace;
}
- (id)copyWithZone:(NSZone *)zone {
return instace;
}
@end

五、NSOperation

NSOperation 是一个抽象的基类, 一般使用子类 NSBlockOperation 或 NSOperationQueue

  • 1 队列的类型
1
2
3
4
5
// 主队列: 添加到主队列中的操作在主线程中执行
[NSOperationQueue mainQueue]
// 非主队列: 添加到非主队列中的操作在子线程中执行
[[NSOperationQueue alloc] init]
  • 2 添加任务到队列
1
2
3
- (void)addOperation:(NSOperation *)operation;
- (void)addOperationWithBlock:(void(^)(void))block;
  • 3 常见用法
1
2
3
4
5
6
7
8
9
10
11
// 最大并发数
@property NSInteger maxConcurrentOperationCount;
// 取消所有操作
[queue cancelAllOperations];
// 暂停所有操作
[queue setSuspended:YES];
// 恢复所有操作
[queue setSuspended:NO];
  • 4 设置依赖
1
2
3
4
// NSOperation 之间可以设置依赖关系来保证执行顺序
// 操作B依赖操作A(A执行完毕之后才会执行B)
[operationB addDependency:operationA];
// 注意: 不能相互依赖, 但可以在不同队列的操作之间设置依赖关系.
  • 5 线程之间的通信
1
2
3
4
5
6
7
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperationWithBlock:^{
// 异步线程执行耗时操作
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
// 回到主线程刷新界面
}];
}];

六、从其他线程回到主线程的三种方式

1
2
3
4
5
6
7
8
9
10
11
12
// perform…
[self performSelectorOnMainThread:(SEL) withObject:(id) waitUntilDone:(BOOL)];
// GCD
dispatch_async(dispatch_get_main_queue(), ^{
// code
});
// NSOperationQueue
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
// code
}];

七、判断编译器的环境

1
2
3
4
5
#if _has_feature(objc_arc)
// ARC环境
#else
// MRC环境
#endif

八、类的初始化方法

1
2
3
4
5
// 启动程序时类第一次加载到内存时调用(绝对只执行一次)
+ (void)load
+
// 第一次使用类(调用alloc方法等)时调用(一般只执行一次)
+ (void)initialize

ObjC 中的@符号

发表于 2016-08-28

我们在写 OC 代码时, 最常用到的一个特殊符号可能就是 @ 了吧, 下面总结一下, OC 中使用到 @ 符号的场景.

@””

快速创建一个由引号内内容初始化的 NSString 对象.

@42, @3.14, @YES, @’Z’

创建一个由对应类型初始化的 NSNumber 对象.

例如 :

1
2
3
@42 == [NSNumber numberWithInteger:42]
@YES == [NSNumber numberWithBool:YES]
@42U == [NSNumber numberWithUnsignedInt:42U]

@[]

创建一个由 , 分隔的对象列表作为内容的 NSArray 对象.

例如 :

1
@[@"A", @NO, @2.718] == [NSArray arrayWithObjects:@"A", @NO, @2.718, nil]

@{}

创建一个由特定 键-值 对作为内容的 NSDictionary 对象.

例如 :

1
@{@"someKey": @"theValue"}

@()

自动装箱, 返回基于其值的合适的对象.

例如 :

1
2
const char * 返回 NSString
int 返回 NSNumber 等等

ObjC Style Guide

发表于 2016-08-23

Objective-C Style Guide

最近看了很多OC方面的编码风格推荐, 个人认为编码规范不管对于个人开发还是团队开发都是非常重要的, 这里总结出来, 给大家提供一个参考.

语言

使用美式英语.

1
2
3
4
5
// 推荐
UIColor *myColor = [UIColor whiteColor];
// 不推荐
UIColor *myColour = [UIColor whiteColor];

组织代码

使用 #pragma mark - 组织代码.

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
#pragma mark - Lifecycle
- (instancetype)init {}
- (void)dealloc {}
- (void)viewDidLoad {}
- (void)viewWillAppear:(BOOL)animated {}
- (void)viewDidAppear:(BOOL)animated {}
- (void)didReceiveMemoryWarning {}
#pragma mark - Custom Accessors
- (void)setCustomProperty:(id)value {}
- (id)customProperty {}
#pragma mark - IBActions
- (IBAction)submitData:(id)sender {}
#pragma mark - Public
- (void)publicMethod {}
#pragma mark - Private
- (void)privateMethod {}
#pragma mark - UITableViewDataSource
//...
#pragma mark - UITableViewDelegate
//...
#pragma mark - NSCopying
- (id)copyWithZone:(NSZone *)zone {}
#pragma mark - NSObject
- (NSString *)description {}

条件语句

1
2
3
4
5
6
7
8
9
10
11
// 推荐
if (!error) {
return success;
}
// 不推荐
if (!error)
return success;
// 或
if (!error) return success;

方法

方法声明中, 应该在(-/+ 符号)的后面留一个空格.

1
- (void)setExampleText:(NSString *)text image:(UIImage *)image;

变量

  • 变量应该尽可能的用描述性的语言命名, 应该避免使用单字母来命名变量除了用于 for() 循环里.

  • *表示变量是一个指针, 例如 NSString *text 不要写成 NSString* text, 也不要写成 NSString * text, 除非是在定义常量的情况下.

  • 属性定义无论在何时都应该是用来代替实例变量定义, 应该避免直接使用实例变量, 除非在初始化方法中(init, initWithCoder:等), dealloc 方法中和实例变量对应的 setter 和 getter 方法中.

1
2
3
4
5
6
7
8
9
10
11
// 推荐
@interface NYTSection: NSObject
@property (nonatomic) NSString *headLine;
@end
// 不推荐
@interface NYTSection : NSObject {
NSString * headLine;
}

命名

遵循 Apple 命名规范

1
2
3
4
5
// 推荐
UIButton *settingsButton;
// 不推荐
UIButton *setBut;

字母前缀(例如SR)应该用于类名和常量, 但是 Core Data 的实体名应该忽略前缀.
常量应该使用驼峰式命名规范, 要与类名有相关性要见面知意.

1
2
3
4
5
// 推荐
static const NSTimeInterval SRNavigationFadeAnimationDuration = 0.2;
// 不推荐
static const NSTimeInterval fadetime = 1.7;
  • 属性和局部变量应该使用开头字母小写的驼峰式命名规范.
  • 实例变量应该使用开头字母小写的驼峰式命名规范,并且前缀是下划线.
  • 局部变量不应该包含下划线.

注释

注释应该是用来解释为什么, 而不是用来说明是什么.

init 和 dealloc
dealloc 方法应该放在 implementation 的上面, init 应该直接放在 dealloc 方法的下面.

1
2
3
4
5
6
7
- (instancetype)init {
self = [super init]; // or call the designated initalizer
if (self) {
// Custom initialization
}
return self;
}

文字变量

  • NSString, NSDictionary, NSArray, 和 NSNumber 这些文字变量无论在何时都应该被用来创建不可变的实例对象.
  • 注意不要把 nil 传入到了 NSArray 和 NSDictionary 中,因为这样会导致c程序崩溃.
1
2
3
4
5
6
7
8
9
10
11
// 推荐
NSArray *names = @[@"Brian", @"Matt", @"Chris", @"Alex", @"Steve", @"Paul"];
NSDictionary *productManagers = @{@"iPhone" : @"Kate", @"iPad" : @"Kamal", @"Mobile Web" : @"Bill"};
NSNumber *shouldUseLiterals = @YES;
NSNumber *buildingZIPCode = @10018;
// 不推荐
NSArray *names = [NSArray arrayWithObjects:@"Brian", @"Matt", @"Chris", @"Alex", @"Steve", @"Paul", nil];
NSDictionary *productManagers = [NSDictionary dictionaryWithObjectsAndKeys: @"Kate", @"iPhone", @"Kamal", @"iPad", @"Bill", @"Mobile Web", nil];
NSNumber *shouldUseLiterals = [NSNumber numberWithBool:YES];
NSNumber *buildingZIPCode = [NSNumber numberWithInteger:10018];

CGRect 函数

当访问 x, y, width,height,应该使用 CGGeometry 函数,而不应该直接访问结构的成员.

1
2
3
4
5
6
7
8
9
10
11
12
13
// 推荐
CGRect frame = self.view.frame;
CGFloat x = CGRectGetMinX(frame);
CGFloat y = CGRectGetMinY(frame);
CGFloat width = CGRectGetWidth(frame);
CGFloat height = CGRectGetHeight(frame);
// 不推荐
CGRect frame = self.view.frame;
CGFloat x = frame.origin.x;
CGFloat y = frame.origin.y;
CGFloat width = frame.size.width;
CGFloat height = frame.size.height;

常量

常量和宏都可以的情况下, 推荐使用常量.

1
2
3
4
5
6
7
// 推荐
static NSString * const NYTAboutViewControllerCompanyName = @"The New York Times Company";
static const CGFloat NYTImageThumbnailHeight = 50.0;
// 不推荐
#define CompanyName @"The New York Times Company"
#define thumbnailHeight 2

枚举类型

1
2
3
4
typedef NS_ENUM(NSInteger, SRRequestState) {
SRRequestStateInactive,
SRRequestStateLoading
};

Case 语句

当在switch中使用枚举类型的时候 default 并不需要.

1
2
3
4
5
6
7
8
9
10
11
12
SRLeftMenuTopItemType menuType = SRTLeftMenuTopItemMain;
switch (menuType) {
case RWTLeftMenuTopItemMain:
// ...
break;
case RWTLeftMenuTopItemShows:
// ...
break;
case RWTLeftMenuTopItemSchedule:
// ...
break;
}

布尔值判断

  • 既然 nil 等同于 NO 那就没有必要在条件语句中进行比较
  • 永远不要直接把某些东西与 YES 相比较.
1
2
3
4
5
6
7
// 推荐
if (!someObject) {
}
// 不推荐
if (someObject == nil) {
}

BOOL 的例子

1
2
3
4
5
6
7
// 推荐
if (isAwesome)
if (![someObject boolValue])
// 不推荐
if (isAwesome == YES)
if ([someObject boolValue] == NO)

BOOL 属性的名字应该取名为形容词, 属性名可以忽略 ‘is’ 前缀, 但 getter 方法要使用传统的命名.

1
@property (assign, getter=isEditable) BOOL editable;

单例

单例对象应该使用安全线程机制来创建它们的共享实例.

1
2
3
4
5
6
7
8
+ (instancetype)sharedInstance {
static id sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}

这能够防止某些时候可能出现各种程序崩溃的问题

(未完持续更新)

参考

官方 OC Style Guides:

  • The Objective-C Programming Language
  • Cocoa Fundamentals Guide
  • Coding Guidelines for Cocoa
  • iOS App Programming Guide

其他 OC Style Guides:

  • New York Times
  • Google
  • GitHub

ObjC 中可变参数的原理

发表于 2016-08-22

最近写了一个自定义的 ActionSheet, 参考系统的 UIActionSheet 的创建方式设计 API. 我们知道系统的 UIAlertView 是使用可变参数来接收最后一个参数的, 我一直也只是知道怎么用, 于是这次查资料了解了可变参数的实现原理.

cocoa的API里使用到可变参数的地方

1
2
3
+ (instancetype)arrayWithObjects:(ObjectType)firstObj, ... NS_REQUIRES_NIL_TERMINATION;
- (instancetype)initWithTitle:(nullable NSString *)title message:(nullable NSString *)message delegate:(nullable id /*<UIAlertViewDelegate>*/)delegate cancelButtonTitle:(nullable NSString *)cancelButtonTitle otherButtonTitles:(nullable NSString *)otherButtonTitles, ... NS_REQUIRES_NIL_TERMINATION NS_EXTENSION_UNAVAILABLE_IOS("Use UIAlertController instead.");
- (instancetype)initWithTitle:(nullable NSString *)title delegate:(nullable id<UIActionSheetDelegate>)delegate cancelButtonTitle:(nullable NSString *)cancelButtonTitle destructiveButtonTitle:(nullable NSString *)destructiveButtonTitle otherButtonTitles:(nullable NSString *)otherButtonTitles, ... NS_REQUIRES_NIL_TERMINATION NS_EXTENSION_UNAVAILABLE_IOS("Use UIAlertController instead.");

首先我们来看看C语言中常用的哨兵参数的实现

###哨兵参数

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
int add(int firstNum, ...)
{
int sum = 0;
int number = firstNum;
va_list argumentList;
va_start(argumentList, firstNum);
while(1)
{
printf("number = %d\n", number);
sum += firstNum;
number = va_arg(argumentList, int);
if (number == 0) // 0作为哨兵参数
{
break;
}
}
va_end(argumentList);
return sum;
}
int main(int argc, char * argv[]) {
@autoreleasepool {
printf("sum is %d\n", add(1, 2, 3, 4, 5, 0)); // 调用add函数传入可变参数必须以0结尾
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}

上面我们注意到, 哨兵参数实际上就是指定一个结束参数, 当遇到这个参数时停止接收参数.

我们再来看看OC里的可变参数的实现

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
// 注意: 可变参数只能是所有参数中的最后一个.
// NS_REQUIRES_NIL_TERMINATION 用于编译时检查结尾是否为nil.
- (void)variableArgumentsMethod:(NSString *)arg1, ... NS_REQUIRES_NIL_TERMINATION {
NSMutableArray *stringArray = [NSMutableArray array];
va_list args;
va_start(args, arg1);
if(arg1) {
NSLog(@"%@", arg1);
[stringArray addObject:arg1];
NSString *nextArg;
while((nextArg = va_arg(args, NSString *))) {
[stringArray addObject:nextArg];
NSLog(@"%@", nextArg);
}
}
NSLog(@"%@", stringArray);
va_end(args);
}
- (void)viewDidLoad {
[super viewDidLoad];
[self variableArgumentsMethod:@"Hello", @"World", @"!", nil];
}

上面我们主要用到了以下几个函数:

  • va_list argumentList : 定义一个指向可变参数列表的指针argumentList.

  • va_start(argumentList, last_determine_param) : 使用argumentList指针指向函数参数列表中的第一个参数.

    • 说明 : 可变参数之前必须有一个或多个确定参数, last_determine_param是位于可变参数之前的最后一个固定参数( … 之前的最后一个参数). 调用va_start()时last_determine_param被用作第二个参数传入.
    • 函数参数列表中参数在内存中的顺序与函数声明时的顺序是一致的, 比如有一个可变参数函数的声明是: void va_test(char a, char b, char c, …), 则它的固定参数依次是a, b, c, last_determine_param为c, 因此就是 va_start(arg_ptr, c).
  • va_arg(argList, type) : 返回argumentList所指的当前可变参数, 返回类型为type, 并使指针argumentList指向可变参数列表中的下一个参数.

  • va_end(argList) : 必须调用va_end()确保堆栈的正确恢复.

上述宏的原型如下:

  • void va_start(va_list argptr, last_parm);
  • type va_arg(va_list argptr, type);
  • void va_end(va_list argptr);

它们都包含在头文件stdarg.h.

下面是自定义ActionSheet中可变参数的实际使用方式

1
2
3
4
5
+ (void)sr_showActionSheetViewWithTitle:(NSString *)title
delegate:(id<SRActionSheetDelegate>)delegate
cancelButtonTitle:(NSString *)cancelButtonTitle
destructiveButtonTitle:(NSString *)destructiveButtonTitle
otherButtonTitles:(NSString *)firstOtherButtonTitle, ... NS_REQUIRES_NIL_TERMINATION;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
+ (void)sr_showActionSheetViewWithTitle:(NSString *)title
delegate:(id<SRActionSheetDelegate>)delegate
cancelButtonTitle:(NSString *)cancelButtonTitle
destructiveButtonTitle:(NSString *)destructiveButtonTitle
otherButtonTitles:(NSString *)firstOtherButtonTitle, ... NS_REQUIRES_NIL_TERMINATION
{
NSMutableArray *otherButtonTitles = [NSMutableArray array];
va_list argumentList;
if (firstOtherButtonTitle) {
[otherButtonTitles addObject:firstOtherButtonTitle];
va_start(argumentList, firstOtherButtonTitle);
NSString *nextOtherButtonTitle = nil;
while ((nextOtherButtonTitle = va_arg(argumentList, NSString *))) {
[otherButtonTitles addObject:nextOtherButtonTitle];
}
va_end(argumentList);
}
[[[self alloc] initWithTitle:title
cancelButtonTitle:cancelButtonTitle
destructiveButtonTitle:destructiveButtonTitle
otherButtonTitles:otherButtonTitles
delegate:delegate] show];
}

传送门

本文Demo:VariableArgument

自定义ActionSheet:SRActionSheet

Have Fun.

Willing Guo

Willing Guo

没有什么事情可以成为你不去学习的理由.

7 日志
GitHub Weibo
© 2017 Willing Guo
由 Hexo 强力驱动
主题 - NexT.Pisces