Alicia's Blog

[LEAF Photo]作者


  • Home

  • Tags

  • Archives

Things you should do before writing an iOS app

Posted on 2016-05-15

创建 iOS 项目之前不要忘记做好以下事情

设置编码规范

一些编码规范推荐

ObjC

Apple 的 Cocoa 编码规范
GitHub 基于 Apple Cocoa 的编码规范
Google 的 ObjC 编码规范(需翻墙)
ObjC 之禅

Swift

Raywenderlich 官方编码规范
GithHub 的编码规范

确定架构

使用传统的 MVC 还是 MVVM 亦或是 VIPER

为项目命名,使用公司及项目名作为前缀,最好是 3 个字母,避免类名冲突

为项目添加 .gitignore

# Xcode
## Build generated
build/
DerivedData/
## Various settings
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata/
## Other
*.xcuserstate
# CocoaPods
Pods
Podfile.lock
Read more »

What can make Core Graphics coding easy

Posted on 2016-05-05

[LEAF Photo] 这个项目用到太多图形绘制,少不了使用 Core Graphics,因为用法简单所以之前也没什么困扰,但是这个项目中复杂图形和曲线绘制的频繁使用导致查询 API 和计算弧度的时间消耗,如果绘制贝塞尔曲线用 Sketch 画好后再计算控制点太费劲了。今天找到一个好工具 [PaintCode],可谓是射鸡屎和程序猿通吃的神器。

img

这个工具最大的好处就是可以像操作[Sketch]一样,可视化的方式操作各种图形,OC 代码就直接生成了,大大滴提高了效率。

img

[Sketch] 设计好的矢量图形可以导出 SVG 格式,直接拖到[PaintCode]代码就生成了。

它的操作也很简单,常用的功能在顶部,属性设置在右方,不学就能会。摸不到头脑的话可以看看 官方文档 或者这个系列的 使用介绍 。

Lighter View Controllers For LEAFPhoto

Posted on 2016-04-19

[LEAF Photo] 整个 app 的大部分功能都是在 LPCollageViewController 中实现的,随着时间的推移,这个 Controller 开始变得臃肿代码很长,记录下瘦身的各种策略。

ViewModel 及组合模式的使用

[LEAF Photo] 为用户提供了拼接图片的不同模板,模板中包含了选择照片的视图、文字视图以及线条等,模板以 json 文件方式保存,将这些元素显示到视图上简单的逻辑就是判断 json 中是否存在节点,如果存在则创建对应的元素,这样创建模板的过程就会变的臃肿。

借鉴下 MVVM 的方式,将 MVC 中的 C 拆分成 VM,使用 ViewModel 的方式处理展示,在利用组合的方式将每种元素的展示方法拆分到各自类中实现,这样创建元素的这个冗长的过程就可以被分散到每个元素的 ViewModel 类中了。

@interface CollageComponentViewModel : NSObject
- (void)showToView:(CollageContainerView *)view target:(id)target;
@end
@interface CollageViewModel : CollageComponentViewModel
@end
@implementation CollageViewModel
- (instancetype)initWithCollageModel:(CollageModel *)collageModel {
    self = [super init];
    if (self) {
        _collageModel = collageModel;
    }
    return self;
}
- (void)showToView:(CollageContainerView *)view target:(id)target {
    NSArray *textDecArray = self.collageModel.textDec;
    if (textDecArray) {
        [self showToView:view target:target array:textDecArray];
    }
    // ...
}
@end
@interface CollageTextDecViewModel : CollageComponentViewModel
@end
- (void)showToView:(CollageContainerView *)view target:(id)target {
    CollageLabel *textLabel = [[CollageLabel alloc] initWithFrame:frame];
    textLabel.text = self.collageTextDecModel.text;
    textLabel.font = [UIFont fontWithName:self.collageTextDecModel.fontName size:fontSize];
    // ...
    [view addSubview:textLabel];
}

原本在 Controller 和 View 中的很多代码,被分散到 CollageTextDecViewModel 等 ViewModel 中去。

除了此例外,通常的应用中一些网络请求的处理和页面设置也可以使用 ViewModel 的方式。

将子视图独立成单独的类

子视图独立成一个类,并使用懒加载的方式创建。

Read more »

What I Talk About When I Talk About Leaks

Posted on 2016-04-01

前言

最近为[伯乐在线]翻译一篇文章(该文章尚未发布),作者 Russ Bishop 谈到自己查找内存泄漏的一次痛苦经历,读后很有感触,但是那些对于 Instruments 使用不太熟练的读者可能由于 Bishop 忽略了一些细节,看后估计会觉得喝白水般不能解渴,所以写一篇自己的经历将 Bishop 忽略的细节补全作为“译后感”吧。

一种麻烦的检查内存泄漏方式

为什么麻烦还要介绍呢,因为看到有人这样调试的。这种方式需要写些代码,这些代码完全是调试用的,而且只能让你知道是否存在泄漏,但是无法定位,看看这种方式如何操作。

在 dealloc 方法中写一个日志,如果退出页面的时候能够打印出日志内容,说明该页面没有问题,如果没有打印,那么就是有问题的。

- (void)dealloc {
     DLog(@"release");
 }

这样如果页面中有输出 release,说明该页面 CollageViewController 是没有内存泄漏的

2016-04-01 12:51:56.992 [267:19048] -[CollageViewController dealloc] - [Line 88] -- release

但是如果故意把循环引用引入,这个时候会有 Warning 警告,同时 release 也不会打印了

self.paperSizeView.paperSizeBlock = ^(PaperSizeModel *sizeModel) {
    self.paperSize = sizeModel.paperSize;
}

要把 self 改为 WeakSelf。

上面的 DLog 是我常用的一个日志宏,调试的时候能输出函数名称和所在行数,定义如下

Read more »

What I Talk About When I Talk About Performance

Posted on 2016-03-30

前言

有一句箴言说:“如果一个产品不注重性能,再华丽也是难用。” ——什么?你没听说过?好吧,这句话是我说的。

要解决一个问题,大多数情况不是我们不知道如何解决,而是我们不知道问题在哪,所以定位问题是非常重要的。突然想到美剧[House]豪斯医生(啊,其实我好久没有看过美剧了,之前太忙了,请忽略我的废话),很多疾病也是很容易解决的,只是那些平庸的医生找不到症结所在。在 iOS 的世界里,查找“病因”的关键工具就是 Instruments 了。这篇文章主要说说如何通过 Instruments 找到问题所在。

另外,由于最近为[伯乐在线]翻译一篇文章(该文章尚未发布),作者 Russ Bishop 谈到自己查找内存泄漏的一次痛苦经历,所以单开一篇文章说如何检测内存泄漏,如果你对下文中的 Allocations & Leaks 章节感觉一览未尽的话,接下来我会写一篇关于调试内存泄漏的文章,里面会讲的更加详尽。

自己设计和编码的项目[LEAF Photo](一款拼图应用)基本完成等待上线前的收尾工作中,其中有一些优化还是挺有代表性的,就拿它作为 Demo 吧。

Time Profiler

调优一般我都是从 Time Profiler 开始
img
打开 Time Profiler,将控制面板 Call Tree 中的 Seperate by Thread、 Hide System Libraries 勾选,Invert Call Tree 也可以选中,查看耗时最多的调用,看看是否有可以调整的地方,下面以我的 Demo 为例,谈谈如何调整。

显然,[MainTplTableViewCell setTplCoverModel] 中有些图片处理的耗时操作,这个可以放到线程中去做,使其不影响主线程。

NSString *coverPath = [[NSBundle mainBundle] pathForResource:cover ofType:@"jpg"];
UIImage *tplImage = [UIImage imageWithContentsOfFile:coverPath];
UIImage *scaledImage = [tplImage scaleImageAspectFitToSize:toImageView.bounds.size];
[toImageView setImage:scaledImage];

将上面的方法放到线程中,另外由于 imageWithContentsOfFile 方法不会进行缓存,所以加载图片后将其加到缓存 (类型是 NSCache )中。

UIImage *tplImage = [self.cache objectForKey:indexPath];
if (tplImage) {
    [toImageView setImage:tplImage];
}
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    NSString *coverPath = [[NSBundle mainBundle] pathForResource:cover ofType:@"jpg"];
    UIImage *tplImage = [UIImage imageWithContentsOfFile:coverPath];
    UIImage *scaledImage = [tplImage scaleImageAspectFitToSize:toImageView.bounds.size];
    [self.cache setObject:scaledImage forKey:indexPath];
    dispatch_async(dispatch_get_main_queue(), ^{
        [toImageView setImage:scaledImage];
    });
});

这样优化后,setTplCoverModel 方法由原来的 32ms 降低到了 2ms。

通过 Time Profiler 找到最耗时的调用,不影响主线程的操作放到应当线程中;需要优化算法的通过算法降低时间复杂度;经常访问的数据放到缓存中,通过以上方法做调整。

看完 Timer Profiler 接下来看看 Allocations 和 Leaks 的使用。

Read more »

Pro Multilthreading and Memory Management for iOS

Posted on 2016-03-21

Code tells everything,所以直接上代码

ARC

先来看看 ARC 下的新产物

weak 修饰符的效率

{
    id __weak o = obj; 
    NSLog(@"1 %@", o); 
    NSLog(@"2 %@", o); 
}

当使用 __weak 修饰符的时候,对象会被自动加到自动释放池里,这样自动释放池能将其自动释放,调用多次 weak 对象,对象就会被多次放到自动释放池中,如上 o 被加入两次,所以建议使用添加加一个强引用来优化如下:

{
    id __weak o = obj; 
    id tmp = o; 
    NSLog(@"1 %@", tmp); 
    NSLog(@"2 %@", tmp);
}

weak 如何在释放时赋值为 nil

{
    id __weak obj1 = obj;
}

以上代码 clang 后为

id obj1;
objc_initWeak(&obj1, obj); 

objc_initWeak 定义如下

id objc_initWeak(id *location, id newObj) {
    if (!newObj) {
        *location = nil;
        return nil;
    }
    //  这里的 template 用法是非类型的类模板参数 
    return storeWeak<false/*old*/, true/*new*/, true/*crash*/>
        (location, (objc_object*)newObj);
}

关于上面 非类型的类模板参数 也有叫非类型形参,可以优化性能,但是具体原因网上没搜索到,有空看看 《C++ Templates》 应该有具体介绍,可以探究下提升性能的原因。

Read more »

Source Code Learning - AFNetworking

Posted on 2016-01-11

NSURLSession 的最简单实用方法

NSURLSession 的不同之处在于,它把 NSURLConnection 替换为
NSURLSession
NSURLSessionConfiguration
NSURLSessionTask 子类:NSURLSessionDataTask、NSURLSessionUploadTask、NSURLSessionDownloadTask

NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];

NSOperationQueue *queue = [NSOperationQueue mainQueue];
NSURLSession *session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:queue];

NSURLSessionDownloadTask *task = [session downloadTaskWithURL:[NSURL URLWithString:@"https://www.baidu.com"]];
[task resume];

可以用这个最简单的例子跟 AF 对比下,看看别人怎么写一个库

设置 NSURL 的几种方式

NSURL *baseURL = [NSURL URLWithString:@"http://example.com/v1/"];
// http://example.com/v1/foo
[NSURL URLWithString:@"foo" relativeToURL:baseURL];           
// http://example.com/v1/foo?bar=baz    
[NSURL URLWithString:@"foo?bar=baz" relativeToURL:baseURL];         

NSURLSession 和 NSURLConnection 的区别

与 NSURLConnection 相比,NSURLSession 最直接的改善就是提供了配置每个会话的缓存,协议,Cookie 和证书政策(Credential Policies),甚至跨应用程序共享它们的能力。

NSURLSessionConfiguration 的类型

Read more »

Source Code Learning - YYKit

Posted on 2015-12-18

微博 Demo 性能的优化

抛弃了以往在 Controller 中保存 Model 数组的传统方法,而是添加了用于存放布局和数据对象的数组,其中的布局是从 API 获取到数据后,在后台线程中计算后并保存的,这样当 Cell 需要显示时直接从内存中获取,不需要再进行计算。优点显而易见,缺点是 UIKit 的控件不是线程安全的,所以依赖 YYText 中的控件实现。

头像图片的圆角实现方式是在图片下载后,使用 Core Graphic 将图片用贝塞尔路径切成圆角后放到内存中使用。YYKit 的 Base 下有常用的 Category 实现,其中包括了切圆角的实现。

Macros 宏

TARGET_INTERFACE_BUILDER 宏用于区分代码是否需要在 IB 中执行

#if !TARGET_INTERFACE_BUILDER
    // this code will run in the app itself
#else
    // this code will execute only in IB
#endif

事务机制

在非 IB 中设置的 YYTextView 需要更新布局时使用了事务机制

[[YYTransaction transactionWithTarget:self selector:@selector(_updateIfNeeded)] commit];

事务提交时调用 YYTransactionSetup

static void YYTransactionSetup() {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        transactionSet = [NSMutableSet new];
        CFRunLoopRef runloop = CFRunLoopGetMain();
        CFRunLoopObserverRef observer;

        observer = CFRunLoopObserverCreate(CFAllocatorGetDefault(),
                                           kCFRunLoopBeforeWaiting | kCFRunLoopExit,
                                           true,      // repeat
                                           0xFFFFFF,  // after CATransaction(2000000)
                                           YYRunLoopObserverCallBack, NULL);
        CFRunLoopAddObserver(runloop, observer, kCFRunLoopCommonModes);
        CFRelease(observer);
    });
}
static void YYRunLoopObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) {
    if (transactionSet.count == 0) return;
    NSSet *currentSet = transactionSet;
    transactionSet = [NSMutableSet new];
    [currentSet enumerateObjectsUsingBlock:^(YYTransaction *transaction, BOOL *stop) {
        [transaction.target performSelector:transaction.selector];
    }];
}
Read more »

Source Code Learning - YYModel

Posted on 2015-12-05

源码看的是 YYKit,因为内容太多所以还是按照功能分开

kCFNull

NSNull 的用法 Mattt Thompson 说过是将空值存到数组或者字典

NSMutableDictionary *mutableDictionary = [NSMutableDictionary dictionary];
mutableDictionary[@"someKey"] = [NSNull null]; // Sets value of NSNull singleton for `someKey`
NSLog(@"Keys: %@", [mutableDictionary allKeys]); // @[@"someKey"]

kCFNull 这个是什么呢,看定义可以知道

typedef const struct CF_BRIDGED_TYPE(NSNull) __CFNull * CFNullRef;
const CFNullRef kCFNull;    // the singleton null instance

看个栗子

NSNull *null1 = (id)kCFNull;
NSNull *null2 = [NSNull null];

输出信息

(NSNull *) null1 = 0x0000000107b10af0
(NSNull *) null2 = 0x0000000107b10af0

说明 (id)kCFNull 就是 [NSNull null]

深拷贝

- (id)deepCopy {
    id obj = nil;
    @try {
        obj = [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:self]];
    }
    @catch (NSException *exception) {
         NSLog(@"%@", exception);
    }
    return obj;
}

下面说说浅拷贝和深拷贝。集合类的拷贝分为浅拷贝和深拷贝

Read more »

How To Record Your App With QuickTime

Posted on 2015-11-12

之前为 Github 上的开源项目录制了一个 Demo 的 Gif,因为有些硬盘洁癖,所以没有下载其他录屏工具,使用的是 Mac 自带的 QuickTime,之后将录制的视频转成 Gif,可是隔段时间以后就忘了如何录制了,在此记录下过程。

将程序在你的 iOS 设备上运行,之后打开 QuickTime 进行以下操作:

  • 点击 “File”
  • 点击 “New Movie Recording”
  • 在出现的窗口中点击录制按钮旁边的三角箭头,选择你的 iOS 设备,开始录制吧

img

视频录制后使用 GIFBrewery 工具生成 Gif,该工具可以设置捕捉帧数及图片颜色数量,基本需求都能满足。

Gifski 也是视频转 Gif 的免费工具。

1…345
Alicia

Alicia

45 posts
23 tags
GitHub E-Mail Twitter weibo
© 2020 Alicia
Powered by Hexo
|
Theme — NexT.Mist v5.1.4