http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/5b99cc6b/ios/sdk/WeexSDK/Sources/Component/RecycleList/WXJSASTParser.mm
----------------------------------------------------------------------
diff --git a/ios/sdk/WeexSDK/Sources/Component/RecycleList/WXJSASTParser.mm 
b/ios/sdk/WeexSDK/Sources/Component/RecycleList/WXJSASTParser.mm
new file mode 100644
index 0000000..7e7a565
--- /dev/null
+++ b/ios/sdk/WeexSDK/Sources/Component/RecycleList/WXJSASTParser.mm
@@ -0,0 +1,919 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#import "WXJSASTParser.h"
+#import "WXLog.h"
+#include <vector>
+#include <sstream>
+
+
+typedef enum : NSUInteger {
+    WXJSTokenTypeBooleanLiteral = 1,
+    WXJSTokenTypeEOF,
+    WXJSTokenTypeIdentifier,
+    WXJSTokenTypeKeyword,
+    WXJSTokenTypeNullLiteral,
+    WXJSTokenTypeNumericLiteral,
+    WXJSTokenTypePunctuator,
+    WXJSTokenTypeStringLiteral,
+    WXJSTokenTypeRegularExpression,
+    WXJSTokenTypeTemplate
+} WXJSTokenType;
+
+struct WXJSToken {
+    WXJSTokenType type;
+    std::string value;
+    double doubleValue;
+    bool octal;
+    int start;
+    int end;
+};
+
+struct WXJSMarker {
+    int index;
+};
+
+static bool isWhiteSpace(int ch) {
+    return (ch == 32) ||  // space
+    (ch == 9) ||      // tab
+    (ch == 0xB) ||
+    (ch == 0xC) ||
+    (ch == 0xA0);
+}
+
+static bool isIdentifierStart(int ch)
+{
+    return (ch == 36) || (ch == 95) ||  // $ or _
+        (ch >= 65 && ch <= 90) ||         // A..Z
+        (ch >= 97 && ch <= 122) ||        // a..z
+        (ch == 92);                      // \ (backslash)
+}
+
+static bool isIdentifierPart(int ch) {
+    return (ch == 36) || (ch == 95) ||  // $ or _
+    (ch >= 65 && ch <= 90) ||         // A..Z
+    (ch >= 97 && ch <= 122) ||        // a..z
+    (ch >= 48 && ch <= 57) ||         // 0..9
+    (ch == 92);                      // \ (backslash)
+}
+
+static bool isDecimalDigit(int ch) {
+    return (ch >= 48 && ch <= 57);   // 0..9
+}
+
+static bool isHexDigit(int ch) {
+    return std::string("0123456789abcdefABCDEF").find(ch) != std::string::npos;
+}
+
+static bool isOctalDigit(int ch) {
+    return std::string("01234567").find(ch) != std::string::npos;
+}
+
+static int binaryPrecedence(WXJSToken *token)
+{
+    int prec = 0;
+    
+    if (token->type != WXJSTokenTypePunctuator) {
+        return prec;
+    }
+    
+    const std::string &value = token->value;
+    
+    if (value == "||") {
+        prec = 1;
+    }
+    
+    else if (value == "&&") {
+        prec = 2;
+    }
+    
+    else if (value == "|") {
+        prec = 3;
+    }
+    
+    else if (value == "^") {
+        prec = 4;
+    }
+    
+    else if (value == "&") {
+        prec = 5;
+    }
+    
+    else if (value == "==" || value == "!=" || value == "===" || value == 
"!==") {
+        prec = 6;
+    }
+    
+    else if (value == "<" || value == ">" || value == "<=" || value == ">=") {
+        prec = 7;
+    }
+    
+    else if (value == "<<" || value == ">>" || value == ">>>") {
+        prec = 8;
+    }
+    
+    else if (value == "+" || value == "-") {
+        prec = 9;
+    }
+    
+    else if (value == "*" || value == "/" || value == "%") {
+        prec = 11;
+    }
+    
+    return prec;
+}
+
+@implementation WXJSASTParser
+{
+    std::string _source;
+    
+    int _length;
+    int _index;
+    
+    WXJSToken *_lookahead;
+    
+    std::vector<WXJSToken *> _tokens;
+}
+
++ (instancetype)parserWithScript:(NSString *)script
+{
+    if (!script) {
+        WXLogError(@"can not init parser with nil script");
+        return nil;
+    }
+    return [[self alloc] initWithScript:script];
+}
+
+- (instancetype)initWithScript:(NSString *)script
+{
+    if (self = [super init]) {
+        _source = std::string([script UTF8String]);;
+        _length = (int)(_source.length());
+        _index = 0;
+        
+        _lookahead = NULL;
+        
+        [self nextToken];
+    }
+    
+    return self;
+}
+
+- (WXJSToken *)nextToken
+{
+    WXJSToken *token = _lookahead;
+    
+    WXJSToken *next = [self lex];
+    
+    _lookahead = next;
+    
+    if (next->type != WXJSTokenTypeEOF) {
+        _tokens.push_back(token);
+    }
+    
+    return token;
+}
+
+- (WXJSToken *)lex
+{
+    [self skipSpaces];
+    
+    if (_index >= _length) {
+        WXJSToken *token = new WXJSToken();
+        token->type = WXJSTokenTypeEOF;
+        token->value = "";
+        token->start = _index;
+        token->end = _index;
+        
+        return token;
+    }
+    
+    int ch = _source[_index];
+    
+    if (isIdentifierStart(ch)) {
+        return [self scanIdentifier];
+    }
+    
+    // ( or ) or ;
+    if (ch == 40 || ch == 41 || ch == 59) {
+        return [self scanPunctuator];
+    }
+    
+    // string starting with ' or " .
+    if (ch == 39 || ch == 34 ) {
+        return [self scanStringLiteral];
+    }
+    
+    // Dot (.) U+002E can also start a floating-point number, hence the need
+    // to check the next
+    if (ch == 0x2E) {
+        if (isDecimalDigit(_source[_index + 1])) {
+            return [self scanNumericLiteral];
+        }
+        return [self scanPunctuator];
+    }
+    
+    if (isDecimalDigit(ch)) {
+        return [self scanNumericLiteral];
+    }
+    
+    // Possible `identifier
+    if (ch >= 0xD800 && ch < 0xDFFF) {
+        if (isIdentifierStart(ch)) {
+            return [self scanIdentifier];
+        }
+    }
+    
+    return [self scanPunctuator];
+}
+
+- (WXJSToken *)scanIdentifier
+{
+    int start = _index;
+    // Backslash (U+005C) starts an escaped
+    std::string identifier = [self getIdentifier];
+    WXJSTokenType type;
+    if (identifier == "null") {
+        type = WXJSTokenTypeNullLiteral;
+    } else if (identifier == "true" || identifier == "false") {
+        type = WXJSTokenTypeBooleanLiteral;
+    } else {
+        type = WXJSTokenTypeIdentifier;
+    }
+    
+    WXJSToken *token = new WXJSToken();
+    token->type = type;
+    token->value = identifier;
+    token->start = start;
+    token->end = _index;
+    
+    return token;
+}
+
+- (std::string)getIdentifier
+{
+    int start = _index++;
+    while (_index < _length) {
+        int ch = _source[_index];
+        if (isIdentifierPart(ch)) {
+            ++_index;
+        } else {
+            break;
+        }
+    }
+    
+    return _source.substr(start, _index - start);
+}
+
+- (WXJSToken *)scanPunctuator
+{
+    int start = _index;
+    
+    // Check for most common single-character punctuators.
+    int ch = _source[_index];
+    std::string str = "";
+    switch (ch) {
+        // single-character punctuators
+        case 46:   // .
+        case 40:   // (
+        case 41:   // )
+        case 59:   // ;
+        case 44:   // ,
+        case 123:  // {
+        case 125:  // }
+        case 91:   // [
+        case 93:   // ]
+        case 58:   // :
+        case 63:   // ?
+        case 126:  // ~
+            ++_index;
+            str = std::string(1, ch);
+            break;
+        default:
+            str =  _source.substr(_index, 3);
+            if (str == "===" || str == "!==" || str == "**=") {
+                _index += 3;
+            } else {
+                str = str.substr(0, 2);
+                if (str == "&&" || str == "||" || str == "==" || str == "!=" ||
+                    str == "+=" || str == "-=" || str == "*=" || str == "/=" ||
+                    str == "++" || str == "--" || str == "&=" || str == "|=" ||
+                    str == "^=" || str == "%=" || str == "<=" || str == ">=" ||
+                    str == "=>" || str == "**") {
+                    _index += 2;
+                }  else {
+                    str = _source[_index];
+                    // parse 2-character punctuators first
+                    if (std::string("<>=!+-*%&|^/").find(ch) != 
std::string::npos) {
+                        ++_index;
+                    }
+                }
+            }
+    }
+    
+    if (_index == start) {
+        [self _throwUnexpectedTokenError];
+    }
+    
+    WXJSToken *token = new WXJSToken();
+    token->type = WXJSTokenTypePunctuator;
+    token->value = str;
+    token->start = start;
+    token->end = _index;
+    
+    return token;
+}
+
+- (WXJSToken *)scanStringLiteral
+{
+    int start = _index;
+    int quote = _source[start];
+    bool octal = false;
+    
+    ++_index;
+    std::string str = "";
+    
+    while (_index < _length) {
+        int ch = _source[_index++];
+        
+        if (ch == quote) {
+            quote = -1;
+            break;
+        } else if (ch == 92) { // \ (backslash)
+            ch = _source[_index++];
+            switch (ch) {
+                case 'u': {
+                    if (_source[_index] == '{') { // {
+                        ++_index;
+                        str += [self scanUnicodeCodePointEscape];
+                    } else {
+                        int unescaped = [self scanHexEscape:ch];
+                        if (unescaped == -1) {
+                            [self _throwError:@"Invalid hexadecimal escape"];
+                        } else {
+                            str += unescaped;
+                        }
+                    }
+                    break;
+                }
+                case 'x': {
+                    int unescaped = [self scanHexEscape:ch];
+                    if (unescaped == -1) {
+                        [self _throwError:@"Invalid hexadecimal escape"];
+                    } else {
+                        str += unescaped;
+                    }
+                    
+                    break;
+                }
+                case 'n': {
+                    str += '\n';
+                    break;
+                }
+                case 'r': {
+                    str += '\r';
+                    break;
+                }
+                case 't': {
+                    str += '\t';
+                    break;
+                }
+                case 'b': {
+                    str += '\b';
+                    break;
+                }
+                case 'f': {
+                    str += '\f';
+                    break;
+                }
+                case 'v': {
+                    str += '\x0B';
+                    break;
+                }
+                case '8':
+                case '9': {
+                    str += ch;
+                    [self _throwUnexpectedTokenError];
+                    break;
+                }
+                    
+                default:
+                    if (isOctalDigit(ch)) {
+                        int code = ch - '0';
+                        
+                        // \0 is not octal escape sequence
+                        if (code != 0) {
+                            octal = true;
+                        }
+                        
+                        if (_index < _length && isOctalDigit(_source[_index])) 
{
+                            octal = true;
+                            code = code * 8 + _source[_index++] - '0';
+                            
+                            // 3 digits are only allowed when string starts
+                            // with 0, 1, 2, 3
+                            if (std::string(1, '0123').find(ch) != 
std::string::npos &&
+                                _index < _length &&
+                                isOctalDigit(_source[_index])) {
+                                code = code * 8 + _source[_index++] - '0';
+                            }
+                        }
+                        str += std::string(1, code);
+                    } else {
+                        str += ch;
+                    }
+                    break;
+            }
+        } else {
+            str += ch;
+        }
+    }
+    
+    if (quote != -1) {
+        _index = start;
+        [self _throwUnexpectedTokenError];
+    }
+    
+    WXJSToken *token = new WXJSToken();
+    token->type = WXJSTokenTypeStringLiteral;
+    token->value = str;
+    token->octal = octal;
+    token->start = start;
+    token->end = _index;
+    
+    return token;
+}
+
+- (int)scanUnicodeCodePointEscape {
+    int ch = _source[_index];
+    int code = 0;
+    
+    if (ch == '}') { // '}'
+        [self _throwError:@"At least one hex digit is required in Unicode"];
+    }
+    
+    while (_index < _length) {
+        ch = _source[_index++];
+        if (!isHexDigit(ch)) {
+            break;
+        }
+        code = (int)(code * 16 + 
std::string("0123456789abcdef").find(tolower(ch)));
+    }
+    
+    if (code > 0x10FFFF || ch != '}') {
+        [self _throwUnexpectedTokenError];
+    }
+    
+    return code;
+}
+
+- (WXJSToken *)scanNumericLiteral
+{
+    int start = _index;
+    int ch = _source[start];
+    std::string num;
+    if (ch != '.') {
+        num = _source[_index++];
+        ch = _source[_index];
+        
+        if (num == "0") {
+            if (ch == 'x' || ch == 'X') {
+                ++_index;
+                return [self scanHexLiteral:start];
+            }
+            if (ch == 'b' || ch == 'B') {
+                ++_index;
+                return [self scanBinaryLiteral:start];
+            }
+            if (ch == 'o' || ch == 'O') {
+                return [self scanOctalLiteral:ch start:start];
+            }
+        }
+        
+        while (isDecimalDigit(_source[_index])) {
+            num += _source[_index++];
+        }
+        ch = _source[_index];
+    }
+    
+    if (ch == '.') {
+        num += _source[_index++];
+        while (isDecimalDigit(_source[_index])) {
+            num += _source[_index++];
+        }
+        ch = _source[_index];
+    }
+    
+    if (ch == 'e' || ch == 'E') {
+        num += _source[_index++];
+        
+        ch = _source[_index];
+        if (ch == '+' || ch == '-') {
+            num += _source[_index++];
+        }
+        if (isDecimalDigit(_source[_index])) {
+            while (isDecimalDigit(_source[_index])) {
+                num += _source[_index++];
+            }
+        } else {
+            [self _throwUnexpectedTokenError];
+        }
+    }
+    
+    if (isIdentifierStart(_source[_index])) {
+        [self _throwUnexpectedTokenError];
+    }
+    
+    WXJSToken *token = new WXJSToken();
+    token->type = WXJSTokenTypeNumericLiteral;
+    token->doubleValue = atof(num.c_str());
+    token->start = start;
+    token->end = _index;
+    
+    return token;
+}
+
+- (WXJSToken *)scanHexLiteral:(int)start
+{
+    std::string num = "";
+    while (_index < _length) {
+        if (!isHexDigit(_source[_index])) {
+            break;
+        }
+        num += _source[_index++];
+    }
+    
+    if (num.length() == 0) {
+        [self _throwUnexpectedTokenError];
+    }
+    
+    if (isIdentifierStart(_source[_index])) {
+        [self _throwUnexpectedTokenError];
+    }
+    
+    WXJSToken *token = new WXJSToken();
+    token->type = WXJSTokenTypeNumericLiteral;
+    token->doubleValue = static_cast<double>(std::stoi(num, nullptr, 16));
+    token->start = start;
+    token->end = _index;
+    
+    return token;
+}
+
+- (WXJSToken *)scanBinaryLiteral: (int)start
+{
+    std::string num = "";
+    int ch;
+    
+    while (_index < _length) {
+        ch = _source[_index];
+        if (ch != '0' && ch != '1') {
+            break;
+        }
+        num += _source[_index++];
+    }
+    
+    if (num.length() == 0) {
+        // only 0b or 0B
+        [self _throwUnexpectedTokenError];
+    }
+    
+    if (_index < _length) {
+        ch = _source[_index];
+        /* istanbul ignore else */
+        if (isIdentifierStart(ch) || isDecimalDigit(ch)) {
+            [self _throwUnexpectedTokenError];
+        }
+    }
+    
+    WXJSToken *token = new WXJSToken();
+    token->type = WXJSTokenTypeNumericLiteral;
+    token->doubleValue = static_cast<double>(std::stoi(num, nullptr, 2));
+    token->start = start;
+    token->end = _index;
+    
+    return token;
+}
+- (WXJSToken *)scanOctalLiteral:(int)prefix start:(int)start
+{
+    std::string num = "";
+    bool octal = false;
+    
+    if (isOctalDigit(prefix)) {
+        octal = true;
+        num = '0' + _source[_index++];
+    } else {
+        ++_index;
+    }
+    
+    while (_index < _length) {
+        if (!isOctalDigit(_source[_index])) {
+            break;
+        }
+        num += _source[_index++];
+    }
+    
+    if (!octal && num.length() == 0) {
+        // only 0o or 0O
+        [self _throwUnexpectedTokenError];
+    }
+    
+    if (isIdentifierStart(_source[_index]) || isDecimalDigit(_source[_index])) 
{
+        [self _throwUnexpectedTokenError];
+    }
+    
+    WXJSToken *token = new WXJSToken();
+    token->type = WXJSTokenTypeNumericLiteral;
+    token->doubleValue = static_cast<double>(std::stoi(num, nullptr, 8));
+    token->octal = octal;
+    token->start = start;
+    token->end = _index;
+    
+    return token;
+}
+
+- (int)scanHexEscape:(int)prefix
+{
+    int i, len, ch, code = 0;
+    
+    len = (prefix == 'u') ? 4 : 2;
+    for (i = 0; i < len; ++i) {
+        if (_index < _length && isHexDigit(_source[_index])) {
+            ch = _source[_index++];
+            code = (int)(code * 16 + 
std::string("0123456789abcdef").find(tolower(ch)));
+        } else {
+            return -1;
+        }
+    }
+    return code;
+}
+
+- (WXJSExpression *)parseExpression
+{
+    return [self parseConditionalExpression];
+}
+
+- (WXJSExpression *)parseConditionalExpression
+{
+    WXJSExpression *expr = [self parseBinaryExpression];
+    
+    if ([self match:"?"]) {
+        [self nextToken];
+        WXJSExpression *consequent = [self parseConditionalExpression];
+        [self expect:":"];
+        WXJSExpression *alternate = [self parseConditionalExpression];
+        
+        WXJSConditionalExpression *conditionalExpr = new 
WXJSConditionalExpression();
+        conditionalExpr->test = expr;
+        conditionalExpr->consequent = consequent;
+        conditionalExpr->alternate = alternate;
+        
+        return conditionalExpr;
+    }
+    
+    return expr;
+}
+
+- (WXJSExpression *)parseBinaryExpression
+{
+    WXJSExpression *expr = [self parseUnaryExpression];
+    
+    std::vector<int> precedenceStack;
+    std::vector<WXJSToken *> operatorStack;
+    std::vector<WXJSExpression *> expressionStack;
+    
+    WXJSToken *token = _lookahead;
+    int prec = binaryPrecedence(token);
+    if (prec == 0) {
+        return expr;
+    }
+    
+    [self nextToken];
+    
+    expressionStack.push_back(expr);
+    precedenceStack.push_back(prec);
+    operatorStack.push_back(token);
+    expressionStack.push_back([self parseUnaryExpression]);
+    
+    WXJSExpression *right;
+    std::string operator_;
+    WXJSExpression *left;
+    while ((prec = binaryPrecedence(_lookahead)) > 0) {
+        while ((expressionStack.size() > 1) && (prec <= 
precedenceStack[precedenceStack.size() - 1])) {
+            right = expressionStack[expressionStack.size() - 1];
+            expressionStack.pop_back();
+            operator_ = operatorStack[operatorStack.size() - 1]->value;
+            operatorStack.pop_back(); precedenceStack.pop_back();
+            left = expressionStack[expressionStack.size() - 1]; 
expressionStack.pop_back();
+            expressionStack.push_back([self createBinaryExpression:operator_ 
left:left right:right]);
+        }
+        
+        // Shift.
+        token = [self nextToken];
+        precedenceStack.push_back(prec);
+        operatorStack.push_back(token);
+        expressionStack.push_back([self parseUnaryExpression]);
+    }
+    
+    // Final reduce to clean-up the stack.
+    int i = (int)(expressionStack.size() - 1);
+    expr = expressionStack[i];
+    while (i > 0) {
+        expr = [self createBinaryExpression:operatorStack[i - 1]->value 
left:expressionStack[i - 1] right:expr];
+        i--;
+    }
+    
+    return expr;
+}
+
+- (WXJSExpression *)parseUnaryExpression
+{
+    WXJSToken *token;
+    
+    if ([self match:"++"] || [self match:"--"] || [self match:"+"] || [self 
match:"-"] || [self match:"~"] || [self match:"!"]) {
+        token = [self nextToken];
+        WXJSExpression *argument = [self parseUnaryExpression];
+        WXJSUnaryExpression *expr = new WXJSUnaryExpression();
+        expr->operator_ = token->value;
+        expr->prefix = ([self match:"++"] || [self match:"--"]) ? true : false;
+        expr->argument = argument;
+        return expr;
+    }
+    
+    return [self parseMemberExpression];
+}
+
+- (WXJSExpression *)parsePrimaryExpression
+{
+    int type = _lookahead->type;
+    
+    if (type == WXJSTokenTypePunctuator) {
+        if (_lookahead->value == "[") {
+            return [self parseArrayExpression];
+        } else if (_lookahead->value == "(") {
+            return [self parseGroupExpression];
+        }
+    }
+    if (type == WXJSTokenTypeIdentifier) {
+        WXJSIdentifier *identifier = new WXJSIdentifier();
+        identifier->name = [self nextToken]->value;
+        return identifier;
+    }
+    
+    if (type == WXJSTokenTypeStringLiteral || type == 
WXJSTokenTypeNumericLiteral || type == WXJSTokenTypeBooleanLiteral || type == 
WXJSTokenTypeNullLiteral) {
+        return [self createLiteral:[self nextToken]];
+    } else {
+        [self _throwUnexpectedTokenError];
+        return new WXJSIdentifier();
+    }
+}
+
+- (WXJSArrayExpression *)parseArrayExpression
+{
+    std::vector<WXJSExpression *> expressions;
+    
+    [self expect:"["];
+    
+    while (![self match:"]"]) {
+        if ([self match:","]) {
+            [self nextToken];
+            expressions.push_back(NULL);
+        } else {
+            expressions.push_back([self parseConditionalExpression]);
+            
+            if (![self match:"]"]) {
+                [self expect:","];
+            }
+        }
+    }
+    
+    [self expect:"]"];
+    
+    WXJSArrayExpression *array = new WXJSArrayExpression();
+    array->expressions = expressions;
+    
+    return array;
+}
+
+- (WXJSExpression *)parseGroupExpression
+{
+    WXJSExpression *expr;
+    [self expect:"("];
+    
+    expr = [self parseConditionalExpression];
+    
+    [self expect:")"];
+    
+    return expr;
+}
+
+
+- (WXJSExpression *)parseMemberExpression
+{
+    WXJSExpression *expr = [self parsePrimaryExpression];
+    
+    if ([self match:"."]) {
+        [self expect:"."];
+        WXJSExpression *property = [self parsePrimaryExpression];
+        WXJSMemberExpression *memberExpr = new WXJSMemberExpression();
+        memberExpr->object = expr;
+        memberExpr->property = property;
+        memberExpr->computed = false;
+        return memberExpr;
+    } else if ([self match:"["]) {
+        [self expect:"["];
+        WXJSExpression *property = [self parseConditionalExpression];
+        [self expect:"]"];
+        WXJSMemberExpression *memberExpr = new WXJSMemberExpression();
+        memberExpr->object = expr;
+        memberExpr->property = property;
+        memberExpr->computed = true;
+        return memberExpr;
+    }
+    
+    return expr;
+}
+
+- (WXJSExpression *)createBinaryExpression:(const std::string &)operator_ 
left:(WXJSExpression *)left right:(WXJSExpression *)right
+{
+    WXJSBinaryExpression *node = new WXJSBinaryExpression();
+    node->operator_ = operator_;
+    node->left = left;
+    node->right = right;
+    return node;
+}
+
+- (WXJSLiteral *)createLiteral:(WXJSToken *)token
+{
+    if (token->type == WXJSTokenTypeNumericLiteral) {
+        WXJSNumericLiteral *node = new WXJSNumericLiteral();
+        node->value = token->doubleValue;
+        return node;
+    } else if (token->type == WXJSTokenTypeNullLiteral) {
+        WXJSNullLiteral *node = new WXJSNullLiteral();
+        return node;
+    } else if (token->type == WXJSTokenTypeStringLiteral) {
+        WXJSStringLiteral *node = new WXJSStringLiteral();
+        node->value = token->value;
+        return node;
+    } else if (token->type == WXJSTokenTypeBooleanLiteral) {
+        WXJSBooleanLiteral *node = new WXJSBooleanLiteral();
+        node->value = token->value == "true";
+        return node;
+    } else {
+        assert(false);
+    }
+}
+
+- (void)skipSpaces
+{
+    int ch;
+    while (_index < _length) {
+        ch = _source[_index];
+        if (isWhiteSpace(ch)) {
+            ++_index;
+        } else {
+            break;
+        }
+    }
+}
+
+- (bool)match:(std::string)value
+{
+    return _lookahead->type == WXJSTokenTypePunctuator && _lookahead->value == 
value;
+}
+
+- (void)expect:(std::string)value
+{
+    WXJSToken *token = [self nextToken];
+    if (token->type != WXJSTokenTypePunctuator || token->value != value) {
+        [self _throwUnexpectedTokenError];
+    }
+}
+
+- (void)_throwUnexpectedTokenError
+{
+    [self _throwError:@"Unexpected Token"];
+}
+
+- (void)_throwError:(NSString *)errorMessage
+{
+    WXLogError(@"%@, index:%d, script:%s", errorMessage, _index, 
_source.c_str());
+}
+
+@end

http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/5b99cc6b/ios/sdk/WeexSDK/Sources/Component/RecycleList/WXRecycleListComponent.h
----------------------------------------------------------------------
diff --git 
a/ios/sdk/WeexSDK/Sources/Component/RecycleList/WXRecycleListComponent.h 
b/ios/sdk/WeexSDK/Sources/Component/RecycleList/WXRecycleListComponent.h
new file mode 100644
index 0000000..a072752
--- /dev/null
+++ b/ios/sdk/WeexSDK/Sources/Component/RecycleList/WXRecycleListComponent.h
@@ -0,0 +1,25 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#import <Foundation/Foundation.h>
+#import "WXScrollerComponent.h"
+
+@interface WXRecycleListComponent : WXScrollerComponent 
+
+@end

http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/5b99cc6b/ios/sdk/WeexSDK/Sources/Component/RecycleList/WXRecycleListComponent.m
----------------------------------------------------------------------
diff --git 
a/ios/sdk/WeexSDK/Sources/Component/RecycleList/WXRecycleListComponent.m 
b/ios/sdk/WeexSDK/Sources/Component/RecycleList/WXRecycleListComponent.m
new file mode 100644
index 0000000..71a52f5
--- /dev/null
+++ b/ios/sdk/WeexSDK/Sources/Component/RecycleList/WXRecycleListComponent.m
@@ -0,0 +1,459 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#import "WXLog.h"
+#import "WXUtility.h"
+#import "WXComponent_internal.h"
+#import "WXComponentManager.h"
+#import "WXSDKInstance_private.h"
+
+#import "WXCellSlotComponent.h"
+#import "WXRecycleListLayout.h"
+#import "WXRecycleListComponent.h"
+#import "WXRecycleListDataManager.h"
+#import "WXRecycleListTemplateManager.h"
+#import "WXRecycleListUpdateManager.h"
+
+@interface WXRecycleListComponent () <WXRecycleListLayoutDelegate, 
WXRecycleListUpdateDelegate, UICollectionViewDelegateFlowLayout, 
UICollectionViewDataSource>
+
+@end
+
+@implementation WXRecycleListComponent
+{
+    WXRecycleListDataManager *_dataManager;
+    WXRecycleListTemplateManager *_templateManager;
+    WXRecycleListUpdateManager *_updateManager;
+    
+    NSString *_templateKey;
+    NSString *_aliasKey;
+    NSString *_indexKey;
+    __weak UICollectionView *_collectionView;
+    
+    NSMutableDictionary *_sizeCache;
+    NSMutableDictionary *_stickyCache;
+    
+    NSUInteger _previousLoadMoreCellNumber;
+}
+
+WX_EXPORT_METHOD(@selector(appendData:))
+WX_EXPORT_METHOD(@selector(insertData:atIndex:))
+WX_EXPORT_METHOD(@selector(updateData:atIndex:))
+WX_EXPORT_METHOD(@selector(removeData:))
+WX_EXPORT_METHOD(@selector(moveData:toIndex:))
+WX_EXPORT_METHOD(@selector(scrollTo:options:))
+
+- (instancetype)initWithRef:(NSString *)ref
+                       type:(NSString *)type
+                     styles:(NSDictionary *)styles
+                 attributes:(NSDictionary *)attributes
+                     events:(NSArray *)events
+               weexInstance:(WXSDKInstance *)weexInstance
+{
+    if (self = [super initWithRef:ref type:type styles:styles 
attributes:attributes events:events weexInstance:weexInstance]) {
+        _dataManager = [[WXRecycleListDataManager alloc] 
initWithData:attributes[@"listData"]];
+        _templateManager = [WXRecycleListTemplateManager new];
+        _updateManager = [WXRecycleListUpdateManager new];
+        _updateManager.delegate = self;
+        _templateKey = [WXConvert NSString:attributes[@"templateKey"]] ? : 
@"templateType";
+        _aliasKey = [WXConvert NSString:attributes[@"alias"]];
+        _indexKey = [WXConvert NSString:attributes[@"index"]];
+        _sizeCache = [NSMutableDictionary dictionary];
+        _stickyCache = [NSMutableDictionary dictionary];
+    }
+    
+    return self;
+}
+
+#pragma mark - WXComponent Methods
+
+- (UIView *)loadView
+{
+    WXRecycleListLayout *layout = [WXRecycleListLayout new];
+    layout.delegate = self;
+    // to show cells that original width / height is zero, otherwise 
cellForItemAtIndexPath will not be called
+    layout.minimumLineSpacing = 0.01;
+    layout.minimumInteritemSpacing = 0.01;
+    return [[UICollectionView alloc] initWithFrame:CGRectZero 
collectionViewLayout:layout];
+}
+
+- (void)viewDidLoad
+{
+    [super viewDidLoad];
+    
+    _collectionView = (UICollectionView *)self.view;
+    _collectionView.allowsSelection = NO;
+    _collectionView.allowsMultipleSelection = NO;
+    _collectionView.dataSource = self;
+    _collectionView.delegate = self;
+    
+    _templateManager.collectionView = _collectionView;
+    _updateManager.collectionView = _collectionView;
+}
+
+- (void)viewWillUnload
+{
+    [super viewWillUnload];
+    
+    _collectionView.dataSource = nil;
+    _collectionView.delegate = nil;
+}
+
+- (void)updateAttributes:(NSDictionary *)attributes
+{
+    if (attributes[@"listData"]) {
+        NSArray *listData = attributes[@"listData"];
+        [self _updateListData:listData withCompletion:nil animation:NO];
+    }
+}
+
+- (CGPoint)absolutePositionForComponent:(WXComponent *)component
+{
+    CGPoint position = CGPointZero;
+    UIView *view = component->_view;
+    while (view) {
+        if ([view isKindOfClass:[UICollectionViewCell class]]) {
+            NSIndexPath *indexPath = [_collectionView 
indexPathForCell:(UICollectionViewCell *)view];
+            if (!indexPath) {
+                return CGPointMake(NAN, NAN);
+            }
+            UICollectionViewLayoutAttributes *attributes = [_collectionView 
layoutAttributesForItemAtIndexPath:indexPath];
+            CGPoint cellOrigin = attributes.frame.origin;
+            position = CGPointMake(position.x + cellOrigin.x,
+                                   position.y + cellOrigin.y);
+            break;
+        }
+        position = CGPointMake(position.x + view.frame.origin.x,
+                               position.y + view.frame.origin.y);
+        view = view.superview;
+    }
+    
+    return position;
+}
+
+- (void)setContentSize:(CGSize)contentSize
+{
+    // Do Nothing
+}
+
+- (void)adjustSticky
+{
+    // Do Nothing, sticky is adjusted by layout
+}
+
+#pragma mark - Load More Event
+
+- (void)loadMore
+{
+    [super loadMore];
+    
+    _previousLoadMoreCellNumber = [_collectionView numberOfItemsInSection:0];
+}
+
+- (BOOL)isNeedLoadMore
+{
+    BOOL superNeedLoadMore = [super isNeedLoadMore];
+    return superNeedLoadMore && _previousLoadMoreCellNumber != 
[_collectionView numberOfItemsInSection:0];
+}
+
+- (void)resetLoadmore
+{
+    [super resetLoadmore];
+    _previousLoadMoreCellNumber = 0;
+}
+
+#pragma mark - Exported Component Methods
+
+- (void)appendData:(NSArray *)appendingData
+{
+    if (![appendingData isKindOfClass:[NSArray class]]) {
+        WXLogError(@"wrong format of appending data:%@", appendingData);
+        return;
+    }
+    
+    NSArray *oldData = [_dataManager data];
+    [_updateManager updateWithAppendingData:appendingData oldData:oldData 
completion:nil animation:NO];
+}
+
+- (void)insertData:(id)data atIndex:(NSUInteger)index
+{
+    // TODO: bring the update logic to UpdateManager
+    // TODO: update cell because index has changed
+    NSMutableArray *newListData = [[_dataManager data] mutableCopy];
+    if (index <= newListData.count) {
+        [newListData insertObject:data atIndex:index];
+        [_dataManager updateData:newListData];
+        
+        NSIndexPath *indexPath = [NSIndexPath indexPathForItem:index 
inSection:0];
+        
+        [UIView performWithoutAnimation:^{
+            [_collectionView insertItemsAtIndexPaths:@[indexPath]];
+        }];
+    }
+}
+
+- (void)updateData:(id)data atIndex:(NSUInteger)index
+{
+    // TODO: bring the update logic to UpdateManager
+    NSMutableArray *newListData = [[_dataManager data] mutableCopy];
+    if (index < newListData.count) {
+        newListData[index] = data;
+        [_dataManager updateData:newListData];
+        
+        NSIndexPath *indexPath = [NSIndexPath indexPathForItem:index 
inSection:0];
+        UICollectionViewCell *cellView = [_collectionView 
cellForItemAtIndexPath:indexPath];
+        WXCellSlotComponent *cellComponent = (WXCellSlotComponent 
*)cellView.wx_component;
+        if (cellComponent) {
+            [self _updateBindingData:data forCell:cellComponent 
atIndexPath:indexPath];
+        }
+    }
+}
+
+- (void)removeData:(NSArray *)indexes
+{
+    // TODO: bring the update logic to UpdateManager
+    NSMutableArray *newListData = [[_dataManager data] mutableCopy];
+    NSMutableIndexSet *indexSet = [NSMutableIndexSet new];
+    NSMutableArray *indexPaths = [NSMutableArray array];
+    for (NSNumber *index in indexes) {
+        if ([index unsignedIntegerValue] >= newListData.count) {
+            WXLogError(@"invalid remove index:%@", index);
+            continue;
+        }
+        [indexSet addIndex:[index unsignedIntegerValue]];
+        [indexPaths addObject:[NSIndexPath indexPathForItem:[index 
unsignedIntegerValue] inSection:0]];
+    }
+    
+    [newListData removeObjectsAtIndexes:indexSet];
+    [_dataManager updateData:newListData];
+    [UIView performWithoutAnimation:^{
+        [_collectionView deleteItemsAtIndexPaths:indexPaths];
+    }];
+}
+
+- (void)moveData:(NSUInteger)fromIndex toIndex:(NSUInteger)toIndex
+{
+    // TODO: bring the update logic to UpdateManager
+    NSMutableArray *newListData = [[_dataManager data] mutableCopy];
+    id data = newListData[fromIndex];
+    [newListData removeObjectAtIndex:fromIndex];
+    [newListData insertObject:data atIndex:toIndex];
+    [_dataManager updateData:newListData];
+    
+    NSIndexPath *fromIndexPath = [NSIndexPath indexPathForItem:fromIndex 
inSection:0];
+    NSIndexPath *toIndexPath = [NSIndexPath indexPathForItem:toIndex 
inSection:0];
+    [UIView performWithoutAnimation:^{
+        [_collectionView moveItemAtIndexPath:fromIndexPath 
toIndexPath:toIndexPath];
+    }];
+}
+
+- (void)scrollTo:(NSUInteger)index options:(NSDictionary *)options
+{
+    NSIndexPath *toIndexPath = [NSIndexPath indexPathForItem:index 
inSection:0];
+    BOOL animated = options[@"animated"] ? [WXConvert 
BOOL:options[@"animated"]] : NO;
+    [_collectionView scrollToItemAtIndexPath:toIndexPath 
atScrollPosition:UICollectionViewScrollPositionTop animated:animated];
+}
+
+#pragma mark - WXComonent Internal Methods
+
+- (void)_insertSubcomponent:(WXComponent *)subcomponent 
atIndex:(NSInteger)index
+{
+   [super _insertSubcomponent:subcomponent atIndex:index];
+    
+    if ([subcomponent isKindOfClass:[WXCellSlotComponent class]]) {
+        WXCellSlotComponent *cell = (WXCellSlotComponent*)subcomponent;
+        [self.weexInstance.componentManager _addUITask:^{
+            [_templateManager addTemplate:cell];
+        }];
+        
+        //TODO: update collection view if adding template
+    }
+}
+
+#pragma mark - Private
+
+- (void)_updateBindingData:(NSDictionary *)data forCell:(WXCellSlotComponent 
*)cellComponent atIndexPath:(NSIndexPath *)indexPath
+{
+    if (_aliasKey) {
+        data = @{_aliasKey:data};
+    }
+    if (_indexKey) {
+        NSMutableDictionary *dataNew = [data mutableCopy];
+        dataNew[_indexKey] = @(indexPath.item);
+        data = dataNew;
+    }
+    
+#ifdef DEBUG
+    NSDate *startTime = [NSDate date];
+#endif
+    WXPerformBlockSyncOnComponentThread(^{
+        [cellComponent updateCellData:data];
+    });
+#ifdef DEBUG
+    double duration = -[startTime timeIntervalSinceNow] * 1000;
+    WXLogDebug(@"cell:%zi update data time:%f", indexPath.item, duration);
+#endif
+    
+    NSValue *cachedSize = _sizeCache[indexPath];
+    if (!cachedSize || !CGSizeEqualToSize([cachedSize CGSizeValue] , 
cellComponent.calculatedFrame.size)) {
+        _sizeCache[indexPath] = [NSValue 
valueWithCGSize:cellComponent.calculatedFrame.size];
+        [_collectionView.collectionViewLayout invalidateLayout];
+    }
+    NSNumber *cachedSticky = _stickyCache[indexPath];
+    BOOL isSticky = cellComponent->_positionType == WXPositionTypeSticky;
+    if (!cachedSticky || [cachedSticky boolValue] != isSticky) {
+        _stickyCache[indexPath] = @(isSticky);
+    }
+}
+
+- (void)_updateListData:(NSArray *)newData
+        withCompletion:(WXRecycleListUpdateCompletion)completion
+             animation:(BOOL)animation
+{
+    if (![newData isKindOfClass:[NSArray class]]) {
+        WXLogError(@"wrong format of list data:%@", newData);
+        completion(NO);
+        return;
+    }
+    
+    NSArray *oldData = [_dataManager data];
+    [_updateManager updateWithNewData:newData oldData:oldData 
completion:completion animation:animation];
+}
+
+#pragma mark - UICollectionViewDataSource
+
+- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView 
*)collectionView
+{
+    return 1;
+}
+
+- (NSInteger)collectionView:(UICollectionView *)collectionView 
numberOfItemsInSection:(NSInteger)section
+{
+    return [_dataManager numberOfItems];
+}
+
+- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView 
cellForItemAtIndexPath:(NSIndexPath *)indexPath
+{
+    // 1. get the data relating to the cell
+    NSDictionary *data = [_dataManager dataAtIndex:indexPath.row];
+    if (!data || ![data isKindOfClass:[NSDictionary class]]) {
+        WXLogError(@"No data or wrong data format for index:%zd, data:%@", 
indexPath.item, data);
+        return nil;
+    }
+    
+    // 2. get the template type specified by data
+    NSString *templateType = data[_templateKey];
+    if (!templateType) {
+        WXLogError(@"Each data should have a value for %@ to indicate template 
type", _templateKey);
+        return nil;
+    }
+    
+    // 3. dequeue a cell component by template type
+    UICollectionViewCell *cellView = [_collectionView 
dequeueReusableCellWithReuseIdentifier:templateType forIndexPath:indexPath];
+    WXCellSlotComponent *cellComponent = (WXCellSlotComponent 
*)cellView.wx_component;
+    if (!cellComponent) {
+        cellComponent = [_templateManager dequeueCellSlotWithType:templateType 
forIndexPath:indexPath];
+        cellView.wx_component = cellComponent;
+        WXPerformBlockOnComponentThread(^{
+            //TODO: How can we avoid this?
+            [super _insertSubcomponent:cellComponent 
atIndex:self.subcomponents.count];
+        });
+    }
+    
+    // 4. binding the data to the cell component
+    [self _updateBindingData:data forCell:cellComponent atIndexPath:indexPath];
+
+    // 5. Add cell component's view to content view.
+    UIView *contentView = cellComponent.view;
+    if (contentView.superview == cellView.contentView) {
+        return cellView;
+    }
+    
+    for (UIView *view in cellView.contentView.subviews) {
+        [view removeFromSuperview];
+    }
+    [cellView.contentView addSubview:contentView];
+    [cellView setAccessibilityIdentifier:contentView.accessibilityIdentifier];
+    
+    WXLogDebug(@"Return cell view:%@, indexPath:%@", cellView, indexPath);
+    
+    dispatch_async(dispatch_get_main_queue(), ^{
+        [self handleAppear];
+    });
+    
+    return cellView;
+}
+
+- (UICollectionReusableView *)collectionView:(UICollectionView 
*)collectionView viewForSupplementaryElementOfKind:(NSString *)kind 
atIndexPath:(NSIndexPath *)indexPath
+{
+    return nil;
+}
+
+#pragma mark - UICollectionViewDelegate
+
+- (void)collectionView:(UICollectionView *)collectionView 
willDisplayCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath 
*)indexPath
+{
+    WXLogDebug(@"will display cell:%@, at index path:%@", cell, indexPath);
+}
+
+- (void)collectionView:(UICollectionView *)collectionView 
didEndDisplayingCell:(UICollectionViewCell *)cell 
forItemAtIndexPath:(NSIndexPath *)indexPath
+{
+    WXLogDebug(@"Did end displaying cell:%@, at index path:%@", cell, 
indexPath);
+}
+
+#pragma mark - UICollectionViewDelegateFlowLayout
+
+- (CGSize)collectionView:(UICollectionView *)collectionView 
layout:(UICollectionViewLayout *)collectionViewLayout 
sizeForItemAtIndexPath:(NSIndexPath *)indexPath
+{
+    NSValue *size = _sizeCache[indexPath];
+    if (size) {
+        return [size CGSizeValue];
+    } else {
+        NSDictionary *data = [_dataManager dataAtIndex:indexPath.row];
+        WXCellSlotComponent *cell = [_templateManager 
templateWithType:data[_templateKey]];
+        CGSize size = cell.calculatedFrame.size;
+        _sizeCache[indexPath] = [NSValue valueWithCGSize:size];
+        return CGSizeMake(_collectionView.frame.size.width, size.height);
+    }
+}
+
+#pragma mark - WXRecycleListLayoutDelegate
+
+- (BOOL)collectionView:(UICollectionView *)collectionView 
layout:(UICollectionViewLayout *)collectionViewLayout 
isNeedStickyForIndexPath:(NSIndexPath *)indexPath
+{
+    NSNumber *cachedSticky = _stickyCache[indexPath];
+    if (cachedSticky) {
+        return [cachedSticky boolValue];
+    } else {
+        return NO;
+    }
+}
+
+#pragma mark - WXRecycleListUpdateDelegate
+
+- (void)updateManager:(WXRecycleListUpdateManager *)manager 
willUpdateData:(id)newData
+{
+    [_dataManager updateData:newData];
+}
+
+- (void)updateManager:(WXRecycleListUpdateManager *)manager 
didUpdateData:(id)newData withSuccess:(BOOL)finished
+{
+    
+}
+
+@end

http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/5b99cc6b/ios/sdk/WeexSDK/Sources/Component/RecycleList/WXRecycleListDataManager.h
----------------------------------------------------------------------
diff --git 
a/ios/sdk/WeexSDK/Sources/Component/RecycleList/WXRecycleListDataManager.h 
b/ios/sdk/WeexSDK/Sources/Component/RecycleList/WXRecycleListDataManager.h
new file mode 100644
index 0000000..fc053c5
--- /dev/null
+++ b/ios/sdk/WeexSDK/Sources/Component/RecycleList/WXRecycleListDataManager.h
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#import <Foundation/Foundation.h>
+
+@interface WXRecycleListDataManager : NSObject
+
+- (instancetype)initWithData:(NSArray *)data;
+
+- (void)updateData:(NSArray *)data;
+
+- (NSArray *)data;
+
+- (NSDictionary *)dataAtIndex:(NSInteger)index;
+
+- (NSInteger)numberOfItems;
+
+@end

http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/5b99cc6b/ios/sdk/WeexSDK/Sources/Component/RecycleList/WXRecycleListDataManager.m
----------------------------------------------------------------------
diff --git 
a/ios/sdk/WeexSDK/Sources/Component/RecycleList/WXRecycleListDataManager.m 
b/ios/sdk/WeexSDK/Sources/Component/RecycleList/WXRecycleListDataManager.m
new file mode 100644
index 0000000..8d27171
--- /dev/null
+++ b/ios/sdk/WeexSDK/Sources/Component/RecycleList/WXRecycleListDataManager.m
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#import "WXRecycleListDataManager.h"
+#import "NSArray+Weex.h"
+#import "WXLog.h"
+#import "WXAssert.h"
+
+@implementation WXRecycleListDataManager
+{
+    NSArray *_data;
+}
+
+- (instancetype)initWithData:(NSArray *)data
+{
+    if (self = [super init]) {
+        if (![data isKindOfClass:[NSArray class]]) {
+            WXLogError(@"list data must be an array!");
+        } else {
+            _data = data;
+        }
+    }
+    
+    return self;
+}
+
+- (void)updateData:(NSArray *)data
+{
+    WXAssertMainThread();
+    
+    _data = data;
+}
+
+- (NSArray *)data
+{
+    WXAssertMainThread();
+    
+    return _data;
+}
+
+- (NSDictionary *)dataAtIndex:(NSInteger)index
+{
+    WXAssertMainThread();
+    
+    return [_data wx_safeObjectAtIndex:index];
+}
+
+- (NSInteger)numberOfItems
+{
+    WXAssertMainThread();
+    
+    return [_data count];
+}
+
+@end

http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/5b99cc6b/ios/sdk/WeexSDK/Sources/Component/RecycleList/WXRecycleListLayout.h
----------------------------------------------------------------------
diff --git 
a/ios/sdk/WeexSDK/Sources/Component/RecycleList/WXRecycleListLayout.h 
b/ios/sdk/WeexSDK/Sources/Component/RecycleList/WXRecycleListLayout.h
new file mode 100644
index 0000000..6e08b94
--- /dev/null
+++ b/ios/sdk/WeexSDK/Sources/Component/RecycleList/WXRecycleListLayout.h
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#import <UIKit/UIKit.h>
+
+@protocol WXRecycleListLayoutDelegate
+
+- (BOOL)collectionView:(UICollectionView *)collectionView 
layout:(UICollectionViewLayout *)collectionViewLayout 
isNeedStickyForIndexPath:(NSIndexPath *)indexPath;
+
+@end
+
+@interface WXRecycleListLayout : UICollectionViewFlowLayout
+
+@property (nonatomic, weak) id<WXRecycleListLayoutDelegate> delegate;
+
+@end

http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/5b99cc6b/ios/sdk/WeexSDK/Sources/Component/RecycleList/WXRecycleListLayout.m
----------------------------------------------------------------------
diff --git 
a/ios/sdk/WeexSDK/Sources/Component/RecycleList/WXRecycleListLayout.m 
b/ios/sdk/WeexSDK/Sources/Component/RecycleList/WXRecycleListLayout.m
new file mode 100644
index 0000000..73851de
--- /dev/null
+++ b/ios/sdk/WeexSDK/Sources/Component/RecycleList/WXRecycleListLayout.m
@@ -0,0 +1,113 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#import "WXRecycleListLayout.h"
+
+@implementation WXRecycleListLayout
+{
+    NSMutableDictionary<NSNumber *, UICollectionViewLayoutAttributes *> 
*_stickyCellsAttributes;
+}
+
+- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
+{
+    NSArray *cellAttributes = [super layoutAttributesForElementsInRect:rect];
+    NSMutableDictionary *lastCellsAttributes = [NSMutableDictionary 
dictionary];
+    
+    __block NSInteger currentStickyIndex = -1;
+    [cellAttributes enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL 
*stop) {
+        UICollectionViewLayoutAttributes *attributes = obj;
+        NSIndexPath *indexPath = attributes.indexPath;
+        if ([self.delegate collectionView:self.collectionView layout:self 
isNeedStickyForIndexPath:indexPath]) {
+            if (!_stickyCellsAttributes) {
+                _stickyCellsAttributes = [NSMutableDictionary dictionary];
+            }
+            
+            currentStickyIndex = indexPath.item;
+            [_stickyCellsAttributes setObject:attributes 
forKey:@(indexPath.item)];
+        } else {
+            [_stickyCellsAttributes removeObjectForKey:@(indexPath.item)];
+            
+            // bottom cell above sticky cell
+            UICollectionViewLayoutAttributes *currentLastCell = 
[lastCellsAttributes objectForKey:@(currentStickyIndex)];
+            if (!currentLastCell || indexPath.item > 
currentLastCell.indexPath.item) {
+                [lastCellsAttributes setObject:obj 
forKey:@(currentStickyIndex)];
+            }
+        }
+        
+        attributes.zIndex = 1;
+    }];
+    
+    NSMutableArray *newCellAttributes = [cellAttributes mutableCopy];
+    [lastCellsAttributes enumerateKeysAndObjectsUsingBlock:^(id  _Nonnull key, 
id  _Nonnull obj, BOOL * _Nonnull stop) {
+        UICollectionViewLayoutAttributes *attributes = obj;
+        
+        UICollectionViewLayoutAttributes *stickyCell = 
_stickyCellsAttributes[key];
+        if (!stickyCell) {
+            NSInteger item = attributes.indexPath.item;
+            while (item >= 0) {
+                if (_stickyCellsAttributes[@(item)]) {
+                    stickyCell = [self.collectionView 
layoutAttributesForItemAtIndexPath:[NSIndexPath indexPathForItem:item 
inSection:0]];
+                    break;
+                } else {
+                    item --;
+                }
+            }
+        }
+        
+        if (stickyCell) {
+            [newCellAttributes addObject:stickyCell];
+            [self _adjustStickyForCellAttributes:stickyCell 
lastCellAttributes:attributes];
+        }
+    }];
+    
+    return newCellAttributes;
+}
+
+- (void)_adjustStickyForCellAttributes:(UICollectionViewLayoutAttributes *)cell
+                    lastCellAttributes:(UICollectionViewLayoutAttributes 
*)lastCell
+{
+    cell.zIndex = 99;
+    cell.hidden = NO;
+    
+    CGFloat maxY = CGRectGetMaxY(lastCell.frame) - cell.frame.size.height;
+    CGFloat minY = CGRectGetMinY(self.collectionView.bounds) + 
self.collectionView.contentInset.top;
+    CGFloat y = MIN(MAX(minY, cell.frame.origin.y), maxY);
+
+//    NSLog(@"%zi : %zi, %.1f, %.1f, %.1f, %.1f", cell.indexPath.item, 
lastCell.indexPath.item, maxY, minY, cell.frame.origin.y, y);
+    
+    CGPoint origin = cell.frame.origin;
+    origin.y = y;
+    
+    cell.frame = (CGRect){
+        origin,
+        cell.frame.size
+    };
+}
+
+- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
+{
+    if (_stickyCellsAttributes.count > 0) {
+        // always return yes to trigger resetting sticky header's frame.
+        return YES;
+    }
+    
+    return [super shouldInvalidateLayoutForBoundsChange:newBounds];
+}
+
+@end

http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/5b99cc6b/ios/sdk/WeexSDK/Sources/Component/RecycleList/WXRecycleListTemplateManager.h
----------------------------------------------------------------------
diff --git 
a/ios/sdk/WeexSDK/Sources/Component/RecycleList/WXRecycleListTemplateManager.h 
b/ios/sdk/WeexSDK/Sources/Component/RecycleList/WXRecycleListTemplateManager.h
new file mode 100644
index 0000000..389dbb8
--- /dev/null
+++ 
b/ios/sdk/WeexSDK/Sources/Component/RecycleList/WXRecycleListTemplateManager.h
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#import <Foundation/Foundation.h>
+#import "WXCellSlotComponent.h"
+
+@interface WXRecycleListTemplateManager : NSObject
+
+@property (nonatomic, weak) UICollectionView *collectionView;
+
+- (void)addTemplate:(WXCellSlotComponent *)component;
+
+- (WXCellSlotComponent *)dequeueCellSlotWithType:(NSString *)type 
forIndexPath:(NSIndexPath *)indexPath;
+
+- (WXCellSlotComponent *)templateWithType:(NSString *)type;
+
+@end

http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/5b99cc6b/ios/sdk/WeexSDK/Sources/Component/RecycleList/WXRecycleListTemplateManager.m
----------------------------------------------------------------------
diff --git 
a/ios/sdk/WeexSDK/Sources/Component/RecycleList/WXRecycleListTemplateManager.m 
b/ios/sdk/WeexSDK/Sources/Component/RecycleList/WXRecycleListTemplateManager.m
new file mode 100644
index 0000000..87ecd90
--- /dev/null
+++ 
b/ios/sdk/WeexSDK/Sources/Component/RecycleList/WXRecycleListTemplateManager.m
@@ -0,0 +1,94 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#import "WXRecycleListTemplateManager.h"
+#import "WXLog.h"
+#import "WXAssert.h"
+
+@interface WXReusableCollectionViewCell : UICollectionViewCell
+
+@end
+
+@implementation WXReusableCollectionViewCell
+
+@end
+
+@implementation WXRecycleListTemplateManager
+{
+    NSMapTable<NSString *, WXCellSlotComponent *> *_templateTypeMap;
+}
+
+- (instancetype)init
+{
+    if (self = [super init]) {
+        _templateTypeMap = [NSMapTable strongToWeakObjectsMapTable];
+    }
+    
+    return self;
+}
+
+- (void)setCollectionView:(UICollectionView *)collectionView
+{
+    WXAssertMainThread();
+    
+    if (_collectionView == collectionView) {
+        return;
+    }
+    
+    _collectionView = collectionView;
+    
+    for (NSString *templateType in [_templateTypeMap.keyEnumerator.allObjects 
copy]) {
+        [self _registerCellClassForReuseID:templateType];
+    }
+}
+
+- (void)addTemplate:(WXCellSlotComponent *)component
+{
+    WXAssertMainThread();
+    
+    NSString *templateType = component.templateType;
+    WXAssert(templateType != nil, @"cell-slot:%@ must have a template id!", 
component);
+    
+    [_templateTypeMap setObject:component forKey:templateType];
+    if (_collectionView) {
+        [self _registerCellClassForReuseID:templateType];
+    }
+}
+
+- (WXCellSlotComponent *)dequeueCellSlotWithType:(NSString *)type 
forIndexPath:(NSIndexPath *)indexPath
+{
+    WXAssertMainThread();
+    
+    WXCellSlotComponent *cellSlot = [_templateTypeMap objectForKey:type];
+    return [cellSlot copy];
+}
+
+- (WXCellSlotComponent *)templateWithType:(NSString *)type
+{
+    return [_templateTypeMap objectForKey:type];;
+}
+
+- (void)_registerCellClassForReuseID:(NSString *)templateID
+{
+    WXLogDebug(@"register cell class for template id:%@", templateID);
+    //TODO: register class update TemplateId
+    [_collectionView registerClass:[WXReusableCollectionViewCell class] 
forCellWithReuseIdentifier:templateID];
+}
+
+@end

http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/5b99cc6b/ios/sdk/WeexSDK/Sources/Component/RecycleList/WXRecycleListUpdateManager.h
----------------------------------------------------------------------
diff --git 
a/ios/sdk/WeexSDK/Sources/Component/RecycleList/WXRecycleListUpdateManager.h 
b/ios/sdk/WeexSDK/Sources/Component/RecycleList/WXRecycleListUpdateManager.h
new file mode 100644
index 0000000..1753b2b
--- /dev/null
+++ b/ios/sdk/WeexSDK/Sources/Component/RecycleList/WXRecycleListUpdateManager.h
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#import <Foundation/Foundation.h>
+
+typedef void(^WXRecycleListUpdateCompletion)(BOOL isFinished);
+@class WXRecycleListUpdateManager;
+
+@protocol WXRecycleListUpdateDelegate
+
+- (void)updateManager:(WXRecycleListUpdateManager *)manager 
willUpdateData:(id)newData;
+
+- (void)updateManager:(WXRecycleListUpdateManager *)manager 
didUpdateData:(id)newData withSuccess:(BOOL)finished;
+
+@end
+
+@interface WXRecycleListUpdateManager : NSObject
+
+@property (nonatomic, weak) UICollectionView *collectionView;
+@property (nonatomic, weak) id<WXRecycleListUpdateDelegate> delegate;
+
+- (void)reload;
+
+- (void)updateWithNewData:(NSArray *)newData
+                  oldData:(NSArray *)oldData
+               completion:(WXRecycleListUpdateCompletion)completion
+                animation:(BOOL)isAnimated;
+
+- (void)updateWithAppendingData:(NSArray *)appendingData
+                        oldData:(NSArray *)oldData
+                     completion:(WXRecycleListUpdateCompletion)completion
+                      animation:(BOOL)isAnimated;
+
+@end

http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/5b99cc6b/ios/sdk/WeexSDK/Sources/Component/RecycleList/WXRecycleListUpdateManager.m
----------------------------------------------------------------------
diff --git 
a/ios/sdk/WeexSDK/Sources/Component/RecycleList/WXRecycleListUpdateManager.m 
b/ios/sdk/WeexSDK/Sources/Component/RecycleList/WXRecycleListUpdateManager.m
new file mode 100644
index 0000000..7ad4caa
--- /dev/null
+++ b/ios/sdk/WeexSDK/Sources/Component/RecycleList/WXRecycleListUpdateManager.m
@@ -0,0 +1,262 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#import "WXRecycleListUpdateManager.h"
+#import "WXLog.h"
+#import "WXAssert.h"
+#import "WXDiffUtil.h"
+
+@interface WXRecycleListDiffResult : NSObject
+
+@property (nonatomic, strong, readonly) NSMutableSet<NSIndexPath *> 
*deleteIndexPaths;
+@property (nonatomic, strong, readonly) NSMutableSet<NSIndexPath *> 
*insertIndexPaths;
+@property (nonatomic, strong, readonly) NSMutableSet<NSIndexPath *> 
*reloadIndexPaths;
+
+- (BOOL)hasChanges;
+
+@end
+
+@implementation WXRecycleListDiffResult
+
+- (instancetype)initWithInsertIndexPaths:(NSMutableSet<NSIndexPath *> 
*)insertIndexPaths
+                        deleteIndexPaths:(NSMutableSet<NSIndexPath *> 
*)deleteIndexPaths
+                        reloadIndexPaths:(NSMutableSet<NSIndexPath *> 
*)reloadIndexPaths
+{
+    if (self = [super init]) {
+        _insertIndexPaths = [insertIndexPaths copy];
+        _deleteIndexPaths = [deleteIndexPaths copy];
+        _reloadIndexPaths = [reloadIndexPaths copy];
+    }
+    
+    return self;
+}
+
+- (BOOL)hasChanges
+{
+    return _insertIndexPaths.count > 0 || _deleteIndexPaths.count > 0 || 
_reloadIndexPaths.count > 0;
+}
+
+- (NSString *)description
+{
+    return [NSString stringWithFormat:@"<%@: %p; insert index paths: %@; 
delete index paths: %@; reload index paths: %@", NSStringFromClass([self 
class]), self, _insertIndexPaths, _deleteIndexPaths, _reloadIndexPaths];
+}
+
+@end
+
+@interface WXRecycleListUpdateManager ()
+
+@property (nonatomic, copy) NSArray *newerData;
+@property (nonatomic, copy) NSArray *appendingData;
+@property (nonatomic, copy) NSArray *olderData;
+@property (nonatomic, assign) BOOL isUpdating;
+@property (nonatomic, strong) NSMutableArray *completions;
+
+@property (nonatomic, strong) NSMutableSet<NSIndexPath *> *reloadIndexPaths;
+
+@end
+
+@implementation WXRecycleListUpdateManager
+
+- (instancetype)init
+{
+    if (self = [super init]) {
+        _completions = [NSMutableArray array];
+    }
+    
+    return self;
+}
+
+- (void)reload
+{
+    [_collectionView reloadData];
+}
+
+- (void)updateWithNewData:(NSArray *)newData
+                  oldData:(NSArray *)oldData
+               completion:(WXRecycleListUpdateCompletion)completion
+                animation:(BOOL)isAnimated
+{
+    WXAssertMainThread();
+    
+    if (!_collectionView) {
+        WXLogError(@"Update list with no collection view");
+        completion(NO);
+        return;
+    }
+    
+    self.newerData = newData;
+    self.appendingData = nil;
+    self.olderData = oldData;
+    
+    if (completion) {
+        [_completions addObject:completion];
+    }
+    
+    [self checkUpdates];
+}
+
+- (void)updateWithAppendingData:(NSArray *)appendingData
+                        oldData:(NSArray *)oldData
+                     completion:(WXRecycleListUpdateCompletion)completion
+                      animation:(BOOL)isAnimated
+{
+    if (!_collectionView) {
+        WXLogError(@"Update list with no collection view");
+        completion(NO);
+        return;
+    }
+    
+    self.appendingData = appendingData;
+    self.olderData = oldData;
+    
+    if (completion) {
+        [_completions addObject:completion];
+    }
+    
+    [self checkUpdates];
+}
+
+
+- (void)checkUpdates
+{
+    dispatch_async(dispatch_get_main_queue(), ^{
+        if (self.isUpdating) {
+            return ;
+        }
+        
+        [self performBatchUpdates];
+    });
+}
+
+- (void)performBatchUpdates
+{
+    WXAssertMainThread();
+    WXAssert(!self.isUpdating, @"Can not perform updates while an updating is 
being performed");
+    
+    UICollectionView *collectionView = self.collectionView;
+    if (!collectionView) {
+        return;
+    }
+    
+    NSArray *newData = [self.newerData copy];
+    NSArray *oldData = [self.olderData copy];
+    NSArray *appendingData = [self.appendingData copy];
+    //TODO use completionBlocks
+//    NSArray *completionBlocks = [self.completions copy];
+    
+    [self cleanup];
+    
+    WXDiffResult *diffResult;
+    if (appendingData) {
+        newData = [oldData arrayByAddingObjectsFromArray:appendingData];
+        NSIndexSet *inserts = [NSIndexSet 
indexSetWithIndexesInRange:NSMakeRange(oldData.count, appendingData.count)];
+        diffResult = [[WXDiffResult alloc] initWithInserts:inserts deletes:nil 
updates:nil];
+    } else if (newData){
+        diffResult = [WXDiffUtil diffWithMinimumDistance:newData 
oldArray:oldData];
+    }
+    
+    WXRecycleListDiffResult *recycleListDiffResult = [self 
recycleListUpdatesByDiffResult:diffResult];
+    
+    if (![diffResult hasChanges] && self.reloadIndexPaths.count == 0) {
+        return;
+    }
+    
+    void (^updates)() = [^{
+        [self.delegate updateManager:self willUpdateData:newData];
+        [UIView setAnimationsEnabled:NO];
+        NSLog(@"UICollectionView update:%@", recycleListDiffResult);
+        [self applyUpdateWithDiffResult:recycleListDiffResult];
+    } copy];
+    
+    void (^completion)(BOOL) = [^(BOOL finished) {
+        [UIView setAnimationsEnabled:YES];
+        self.isUpdating = NO;
+        [self.delegate updateManager:self didUpdateData:newData 
withSuccess:finished];
+        
+        [self.reloadIndexPaths removeAllObjects];
+        [self checkUpdates];
+    } copy];
+    
+    self.isUpdating = YES;
+    
+    if (!self.delegate || !collectionView.dataSource) {
+        return;
+    }
+    
+    [collectionView performBatchUpdates:updates completion:completion];
+}
+
+- (WXRecycleListDiffResult *)recycleListUpdatesByDiffResult:(WXDiffResult 
*)diffResult
+{
+    NSMutableSet<NSIndexPath *> *reloadIndexPaths = [NSMutableSet set];
+    NSMutableSet<NSIndexPath *> *deleteIndexPaths = [NSMutableSet set];
+    NSMutableSet<NSIndexPath *> *insertIndexPaths = [NSMutableSet set];
+    
+    for (WXDiffUpdateIndex *update in diffResult.updates) {
+        NSIndexPath *reloadIndexPath = [NSIndexPath 
indexPathForItem:update.oldIndex inSection:0];
+        [reloadIndexPaths addObject:reloadIndexPath];
+    }
+    
+    [diffResult.updates enumerateObjectsUsingBlock:^(WXDiffUpdateIndex * 
_Nonnull update, NSUInteger idx, BOOL * _Nonnull stop) {
+        NSIndexPath *reloadIndexPath = [NSIndexPath 
indexPathForItem:update.oldIndex inSection:0];
+        [reloadIndexPaths addObject:reloadIndexPath];
+    }];
+    
+    [diffResult.inserts enumerateIndexesUsingBlock:^(NSUInteger insertIndex, 
BOOL * _Nonnull stop) {
+        NSIndexPath *insertIndexPath = [NSIndexPath 
indexPathForItem:insertIndex inSection:0];
+        [insertIndexPaths addObject:insertIndexPath];
+    }];
+    
+    [diffResult.deletes enumerateIndexesUsingBlock:^(NSUInteger deleteIndex, 
BOOL * _Nonnull stop) {
+        NSIndexPath *deleteIndexPath = [NSIndexPath 
indexPathForItem:deleteIndex inSection:0];
+        [deleteIndexPaths addObject:deleteIndexPath];
+    }];
+    
+    WXRecycleListDiffResult *result = [[WXRecycleListDiffResult alloc] 
initWithInsertIndexPaths:insertIndexPaths deleteIndexPaths:deleteIndexPaths 
reloadIndexPaths:reloadIndexPaths];
+    
+    return result;
+}
+
+
+- (void)applyUpdateWithDiffResult:(WXRecycleListDiffResult *)diffResult
+{
+    if (!_collectionView) {
+        return;
+    }
+    
+    // reload index paths should not inculde delete index paths, otherwise it 
will cause crash:
+    // Assertion failure in
+    // -[UICollectionView 
_endItemAnimationsWithInvalidationContext:tentativelyForReordering:animator:]
+    NSMutableSet *reloadIndexPaths = self.reloadIndexPaths ? 
[[diffResult.reloadIndexPaths setByAddingObjectsFromSet:self.reloadIndexPaths] 
mutableCopy]: [diffResult.reloadIndexPaths mutableCopy];
+    [reloadIndexPaths minusSet:diffResult.deleteIndexPaths];
+    
+    [_collectionView deleteItemsAtIndexPaths:[diffResult.deleteIndexPaths 
allObjects]];
+    [_collectionView insertItemsAtIndexPaths:[diffResult.insertIndexPaths 
allObjects]];
+    [_collectionView reloadItemsAtIndexPaths:[reloadIndexPaths allObjects]];
+}
+
+- (void)cleanup
+{
+    self.newerData = nil;
+    self.appendingData = nil;
+    self.olderData = nil;
+    [self.completions removeAllObjects];
+}
+
+@end

http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/5b99cc6b/ios/sdk/WeexSDK/Sources/Component/Recycler/WXSectionDataController.m
----------------------------------------------------------------------
diff --git 
a/ios/sdk/WeexSDK/Sources/Component/Recycler/WXSectionDataController.m 
b/ios/sdk/WeexSDK/Sources/Component/Recycler/WXSectionDataController.m
index af1f760..1bc7d23 100644
--- a/ios/sdk/WeexSDK/Sources/Component/Recycler/WXSectionDataController.m
+++ b/ios/sdk/WeexSDK/Sources/Component/Recycler/WXSectionDataController.m
@@ -65,7 +65,7 @@
     return [super hash];
 }
 
-- (BOOL)isEqualToWXObject:(id<WXDiffable>)object
+- (BOOL)weex_isEqualTo:(id<WXDiffable>)object
 {
     if ([object isKindOfClass:[WXSectionDataController class]]) {
         WXSectionDataController *controller = (WXSectionDataController 
*)object;

http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/5b99cc6b/ios/sdk/WeexSDK/Sources/Component/WXCellComponent.m
----------------------------------------------------------------------
diff --git a/ios/sdk/WeexSDK/Sources/Component/WXCellComponent.m 
b/ios/sdk/WeexSDK/Sources/Component/WXCellComponent.m
index e579139..07621c7 100644
--- a/ios/sdk/WeexSDK/Sources/Component/WXCellComponent.m
+++ b/ios/sdk/WeexSDK/Sources/Component/WXCellComponent.m
@@ -56,7 +56,7 @@
     
 }
 
-- (BOOL)isEqualToWXObject:(id<WXDiffable>)object
+- (BOOL)weex_isEqualTo:(id<WXDiffable>)object
 {
     return self == object;
 }
@@ -100,7 +100,7 @@
 
 - (void)_moveToSupercomponent:(WXComponent *)newSupercomponent 
atIndex:(NSUInteger)index
 {
-    if (self.delegate == newSupercomponent) {
+    if (self.delegate == (id<WXCellRenderDelegate>)newSupercomponent) {
         [self.delegate cell:self didMoveToIndex:index];
         [super _removeFromSupercomponent];
         [newSupercomponent _insertSubcomponent:self atIndex:index];

http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/5b99cc6b/ios/sdk/WeexSDK/Sources/Component/WXComponent_internal.h
----------------------------------------------------------------------
diff --git a/ios/sdk/WeexSDK/Sources/Component/WXComponent_internal.h 
b/ios/sdk/WeexSDK/Sources/Component/WXComponent_internal.h
index 0316321..5d9b687 100644
--- a/ios/sdk/WeexSDK/Sources/Component/WXComponent_internal.h
+++ b/ios/sdk/WeexSDK/Sources/Component/WXComponent_internal.h
@@ -25,6 +25,7 @@
 @class WXTouchGestureRecognizer;
 @class WXThreadSafeCounter;
 
+typedef id (^WXDataBindingBlock)(NSDictionary *data, BOOL *needUpdate);
 
 /**
  * The following variables and methods are used in Weex INTERNAL logic.
@@ -132,6 +133,25 @@
     BOOL _lazyCreateView;
     
     WXTransform *_transform;
+    
+    /**
+     * Data Binding
+     */
+    BOOL _isTemplate;
+    WXComponent *_templateComponent;
+    WXDataBindingBlock _bindingMatch;
+    WXDataBindingBlock _bindingRepeat;
+    NSString *_repeatIndexIdentify;
+    NSString *_repeatLabelIdentify;
+    BOOL _isRepeating;
+    BOOL _isSkipUpdate;
+    
+    NSMutableDictionary<NSString *, WXDataBindingBlock> *_bindingProps;
+    NSMutableDictionary<NSString *, WXDataBindingBlock> *_bindingAttributes;
+    NSMutableDictionary<NSString *, WXDataBindingBlock> *_bindingStyles;
+    NSMutableDictionary<NSString *, WXDataBindingBlock> *_bindingEvents;
+    
+    NSMutableDictionary<NSString *, NSArray *> *_eventParameters;
 }
 
 ///--------------------------------------
@@ -207,6 +227,10 @@
 
 - (void)_removeAllEvents;
 
+- (void)_addEventParams:(NSDictionary *)params;
+
+- (NSArray *)_paramsForEvent:(NSString *)eventName;
+
 - (void)_setupNavBarWithStyles:(NSMutableDictionary *)styles 
attributes:(NSMutableDictionary *)attributes;
 
 - (void)_initCompositingAttribute:(NSDictionary *)attributes;
@@ -227,6 +251,10 @@
 
 - (void)setGradientLayer;
 
+- (void)_storeBindingsWithProps:(NSDictionary *)props styles:(NSDictionary 
*)styles attributes:(NSDictionary *)attributes events:(NSDictionary *)events;
+
+- (void)_didInserted;
+
 @end
 
 

http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/5b99cc6b/ios/sdk/WeexSDK/Sources/Component/WXScrollerComponent.h
----------------------------------------------------------------------
diff --git a/ios/sdk/WeexSDK/Sources/Component/WXScrollerComponent.h 
b/ios/sdk/WeexSDK/Sources/Component/WXScrollerComponent.h
index 39bf0d7..db0c155 100644
--- a/ios/sdk/WeexSDK/Sources/Component/WXScrollerComponent.h
+++ b/ios/sdk/WeexSDK/Sources/Component/WXScrollerComponent.h
@@ -34,5 +34,7 @@
 
 - (void)handleAppear;
 
+- (CGPoint)absolutePositionForComponent:(WXComponent *)component;
+
 @end
 

http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/5b99cc6b/ios/sdk/WeexSDK/Sources/Component/WXScrollerComponent.m
----------------------------------------------------------------------
diff --git a/ios/sdk/WeexSDK/Sources/Component/WXScrollerComponent.m 
b/ios/sdk/WeexSDK/Sources/Component/WXScrollerComponent.m
index a960cc6..306631b 100644
--- a/ios/sdk/WeexSDK/Sources/Component/WXScrollerComponent.m
+++ b/ios/sdk/WeexSDK/Sources/Component/WXScrollerComponent.m
@@ -566,25 +566,30 @@ WX_EXPORT_METHOD(@selector(resetLoadmore))
     }
 }
 
+- (CGPoint)absolutePositionForComponent:(WXComponent *)component
+{
+    return [component->_view.superview 
convertPoint:component->_view.frame.origin toView:_view];
+}
+
 #pragma mark  Private Methods
 
 - (void)scrollToTarget:(WXScrollToTarget *)target scrollRect:(CGRect)rect
 {
     WXComponent *component = target.target;
-    if (![component isViewLoaded]) {
+    if (![component isViewLoaded]) { 
         return;
     }
     
     CGFloat ctop;
     if (component && component->_view && component->_view.superview) {
-        ctop = [component->_view.superview 
convertPoint:component->_view.frame.origin toView:_view].y;
+        ctop = [self absolutePositionForComponent:component].y;
     } else {
         ctop = 0.0;
     }
     CGFloat cbottom = ctop + CGRectGetHeight(component.calculatedFrame);
     CGFloat cleft;
     if (component && component->_view && component->_view.superview) {
-        cleft = [component->_view.superview 
convertPoint:component->_view.frame.origin toView:_view].x;
+        cleft = [self absolutePositionForComponent:component].x;
     } else {
         cleft = 0.0;
     }
@@ -595,6 +600,7 @@ WX_EXPORT_METHOD(@selector(resetLoadmore))
         if(!target.hasAppear && component){
             target.hasAppear = YES;
             if (component->_appearEvent) {
+//                NSLog(@"appear:%@, %.2f", component, ctop);
                 [component fireEvent:@"appear" params:_direction ? 
@{@"direction":_direction} : nil];
             }
         }
@@ -602,6 +608,7 @@ WX_EXPORT_METHOD(@selector(resetLoadmore))
         if(target.hasAppear && component){
             target.hasAppear = NO;
             if(component->_disappearEvent){
+//                NSLog(@"disappear:%@", component);
                 [component fireEvent:@"disappear" params:_direction ? 
@{@"direction":_direction} : nil];
             }
         }

http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/5b99cc6b/ios/sdk/WeexSDK/Sources/Engine/WXSDKEngine.m
----------------------------------------------------------------------
diff --git a/ios/sdk/WeexSDK/Sources/Engine/WXSDKEngine.m 
b/ios/sdk/WeexSDK/Sources/Engine/WXSDKEngine.m
index cc9373b..e86221d 100644
--- a/ios/sdk/WeexSDK/Sources/Engine/WXSDKEngine.m
+++ b/ios/sdk/WeexSDK/Sources/Engine/WXSDKEngine.m
@@ -108,6 +108,10 @@
     [self registerComponent:@"textarea" 
withClass:NSClassFromString(@"WXTextAreaComponent")];
        [self registerComponent:@"canvas" 
withClass:NSClassFromString(@"WXCanvasComponent")];
     [self registerComponent:@"slider-neighbor" 
withClass:NSClassFromString(@"WXSliderNeighborComponent")];
+    
+    [self registerComponent:@"recycle-list" 
withClass:NSClassFromString(@"WXRecycleListComponent")];
+    [self registerComponent:@"cell-slot" 
withClass:NSClassFromString(@"WXCellSlotComponent") withProperties: 
@{@"append":@"tree", @"isTemplate":@YES}];
+    
 }
 
 + (void)registerComponent:(NSString *)name withClass:(Class)clazz

http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/5b99cc6b/ios/sdk/WeexSDK/Sources/Events/WXComponent+Events.m
----------------------------------------------------------------------
diff --git a/ios/sdk/WeexSDK/Sources/Events/WXComponent+Events.m 
b/ios/sdk/WeexSDK/Sources/Events/WXComponent+Events.m
index 8eb799b..6247f07 100644
--- a/ios/sdk/WeexSDK/Sources/Events/WXComponent+Events.m
+++ b/ios/sdk/WeexSDK/Sources/Events/WXComponent+Events.m
@@ -109,7 +109,10 @@
         [dict addEntriesFromDictionary:params];
     }
     
-    [[WXSDKManager bridgeMgr] fireEvent:self.weexInstance.instanceId 
ref:self.ref type:eventName params:dict domChanges:domChanges];
+    NSArray *handlerArguments = [self _paramsForEvent:eventName];
+    NSString *ref = _templateComponent ? _templateComponent.ref  : self.ref;
+    
+    [[WXSDKManager bridgeMgr] fireEvent:self.weexInstance.instanceId ref:ref 
type:eventName params:dict domChanges:domChanges 
handlerArguments:handlerArguments];
 }
 
 - (void)addEvent:(NSString *)addEventName

http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/5b99cc6b/ios/sdk/WeexSDK/Sources/Manager/WXBridgeManager.h
----------------------------------------------------------------------
diff --git a/ios/sdk/WeexSDK/Sources/Manager/WXBridgeManager.h 
b/ios/sdk/WeexSDK/Sources/Manager/WXBridgeManager.h
index fee0be7..7ff2e01 100644
--- a/ios/sdk/WeexSDK/Sources/Manager/WXBridgeManager.h
+++ b/ios/sdk/WeexSDK/Sources/Manager/WXBridgeManager.h
@@ -125,12 +125,23 @@ extern void WXPerformBlockOnBridgeThread(void (^block)());
  *  @param instanceId   instance id
  *  @param ref       :   node reference
  *  @param type      :   event type
- *  @param params    :   parameters
+ *  @param params    :   parameters in event object
  *  @param domChanges   dom value changes, used for two-way data binding
  **/
 - (void)fireEvent:(NSString *)instanceId ref:(NSString *)ref type:(NSString 
*)type params:(NSDictionary *)params domChanges:(NSDictionary *)domChanges;
 
 /**
+ *  FireEvent
+ *  @param instanceId   instance id
+ *  @param ref       :   node reference
+ *  @param type      :   event type
+ *  @param params    :   parameters in event object
+ *  @param domChanges:   dom value changes, used for two-way data binding
+ *  @param eventArguments : arguments passed to event handler
+ **/
+- (void)fireEvent:(NSString *)instanceId ref:(NSString *)ref type:(NSString 
*)type params:(NSDictionary *)params domChanges:(NSDictionary *)domChanges 
handlerArguments:(NSArray *)handlerArguments;
+
+/**
  *  callBack
  *
  *  @param instanceId instanceId

http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/5b99cc6b/ios/sdk/WeexSDK/Sources/Manager/WXBridgeManager.m
----------------------------------------------------------------------
diff --git a/ios/sdk/WeexSDK/Sources/Manager/WXBridgeManager.m 
b/ios/sdk/WeexSDK/Sources/Manager/WXBridgeManager.m
index 0bf5aca..791aceb 100644
--- a/ios/sdk/WeexSDK/Sources/Manager/WXBridgeManager.m
+++ b/ios/sdk/WeexSDK/Sources/Manager/WXBridgeManager.m
@@ -304,12 +304,22 @@ void WXPerformBlockOnBridgeThread(void (^block)())
 
 - (void)fireEvent:(NSString *)instanceId ref:(NSString *)ref type:(NSString 
*)type params:(NSDictionary *)params domChanges:(NSDictionary *)domChanges
 {
+    [self fireEvent:instanceId ref:ref type:type params:params 
domChanges:domChanges handlerArguments:nil];
+}
+
+- (void)fireEvent:(NSString *)instanceId ref:(NSString *)ref type:(NSString 
*)type params:(NSDictionary *)params domChanges:(NSDictionary *)domChanges 
handlerArguments:(NSArray *)handlerArguments
+{
     if (!type || !ref) {
         WXLogError(@"Event type and component ref should not be nil");
         return;
     }
     
     NSArray *args = @[ref, type, params?:@{}, domChanges?:@{}];
+    if (handlerArguments) {
+        NSMutableArray *newArgs = [args mutableCopy];
+        [newArgs addObject:@{@"params":handlerArguments}];
+        args = newArgs;
+    }
     WXSDKInstance *instance = [WXSDKManager instanceForID:instanceId];
     
     WXCallJSMethod *method = [[WXCallJSMethod alloc] initWithModuleName:nil 
methodName:@"fireEvent" arguments:args instance:instance];

http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/5b99cc6b/ios/sdk/WeexSDK/Sources/Manager/WXComponentFactory.h
----------------------------------------------------------------------
diff --git a/ios/sdk/WeexSDK/Sources/Manager/WXComponentFactory.h 
b/ios/sdk/WeexSDK/Sources/Manager/WXComponentFactory.h
index f2d2f6a..73b08e1 100644
--- a/ios/sdk/WeexSDK/Sources/Manager/WXComponentFactory.h
+++ b/ios/sdk/WeexSDK/Sources/Manager/WXComponentFactory.h
@@ -18,6 +18,16 @@
  */
 
 #import <Foundation/Foundation.h>
+#import "WXInvocationConfig.h"
+
+@interface WXComponentConfig : WXInvocationConfig
+
+@property (nonatomic, strong) NSDictionary *properties;
+
+- (instancetype)initWithName:(NSString *)name class:(NSString *)clazz 
pros:(NSDictionary *)pros;
+
+@end
+
 
 @interface WXComponentFactory : NSObject
 
@@ -53,6 +63,8 @@
  */
 + (Class)classWithComponentName:(NSString *)name;
 
++ (WXComponentConfig *)configWithComponentName:(NSString *)name;
+
 /**
  * @abstract Returns the registered components.
  */

http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/5b99cc6b/ios/sdk/WeexSDK/Sources/Manager/WXComponentFactory.m
----------------------------------------------------------------------
diff --git a/ios/sdk/WeexSDK/Sources/Manager/WXComponentFactory.m 
b/ios/sdk/WeexSDK/Sources/Manager/WXComponentFactory.m
index 6970376..0101d7a 100644
--- a/ios/sdk/WeexSDK/Sources/Manager/WXComponentFactory.m
+++ b/ios/sdk/WeexSDK/Sources/Manager/WXComponentFactory.m
@@ -20,17 +20,9 @@
 #import "WXComponentFactory.h"
 #import "WXAssert.h"
 #import "WXLog.h"
-#import "WXInvocationConfig.h"
 
 #import <objc/runtime.h>
 
-@interface WXComponentConfig : WXInvocationConfig
-@property (nonatomic, strong) NSDictionary *properties;
-
-- (instancetype)initWithName:(NSString *)name class:(NSString *)clazz 
pros:(NSDictionary *)pros;
-
-@end
-
 @implementation WXComponentConfig
 
 - (instancetype)initWithName:(NSString *)name class:(NSString *)clazz 
pros:(NSDictionary *)pros
@@ -82,7 +74,17 @@
 
 + (Class)classWithComponentName:(NSString *)name
 {
-    return [[self sharedInstance] classWithComponentName:name];
+    WXComponentConfig *config = [self configWithComponentName:name];
+    if(!config || !config.clazz) {
+        return nil;
+    }
+    
+    return NSClassFromString(config.clazz);
+}
+
++ (WXComponentConfig *)configWithComponentName:(NSString *)name
+{
+    return [[self sharedInstance] configWithComponentName:name];
 }
 
 + (void)registerComponent:(NSString *)name withClass:(Class)clazz 
withPros:(NSDictionary *)pros
@@ -195,9 +197,9 @@
     return componentDic;
 }
 
-- (Class)classWithComponentName:(NSString *)name
+- (WXComponentConfig *)configWithComponentName:(NSString *)name
 {
-    WXAssert(name, @"Can not find class for a nil component name");
+    WXAssert(name, @"Can not find config for a nil component name");
     
     WXComponentConfig *config = nil;
     
@@ -209,11 +211,7 @@
     }
     [_configLock unlock];
     
-    if(!config || !config.clazz) {
-        return nil;
-    }
-    
-    return NSClassFromString(config.clazz);
+    return config;
 }
 
 - (void)registerComponent:(NSString *)name withClass:(Class)clazz 
withPros:(NSDictionary *)pros

http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/5b99cc6b/ios/sdk/WeexSDK/Sources/Manager/WXComponentManager.h
----------------------------------------------------------------------
diff --git a/ios/sdk/WeexSDK/Sources/Manager/WXComponentManager.h 
b/ios/sdk/WeexSDK/Sources/Manager/WXComponentManager.h
index 729e6d7..ff5182c 100644
--- a/ios/sdk/WeexSDK/Sources/Manager/WXComponentManager.h
+++ b/ios/sdk/WeexSDK/Sources/Manager/WXComponentManager.h
@@ -24,8 +24,16 @@
 @class WXSDKInstance;
 @class WXComponent;
 
-extern void WXPerformBlockOnComponentThread(void (^block)());
-
+#ifdef __cplusplus
+extern "C" {
+#endif
+    
+void WXPerformBlockOnComponentThread(void (^block)());
+void WXPerformBlockSyncOnComponentThread(void (^block)());
+    
+#ifdef __cplusplus
+}
+#endif
 
 @interface WXComponentManager : NSObject
 
@@ -91,6 +99,7 @@ extern void WXPerformBlockOnComponentThread(void (^block)());
  */
 - (NSUInteger)numberOfComponents;
 
+- (void)addComponent:(WXComponent *)component toIndexDictForRef:(NSString 
*)ref;
 
 ///--------------------------------------
 /// @name Updating


Reply via email to