枫叶

枫叶


  • 首页

  • 分类

  • 归档

  • 标签

  • 关于

  • 搜索

Objective-C 消息转发机制

发表于 2017-02-11 | 分类于 iOS |

一图胜千言,习惯性的先来一张图以便对消息转发有个整体的把握

运行时系统库方法查询流程图

运行时系统库方法查询

对于对象无法处理的消息,如果不做转发处理的话,程序最终会调用NSObjective的doesNotRecognizeSelector:消息将程序crash掉。

Objective-C提供了两种消息转发选项

  • 快速转发:NSObject类的子类A可以通过重写NSObject类的forwardingTargetForSelector:方法,将A的实例无法识别的消息转发给目标对象B,从而实现快速转发。该技巧就像是将对象的实现代码与转发对象合并到一起。这类似于实现的多继承行为。如果你有一个定了对象 能够消化哪些消息的目标类,这个技巧可以取得很好的效果
  • 标准(完整)转发:NSObject类的子类A可以通过重写NSObject类的forwardInvocation:方法,实现标准转发。标准转发巧可以通过methodSignatureForSelector:方法获取一个methodsignature对象最终被封为NSInvocation对象传递给forwardInvocation:方法(注意如果methodSignatureForSelector:方法返回一个nil,程序会crash)从该对象能获取消息的全部内容(包含目标,方法名,和参数)。

如果你拥有了一个定义了对象能够消化哪些消息的目标类,快速转发可以取得很好的效果。如果你没有这样目标类或想要执行其他处理过程(如记录日志并‘吞下’消息),就应该使用完整转发。

写了一大推字感觉很抽象,下面来点干货

下面我要把Test实例的logName消息转发给Target实例,代码如下
Test头文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//
// Test.h
// ForwardMsg
//
// Created by 孙磊 on 2017/2/25.
// Copyright © 2017年 孙磊. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface Test : NSObject
-(void)logName;
@end

Test实现文件

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
//
//  Test.m
//  ForwardMsg
//
//  Created by 孙磊 on 2017/2/25.
//  Copyright © 2017年 孙磊. All rights reserved.
//
#import "Test.h"
#import "Target.h"
#import <objc/runtime.h>
@implementation Test{
    Target *mTarget;
}
- (instancetype)init
{
    self = [super init];
    if (self) {
        
        //创建目标对象
        mTarget = [Target new];
    }
    return self;
}
#if 0
//当一个对象无法识别消息后,会执行resolveInstanceMethod或者resolveClassMethod方法
//如果不想进行消息转发,可以在此方法中动态添加消息来做处理
//如果不重写此方法或者此方法返回NO,系统会执行forwardingTargetForSelector进行快速转发
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
    
    if(sel == @selector(logName)){
        //第四个参数详解地址  https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html
        //v代表返回类型为void
        //@代表一个对象
        //:代表一个selector
        //因为OC中的每个方法都有默认的两个参数sel 和 selector,所以一般都是v@:
        class_addMethod([self class],sel,(IMP)dynamicMethodIMP,"v@:");
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}
//万年备胎
void dynamicMethodIMP(id self, SEL _cmd)
{
    //对无法识别的消息做处理
    NSLog(@"该对象无法识别 %@ 方法------%s", NSStringFromSelector(_cmd),__func__);
}
#else 
/***************==========1、快速消息转发,快速转发只可以获取到方法签名==========*******************/
-(id)forwardingTargetForSelector:(SEL)aSelector{
    NSLog(@"%s",__func__);
    if ([mTarget respondsToSelector:aSelector]) {
        //目标对象有对应的处理方法,则就会快速消息转发,不会再执行完整消息转发了
        return mTarget;
    }
    //目标对象也没有对应的方法,此时系统会执行forwardInvocation进行完整消息转发
    return nil;
}
/***********=============2、标准(完整)消息转发,完整消息转发,可以获取方法签名,参数等详细信息==========*********/
//返回一个完整的方法签名,提供给forwardInvocation以便完整转发消息
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    
    NSMethodSignature* signature = [super methodSignatureForSelector:aSelector];
    
     if (!signature)
        signature = [mTarget methodSignatureForSelector:aSelector];
     return signature;
}
-(void)forwardInvocation:(NSInvocation *)anInvocation{
    NSLog(@"%s-----完整消息转发------",__func__);
    SEL invSEL = anInvocation.selector;
    if ([mTarget respondsToSelector:invSEL]){
        
        //利用forwardInvocation方法来重新指定消息处理对象
        [anInvocation invokeWithTarget:mTarget];
    }
    else {
        [self doesNotRecognizeSelector:invSEL];
    }
}
#endif
@end

目标文件的头文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//
// Target.h
// ForwardMsg
//
// Created by 孙磊 on 2017/2/25.
// Copyright © 2017年 孙磊. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface Target : NSObject
-(void)logName;
@end

目标文件的实现文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//
// Target.m
// ForwardMsg
//
// Created by 孙磊 on 2017/2/25.
// Copyright © 2017年 孙磊. All rights reserved.
//
#import "Target.h"
@implementation Target
-(void)logName{
NSLog(@"我是备用方法---%s",__func__);
}
@end

推荐一个国外大大利用消息转发避免后台返回NSNull(后台有时候会返回)而引起的奔溃问题,例如你需要一个字符串他却给你返回了一个“”这样一个NSNull对象。用法很简单,直接把NullSafe.m拖到项目中即可,该文件会在运行时自动加载

扩展:简单说一下NULL,nil,Nil,NSNull的用处
NULL:用于普通类型,例如NSInteger
nil:用于OC对象(除了类这个对象),给nil对象发送消息不会crash
Nil:用于Class类型对象的赋值(类是元类的实例,也是对象)
NSNull:用于OC对象的站位,一般会作为集合中的占位元素,给NSNull对象发送消息会crash的,后台给我们返回的就是NSNull对象

iOS中为什么非要用runtime才能在分类中扩展属性呢

发表于 2017-02-11 | 分类于 iOS |

想必大家都知道在分类中能扩展属性,而对于能不能扩展属性,能不能扩展成员变量是不是有点模糊,今天元宵节,我就清清嗓子说上两句吧

论点

1.分类中是能扩展属性的
2.分类是不能给一个类扩展成员变量的

先唠唠嗑

强调一下,分类不是类,它只是对类的一个扩展,没有ISA指针,我们知道我们可以通过runtime在不影响原来模块的情况下给模块扩展方法,有没有感觉这一句话好像也可以描述分类,所以你可以认为分类是实现这一功能的捷径,不然你觉得是用分类扩展方法容易还是用runtime来实现容易啊(正值年轻,说话有点冲~_~ !!)。

论据

先说说属性,属性是对成员变量的一个封装,当我们声明一个属性的时候,Xcode会给我们默认创建一个 _属性名 的成员变量,也会给我们自动创建getter和setter方法。当然我们也可以用@synthesize指定其关联的变量
例如给属性name指定其关联的变量@synthesize name = xxx;self.name其实是操作的实例变量xxx,而不是_name了。
窝草,扯远了,回归正传。。
所以我们要添加一个属相得有三样东西,setter、getter以及关联的成员变量。
在分类中Xcode不会为我们自动创建setter、getter方法,我们可以手动实现,但是如何把一个变量关联到属性上呢,直接声明一个全局变量然后不行吗,事实证明不太行,对于getter方法还好说,直接返回一个变量就行,可是setter方法却不行,因为你要找到该属性关联的变量你才能给人家赋值啊,怎么办?怎么办??这只能用runtime的对象关联来实现了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
.m文件中
// 定义关联的key
static const char *key = "name";
@implementation NSObject (Property)
- (NSString *)name
{
// 根据关联的key,获取关联的值。
return objc_getAssociatedObject(self, key);
}
- (void)setName:(NSString *)name
{
// 参数一:目标对象
// 参数二:关联的key,可以通过这个key获取
// 参数三:关联的value
// 参数四:关联的策略
objc_setAssociatedObject(self, key, name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end

看到这里你也许就会说,这TMD不是把变量给添加上去了吗?我表示默默一笑,如果你打印IVarList你就会发现并没有这个变量,只能打印出添加的属性。
其实对象关联只是关联上去了,并没有把变量添加进去,说了这么多这回你该信了吧。
原因就是分类不是类,他没有ISA指针,下面是ISA指针,可以看出他本质上是一个结构体(只是换了个马甲被称之为Class类型,怕你迷糊,再说明白一点,ISA指针就是Class类型),通过ISA指针才能找指向变量的ivars,也就是说你都不知道变量的家,你怎么去给它生猴子啊,但是奇怪了,ISA指针里没有指向属性数组的指针,没有是对的,要不然这一段的解释就废了,可是讲真,属性指针在哪啊???有知道的小伙伴请告诉我一下 >_< !!!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE;
const char *name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE;
struct objc_method_list **methodLists OBJC2_UNAVAILABLE;
struct objc_cache *cache OBJC2_UNAVAILABLE;
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
/* Use Class instead of `struct objc_class *` */
结论

分类中可以给一个对象(类也是对象)添加属性,但是不能添加成员变量,只能关联上去。
注意:如果不信,你可以自己再打印一遍吧,会发现只能打印出添加的属性,打印不出变量。

运行react native 官方例子出错的解决办法

发表于 2017-01-07 | 分类于 react-native |

学习一样新的东西官网文档和官方源码例子无疑是最好的选择,所以今天clone了官方的例子,100多兆,如果网速不好的话会等待很长时间 ~_~!!,然后直接cd到根目录执行了npm install安装了依赖,可是安装的过程中出错了(也有可能安装成功了,但是运行失败了),估计例子上的版本跟我安装的rn版本不兼容吧。
我目前的RN版本是0.39,命令行的版本是react-native-cli:2.0.1

先上官方运行效果图:
官方效果图

解决办法:

情况一:如果npm install安装失败的话

1、git checkout 0.xx-stable切换到稳定的分支,我切换的是0.41-stable版本,安装运行成功。

2、npm install

3、找到Examples,里面有四个例子,可以用Xcode打开运行

情况二:npm install 安装成功,但运行失败

1、git checkout 0.xx-stable切换到稳定的分支

2、fm -rf node_modules && npm install

3、找到Examples,里面有四个例子,可以用Xcode打开运行

看了我这篇RN你就入门了

发表于 2016-12-30 | 分类于 react-native |

前言

React认为每个组件都是一个有限状态机,状态与UI是一一对应的。我们只需管理好APP的state就能控制UI的显示,我们可以在每个component类中来通过this.state和this.setState来管理组件的state,但是如果APP交互比较多比较复杂,或者说该组件的某一状态需要和其他组件共享的话,这种方式就有点复杂了。
有没有一种能统一管理APP状态的框架呢,这时候Redux就应用而生了,它是一个用于统一管理APP 所有的state的一个的js框架,它不建议我们在component中直接操作state,而是交给redux的store中进行处理。而react-redux是在react的基础上为移动端定制的状态管理容器。

redux的设计思想

(1)Web 应用是一个状态机,视图与状态是一一对应的。
(2)所有的状态,保存在一个对象里面,由其统一管理。
(3)遵循严格的单项数据流,一个组件所需要的数据,必须由父组件传过来,而不能像flux中直接从store取。
(4)状态在组件中是‘只读’的,要交给redux处理

redux概念

有图有真相,先来一张redux数据流图,让你有一个整体的把握
redux flow

action

一般是不允许用户直接操作类的state,而是通过触发消息来执行对应的操作来产生一个新的state,用户或后台服务器可以通过store.dispatch(action)来向store发送一个消息(消息至少一个标识该消息的字段type,还可以添加其他字段用于数据传送),store会在内部根据消息的类型type去reducer中执行相应的处理,这个消息我们就叫他为Action,Action本质上是一个JavaScript对象。

实际编码中一般会把整个应用的消息类型统一放在一个文件ActionTypes.js中

1
export const ADD_TODO = 'ADD_TODO'

Action的结构如下,各个字段的key的名字可以随意命名,但是类型的key一般都是type,数据类型最好为字符串

1
2
3
4
{
type: ADD_TODO,
text: 'Build my first Redux app'
}

随着程序越来越大,你会发现一个组件中的action太多太乱了,所以我们也会把action按业务分类放在各个指定的文件中,但是又有一个问题,若果每个action的字段都有五六个,我们在如下写法岂不是太乱了

1
2
3
4
store.dispatch({
type: ADD_TODO,
text: 'Build my first Redux app'
})

于是乎我们就想起来可以将action对象封装在函数中,这个函数返回一个action对象,这个返回一个action对象的函数我们就称之为ActionCreator,如下所示

1
2
3
4
5
6
export let todo = ()=> {
return {
type: ADD_TODO,
text: 'Build my first Redux app'
}
}

我们直接store.dispatch(todo)就好了,看着是不是整洁多了啊

reducer

它是一个纯函数,要求有相同的输入(参数)就一定会有相同的输出,它会根据当前的state和action来进行逻辑处理返回一个新的state
参数一:当前的state对象
参数二:action对象
返回值:产生一个新的state对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import { VisibilityFilters } from './actions'
//初始state
const initialState = {
visibilityFilter: VisibilityFilters.SHOW_ALL,
todos: []
};
function todoApp(state = initialState, action) {
switch (action.type) {
case SET_VISIBILITY_FILTER:
return Object.assign({}, state, {
visibilityFilter: action.filter
})
default:
return state
}
}

注意:reducer函数中一定不要去修改state,而是用Object.assign()函数生成一个新的state对象,如上所示

combineReducers:随着应用变得复杂,把APP的所有状态都放在一个reducer中处理会造成reducer函数非常庞大,因此需要对 reducer 函数 进行拆分,拆分后的每一个子reducer独立负责管理 APP state 的一部分。combineReducers 辅助函数的作用是,把多个不同子reducer 函数合并成一个最终的根reducer ,最后将根 reducer 作为createStore的参数就可以创建store对象了。合并后的 reducer 可以调用各个子 reducer,并把它们的结果合并成一个 state 对象。state 对象的结构由传入的多个 reducer 的 key 决定。

最终,state 对象的结构会是这样的:

1
2
3
4
{
reducer1: ...
reducer2: ...
}

使用方法如下所示

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
import { combineReducers } from 'redux';
import Strolling from './strollingReducer';
import Foods from './foodsReducer';
import FoodsList from './foodsListReducer';
import FoodCompare from './foodCompareReducer';
import FoodInfo from './foodInfoReducer';
import Search from './searchReducer';
import User from './userReducer';
export default rootReducer = combineReducers({
Strolling,
Foods,
FoodsList,
FoodCompare,
FoodInfo,
Search,
User,
})
// export default rootReducer = combineReducers({
// Strolling:Strolling,
// Foods:Foods,
// FoodsList:FoodsList,
// FoodCompare:FoodCompare,
// FoodInfo:FoodInfo,
// Search:Search,
// User:User,
// })
// export default function rootReducer(state = {},action){
// return{
// Strolling: Strolling(state.Strolling,action),
// Foods:Foods(state.Foods,action),
// FoodsList:FoodsList(state.FoodsList,action),
// FoodCompare:FoodCompare(state.FoodCompare,action),
// FoodInfo:FoodInfo(state.FoodInfo,action),
// Search:Search(state.Search,action),
// User:User(state.User,action)
// }
// }
//以上三种方式是等价的,key可以设置也可以省略

注意:我们不一定非要用combineReducers来组合子reducer,我们可以自定义类似功能的方法来组合,state的结构完全由我们决定。

store

一个应用只有一个store,store 就是用来维持应用所有的 state 树 的一个对象。 改变 store 内 state 的惟一途径是对它 dispatch 一个 action,它有三个函数

  • getState()
    返回应用当前的 state 树。
  • dispatch(action)
    分发 action。这是触发 state 变化的惟一途径。
    会使用当前 getState() 的结果和传入的 action 以同步方式的调用 store 的 reduce 函数。返回值会被作为下一个 state。从现在开始,这就成为了 getState() 的返回值,同时变化监听器(change listener)会被触发。
  • subscribe(listener)
    当state树发生变化的时候store会调用subscribe函数,我们可以传一个我们订制的函数作为参数来进行处理
    参数:一个函数
    返回值:返回一个解绑定函数

    1
    2
    3
    4
    //添加监听
    let unsubscribe = store.subscribe(handleChange)
    //解除监听
    unsubscribe()
  • replaceReducer(nextReducer)
    替换 store 当前用来计算 state 的 reducer。
    这是一个高级 API。只有在你需要实现代码分隔,而且需要立即加载一些 reducer 的时候才可能会用到它。在实现 Redux 热加载机制的时候也可能会用到。

react-redux基础

前言已经提到过react-redux的由来,这里在啰嗦一下,react-redux是redux作者专门为react native订制的,这样使用起来更方便,我们只需在我们的组件中通过属性props获取dispatch方法,就可以直接向store发送一个action,而不需要再获取store对象,通过store对象的dispatch方法发送。
react-redux有两宝,provider和connect,下面详细介绍一下。

Provider:

有一个store属性,我们要将应用的根标签放到Provider标签中,这样应用的所有标签就可以通过context来获取store对象了,但是我们一般不会通过此法来获取store对象,Provider是为了给connect函数使用的,这样才能通过connect函数的参数获取到store的state和dispatch了。

connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])

connect是一个高阶函数,connect()本身会返回一个函数变量(假如名字为func),给这个函数变量传递一个参数func(MainContainer)会生成一个MainContainer容器组件,形如下面的写法:

1
2
3
4
5
6
export default connect((state) => {
const { Main } = state;
return {
Main
}
})(MainContainer);

参数一:[mapStateToProps(state, [ownProps]): stateProps] (Function)

如果定义该参数,组件将会监听 Redux store 的变化。任何时候,只要 Redux store 发生改变,mapStateToProps 函数就会被调用。该回调函数必须返回一个纯对象,这个对象会与组件的 props 合并。如果你省略了这个参数,你的组件将不会监听 Redux store。如果指定了该回调函数中的第二个参数 ownProps,则该参数的值为传递到组件的 props,而且只要组件接收到新的 props,mapStateToProps 也会被调用(例如,当 props 接收到来自父组件一个小小的改动,那么你所使用的 ownProps 参数,mapStateToProps 都会被重新计算)。

参数二:[mapDispatchToProps(dispatch, [ownProps]): dispatchProps] (Object or Function):

如果传递的是一个对象,那么每个定义在该对象的函数都将被当作 Redux action creator,而且这个对象会与 Redux store 绑定在一起,其中所定义的方法名将作为属性名,合并到组件的 props 中。如果传递的是一个函数,该函数将接收一个 dispatch 函数,然后由你来决定如何返回一个对象,这个对象通过 dispatch 函数与 action creator 以某种方式绑定在一起(提示:你也许会用到 Redux 的辅助函数 bindActionCreators())。如果你省略这个 mapDispatchToProps 参数,默认情况下,dispatch 会注入到你的组件 props 中。如果指定了该回调函数中第二个参数 ownProps,该参数的值为传递到组件的 props,而且只要组件接收到新 props,mapDispatchToProps 也会被调用。

参数三:[mergeProps(stateProps, dispatchProps, ownProps): props] (Function)

如果指定了这个参数,mapStateToProps() 与 mapDispatchToProps() 的执行结果和组件自身的 props 将传入到这个回调函数中。该回调函数返回的对象将作为 props 传递到被包装的组件中。你也许可以用这个回调函数,根据组件的 props 来筛选部分的 state 数据,或者把 props 中的某个特定变量与 action creator 绑定在一起。如果你省略这个参数,默认情况下返回 Object.assign({}, ownProps, stateProps, dispatchProps) 的结果。
[options] (Object) 如果指定这个参数,可以定制 connector 的行为。

参数四:[options] (Object) 如果指定这个参数,可以定制 connector 的行为。

[pure = true] (Boolean): 如果为 true,connector 将执行 shouldComponentUpdate 并且浅对比 mergeProps 的结果,避免不必要的更新,前提是当前组件是一个“纯”组件,它不依赖于任何的输入或 state 而只依赖于 props 和 Redux store 的 state。默认值为 true。
[withRef = false] (Boolean): 如果为 true,connector 会保存一个对被包装组件实例的引用,该引用通过 getWrappedInstance() 方法获得。默认值为 false。

redux-redux使用

上面说了provider和connect方法,下面是实用讲解

创建store对象的js文件

下面的代码里包括应用中间件redux-thunk,和创建store对象两步,这里有更多关于中间件的详情

1
2
3
4
5
6
7
8
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from '../reducers/rootRudcer';
//使用thunk中间件
let createStoreWithMiddleware = applyMiddleware(thunk)(createStore);
//创建store对象,一个APP只有一个store对象
let store = createStoreWithMiddleware(rootReducer);
export default store;

程序的入口文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import React from 'react';
import { Provider } from 'react-redux';
import store from './store/store';
import App from './containers/app';
export default class Root extends React.Component {
render() {
return (
//将APP的根视图组件包含在provider标签中
<Provider store = {store} >
<App />
</Provider>
)
}
}

在容器组件中,将redux和容器组件关联起来,这里是redux与组件关联的地方,大多数童鞋使用redux最迷惑的地方估计就在这一块了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import React from 'react';
import {connect} from 'react-redux';
import Brand from '../Components/Brand';
//BrandContainer容器组件
class BrandContainer extends React.Component {
render() {
return (
//把容器组件的属性传递给UI组件
<Brand {...this.props} />
)
}
}
export default connect((state) => {
const { BrandReducer } = state;
return {
BrandReducer
}
})(BrandContainer);

这样UI组件Brand中就可以通过属性获取dispatch方法以及处理后的最新state了

1
const {dispatch, BrandReducer} = this.props;

下面来解释一下上面的代码

将当前的BrandContainer组件关联起来,上面介绍了store中的state对象的结构会是这样的:

{

reducer1: …

reducer2: …

}

所以可以通过解构的方式,获取对应模块的state,如下面的const { BrandReducer } = state;

下面这一块代码的作用就是将store中state传递给关联的容器组件中,当store中的state发生变化的时候,connect的第一参数mapStateToProps回调函数就会被调用,并且将该回调函数的返回值映射成其关联组件的一个属性,这样容器组件的属性就会发生变化,而UI组件又通过{…this.props}将容器组件的属性传递给了UI组件,所以UI组件的属性也会发生变化,我们知道属性的变化会导致UI组件重新render。好了,我们就能知道为什么我们在UI组件中dispatch一个action后UI组件能更新了,因为UI组件的属性发生变化导致RN重绘了UI。

react native 组件的生命周期

弄明白了这个图我认为你就能基本掌握RN了

react-native lifecircle
注意:上图的最右边componentWillMount改成componentWillUnmount

项目的推荐目录

这种结构适合业务逻辑不太复杂的中小型项目,其优点是逻辑模块清晰,缺点是文件目录跨度较大,对于大型项目建议按项目的功能模块来划分。
项目的推荐目录

热更新

暂时不说了,苹果粑粑这两天不高兴了,凡是热更新的APP不能上架,已上线的APP也应该收到了一份批评邮件了。
你尽管hot patch吧

相关文章

React 实践心得:react-redux 之 connect 方法详解
Redux 入门教程(一):基本用法
redux中文文档

注:部分图片来源于互联网

原文链接,转载请注明此链接

iOS小知识

发表于 2016-09-02 | 分类于 iOS |

原文https://slpowercoder.github.io/

  1. 去掉按钮的高亮黑

    1
    [funcImgBtn setAdjustsImageWhenHighlighted:NO];
  2. 设置视图属性防止背景图片变形
    关于UIViewContentMode的详解
    http://blog.csdn.net/iunion/article/details/7494511

    1
    2
    3
    4
    5
    //对于UIButton需要设置button中的imageView的contentMode属性
    button.imageView.contentMode = UIViewContentModeScaleAspectFill;
    //
    视图.contentMode = UIViewContentModeScaleAspectFill;
    视图.clipsToBounds = YES;
  3. 当一个页面有多个scrollView(或继承自scrollVIew)的时候,点击状态栏不会回到顶部,可以将非当前显示页的scrollVIew. scrollToTop设置为NO。

  4. 去掉在UITableViewStylePlain 样式的时候多余的默认cell

    1
    _tableView.tableFooterView = [[UIView alloc]init];
  5. 去掉在UITableViewStyleGrouped 样式的时候默认的组脚高度

    1
    2
    3
    -(CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section{
    return 0.001; //越小越好,不能设置为0
    }
  6. 自定义导航栏的返回按钮而造成返回手势的失效的解决办法,对于最顶层的VC我们需要设置enabled = NO,因为最外面一层是不需要该手势的

    1
    2
    self.navigationController.interactivePopGestureRecognizer.delegate = self;
    self.navigationController.interactivePopGestureRecognizer.enabled = YES;
  7. 根据十六进制色值返回一个UIColor对象的宏

    1
    #define USERCOLOR(string) [UIColor colorWithRed:((float)((string & 0xFF0000) >> 16))/255.0 green:((float)((string & 0xFF00) >> 8))/255.0 blue:((float)(string & 0xFF))/255.0 alpha:1.0]
  8. 控制屏幕是否锁屏

    1
    2
    //值为yes的时候可以防止屏幕黑屏,注意在程序结束点时候要设置为no
    [[UIApplication sharedApplication] setIdleTimerDisabled:YES];
  9. 防止自定义导航栏返回按钮时,返回手势失效

    1
    self.navigationController.interactivePopGestureRecognizer.enabled = YES;
  10. 打印函数,可以打印所在的函数,行数,以及你要打印的值

    1
    #define DLog(fmt, ...) NSLog((@"%s [Line %d] " fmt), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__);
  11. UISwitch的大小直接设置frame是没有用的,可以通过CGAffineTransformMakeScale缩放来设置

  12. load和initialize

    1
    2
    3
    4
    //程序在加载类文件时候会调用load,无论实现该方法的类有没有被引用(只要程序运行的时候就会加载该类文件)
    +(void)load
    //在类被初始化之前会调用一次
    +(void)initialize
  13. iPad使用UIAlertController会crash
    http://stackoverflow.com/questions/31577140/uialertcontroller-is-crashed-ipad

Mac上 Hexo安装与配置

发表于 2016-09-02 | 分类于 hexo |

安装

1. 安装node.js传送

下载node的pkg包,点击直接安装,安装的过程下一步下一步就行了,新版的node.js包含有npm(npm用来安装hexo)

2. 安装hexo

1
$ sudo npm install -g hexo-cli

注意:如果没有用sudo来执行命令可能会出现错误

3. 建立本地站点

现在本地创建一个文件夹,作为本地站点的根目录,例如建立一个HexoBlog文件夹,cd到HexoBlog的上一级目录执行以下命令

1.初始化一个本地站点

1
hexo init HexoBlog

2.进入站点根目录然后进行安装本地站点

1
cd HexoBlog
1
npm install

3.生成静态页面

1
hexo g

4.启动本地站点服务器查看效果,执行本命令之后会提示出一个URL,将URL放入浏览器查看效果,按Ctrl+c关闭本地站点服务器

1
hexo server

配置

1. 基本配置

1.在github上创建一个仓库,仓库的名字必须为”github用户名.github.io”

2.如果你使用过github,我猜你已经配置好了SSH key,这里我就不多说了,不过即使没有配置SSH key也没关系,只是以后每次提交的时候会提示要求输入密码

3.修改站点_config.xml文件,如下:

1
2
3
4
5
6
# Deployment
# Docs: https://hexo.io/docs/deployment.html
deploy:
type: git #部署类型, 本文使用Git,发现使用github+SSH的方式会提示找不到github
repository: https://github.com/SLPowerCoder/SLPowerCoder.github.io #部署的仓库url,发现使用github+SSH的方式会提示找不到github
branch: master #部署分支,一般使用master主分支

4.这些基本的配置完成之后就可以执行命令部署到github上了

执行下面的命令,安装git部署插件,不然执行了 hexo deploy之后会没有反应,也没有任何提示部署失败,其实是失败的

1
$ npm install hexo-deployer-git --save

5.执行下面命令,用于生成静态文件并部署到远程站点,你也可以分两步写

1
$ hexo g -d

上述然后在浏览器中输入 github用户名.github.io就可浏览了

2. 博客主题配置

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
# Hexo Configuration
## Docs: https://hexo.io/docs/configuration.html
## Source: https://github.com/hexojs/hexo/
# Site
title: 枫叶
subtitle: 枫叶
description: 坐看云起时
author: 枫叶
language: zh-Hans
timezone:
email: sunlei_1030@126.com # 邮箱
# URL
## If your site is put in a subdirectory, set url as 'http://yoursite.com/child' and root as '/child/'
url: https://slpowercoder.github.io
root: /
permalink: :year/:month/:day/:title/
permalink_defaults:
# Directory
source_dir: source
public_dir: public
tag_dir: tags
archive_dir: archives
category_dir: categories
code_dir: downloads/code
i18n_dir: :lang
skip_render:
# Writing
new_post_name: :title.md # File name of new posts
default_layout: post
titlecase: false # Transform title into titlecase
external_link: true # Open external links in new tab
filename_case: 0
render_drafts: false
post_asset_folder: false
relative_link: false
future: true
highlight:
enable: true
line_number: true
auto_detect: false
tab_replace:
# Category & Tag
default_category: uncategorized
category_map:
tag_map:
# Date / Time format
## Hexo uses Moment.js to parse and display date
## You can customize the date format as defined in
## http://momentjs.com/docs/#/displaying/format/
date_format: YYYY-MM-DD
time_format: HH:mm:ss
# Pagination
## Set per_page to 0 to disable pagination
per_page: 10
pagination_dir: page
# Disqus disqus评论, 与多说类似, 国内一般使用多说
# disqus_shortname:
duoshuo_shortname: fengye1030 # 这里添加多说评论
# Extensions
## Plugins: https://hexo.io/plugins/
## Themes: https://hexo.io/themes/
theme: yelee #默认是landscape 还有yelee,yilia等等
# Deployment
## Docs: https://hexo.io/docs/deployment.html
deploy:
type: git #部署类型, 本文使用Git,发现使用github+SSH的方式会提示找不到github
repository: https://github.com/SLPowerCoder/SLPowerCoder.github.io #部署的仓库url,采用https的,发现使用github+SSH的方式会提示找不到github
branch: master #部署分支,一般使用master主分支

hexo的使用(下面的命令如果报错,请用sudo权限执行)

1.创建文章(也可以把创建好的md文件直接放到根目录source/_posts目录中)

1
$ hexo new "My New Post"

2.清楚缓存的静态页面

1
$ hexo clean

3.生成静态页面

1
$ hexo generate

4.运行本地服务器查看效果

1
$ hexo server

5.部署到远程站点

1
$ hexo deploy

参考文献

hexo官网

12
枫叶

枫叶

坐看云起时

16 日志
3 分类
20 标签
CSDN GitHub 简书 Email
© 2019 枫叶
由 Hexo 强力驱动
主题 - NexT.Muse