第7期博客(查违章日志系统的接入NSLogger)

查违章日志系统的接入NSLogger

NSLogger是一款强力的日志记录和分析工具,其强大的功能,可以完全替代Xcode自带的Debugger,本文将介绍一些其主要特点和用法以及接入查违章的思路。github地址.

安装

  • Step 1. 在Mac上下载并安装NSLogger desktop viewer.
  • Step 2. 将NSLogger framework添加到工程中.
  • Step 3. There is no step 3…

效果



配置

NSLogger支持TCP和Bonjour两种方式连接终端设备,Bonjour连接一般不需要配置,如果要是使用TCP连接,要通过LoggerSetViewerHost()配置IP地址和端口(同时需配置NSLoggerViewer,在Preferences的Network中,勾选 “Listen for loggers on TCP port”打开监听):

1
LoggerSetViewerHost(NULL, (__bridge CFStringRef)@"192.168.11.38", (UInt32)50000);

使用

1
#import "NSLogger.h"
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
#ifdef DEBUG
#define NSLog(...) LogMessageF(__FILE__, __LINE__, __FUNCTION__, @"NSLog", 0, __VA_ARGS__)
#define LoggerError(level, ...) LogMessageF(__FILE__, __LINE__, __FUNCTION__, @"Error", level, __VA_ARGS__)
#define LoggerApp(level, ...) LogMessageF(__FILE__, __LINE__, __FUNCTION__, @"App", level, __VA_ARGS__)
#define LoggerView(level, ...) LogMessageF(__FILE__, __LINE__, __FUNCTION__, @"View", level, __VA_ARGS__)
#define LoggerService(level, ...) LogMessageF(__FILE__, __LINE__, __FUNCTION__, @"Service", level, __VA_ARGS__)
#define LoggerModel(level, ...) LogMessageF(__FILE__, __LINE__, __FUNCTION__, @"Model", level, __VA_ARGS__)
#define LoggerData(level, ...) LogMessageF(__FILE__, __LINE__, __FUNCTION__, @"Data", level, __VA_ARGS__)
#define LoggerNetwork(level, ...) LogMessageF(__FILE__, __LINE__, __FUNCTION__, @"Network", level, __VA_ARGS__)
#define LoggerLocation(level, ...) LogMessageF(__FILE__, __LINE__, __FUNCTION__, @"Location", level, __VA_ARGS__)
#define LoggerPush(level, ...) LogMessageF(__FILE__, __LINE__, __FUNCTION__, @"Push", level, __VA_ARGS__)
#define LoggerFile(level, ...) LogMessageF(__FILE__, __LINE__, __FUNCTION__, @"File", level, __VA_ARGS__)
#define LoggerSharing(level, ...) LogMessageF(__FILE__, __LINE__, __FUNCTION__, @"Sharing", level, __VA_ARGS__)
#define LoggerAd(level, ...) LogMessageF(__FILE__, __LINE__, __FUNCTION__, @"Ad and Stat", level, __VA_ARGS__)
#else
#define NSLog(...) LogMessageCompat(__VA_ARGS__)
#define LoggerError(...) while(0) {}
#define LoggerApp(level, ...) while(0) {}
#define LoggerView(...) while(0) {}
#define LoggerService(...) while(0) {}
#define LoggerModel(...) while(0) {}
#define LoggerData(...) while(0) {}
#define LoggerNetwork(...) while(0) {}
#define LoggerLocation(...) while(0) {}
#define LoggerPush(...) while(0) {}
#define LoggerFile(...) while(0) {}
#define LoggerSharing(...) while(0) {}
#define LoggerAd(...) while(0) {}
#endif

##特点

  • 标签输出
  • 自定义优先级
  • 直接输出图片
  • 多线程标记
  • 时序控制
  • 远程记录

##查违章是如何接入的

1.该日志直接接入已有的BPLogger中,对比NSLogger.h中提供的宏定义,我们可以有符合我们项目的宏定义,对于Release下不输出日志,比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#ifdef DEBUG
#define BPLog(...) LogMessageF(__FILE__, __LINE__, __FUNCTION__, @"Normol Log", 0, __VA_ARGS__)
#define BPLoggerNetwork(...) LogMessageF(__FILE__, __LINE__, __FUNCTION__, @"NetWork Log", 0, __VA_ARGS__)
#define BPLoggerAd(...) LogMessageF(__FILE__, __LINE__, __FUNCTION__, @"AD Log", 0, __VA_ARGS__)
#define BPLoggerEvent(...) LogMessageF(__FILE__, __LINE__, __FUNCTION__, @"Event Log", 0, __VA_ARGS__)
#define BPLoggerCache(...) LogMessageF(__FILE__, __LINE__, __FUNCTION__, @"Cache Log", 0, __VA_ARGS__)
#define BPLoggerSkipping(...) LogMessageF(__FILE__, __LINE__, __FUNCTION__, @"Skipping Log", 0, __VA_ARGS__)
#define BPLoggerCWZSDK(...) LogMessageF(__FILE__, __LINE__, __FUNCTION__, @"Cwzsdk Dispatch Log", 0, __VA_ARGS__)
#define BPLoggerCustom(CustomStr,...) LogMessageF(__FILE__, __LINE__, __FUNCTION__, CustomStr, 0, __VA_ARGS__)
#else
#define BPLog(...) while(0) {}
#define BPLoggerNetwork(...) while(0) {}
#define BPLoggerAd(level, ...) while(0) {}
#define BPLoggerEvent(...) while(0) {}
#define BPLoggerCache(...) while(0) {}
#define BPLoggerSkipping(...) while(0) {}
#define BPLoggerCWZSDK(...) while(0) {}
#define BPLoggerCustom(CustomStr,...) while(0) {}
#endif

2.由于日志系统可能会嵌入查违章的主工程和很多基础库中,比如CLAdsSDK,CLSafeKit,BPCommon等,正常的使用就是在最基础库中依赖NSLogger,但是这样会使项目对该工具的使用和剥离增加更多的人为操作,这并不是最好的解决方法,我们期望的是所有的基础库都不依赖NSLogger,如果需要这个功能,在主工程的podfile中pod ‘NSLogger’就行,就像一个”插件”那样,可以随意加入工程,也可以随时拿走,所以对BPLogger.h进行改造,比如这样:

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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#if MainProjectMarco(一个在主工程的宏定义)
#import "LoggerClient.h"
#endif
#if MainProjectMarco(一个在主工程的宏定义)
#ifdef DEBUG
#define BPLog(...) LogMessageF(__FILE__, __LINE__, __FUNCTION__, @"Normol Log", 0, __VA_ARGS__)
#define BPLoggerNetwork(...) LogMessageF(__FILE__, __LINE__, __FUNCTION__, @"NetWork Log", 0, __VA_ARGS__)
#define BPLoggerAd(...) LogMessageF(__FILE__, __LINE__, __FUNCTION__, @"AD Log", 0, __VA_ARGS__)
#define BPLoggerEvent(...) LogMessageF(__FILE__, __LINE__, __FUNCTION__, @"Event Log", 0, __VA_ARGS__)
#define BPLoggerCache(...) LogMessageF(__FILE__, __LINE__, __FUNCTION__, @"Cache Log", 0, __VA_ARGS__)
#define BPLoggerSkipping(...) LogMessageF(__FILE__, __LINE__, __FUNCTION__, @"Skipping Log", 0, __VA_ARGS__)
#define BPLoggerCWZSDK(...) LogMessageF(__FILE__, __LINE__, __FUNCTION__, @"Cwzsdk Dispatch Log", 0, __VA_ARGS__)
#define BPLoggerCustom(CustomStr,...) LogMessageF(__FILE__, __LINE__, __FUNCTION__, CustomStr, 0, __VA_ARGS__)
#else
#define BPLog(...) while(0) {}
#define BPLoggerNetwork(...) while(0) {}
#define BPLoggerAd(level, ...) while(0) {}
#define BPLoggerEvent(...) while(0) {}
#define BPLoggerCache(...) while(0) {}
#define BPLoggerSkipping(...) while(0) {}
#define BPLoggerCWZSDK(...) while(0) {}
#define BPLoggerCustom(CustomStr,...) while(0) {}
#endif
#else
#ifdef DEBUG
#define BPLog(...) NSLog(__VA_ARGS__)
#define BPLoggerNetwork(...) NSLog(__VA_ARGS__)
#define BPLoggerAd(...) NSLog(__VA_ARGS__)
#define BPLoggerEvent(...) NSLog(__VA_ARGS__)
#define BPLoggerCache(...) NSLog(__VA_ARGS__)
#define BPLoggerSkipping(...) NSLog(__VA_ARGS__)
#define BPLoggerCWZSDK(...) NSLog(__VA_ARGS__)
#define BPLoggerCustom(CustomStr,...) NSLog(__VA_ARGS__)
#else
#define BPLog(...) while(0) {}
#define BPLoggerNetwork(...) while(0) {}
#define BPLoggerAd(level, ...) while(0) {}
#define BPLoggerEvent(...) while(0) {}
#define BPLoggerCache(...) while(0) {}
#define BPLoggerSkipping(...) while(0) {}
#define BPLoggerCWZSDK(...) while(0) {}
#define BPLoggerCustom(CustomStr,...) while(0) {}
#endif
#endif

3.尽管如此,我们还是依赖主工程需要提供的宏定义,离”插件”的定义还差最后一步,我们要想办法把这个宏定义也去掉,比如这样:

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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#import <Foundation/Foundation.h>
#import <CoreGraphics/CoreGraphics.h>
#if __has_include(<NSLogger/LoggerClient.h>)
#import "LoggerClient.h"
#endif
#if __has_include(<NSLogger/LoggerClient.h>)
#ifdef DEBUG
#define BPLog(...) LogMessageF(__FILE__, __LINE__, __FUNCTION__, @"Normol Log", 0, __VA_ARGS__)
#define BPLoggerNetwork(...) LogMessageF(__FILE__, __LINE__, __FUNCTION__, @"NetWork Log", 0, __VA_ARGS__)
#define BPLoggerAd(...) LogMessageF(__FILE__, __LINE__, __FUNCTION__, @"AD Log", 0, __VA_ARGS__)
#define BPLoggerEvent(...) LogMessageF(__FILE__, __LINE__, __FUNCTION__, @"Event Log", 0, __VA_ARGS__)
#define BPLoggerCache(...) LogMessageF(__FILE__, __LINE__, __FUNCTION__, @"Cache Log", 0, __VA_ARGS__)
#define BPLoggerSkipping(...) LogMessageF(__FILE__, __LINE__, __FUNCTION__, @"Skipping Log", 0, __VA_ARGS__)
#define BPLoggerCWZSDK(...) LogMessageF(__FILE__, __LINE__, __FUNCTION__, @"Cwzsdk Dispatch Log", 0, __VA_ARGS__)
#define BPLoggerCustom(CustomStr,...) LogMessageF(__FILE__, __LINE__, __FUNCTION__, CustomStr, 0, __VA_ARGS__)
#else
#define BPLog(...) while(0) {}
#define BPLoggerNetwork(...) while(0) {}
#define BPLoggerAd(level, ...) while(0) {}
#define BPLoggerEvent(...) while(0) {}
#define BPLoggerCache(...) while(0) {}
#define BPLoggerSkipping(...) while(0) {}
#define BPLoggerCWZSDK(...) while(0) {}
#define BPLoggerCustom(CustomStr,...) while(0) {}
#endif
#else
#ifdef DEBUG
#define BPLog(...) NSLog(__VA_ARGS__)
#define BPLoggerNetwork(...) NSLog(__VA_ARGS__)
#define BPLoggerAd(...) NSLog(__VA_ARGS__)
#define BPLoggerEvent(...) NSLog(__VA_ARGS__)
#define BPLoggerCache(...) NSLog(__VA_ARGS__)
#define BPLoggerSkipping(...) NSLog(__VA_ARGS__)
#define BPLoggerCWZSDK(...) NSLog(__VA_ARGS__)
#define BPLoggerCustom(CustomStr,...) NSLog(__VA_ARGS__)
#else
#define BPLog(...) while(0) {}
#define BPLoggerNetwork(...) while(0) {}
#define BPLoggerAd(level, ...) while(0) {}
#define BPLoggerEvent(...) while(0) {}
#define BPLoggerCache(...) while(0) {}
#define BPLoggerSkipping(...) while(0) {}
#define BPLoggerCWZSDK(...) while(0) {}
#define BPLoggerCustom(CustomStr,...) while(0) {}
#endif
#endif

这里面有个关键的宏定义:

1
#if __has_include()

此宏传入一个你想引入文件的名称作为参数,如果该文件能够被引入则返回1,否则返回0。

现在我们的所有基础库都不依赖NSLogger,只要主工程podfile中有NSLogger,那么这个工具就能工作,如果没有,就是普通的NSLog

基于上面接入查违章的过程,#if __has_include()这个方式是否可以进行延展使用

比如CLSafeKit/Log是不是可以以”插件”的形式接入查违章,而不是直接依赖在BPCommon中。

第6期博客(响应链)

iOS响应链中HitTest、nextReponder的介绍

iOS的UIEvent事件有好几种:Touch Events(触摸事件)、Motion Events(运动事件)、Remote Events(远程事件),其中最常用的应该就是Touch Events了,今天我们主要就讲它,核心就是到UIView
func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView?
以及nextReponder两个

比如成果:魔图&图片浏览 复杂的视图层级拆分

wechatimg166
pb

考一考: 直接继承于UIResponder的类有哪些?

UIResponder ?? 响应链 ??

  • 响应链是什么时候怎样构建的?
  • 事件第一个响应者是怎么确定的?
  • 事件第一个响应者确定后,系统是怎样传递事件的?

响应链的构建

先看看UIResponder类

1
2
3
4
5
6
7
8
9
10
11
12
13
open var next: UIResponder? { get }
open var canBecomeFirstResponder: Bool { get } // default is NO
// default is NO
open func becomeFirstResponder() -> Bool
open var canResignFirstResponder: Bool { get } // default is YES
// default is YES
open func resignFirstResponder() -> Bool
open var isFirstResponder: Bool { get }
open func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?)
open func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?)
open func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?)
open func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?)

常见的触摸响应链构建

  1. addSubview
  2. ViewController 初始化

res

事件响应过程

核心的方法

1
2
func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView?
func point(inside point: CGPoint, with event: UIEvent?) -> Bool

注意:hittest & pointinside是uiview的实例方法

比如tableview的cell点击,hitTest寻找响应cell的过程

  • windows
  • windows.rootController (navController).view
  • navController.rootControler (vc).view
  • tableview
  • some cell
  • contentView

Note: 查询过程也不是这么顺利的,有弯路,也有属性限制

  • hittest打算从后往前遍历subviews,直到找到才停止遍历
  • subview必须符合
    • pointInside return YES
    • 属性限制有
      • alpha > 0.01
      • hidden == NO
      • userInteractionEnabled == YES
1
2
3
4
5
6
7
8
9
10
11
// 模拟原生未干预下的代码
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if !isUserInteractionEnabled || isHidden || alpha<=0.01 { return nil }
if !self.point(inside: point, with: event) { return nil }
for subview in subviews.reversed() {
let convertPoint = subview.convert(point, from: self)
let subHitTestView = subview.hitTest(convertPoint, with: event)
if let hitTestView = subHitTestView { return hitTestView }
}
return self
}

那我们干预后,HitTest的实战应用就有

  • 复杂的视图层级的拆分
1
2
3
4
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
let view = super.hitTest(point, with: event)
return view == self ? nil : view
}
  • 自定义Touch事件传递对象
1
2
3
4
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
let view = super.hitTest(point, with: event)
return view == someBtn ? anotherBtn : view
}
  • 自定义控件的响应区域及响应层级

view修改响应区域方式有两种,一种如下,它的superview来hitTest,第二种自己hitTest

1
2
3
4
5
6
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if someBtn.frame.insetBy(dx: -10, dy: -10).contains(point) {
return someBtn
}
return super.point(inside: point, with: event)
}

响应链 .nextResponder

和 hitTest自底向上相反,它是从最末端的responder向下传递,hitTest自下而上查找响应的view,nextResponder是基于hitTest基础上,自上而下寻找event的响应者

常见的应用案例:BPUIResponderAdditions中的方法,可以查找view的Controller或navigationController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- (nullable __kindof UIResponder *)findNextResonderInClass:(nonnull Class)responderClass
{
UIResponder *next = self;
do {
next = [next nextResponder];
if ([next isKindOfClass:responderClass]) {
break;
}
// next 不为空 且 不是达到最底层的 appdelegate
} while (next!=nil && ![next conformsToProtocol:@protocol(UIApplicationDelegate)]);
return next;
}
// 查询响应最近的 controller
UIViewController *vc = [view findNextResonderInClass:[UIViewController class]];

知识点:

  • 响应链事件传递

通过nextResponder向下传递event

演示

touchesBegan / super.touchesBegan 或 nextResponder.touchesBegan 一直调用下去

  • 第一响应者

    有拦腰截断的意思,原有的顶部第一响应者被修改,响应链中接收事件第一人(常见textField,webView及UImenuController,见demo)

演示

  • Button event 先下传递
  • becomeFirstResponder后,nextResponder无事件下传
  • canBecomeFirstResponder \ becomeFirstResponder 拦截
  • cancelsTouchesInView

系统会将Touch cancel消息发送给hitTestView ,并调用hitTestView的TouchCancel

演示

demo:button的touchUpInside对UIPanGestureRecognizer的影响

第三期博客(SafeKit,混淆,m34)

CATransform3D 的 m34 属性

m34的默认值是0,我们可以通过设置m34-1.0 / d来应用透视效果,d代表了想象中视角相机和屏幕之间的距离,以像素为单位

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
40
41
42
43
44
45
#import "ViewController.h"
#import <QuartzCore/QuartzCore.h>
#define WIDTH 10
#define HEIGHT 10
#define DEPTH 10
#define SIZE 100
#define SPACING 150
#define CAMERA_DISTANCE 500
@interface ViewController ()
@property (nonatomic, strong) IBOutlet UIScrollView *scrollView;
@end
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.scrollView.contentSize = CGSizeMake((WIDTH - 1)*SPACING, (HEIGHT - 1)*SPACING);
CATransform3D transform = CATransform3DIdentity;
transform.m34 = -1.0 / CAMERA_DISTANCE;
self.scrollView.layer.sublayerTransform = transform;
for (int z = DEPTH - 1; z >= 0; z--) {
for (int y = 0; y < HEIGHT; y++) {
for (int x = 0; x < WIDTH; x++) {
//create layer
CALayer *layer = [CALayer layer];
layer.frame = CGRectMake(0, 0, SIZE, SIZE);
layer.position = CGPointMake(x*SPACING, y*SPACING);
layer.zPosition = -z*SPACING;
//set background color
layer.backgroundColor = [UIColor colorWithWhite:1-z*(1.0/DEPTH) alpha:1].CGColor;
//attach to scroll view
[self.scrollView.layer addSublayer:layer];
}
}
}
}
@end

m34的运用例子: XYDebugView

详情查看

SafeKit 及其他 by Joliy

CLSafeKit 具体见:README.md

第4期博客(混编尝试)

参考资料

网络库的转换内容,需要注意的事项

  • 用新的网络库,请求接口携带cookie进行的安全验证问题 (需要与objc的使用进行隔离)
  • 如果使用ASI进行请求时,混编是否会出现类型转换的问题
  • 需要对CKDataResult,CKDataService进行桥接,进行试验
  • 数据请求模型的构建,会出现继承Object-c出现的问题 比如CKListDataModel
  • 新出一套的话需要对网络层进行封装处理,关键点在于cookie处
  • JsonKit的选择,model类型转换的问题

图片加载的问题

  • 牵涉到社区小视频的问题

缓存的问题

  • 应该可以接受桥接转换的使用方式进行完成

UI框架层的问题

  • 网络请求层,下拉刷新,上拉加载更多的框架需要按照Swift的思想重新封装(需要与objc的使用进行隔离)

使用Swift重构 方案

需要自行完善的SDK

  • UIKit 扩展
  • Foundation 扩展
  • 网络层 扩展
  • 图片加载层 扩展 (如果用到小视频时需要此扩展)
  • 下拉刷新,上拉加载更多的逻辑 扩展
  • 路由层的处理
  • 定位问题
  • 分享问题
  • 扫描库的问题
  • searchKit scanKit 等等的依赖问题解决,使用自己的依赖的时候不要用尖括号
  • webController的支持,包括JSbridgeWebController

制作模拟器和真机都兼容的动态库需要加入的shell脚本

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
if [ "${ACTION}" = "build" ]
then
INSTALL_DIR=${SRCROOT}/Products/${PROJECT_NAME}.framework
DEVICE_DIR=${BUILD_ROOT}/${CONFIGURATION}-iphoneos/${PROJECT_NAME}.framework
SIMULATOR_DIR=${BUILD_ROOT}/${CONFIGURATION}-iphonesimulator/${PROJECT_NAME}.framework
if [ -d "${INSTALL_DIR}" ]
then
rm -rf "${INSTALL_DIR}"
fi
mkdir -p "${INSTALL_DIR}"
cp -R "${DEVICE_DIR}/" "${INSTALL_DIR}/"
#ditto "${DEVICE_DIR}/Headers" "${INSTALL_DIR}/Headers"
lipo -create "${DEVICE_DIR}/${PROJECT_NAME}" "${SIMULATOR_DIR}/${PROJECT_NAME}" -output "${INSTALL_DIR}/${PROJECT_NAME}"
open "${DEVICE_DIR}"
open "${SRCROOT}/Products"
fi

混编需要注意的地方

1、swift不认识NS_ENUM宏,直接typedef enum…

2、DispatchQueue.main.async

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
DispatchQueue.main.async {
let topC: UIViewController = UIApplication.shared.topViewController()
//取最顶部的vc,如果vc的navigationController存在,则用该nav做处理
if let topNav = topC.navigationController {
//注意isKindOfClass的调用
if controller.isKind(of: UINavigationController.self) {
topNav.present(controller, animated: true, completion: nil)
} else {
topNav.pushViewController(controller, animated: true)
}
} else {
if controller.isKind(of: UINavigationController.self) {
topC.present(controller, animated: true, completion: nil)
} else {
topC.present(CLHNavigationController.init(rootViewController: controller), animated: true, completion: nil)
}
}
}

3、注意NSDictionary和Dictionary的区别,通过objc传值过来的都是NSDictionary

4、NSthread 要用Thread

1
2
3
4
while waitTime < 8.0 && CLHCoreUtil.mainController() == nil {
Thread.sleep(forTimeInterval: 0.1)
waitTime = waitTime + 0.1
}

5、注册通知或者prformSelector调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//注册推送通知
let notificationDidOpenName = NSNotification.Name(APP_EVENT_PUSH_NOTIFICATION_DID_OPEN)
NotificationCenter.default.addObserver(self, selector: #selector(pushNotificationDidOpen(notification:)), name: notificationDidOpenName, object: nil)
let notificationDidReciveName = NSNotification.Name(APP_EVENT_PUSH_NOTIFICATION_DID_RECIVE)
NotificationCenter.default.addObserver(self, selector: #selector(pushNotificationDidOpen(notification:)), name: notificationDidReciveName, object: nil)
@objc func pushNotificationDidOpen(notification: NSNotification) {
}
@objc func pushNotificationDidRecive(notification: NSNotification) {
}
//peform
self.performSelector(inBackground: #selector(safePushViewController(controller:)), with: navController)
//通过通知所进行的一系列的vc展示
@objc func safePushViewController(controller: UIViewController) {
}

6、 UIAlertView 之坑,otherButtonTitle的问题

7、如何添加warning标识,添加 New Run Script Phase,点击了之后就出现了下图的Run Script栏 ,添加如下shell脚本

1
2
3
TAGS="TODO:|FIXME:"
echo "searching ${SRCROOT} for ${TAGS}"
find "${SRCROOT}" \( -name "*.swift" \) -print0 | xargs -0 egrep --with-filename --line-number --only-matching "($TAGS).*\$" | perl -p -e "s/($TAGS)/ warning: \$1/"

8、 CGRect 和 枚举实例,如下代码:

1
2
3
4
5
// 添加滑动返回的左边阴影
let shadowImageView = UIImageView(image: UIImage.init(named: "leftside_shadow_bg"))
shadowImageView.frame = CGRect(x: -10, y: 0, width: 10, height: self.window!.rootViewController!.view.bounds.height)
shadowImageView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
self.window!.rootViewController!.view.addSubview(shadowImageView)

9、 swift中混编调用oc的实例方法和静态方法 QCPWelcomeView 举例

10、关于私有库中bundle资源文件的管理,各个framework自己管理自己的bundle文件,不要依赖其他私有库或者从主项目中获取,可以传入实例化的资源问价载体

第5期博客(Moya & 绘图)

绘画代码

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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
//
// ViewController.swift
// DrawDemo
//
// Created by XcodeYang on 07/12/2017.
// Copyright © 2017 XcodeYang. All rights reserved.
//
import UIKit
let numberOfDrawer = 30
let drawColor = UIColor.yellow
let lineWidth: CGFloat = 1.0
enum PenType: String {
case pencil = "铅笔"
case bucket = "水桶"
case eraser = "橡皮擦"
mutating func next() {
switch self {
case .pencil:
self = .bucket
case .bucket:
self = .eraser
case .eraser:
self = .pencil
}
}
}
class ViewController: UIViewController {
var pentype = PenType.pencil
var drawLayer: CAShapeLayer!
var drawPaths = [CGMutablePath]()
@IBOutlet weak var penButton: UIButton!
var historyAllRecords = [CAShapeLayer]()
var historyShowingRecords = [CAShapeLayer]()
private func creatNewLayer()-> CAShapeLayer {
let layer = CAShapeLayer()
layer.lineWidth = lineWidth;
layer.strokeColor = drawColor.cgColor
layer.fillColor = penButton.isSelected ? drawColor.cgColor : UIColor.clear.cgColor
view.layer.addSublayer(layer)
return layer
}
override func viewDidLoad() {
super.viewDidLoad()
}
override var prefersStatusBarHidden: Bool {
return true
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if pentype == .eraser { return }
drawPaths.removeAll()
drawLayer = creatNewLayer()
guard let point = touches.first?.location(in: view) else { return }
(0...numberOfDrawer).forEach { index in
let path = CGMutablePath()
path.move(to: convertPointByCenterOfPoint(point: point, index: index))
drawPaths.append(path)
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let point = touches.first?.location(in: view) else { return }
if pentype == .eraser {
let deleteLayers = historyShowingRecords.filter({ (pathLayer) -> Bool in
return pathLayer.path!.contains(point)
})
deleteLayers.forEach{ $0.removeFromSuperlayer() }
return
}
drawPaths.enumerated().forEach { (index, path) in
path.addLine(to: convertPointByCenterOfPoint(point: point, index: index))
}
if pentype == .bucket {
let resultPath = drawPaths.reduce(CGMutablePath(), { (resultPath, nextPath) -> CGMutablePath in
let newNext = nextPath.mutableCopy()
newNext?.closeSubpath()
resultPath.addPath(newNext!)
return resultPath
})
drawLayer.path = resultPath
} else {
let resultPath = drawPaths.reduce(CGMutablePath(), { (resultPath, nextPath) -> CGMutablePath in
resultPath.addPath(nextPath)
return resultPath
})
drawLayer.path = resultPath
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
if pentype == .eraser { return }
historyShowingRecords.append(drawLayer)
historyAllRecords = historyShowingRecords
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
if pentype == .eraser { return }
historyShowingRecords.append(drawLayer)
historyAllRecords = historyShowingRecords
}
@IBAction func penChange(_ sender: UIButton) {
pentype.next()
sender.setTitle(pentype.rawValue, for: .normal)
}
@IBAction func backAction(_ sender: UIButton) {
if let deleteLayer = historyShowingRecords.last {
deleteLayer.removeFromSuperlayer()
historyShowingRecords.removeLast()
}
}
@IBAction func recoverAction(_ sender: Any) {
if historyShowingRecords.count < historyAllRecords.count {
let recoverLayer = historyAllRecords[historyShowingRecords.count]
historyShowingRecords.append(recoverLayer)
view.layer.addSublayer(recoverLayer)
}
}
@IBAction func cleanAction(_ sender: Any) {
historyShowingRecords.forEach{ $0.removeFromSuperlayer() }
historyShowingRecords.removeAll()
historyAllRecords.removeAll()
}
}
extension ViewController {
func convertPointByCenterOfPoint(point:CGPoint, index:Int) -> CGPoint {
let center = CGPoint(x:view.bounds.width/2.0, y:view.bounds.height/2.0)
let increaseAngle = Double.pi * 2.0 * Double(index) / Double(numberOfDrawer);
let radius: Double = sqrt(pow(Double(point.x) - Double(center.x), 2) + pow(Double(point.y) - Double(center.y), 2))
let angle: Double = getAngle(startPoint: center, endPoint: point) + increaseAngle
return CGPoint(x: cos(angle) * radius + Double(center.x), y: sin(angle)*radius + Double(center.y))
}
func getAngle(startPoint:CGPoint, endPoint:CGPoint) -> Double {
let y = Double(endPoint.y - startPoint.y)
let x = Double(endPoint.x - startPoint.x)
let length = sqrt(y*y + x*x)
var rads = asin(y/length)
if x < 0 {
rads = (Double.pi - rads)
}
return rads
}
}

Autolayout和Masrony的使用

#iOS界面布局

目前来说iOS目前几种主流的UI布局:

  • Frame布局
  • AutoLayout
    • 使用代码实现AutoLayout
    • 使用xib或者StoryBoard
      在代码可读性上 frame > 使用代码实现AutoLayout > 使用xib或者StoryBoard
      在开发效率上 使用xib或者StoryBoard > 使用代码实现AutoLayout > Frame布局

AutoLayout是通常是通过定义一系列的约束(constrains)来进行定的。和Frame定位一样,它同样需要你提供位置和尺寸信息,但是和Frame不同的是,它不是让你直接提供x,y,width,height,而是通过你给的约束来推断出相应的尺寸和位置。

AutoLayout本质

一次函数:

y = ax + b

item1.attribute1 = multiplier × item2.attribute2 + constant

看一段原生的代码实现AutoLayout(NSLayoutConstraint)

VFL是这么写的:

1
2
3
4
5
6
7
NSDictionary *viewsDictionary = [NSDictionary dictionaryWithObjectsAndKeys:label1,@"label1",label2,@"label2",label3,@"label3",label4,@"label4", nil];
NSDictionary *metrics = [NSDictionary dictionaryWithObject:@(88) forKey:@"labelHeight"];
NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|[label1(labelHeight)]-[label2(labelHeight)]-[label3(labelHeight)]-[label4(labelHeight)]-(>=10)-|" options:NSLayoutFormatAlignAllLeft metrics:metrics views:viewsDictionary];
[view addConstraints:constraints];

还有一种写法:

1
2
3
4
5
6
7
8
9
10
view.translatesAutoresizingMaskIntoConstraints = NO;
NSLayoutConstraint * constraint = [NSLayoutConstraint
constraintWithItem:view
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:view
attribute:NSLayoutAttributeTop
multiplier:1.0
constant:10];
[view addConstraint:constraint];

苹果好像也发现这样写有点尴尬,于是在iOS8和iOS9中对autolayout的api进行改动:

1
2
3
4
5
6
7
8
9
//iOS8
[NSLayoutConstraint constraintWithItem:view1 attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:view2 attribute:NSLayoutAttributeCenterX multiplier:1 constant:10];
//iOS9
view.translatesAutoresizingMaskIntoConstraints = NO;
[view.widthAnchor constraintEqualToConstant:100];
[view.heightAnchor constraintEqualToConstant:100];
[view.centerXAnchor constraintEqualToAnchor:view1.centerXAnchor];
[view.centerYAnchor constraintEqualToAnchor:view1.centerYAnchor];

一个view的位置确定,正常情况下需要来自内部或者外部的4条约束才能确定其位置。所以使用原生代码实现起来太过繁琐,所以为了解决这个问题,将NSLayoutConstraint简化封装使用就非常有必要了,开源项目中有很多项目已经很成熟了,比如Masonry和purelayout等,下面简单介绍Masonry。

Masonry的介绍

Masonry是iOS在控件布局中经常使用的一个轻量级框架,Masonry让NSLayoutConstraint使用起来更为简洁。Masonry简化了NSLayoutConstraint的使用方式,让我们可以以链式的方式为我们的控件指定约束。

使用Masonry完成上面的代码

1
2
3
[view mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(@(10));
}];

当然一条约束什么都干不了,完成一个View的布局,至少有4个来自内部或者外部的约束,比如:

1
2
3
4
5
6
[view mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(@(10));
make.left.equalTo(@(10));
make.bottom.equalTo(@(-10));
make.right.equalTo(@(-10));
}];

Masonry最重要的4个方法

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
40
/**
* Finds the closest common superview between this view and another view
*
* @param view other view
*
* @return returns nil if common superview could not be found
*/
- (instancetype)mas_closestCommonSuperview:(MAS_VIEW *)view;
/**
* Creates a MASConstraintMaker with the callee view.
* Any constraints defined are added to the view or the appropriate superview once the block has finished executing
*
* @param block scope within which you can build up the constraints which you wish to apply to the view.
*
* @return Array of created MASConstraints
*/
- (NSArray *)mas_makeConstraints:(MASConstraintMakerConfigBlock)block;
/**
* Creates a MASConstraintMaker with the callee view.
* Any constraints defined are added to the view or the appropriate superview once the block has finished executing.
* If an existing constraint exists then it will be updated instead.
*
* @param block scope within which you can build up the constraints which you wish to apply to the view.
*
* @return Array of created/updated MASConstraints
*/
- (NSArray *)mas_updateConstraints:(MASConstraintMakerConfigBlock)block;
/**
* Creates a MASConstraintMaker with the callee view.
* Any constraints defined are added to the view or the appropriate superview once the block has finished executing.
* All constraints previously installed for the view will be removed.
*
* @param block scope within which you can build up the constraints which you wish to apply to the view.
*
* @return Array of created/updated MASConstraints
*/
- (NSArray *)mas_remakeConstraints:(MASConstraintMakerConfigBlock)block;

MASViewConstraint的对象链式调用

如何实现链式调用

Masonry中大量的使用了链式调用的,比如

1
view.top.left.equalTo(superView).offset(10);

上面的这种方式就是链式调用,而且像equalTo(superView)这种形式也不是Objective-C中函数调用的方式,在Objective-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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
class ClassB;
interface ClassA : NSObject
// 1. 定义一些 block 属性
property(nonatomic, readonly) ClassA *(^aaa)(BOOL enable);
property(nonatomic, readonly) ClassA *(^bbb)(NSString* str);
@property(nonatomic, readonly) ClassB *(^ccc)(NSString* str);
@implement ClassA
// 2. 实现这些 block 方法,block 返回值类型很关键,影响着下一个链式
- (ClassA *(^)(BOOL))aaa
{
return ^(BOOL enable) {
//code
if (enable) {
NSLog(@"ClassA yes");
} else {
NSLog(@"ClassA no");
}
return self;
}
}
- (ClassA *(^)(NSString *))bbb
{
return ^(NSString *str)) {
//code
NSLog(@"%@", str);
return self;
}
}
// 这里返回了ClassB的一个实例,于是后面就可以继续链式 ClassB 的 block 方法
// 见下面例子 .ccc(@"Objective-C").ddd(NO)
- (ClassB * (^)(NSString *))ccc
{
return ^(NSString *str) {
//code
NSLog(@"%@", str);
ClassB* b = [[ClassB alloc] initWithString:ccc];
return b;
}
}
//------------------------------------------
@interface ClassB : NSObject
@property(nonatomic, readonly) ClassB *(^ddd)(BOOL enable);
- (id)initWithString:(NSString *)str;
@implement ClassB
- (ClassB *(^)(BOOL))ddd
{
return ^(BOOL enable) {
//code
if (enable) {
NSLog(@"ClassB yes");
} else {
NSLog(@"ClassB no");
}
return self;
}
}
// 最后我们可以这样做
id a = [ClassA new];
a.aaa(YES).bbb(@"HelloWorld!").ccc(@"Objective-C").ddd(NO)

offset(10)这种调用方式是如何实现的呢?

1
2
3
4
5
6
- (MASConstraint * (^)(CGFloat))offset {
return ^id(CGFloat offset){
self.offset = offset;
return self;
};
}

不难发现,offset是一个getter方法的名,offset函数的返回值是一个匿名Block, 也就是offset后边的()。这个匿名闭包有一个CGFloat的参数,为了支持链式调用该匿名闭包返回一个MASConstraint的对象。

优化UITableViewCell高度计算的那些事:

https://github.com/forkingdog/UITableView-FDTemplateLayoutCell

http://blog.sunnyxx.com/2015/05/17/cell-height-calculation/