前言

随著blockiOS4.0OS X 10.6的引入,给事件传递一种新的方式实现,在开发中用得最多的场景莫过于事件回调。使用block相对与delegate的优势在于,业务集中,可读性强,代码内联,不像代理需要实现很多函数,在适当的场景选择这种方式实现事件传递或者传参效果非常好,现在很多开源项目都实现了两种方法的事件回调。

block用起来虽然很爽,但也有它的不足,存在循环引用,轻者内存泄露,甚至导致App崩溃,不易调试追溯,因此使用它使一定要小心。鉴于实践中的踩过各种坑,总结下来,方便自己和他人以后查阅,这就是block备忘录写作的初衷。

block的本质

block实际上是指向结构体的指针,编译时,block的内部代码生产对应的函数。

具体结构如下:

block结构图

与C语言的函数指针的区别

  • block的代码是内联的,效率高于函数调用
  • block对于外部变量默认是只读属性
  • blockObjective-C看成是对象处理

block声明

  • 作为property

    @property (nonatomic, copy) returnType (^blockName)(parameterTypes);

  • 作为方法参数

    - (void)someMethodThatTakesABlock:(returnType (^)(parameterTypes))blockName;

  • 作为一个方法调用参数

    [someObject someMethodThatTakesABlock:^returnType (parameters) {…}];

  • 作为一个typedef

    typedef returnType (^TypeName)(parameterTypes);

    TypeName blockName = ^returnType(parameters) {…};

  • 作为函数参数

    int (^sumOfNumbers)(int a, int b) = ^(int a, int b) { return a + b; };

SDWebImage中使用的block示例:

1
2
3
4
5
typedef void(^SDWebImageCompletionBlock)(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL);

typedef void(^SDWebImageCompletionWithFinishedBlock)(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL);

typedef NSString *(^SDWebImageCacheKeyFilterBlock)(NSURL *url);

block调用

跟C函数类似使用(),括号里面还可以带一个或者多个参数

1
2
3
4
5
6
7
8
9
10
// block声明
void(^loggerBlock)(void);

// block定义
loggerBlock = ^{
     NSLog("hello world")
};

// block调用
loggerBlock();

block内存管理

默认情况下,block是在栈内存中,它不会对所引用的对象进行任何操作;如果对block进行一次copy操作,block就会在堆内存中,并且它会它所有的引用的对象做一次retain操作

  1. 对于block外的变量引用,block 默认是将其复制到其数据结构中来实现访问的
  2. 对于用 __block 修饰的外部变量引用,block 是复制其引用地址来实现访问的
  3. 而block会捕获代码外的局部变量,并且仅限于只读操作
  4. 在block中希望修改的外界局部对象,必须加上__block关键词

ARC

如果对象使用`__unsafe_unretained`或`__weak`修饰,就不会对其做`retain`操作

MRC

如果对象使用了`__block`修饰, 就不会对其做`retain`操作

为了防止block中的循环引用,可以用__weak关键词把相应的对象声明为弱引用,在block快内部需要多次访问,防止该对象被释放,可以用__strong关键词将声明为强引用:

1
2
3
4
5
6
7
8
9
__weak __typeof__(self) weakSelf = self;

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

    __strong __typeof(self) strongSelf = weakSelf;

    [strongSelf doSomething];
    [strongSelf doOtherThing];
});

参考链接

前言

此文是基于这些年工作中项目里面常见崩溃的一些总结,整理出来方便查阅,希望对大家都有所帮助。

App常见崩溃

  1. 数组下标越界
  2. 字典构造与修改
  3. NSAttributedString相关
  4. 呈现一个空控制器
  5. 强引用一个单例对象
  6. unrecognized selector
  7. 操作tableView数据
  8. Push到同一个控制器多次

1.数组下标越界

示例代码

1
2
3
4
5
6
- (void)testArrayOutOfBounds
{
    NSArray *testArray = @[@1,@2,@3];

    NSNumber *num = testArray[3];
}

异常现象

Terminating app due to uncaught exception ‘NSRangeException’, reason: ‘*** -[__NSArrayI objectAtIndex:]: index 3 beyond bounds [0 .. 2]’

预防方案

在数组中取值时需要先进组下标索引边界检查,如果没有越界方可取值。

2.字典构造造与修改

示例代码

1
2
3
4
5
6
7
8
- (void)testDicSetNilValueCrash
{
    // 构造不可变字典时 key和value都不能为空
    NSString *nilValue = nil;
    NSString *nilKey = nil;
    NSDictionary *dic1 = @{@"key" : nilValue};
    NSDictionary *dic2 = @{nilKey : @"value"};
}

异常现象

Terminating app due to uncaught exception ‘NSInvalidArgumentException’, reason: ‘*** -[__NSPlaceholderDictionary initWithObjects:forKeys:count:]: attempt to insert nil object from objects[0]’

预防方案

在我们使用字面量快速创建一个字典的时候需要特别小心,因为很可能字典的键和值不能保证同时不为空。有潜在崩溃的风险,这种崩溃非常容易出现,需要特别小心,但是当你留心的话也非常好避免,就是设置字典的键或者值的时候判断是否非空,可变字典设置某个键的值是可以为空,相当于删除字典中的某个键值。为了使App保持健壮推荐使用KVO的方式来设置字典的值

1
2
3
4
5
6
7
8
9
10
11
- (void)testMutableDicSetNilValueCrash
{
    NSString *value = nil;
    NSMutableDictionary *mDic = [NSMutableDictionary dictionary];

    // via Dic set, leading crash
    [mDic setObject:value forKey:@"key"];

    // via KVO set, it's safe
    [mDic setValue:value forKey:@"key"];
}

3.NSAttributedString相关

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- (void)testAttributedStringInitCrash
{
    NSString *nilStr = nil;
    NSMutableAttributedString *attributedStr = [[NSMutableAttributedString alloc] initWithString:nilStr];
}


- (void)testAttributedStringAddAttributeCrash
{
    NSString *nonnullStr = @"str";
    NSMutableAttributedString *attributedStr = [[NSMutableAttributedString alloc] initWithString:nonnullStr];

    NSString *nilValue = nil;
    [attributedStr addAttribute:NSAttachmentAttributeName value:nilValue range:NSMakeRange(0, 1)];
}

异常现象

Terminating app due to uncaught exception ‘NSInvalidArgumentException’, reason: ‘NSConcreteMutableAttributedString initWithString:: nil value’

预防方案

在构造NSMutableAttributedString或者NSAttributedString需要留意,设置的属性的值是否有可能存在nil的情况。这个很容易被人忽视,值得注意。

4.强引用一个单例对象

异常现象

App随时有可能面临崩溃,这个在曾经的一次网络请求封装的过程中遇到过,NSURLSession,不要强引用该对象,否则当你释放引用它的对象然后创建新的对象引用它很可能导致App崩溃。

预防方案

对单例的Property不要使用strong,非要引用的话使用week

5.unrecognized selector

示例代码

1
2
3
4
- (void)testUnrecogernizedSelectorCash
{
    [self performSelector:@selector(testSel) withObject:nil afterDelay:0];
}

异常现象

Terminating app due to uncaught exception ‘NSInvalidArgumentException’, reason: ‘-[ViewController testSel]: unrecognized selector sent to instance 0x7ffd41609d10’

预防方案

此类崩溃经常出现,特别是当服务器数据放回异常时,比如本来应该返回一个NSString类型字符串,结果返回NULL,当你调用字符串的length方式时,导致App崩溃。预防方法,重要的地方对类型进行判断再调用该类的相关方法,或者写一个分类统一处理此类逻辑。

6.操作tableView数据

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- (void)testTableViewUpdateCrash
{
    NSIndexPath *insertIndexPath = [NSIndexPath indexPathForRow:0 inSection:0];
    NSIndexPath *deleteIndexPath = [NSIndexPath indexPathForRow:1 inSection:0];
    NSIndexPath *reloadIndexPath = [NSIndexPath indexPathForRow:2 inSection:0];
    NSIndexPath *moved1IndexPath = [NSIndexPath indexPathForRow:3 inSection:0];
    NSIndexPath *moved2IndexPath = [NSIndexPath indexPathForRow:4 inSection:0];

    [self.tableView beginUpdates];

    [self.tableView insertRowsAtIndexPaths:@[insertIndexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
    [self.tableView deleteRowsAtIndexPaths:@[deleteIndexPath]withRowAnimation:UITableViewRowAnimationAutomatic];
    [self.tableView reloadRowsAtIndexPaths:@[reloadIndexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
    [self.tableView moveRowAtIndexPath:moved1IndexPath toIndexPath:moved2IndexPath];

    [self.tableView endUpdates];
}

异常现象

Fatal Exception: NSInternalInconsistencyException Invalid update: invalid number of sections. The number of sections contained in the table view after the update (1) must be equal to the number of sections contained in the table view before the update (1), plus or minus the number of sections inserted or deleted (1 inserted, 0 deleted).

预防方案

当需要动态更新tableView的数据时,计算好模型的数据使模型的数据和更新tableView的后的数据保持同步。

7.Push到同一个控制器多次

异常现象

Fatal Exception: NSInvalidArgumentException Pushing the same view controller instance more than once is not supported (<PPSelectPayMethodViewControllerIOS7: 0x10d7e7f10>)

参考链接

以上就是工作中常见的异常崩溃以及处理方案,下面的异常分类内容来自Apple的官方文档,有兴趣的可以查阅。☕️

Apple官方常见异常类型(Exception types)

  1. 访问一块坏内存[EXC_BAD_ACCESS] // SIGSEGV // SIGBUS]
  2. 异常退出[EXC_CRASH // SIGABRT]
  3. 追踪受限[EXC_BREAKPOINT // SIGTRAP]
  4. 非法指令[EXC_BAD_INSTRUCTION // SIGILL]
  5. 被保护的资源遭到侵害[EXC_GUARD]
  6. 资源限制[EXC_RESOURCE]
  7. 其他异常类型

1.访问一块坏内存(Bad Memory Access)

当程序试图接入无效内容或者尝试以不被允许的方式接入由于内存的保护等级(例如,尝试写入只读的内存)。Exception Subtype字段包含一个kern_return_t结构体用来描述错误和不正确接入的内存地址。

下面是一些调试坏内存接入导致崩溃的建议:

  • 假如objc_msgSend或者objc_release在崩溃线程回溯(Backtraces)的顶部附近,这个线程可能尝试给一个释放的对象发消息。你应该profile应用使用Zombies instrument来更好的理解这个崩溃发生的条件。
  • 假如gpus_ReturnNotPermittedKillClient在崩溃线程回溯(Backtraces)的顶部附近,线程被终结因为它尝试用OpenGL ES或者Metal执行渲染当程序处于后台时。查看QA1766: How to fix OpenGL ES application crashes when moving to the background
  • 打开Address Sanitizer运行你的应用。Address Sanitizer添加了额外的说明在内容接入当你编译代码的时候。随着你应用的运行,Xcode将⚠️你假如内存以一种可能导致崩溃的方式接入。

2.异常退出(Abnormal Exit)

程序异常退出,是最常见导致这类异常崩溃的原因是捕获到Objective-C/C++异常和调用了abort()函数。

App Extensions将被终结发生这种类型的异常,假如他们初始化花费太多的时间(watchdog终结)。假如一个extension由于载入时间太长被终结,产生崩溃报告的Exception Subtype将是LAUNCH_HANG。因为extensions并没有一个main函数,任何花销在初始化的时间都发生在static constructors和呈现在你的extensions和依赖库的+load方法。你应该尽可能的延迟做这些工作。

3.追踪受限(Trace Trap)

和异常退出类似,这种异常的目的是给一个追加的调试器,让它有机会来打断在一个当它执行时候指定的点的进程。你可以使用__builtin_trap()函数在你的代码中来触发这个异常。假如没有调试器追加的话,线程将被终结并且产生一个崩溃报告。

低等级的库(例如libdispatch)将受限这个进程一旦遇到一个重大的错误。关于错误的额外信息可以在Additional Diagnostic Information章节中的崩溃报告找到,或者在设备的控制台。

假如在runtime遇到诸如下面的一个意外的条件,Swift代码将终结出现这种类型的异常:

  • 非可选类型带有一个nil
  • 错误的强制类型转换

查看Backtraces来决发生定异常条件的位置。额外的信息可能已经在设备的控制台打印出来了。你应该修改崩溃处的代码来优雅的处理runtime错误。例如,使用Optional Binding而不是强制解包一个可选变量。

4.非法指令(Illegal Instruction)

进程尝试执行一个非法或者未定义的指令。进程可能已经尝试跳进到一个无效的地址通过一个配置错误的函数指针。在Intel处理器中,ud2操作码导致一个EXC_BAD_INSTRUCTION异常,但是它通常被用来困住进程达到调试的目的。Swift代码在Intel处理器中将以这种异常终结,假如在runtime位置条件发生。更多详情查看Trace Trap

5.被保护的资源遭到侵害(Guarded Resource Violation)

进程侵犯一个被保护的资源。系统库可能某个文件的描述器成guarded,在那以后,所有不正常的操作在这些描述器上都将触发一个EXC_GUARD异常(当它想操作在这些文件描述器上,系统可以使用特殊的guarded标记的私有APIs)。这可以帮你向下快速追踪问题,例如关闭一个已经被系库打开的文件描述器。例如,假如一个app关闭文件秒杀器通过使用截图SQLite文件到一个Core Data存储,Core Data将会在随后诡异的崩溃。guard exception将让这些问题尽早引起你的注意,这样也让他们变得更容易调试。

崩溃报告来自新版的iOS包含了人类可读的详细信息关于引起EXC_GUARD异常的操作在Exception SubtypeException Message字段中。在来自macOS或者老版本的iOS的崩溃报告中,这些信息被编码到第一个Exception Code就像一个分解成如下的位段:

  • [63:61] - Guard Type:被保护的资源类型。0x2代表一个文件描述器资源。
  • [60:32] - Flavor:侵害被处罚时的条件
    • 假如第一个(1 << 0)位被设置,进程尝试执行close()函数在一个受保护的文件描述器。
    • 假如第二个(1 << 1)位被设置,进程尝试执行dup()dup2(),或者fcntl()F_DUPFD或者F_DUPFD_CLOEXEC命令在一个受保护的文件描述器。
    • 假如第三个(1 << 2)位被设置,进程尝试通过一个socket发送给一个受保护的文件描述器。
    • 假如第三个(1 << 3)位被设置,进程尝试写入到一个受保护的文件描述器。
  • [31:0] - File Descriptor:进程尝试修改的受保护的文件描述器。

6.资源限制(Resource Limit)

进程超出了一个资源消耗的限制。这是一个来自操作系统通知,告诉进程正在使用的资源过多。精确的资源列在Exception Subtype字段中。假如Exception Note字段包含NON-FATAL CONDITION,进程不会被终结即使产生了一个崩溃报告。

  • 异常子类型MEMORY表明进程已经越过系统应用的内存限制。这可能是一个终结的先兆由于超额的使用内存。
  • 异常子类型WAKEUPS表明在进程中的线程每秒被唤醒太多次,这强制CPU非常频繁的唤醒消耗电池寿命。

    典型的,这个通过由线程与线程的通信产生(通常是使用peformSelector:onThread:dispatch_async),那样无意的发生了远远超出它正常应该的切换频率。因为通信的协调发生得非常频繁而出发此类的异常,这个通常和多个后台线程有着相似Backtraces – 表明那些地方发生过通信。

7.其他异常类型(Other Exception Types)

一些崩溃报告可能含有一个未命名的Exception Type,将以一个16进制的值(例如,00000020)的形式打印。假如你的设备收到了一个这样的崩溃报告,直接查看Exception Codes字段寻找更多的信息。

  • 异常代码0xbaaaaaad表明记录是整个系统的stackshot,不是一个崩溃报告。为了获得一个stackshot,按Home键和任意音量键。这些记录经常被用户偶然创建,并不表明是一个错误。
  • 异常代码0xbad22222表明一个VoIP应用已经被iOS终结,因为它启动得太频繁。
  • 异常代码0x8badf00d表明应用已经被iOS终结因为发生watchdog超时。应用花费太长时间启动,终结,或者响应系统事件。通常导致这歌问题是做了在主线程执行了同步的网络请求。无论什么操作在Thread 0都需要移动到后台线程,或者异步处理,以免它阻塞主线程。
  • 异常代码0xc00010ff表明引用被操作系统终结为了响应一个发热事件。这个可能由于一个发生崩溃的特定的设备的问题或者环境被操作导致。为了使你的应用更高效运行的建议,查看WWDC session iOS Performance and Power Optimization with Instruments
  • 异常代码0xdead10cc表明应用被iOS终结,由于当在后台运行时它持有了一个系统的资源(像通信录数据库)。
  • 异常代码0xdeadfa11表明应用被用户强制退出。强制退出发生在当用户第一次按下开关机按钮直到"滑动来关机"出现,然后在按下Home键。这是合理的假如用户这样做了,因为应用已经变得不可响应,但是这并不能保证 - 强制退出任何正在运行的任务。

    注意:终结一个挂起的app通过从多任务关系面板中移除并不会产生一个崩溃报告。一旦一个app被挂起,iOS它有资格在任何时候终结它,所有没有崩溃报告产生。

参考资料

前言

一直在观察各种App的LoadingView,比较有代表性的是MBProgressVHUDSVProgressHUD,这两个使用得非常广泛,大到QQ,支付宝,小到各种不知道名的App,长时间的迭代让它们的逻辑非常完善,同时也导致了有些累赘,如果把它们当一个产品来分析,可以看出,它们在不断地增加需求和使用场景,有没有一个非常简洁的HUD没有我们不需要的那些多余的逻辑只是负责显示指示和隐藏呢?

好像没有,所有今天动手准备自己封装一个LoadingView,灵感来自Airbnb,用过Airbnb的同学都知道,它的LoadingView很有风格,Airbnb是几张图片循环翻转切换,当然我不准备复制他们的idea,我准备做一个循环左右上下切换的LoadingView

废话不多说,先来看一下,最终效果的原型图

RFLoadingView

思路

分析一下思路:

  • 四条虚线交叉形成的区域就是我们能够看到的图片
  • 首先准备两张图片,位置 中+上
  • 开始第一段动画,向下切换,位置 变成 中+下
  • 第一段动画结束,将下面的图片移动到右边,准备开始第二段动画
  • 第二动画跟第一段类似,只是方向是从右向左,动画结束后 位置变成 中+左
  • 将左边的图片移动到上方位置,完成一个循环

为了使动画更流畅不至于生硬,我们使用iOS7推出的带弹簧效果的API

1
2
3

+ (void)animateWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay usingSpringWithDamping:(CGFloat)dampingRatio initialSpringVelocity:(CGFloat)velocity options:(UIViewAnimationOptions)options animations:(void (^)(void))animations completion:(void (^ __nullable)(BOOL finished))completion NS_AVAILABLE_IOS(7_0);

API接口

在API接口设计上,我希望尽量简单实用,封装了两个方法,一个用来显示,一个用来隐藏,可以指定显示到某个view,以及指定显示和隐藏时是否使用动画。

1
2
3

+ (void)showViewAddedTo:(UIView *)view animated:(BOOL)animated;
+ (void)hideViewForView:(UIView *)view animated:(BOOL)animated;

API实现

根据刚才的分析,核心的动画实现已经有了思路,现在就是怎么设计内部代码实现,为了方便显示蒙版阻止加载的时候用户交互,我把view的背景颜色设置了一个淡灰色,view的中间有三个子view,一个是位于中间的容器centralView,负责显示我们所看到的区域,方便实现圆角和动画效果,里面加入两个子view,firstViewsecondView用于显示动画切换的图片。

内部接口大概这样

1
2
3
@property (nonatomic, strong) UIView      *centralView;
@property (nonatomic, strong) UIImageView *firstView;
@property (nonatomic, strong) UIImageView *secondView;

为了防止多次添加LoadingView,在每次添加前,我们会查找该view是否存在,如果不存在,创建一个新的对象,如果存在直接跳过添加操作。反向遍历,快速查找。

1
2
3
4
5
6
7
8
9
10
11
+ (RFLoadingView *)loadingViewForView:(UIView *)view
{
    NSEnumerator *subviewsEnum = [view.subviews reverseObjectEnumerator];
    for (UIView *subview in subviewsEnum) {
        if ([subview isKindOfClass:self]) {
            return (RFLoadingView *)subview;
        }
    }

    return nil;
}

动画核心实现,两端动画,封装成两个方法

  • - (void)animatedImageFromTopToBottom
  • - (void)animatedImageFromTopToBottom

具体实现如下

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
- (void)animatedImageFromTopToBottom
{
    [UIView animateWithDuration:0.3 delay:0.5 usingSpringWithDamping:0.7 initialSpringVelocity:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
        _firstView.centerY  += kCenterViewSize;
        _secondView.centerY += kCenterViewSize;
    } completion:^(BOOL finished) {
        _firstView.centerX += kCenterViewSize;
        _firstView.centerY -= kCenterViewSize;

        [self changeFirstImage];
        [self animatedImageFromRightToLeft];
    }];
}

- (void)animatedImageFromRightToLeft
{

    [UIView animateWithDuration:0.3 delay:0.5 usingSpringWithDamping:0.7 initialSpringVelocity:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
        _firstView.centerX  -= kCenterViewSize;
        _secondView.centerX -= kCenterViewSize;
    } completion:^(BOOL finished) {
        _secondView.centerX += kCenterViewSize;
        _secondView.centerY -= kCenterViewSize;

        if (self.alpha && self) {
            [self changeSecondImage];
            [self animatedImageFromTopToBottom];
        }

    }];
}

需要非常注意的是,一定要写上终止动画的条件,不然会无限循环,影响性能

show和hide

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
- (void)showAnimated:(BOOL)animated
{
    RFMainThreadAssert();

    self.alpha = 1;

    [UIView animateWithDuration:animated ? 0.3 : 0 animations:^{
        _centralView.alpha = 1;
    } completion:^(BOOL finished) {
        [self animatedImageFromTopToBottom];
    }];
}

- (void)hideAnimated:(BOOL)animated
{
    RFMainThreadAssert();

    [UIView animateWithDuration:animated ? 0.3 : 0 animations:^{
        self.alpha = 0;
    } completion:^(BOOL finished) {
        [self removeFromSuperview];
    }];
}

最终效果

简介

此文用于总结,本人使用过或者收藏过的Github第三方类库,以便日后查阅,也便他人借鉴。

资料整理中不定期更新。。。

开源项目

APP相关

自动布局

  • Masonry

    最优雅的自动布局解决方案,支持链式编程

  • PureLayout

    又一强大的自动布局解决方案

网络请求

基础工具类以及Category

  • sstoolkit

    一个不错的工具包,提供各种比如编码、加密、字符串处理等等东西

  • BFKit Swift版本

    国外的一个大神写的很好用的分类,比较齐全

  • YYKit

    ibireme用心之作

  • BlocksKit

    为基础类提供Block支持,很好用

  • DateTools

    非常强大的日期处理工具

  • FlatUIKit

    扩展Foundation基本UI主件,扁平化风格

  • iOS-Categories

    iOS Objective-C Category, a collection of useful Objective-C Categories extending iOS Frameworks such as Foundation,UIKit,CoreData,QuartzCore,CoreLocation,MapKit Etc.

  • PinYin4Objc

    拼音解析

  • ZipArchive

    解压缩

  • Chameleon

    扁平颜色框架,可以创建非常漂亮的颜色

  • CYLTabBarController

    低耦合集成TabBarController

  • RKNotificationHub

    在右上方显示通知小角标

  • Material-Controls-For-iOS

    Google风格的控件

  • YYKit

    A collection of iOS components.

弹框

动画

Gif引擎

缓存

文本相关

加载进度

键盘类

抽屉

NavigationBar

ScrollView相关

  • SwipeView

    SwipeView is a class designed to simplify the implementation of horizontal, paged scrolling views on iOS. It is based on a UIScrollView, but adds convenient functionality such as a UITableView-style dataSource/delegate interface for loading views dynamically, and efficient view loading, unloading and recycling.

  • SWTableViewCell

    自定义Cell侧滑动作

  • MGSwipeTableCell

    自定义Cell侧滑动作,效果多样

  • CHTCollectionViewWaterfallLayout

    瀑布流布局

  • TYSlidePageScrollView

    An easy solution to page views or controllers with header and page tabbar,footer

  • XLPagerTabStrip

    Android PagerTabStrip for iOS. nice

MJ系列

数据库

  • realm-cocoa

    跨平台轻量级数据库解决方案,励志取代Core Data & SQLite

  • fmdb

    sqlite实现面向对象的封装

图标库

  • PNChart

    提供各种图标样式,性能还非常不错

  • Charts

    Beautiful charts for iOS/tvOS/OSX! The Apple side of the crossplatform MPAndroidChart.

图片处理以及展示相关

日历选择

  • FSCalendar

    iOS7+风格的日历控件,非常漂亮

音频/视频

内购/推广

  • IAPHelper

    in app purchases helper for iOS

  • DAAppsViewController

    应用推广界面,填写合作app的appleId即可

  • TAPromotee

    交叉推广应用是你可以免费实现的最佳市场推广策略之一。

存储相关

实用控件

辅助类

  • Surge

    利用Accelerate高性能数学计算框架

  • KMCGeigerCounter

    显示当前动画的帧率

  • XNGMarkdownParser

    Markdown解析器,将Markdown格式的字符串解析成NSAttributedString格式的字符串

  • TransformerKit

    封装了一些常用的NSValueTransformer

  • RuntimeBrowser

    Objective-C Runtime Browser, for Mac OS X and iOS

  • iOS-Runtime-Headers

    iOS Objective-C headers as derived from runtime introspection

  • appledoc

    Objective-C代码,苹果风格文档生成器

  • Markingbird

    Markdown processor written in Swift (translation of MarkdownSharp)

Swift

其它

  • FreeCodeCamp

    The https://FreeCodeCamp.com open source codebase and curriculum. Learn to code and help nonprofits.

  • libextobjc

    拓展了一些Cocoa类库

  • ParseSourceCodeStudy

    Facebook开源的Parse源码分析【系列】

  • hugo

    使用Google Go语言书写的静态博客网站生成器

  • CocoaPods

    Cocoa包依赖管理工具

  • MDCSwipeToChoose

    实现某App,左滑不喜欢,右滑喜欢的卡片堆叠功能

  • Dash-Plugin-for-Xcode

    Xcode的一个插件,整合Dash,方便快速查阅文档

  • iOS 开发面试问题

    汇总一些常见的iOS面试问题

  • XMPPFramework

    XMPP即时通信协议框架

  • WebViewJavascriptBridge

    Objective-CJavaScriptUIWebViews/WKWebView之间交互解决方案

  • articles-1

    Articles for objccn.io. objc.io的完整、准确、优雅的中文翻译版本

  • articles

    Weekly articles for NSHipster.com

  • ebook

    收集了一些coding相关的电子书

  • dev-blog

    nixzhu的一些 iOS / Web 开发相关的翻译或原创博客文章

  • KVOController

    Simple, modern, thread-safe key-value observing for iOS and OS X.

  • LBXScan

    二维码、扫码、扫一扫、ZXing和ios系统自带扫码封装,扫码界面效果封装

参考链接:

介绍

鉴于现在调试的频繁性和重要性,此文记录了一些基本的LLDB调试命令,方便日后查阅。

常用命令 p、po、call、print

1
2
3
4
5
6
7
8
9
10
11
12
p         -- ('expression --')  Evaluate an expression (ObjC++ or Swift) in
             the current program context, using user defined variables and
             variables currently in scope.
po        -- ('expression -O  -- ')  Evaluate an expression (ObjC++ or Swift)
             in the current program context, using user defined variables and
             variables currently in scope.
print     -- ('expression --')  Evaluate an expression (ObjC++ or Swift) in
             the current program context, using user defined variables and
             variables currently in scope.
call      -- ('expression --')  Evaluate an expression (ObjC++ or Swift) in
             the current program context, using user defined variables and
             variables currently in scope.

从官方的描述来看,pprintcall是一样的,但是po就不太一样了,输入一样但是输出不一样。po的输出的是具体对象的内容。

如果想要按照特定的格式来打印,如下:

1
2
3
4
5
6
7
8
9
10
(lldb) p/s self.title
(__NSCFString *) $0 = @"目的地"
(lldb) p self.title
(__NSCFString *) $1 = 0x00007fe0c0d4d230 @"目的地"
(lldb) p/x self.title
(__NSCFString *) $2 = 0x00007fe0c0d4d230 @"目的地"
(lldb) p/t self.title
(__NSCFString *) $3 = 0b0000000000000000011111111110000011000000110101001101001000110000 @"目的地"
(lldb) p/a self.title
(__NSCFString *) $4 = 0x00007fe0c0d4d230 -> 0x000000010d2fd2c8 (void *)0x000000010d2fd278: __NSCFString @"目的地"

打印输出格式化

name description
x Regard the bits of the value as an integer, and print the integer in hexadecimal.
d Print as integer in signed decimal.
u Print as integer in unsigned decimal.
o Print as integer in octal.
t Print as integer in binary. The letter ‘t’ stands for “two”.
a Print as an address, both absolute in hexadecimal and as an offset from the nearest preceding symbol. You can use this format used to discover where (in what function) an unknown address is located:(gdb) p/a 0x54320 $3 = 0x54320 <_initialize_vx+396>
c Regard as an integer and print it as a character constant. This prints both the numerical value and its character representation. The character representation is replaced with the octal escape ‘\nnn’ for characters outside the 7-bit ascii range.Without this format, gdb displays char, unsigned char, and signed char data as character constants. Single-byte members of vectors are displayed as integer data.
f Regard the bits of the value as a floating point number and print using typical floating point syntax.
s Regard as a string, if possible. With this format, pointers to single-byte data are displayed as null-terminated strings and arrays of single-byte data are displayed as fixed-length strings. Other values are displayed in their natural types.Without this format, gdb displays pointers to and arrays of char, unsigned char, and signed char as strings. Single-byte members of a vector are displayed as an integer array.
z Like ‘x’ formatting, the value is treated as an integer and printed as hexadecimal, but leading zeros are printed to pad the value to the size of the integer type.
r Print using the ‘raw’ formatting. By default, gdb will use a Python-based pretty-printer, if one is available (see Pretty Printing). This typically results in a higher-level display of the value’s contents. The ‘r’ format bypasses any Python pretty-printer which might exist.

格式: p/x $pc

参考链接:打印输出格式化

lldb声明变量

1
2
3
(lldb) e NSString *$str = @"http://www.baidu.com"
(lldb) po $str
http://www.baidu.com

我们使用e开头声明了变量

调用变量的API

1
(lldb) po [self.title uppercaseString]

强转返回值类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
(lldb) po [self.title characterAtIndex:0]
U+76ee u'目'

(lldb) po (unsigned int)[self.title characterAtIndex:0]
30446

(lldb) po (char)[self.title characterAtIndex:0]
'\xee'

(lldb) po (NSString *)[self.title characterAtIndex:0]
0x00000000000076ee

(lldb) po (unichar)[self.title characterAtIndex:0]
U+76ee u'目'

添加断点

1
2
b 120
b [KLNewDestinationVC setupUI]

b命令后面加行号,或者指定某个对象调用的方法

设置断点触发条件

右键断点处,在condition处编辑,如果设置了条件,只要当条件满足时,才会进入断点,也可以设置条件满足时发出声音和打印提示语。

常用打印视图层次结构

1
(lldb) po [self.view recursiveDescription]

临时刷新界面UI

1
2
3
(lldb) e ((UIButton *)sender).backgroundColor = [UIColor redColor]
(UICachedDeviceRGBColor *) $41 = 0x00007fdd10715b00
(lldb) e (void)[CATransaction flush]

介绍

chiselfacebook开源的调试框架,封装了很多方便的LLDB命令便于调试iOS App,今天介绍一下使用心得以及一些常用的命令,希望对你有写帮助。

测试环境

Xcode 7.3 and iOS 9.3 simulator. OS:OSX 10.11.4

pviews and presponder doesn’t work and no output.

Chisel issue on Xcode 7.3 #149

作者正在努力修复中。。。

Xcode7.2版本没有问题

安装

brew update
brew install chisel

创建~/.lldbinit 假如不存在

编辑内容: command script import /usr/local/opt/chisel/libexec/fblldb.py

查看命令

(lldb)help

查看具体怎样使用

(lldb) help border

常用命令说明

Command Description
pviews Print the recursive view description for the key window.
pvc Print the recursive view controller description for the key window.
visualize Open a UIImage, CGImageRef, UIView, CALayer, NSData (of an image), UIColor, CIColor, or CGColorRef in Preview.app on your Mac.
fv Find a view in the hierarchy whose class name matches the provided regex.
fvc Find a view controller in the hierarchy whose class name matches the provided regex.
show/hide Show or hide the given view or layer. You don’t even have to continue the process to see the changes!
mask/unmask Overlay a view or layer with a transparent rectangle to visualize where it is.
border/unborder Add a border to a view or layer to visualize where it is.
caflush Flush the render server (equivalent to a “repaint” if no animations are in-flight).
bmessage Set a symbolic breakpoint on the method of a class or the method of an instance without worrying which class in the hierarchy actually implements the method.
wivar Set a watchpoint on an instance variable of an object.
presponder Print the responder chain starting from the given object.

Tips:直接点控制台上方第三个按钮呼出LLDB

pviews(暂不可用)

显示view的层级。

border&unborder

border:给view或者layer添加边框颜色和边框的宽度。

使用如下:

(lldb) border 0x7ffc017b2970 -c green -w 10

标示设置0x7ffc017b2970视图或者图层一个宽度为2的绿色边框。

unborder:去掉view或者layer的边框颜色和边框宽度。

使用如下:

unborder 0x7ffc017b2970

pinternals

这个命令就是打印出来的一个控件(id)类型的内部结构。

使用如下:

pinternals 0x7ffc017b2970

presponder(暂不可用)

打印出一个继承于UIResponder控件的消息传递链。

visualize

可以使用mac下的预览app打开我们的图片UIImage, CGImageRef格式的图片,甚至view和layer的图片。

使用如下:

visualize 0x79ec3140//或者变量名,此地址是id类型的

pclass

pclass可以打印出一个对象的继承关系。

使用如下:

pclass 0x7fcd15108800

taplog

这个命令是模拟敲击一下屏幕,并且打印出你敲击屏幕时候事件接收的对象。

hide&show

hide命令可以直接隐藏一个对象,移除当前遮挡的对象便于你观察后面的对象。show命令会让它再次显示出来。

bmessage

这个命令就是LLDB添加一个断点,譬如-viewWillAppear:这个方法,在当前控制器中你没有实现它,但是你又想在调用它的时机触发中断。

这个我就不解释了,需要补充一点的是Objectiv-c的方法是带的。

pvc

打印出当前的控制器层级。

wivar

这个命令是加watchPoint

参考链接:

Associated Objects 介绍

<objc/runtime.h>中的函数号称是iOS中最后一把神兵利刃,具有其他方式做不到的,能为应用和框架提供强大功能的能力。但使用不当也可能废掉代码的,一切代码和逻辑都可能被异常糟糕的副作用影响。

就像和魔鬼做交易一样常常让人怀着巨大的恐惧。

历史

对象关联(或称为关联引用)本来是Objective-C 2.0运行时的一个特性,起始于OS X Snow LeopardiOS4

核心函数3个 – 允许将任何键值在允许时关联到对象上

  • objc_setAssociatedObject
  • objc_getAssociatedObject
  • objc_removeAssociatedObjects

这有什么用呢?这允许开发者对已经存在的类在扩展中添加自定义的属性

实现的3种方式

  • static char
  • selector
  • _cmd_
1.使用 static char 实现

NSObject+AssociatedObject.h

1
2
3
@interface NSObject (AssociatedObject)
@property (nonatomic, strong) id associatedObject;
@end

NSObject+AssociatedObject.m

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@implementation NSObject (AssociatedObject)

static char kAssociatedObjectKey;

- (id)associatedObject
{
    return objc_getAssociatedObject(self,&kAssociatedObjectKey);
}

- (void)setAssociatedObject:(id)associatedObject
{
    objc_setAssociatedObject(self, &kAssociatedObjectKey, associatedObject, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

@end
2.使用 selector 实现

NSObject+AssociatedObject.h

1
2
3
@interface NSObject (AssociatedObject)
@property (nonatomic, strong) id associatedObject;
@end

NSObject+AssociatedObject.m

1
2
3
4
5
6
7
8
9
10
11
12
13
@implementation NSObject (AssociatedObject)

- (id)associatedObject
{
    return objc_getAssociatedObject(self, @selector(associatedObject));
}

- (void)setAssociatedObject:(id)associatedObject
{
    objc_setAssociatedObject(self, @selector(associatedObject), associatedObject, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

@end
2.使用 _cmd_ 实现

NSObject+AssociatedObject.h

1
2
3
@interface NSObject (AssociatedObject)
@property (nonatomic, strong) id associatedObject;
@end

NSObject+AssociatedObject.m

1
2
3
4
5
6
7
8
9
10
11
12
13
@implementation NSObject (AssociatedObject)

- (id)associatedObject
{
    return objc_getAssociatedObject(self, _cmd_);
}

- (void)setAssociatedObject:(id)associatedObject
{
    objc_setAssociatedObject(self, @selector(associatedObject), associatedObject, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

@end

关联对象的行为

属性根据枚举类型objc_AssociationPolicy来决定被关联在对象上的行为:

Behavior 与之等效的@property
OBJC_ASSOCIATION_ASSIGN @property (assign) 或@property (unsafe_unretained)
OBJC_ASSOCIATION_RETAIN_NONATOMIC @property (nonatomic, strong)
OBJC_ASSOCIATION_COPY_NONATOMIC @property (nonatomic, copy)
OBJC_ASSOCIATION_RETAIN @property (atomic, strong)
OBJC_ASSOCIATION_COPY @property (atomic, copy)

OBJC_ASSOCIATION_ASSIGN类型关联对象的弱引用不代表weak的弱引用,行为上更像unsafe_unretained属性。

对象销毁时间

被关联的对象在声明周期内比对象本身释放要晚很多,在对象调用-dealloc中的object_dispose()中释放。

删除属性

你或许想要使用objc_removeAssociatedObjects()来进行删除操作,但官方文档不建议手动调用这个函数。

这个函数可能会把其他用户对其添加的属性也移除了,正确的方式是调用objc_setAssociatedObject方法,传入nil值来清楚一个关联。

优秀样例

  • 添加私有属性用于更好地去实现细节。
  • 添加public属性来增强category的功能。
  • 创建一个用于KVO的关联观察者。

应用举例

1.UIImagePickerController 图片选择回调

关联一个block实现完成选择图片后的回调。

UIImagePickerController+RFBlocks.h
1
2
3
4
5
6
7
8
9
@interface UIImagePickerController (RFBlocks)<UIImagePickerControllerDelegate, UINavigationControllerDelegate>

typedef void(^RFImagePickerFinishBlock)(NSDictionary *info);

@property (nonatomic, copy) RFImagePickerFinishBlock rf_finishBlock;

+ (UIImagePickerController *)rf_imagePickerWithFinishBlock:(RFImagePickerFinishBlock)finishBlock;

@end
UIImagePickerController+RFBlocks.m
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
static const char kFinishBlockKey;

@implementation UIImagePickerController (RFBlocks)

+ (UIImagePickerController *)rf_imagePickerWithFinishBlock:(RFImagePickerFinishBlock)finishBlock
{
    UIImagePickerController *picker = [[UIImagePickerController alloc] init];
    picker.rf_finishBlock = finishBlock;

    return picker;
}

- (RFImagePickerFinishBlock)rf_finishBlock {
    return objc_getAssociatedObject(self, &kFinishBlockKey);
}

- (void)setRf_finishBlock:(RFImagePickerFinishBlock)rf_finishBlock {
    self.delegate = self;

    objc_setAssociatedObject(self, &kFinishBlockKey, rf_finishBlock, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

#pragma mark - UIImagePickerControllerDelegate

- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<NSString *,id> *)info
{
    RFImagePickerFinishBlock block = self.rf_finishBlock;

    if (block)  block(info);

    [self dismissViewControllerAnimated:YES completion:nil];

    self.rf_finishBlock = nil;
}

@end

使用:

1
2
3
4
5
6
7
8
9
__weak typeof(self)weakSelf = self;

UIImagePickerController *picker =  [UIImagePickerController rf_imagePickerWithFinishBlock:^(NSDictionary *info) {
        NSLog(@"finish picke image\n info:%@",info);

        UIImage *img = info[@"UIImagePickerControllerOriginalImage"];
        weakSelf.view.layer.contents = (id)img.CGImage;
    }];
[self presentViewController:picker animated:YES completion:nil];

或者

1
2
3
4
5
6
7
8
9
10
11
12
13
UIImagePickerController *picker = [[UIImagePickerController alloc] init];

__weak typeof(self)weakSelf = self;

picker.rf_finishBlock = ^(NSDictionary *info) {
    NSLog(@"finish picke image\n info:%@",info);

    UIImage *img = info[@"UIImagePickerControllerOriginalImage"];
    weakSelf.view.layer.contentsGravity = kCAGravityResizeAspect;
    weakSelf.view.layer.contents = (id)img.CGImage;
};

[self presentViewController:picker animated:YES completion:nil];

1.UIButton 按钮事件回调

关联一个按钮事件block,当触发按钮UIControlEventTouchUpInside事件时回调。

UIButton+RFBlcoks.h
1
2
3
4
5
6
7
@interface UIButton (RFBlcoks)

typedef void(^RFButtonClickBlock)(UIButton *button);

@property (nonatomic, copy) RFButtonClickBlock rf_buttonClickBlock;

@end
UIButton+RFBlcoks.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
static char kButttonClickBlockKey;

@implementation UIButton (RFBlcoks)

- (RFButtonClickBlock)rf_buttonClickBlock
{
    return objc_getAssociatedObject(self, &kButttonClickBlockKey);
}

- (void)setRf_buttonClickBlock:(RFButtonClickBlock)rf_buttonClickBlock
{
    objc_setAssociatedObject(self, &kButttonClickBlockKey, rf_buttonClickBlock, OBJC_ASSOCIATION_COPY_NONATOMIC);

    [self addTarget:self action:@selector(buttonClicked) forControlEvents:UIControlEventTouchUpInside];
}

- (void)buttonClicked
{
    if (self.rf_buttonClickBlock) {
        self.rf_buttonClickBlock(self);
    }
}

@end

使用:

1
2
3
self.button.rf_buttonClickBlock = ^(UIButton *button){
        NSLog(@"%@ clicked",button);
};

Demo地址:

参考资料:

前言

移动应用风靡的今天,大家越来越觉得,一个App的处理事情的效率至关重要,包括一些细节处理,像本地化localization)和辅助功能accessibility)的支持是高品质的应用和其它应用区分开的两个特性,今天我们来聊聊一个增加用户体验和效率的功能–编辑操作

编辑操作

标准编辑动作

处理复制,剪切,删除和粘贴命令

  • 复制copy:
  • 剪切cut:
  • 删除delete:
  • 粘贴paste:

处理选择命令

  • 选择select:
  • 全选selectAll:

富文本编辑命令

  • 加粗toggleBoldface:
  • 斜体toggleItalics:
  • 下划线toggleUnderline:

改变书写方向命令

  • 书写方向左到右makeTextWritingDirectionLeftToLeft:
  • 书写方向右到左makeTextWritingDirectionRightToLeft:

改变文本尺寸命令

  • 变大increaseSize:
  • 变小decreaseSize:

实现label的复制粘贴功能

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
// RFLabel.h
@interface RFLabel : UILabel
@end

// RFLabel.m
@implementation RFLabel

- (BOOL)canBecomeFirstResponder {
    return YES;
}

- (BOOL)canPerformAction:(SEL)action
              withSender:(id)sender
{
    return (action == @selector(copy:) || action == @selector(paste:));
}

#pragma mark - UIResponderStandardEditActions

- (void)copy:(id)sender {
    [[UIPasteboard generalPasteboard] setString:self.text];
}

- (void)paste:(id)sender {
    NSString *toBePastedString = [[UIPasteboard generalPasteboard] string];
    self.text = toBePastedString;
}

@end

在控制器中使用它:

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
#import "ViewController.h"
#import "RFLabel.h"

@interface ViewController ()

@property (weak, nonatomic) IBOutlet RFLabel *topLabel;
@property (weak, nonatomic) IBOutlet RFLabel *bottomLabel;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.

    self.topLabel.userInteractionEnabled = YES;
    self.bottomLabel.userInteractionEnabled = YES;

    UIGestureRecognizer *gestureRecognizer1 = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleLongPressGesture:)];
    UIGestureRecognizer *gestureRecognizer2 = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleLongPressGesture:)];

    [self.topLabel addGestureRecognizer:gestureRecognizer1];
    [self.bottomLabel addGestureRecognizer:gestureRecognizer2];
}

- (void)handleLongPressGesture:(UIGestureRecognizer *)recognizer
{
    if (recognizer.state == UIGestureRecognizerStateRecognized) {
        [recognizer.view becomeFirstResponder];

        UIMenuController *menuController = [UIMenuController sharedMenuController];
        [menuController setTargetRect:recognizer.view.frame inView:recognizer.view.superview];
        [menuController setMenuVisible:YES animated:YES];
    }
}

@end

总结一下,为了能够支持复制粘贴一个label的文字,需要完成下面几步:

  • 必须继承UILabel,在子类中实现canBecomeFirstRespondercanPerformAction:withSender:方法
  • 实现每个可以执行的操作,在方法中和UIPasteboard进行交互
  • 初始化label时,将label的userInteractionEnabled属性设置为YES
  • 向label添加一个手势(或者手动在子类中实现UIResponder的方法,例如touchesBegan:withEvent:
  • 响应手势识别事件,指明UIMenuController的位置,设置为可见
  • 最后,使label成为第一响应者

UIMenuController

UIMenuController负责展示编辑动作的菜单项。每个应用都持有自己的一个单例对象 sharedMenuController。 默认情况下,菜单控制器会展示UIResponderStandardEditActions这个非正式协议(即不需要对应实现的协议)中的方法, 假如canPerformAction:withSender:返回YES

UIMenuItem

可以通过UIMenuControllermenuItems属性添加自定义的命令,每一个都是UIMenuItem对象,只需要指定每个动作的titleaction

1
- (instancetype)initWithTitle:(NSString *)title action:(SEL)action NS_DESIGNATED_INITIALIZER;

UITableView 和 UICollectionView

UITableViewUICollectionView可以在cell上调出编辑菜单,其实任何自定义的UIView都能实现,UIViewController的子类只是以不同的方式实现而已。为了让UITableViewUICollectionView也能实现,你需要使用一些额外的方法否则屏幕上不会显示任何东西。这些方法给予了你更多的控制在显示指定的上下文菜单基于你将要显示在UITableViewUICollectionView的内容。

UITableViewDelegate 方法
  • – tableView:shouldShowMenuForRowAtIndexPath:
  • – tableView:canPerformAction:forRowAtIndexPath:withSender:
  • – tableView:performAction:forRowAtIndexPath:withSender:
UICollectionViewDelegate 方法
  • – collectionView:shouldShowMenuForItemAtIndexPath:
  • – collectionView:canPerformAction:forItemAtIndexPath:withSender:
  • – collectionView:performAction:forItemAtIndexPath:withSender:

Bonus

实现UIImageView图片的复制粘贴功能

Demo地址:

参考链接:

曾想自定义的View,可以像系统自带属性一样,并且实时渲染,动态更新内容,现在在Xcode6终于让你可以轻松的做到。

你不用编译就能实时预览,现在配置自定义界面方便多了,IBInspectableIBDesignable使它成为可能。

IBInspectable

IBInspectable属性提供访问旧功能的新方法:用户自定义的运行时属性。从目前的身份检查器(identity inspector)中访问,这些属性在Interface Builder整合到Xcode。可以通过它来配置Nib,Xib,storyboard实例中的任何键值编码(key-value-coded)属性:

runtime Attributes setting

以前想实现,只能点+号手动添加例如想给UIView添加一个圆角半径,设置Key Path 为:layer.cornerRadius,type为Number ,Value为4,如果使用IBInspectable只需要在@property 声明后加上IBInspectable(或者swift 加上@IBInspectable),就可以在IB的观察面板中(inspector pannel)里直接编辑。其他交由Xcode自动完成,属性名字会自动分组,名称从驼峰(camel-)转换成 title-模式。

inspector pannel

目前可检查属性支持

supported  types

备注 :

  1. 经本人亲测,除NSNumber,NSRange不会产生观察面板属性,NSRange不会在runtime Attributes 中自动添加,其他都会自动生成。

  2. 支持颜色类型UIColor但是不支持CALayerCGColor,设置颜色时只需设置UIColor,不要要设置CGColor,系统会自动将UIColor转换,例如设置borderColor。

IBDesignable

当你应用到UIView或者UIView的子类中的时候,只需加上IBDesignable,就可以让IB在画布上实时渲染视图。当你更新属性后,视图会自动更新不需要重新运行程序。

标记一个自定义视图为IBDesignable,只需要在类名前面加上IB_DESIGNABLESwift里加上@IBDesignable)。你的初始化,布置和绘制方法都将用来在画布上渲染你的自定义视图:

1
2
3
4
5
6
7
8
9
10
11
12
IB_DESIGNABLE

@interface CustomView :UIView {

@property (nonatomic, strong) IBInspectable UIColor *borderColor;       
@property (nonatomic, assign) IBInspectable CGFloat borderWidth;

...

}

@end

实时效果:

custom view

有了这个功能,一个设计师或者开放人员可以轻松调整自定义的控件呈现。任何改变,都将立即呈现,有的想Swift里面的playground功能,实现:所见即所得

由于在 Interface Builder 中呈现自定义视图不会有应用程序的完整上下文,你可能需要生成模拟数据以便显示,例如一个默认用户头像图片或仿制的天气数据。有两种方法可以为这个特殊的上下文添加代码:

  1. prepareForInterfaceBuilder() :此方法与你代码的其余部分一起编译,但只有当视图正在准备在Interface Builder显示时执行

  2. TARGET_INTERFACE_BUILDER:#if TARGET_INTERFACE_BUILDER预处宏,在 Objective-CSwift 下都是工作的,它会视情况编译正确代码

1
2
3
4
5
6
7
8
#if !TARGET_INTERFACE_BUILDE

// this code will run in the app it self

#els

// this code will execute only in IB
#endif

参考资料:

Demo地址:

输出文件内容到基本输出

cat filename

改变文件的权限

chmod 或
change mode

当前的时间和日期

date

拷贝

copy

显示当前目录下所有文件以及文件夹

ls

显示dir文件夹下所有文件以及文件夹

ls dir

查看磁盘空间状况

du

查看当前进程状况

ps

你的用户名和终端类型

who

创建目录

mkdir  dir

删除目录

rmdir  dir

进入目录

cd     dir

删除文件

rm     file

显示文件

more file

显示指定文本

echo

改文件名、移动文件

mv  source target

显示目录路径命令

pwd

清屏

clear

退出输入命令

Ctrl + c   

改变文件或目录之最后修改时间

touch  name

删除目录

 rmdir directory-name 或
 rm -r directory-name
  • rmdir dir1 删除目录 dir1,但 dir1 下必须没有文件存在,否则无法删除。
  • rm -r dir1 删除目录 dir1,及其下所有文件及子目录。

删除文件

rm filename (filename 可为文件名,或文件名缩写符号。)
  • rm file1 删除文件名为 file1 之文件。
  • rm file? 删除文件名中有五个字符,前四个字符为file 之所有文件。
  • rm f* 删除文件名中,以 f 为字首之所有文件。
  • rm -rf file 删除名为file的文件夹及其里面的内容