{ "id": "122849", "key": "TIMOB-15889", "fields": { "issuetype": { "id": "1", "description": "A problem which impairs or prevents the functions of the product.", "name": "Bug", "subtask": false }, "project": { "id": "10153", "key": "TIMOB", "name": "Titanium SDK/CLI", "projectCategory": { "id": "10100", "description": "Titanium and related SDKs used in application development", "name": "Client" } }, "fixVersions": [ { "id": "15927", "description": "2014 Sprint 04", "name": "2014 Sprint 04", "archived": true, "released": true, "releaseDate": "2014-02-28" }, { "id": "15929", "description": "2014 Sprint 04 API", "name": "2014 Sprint 04 API", "archived": true, "released": true, "releaseDate": "2014-02-28" }, { "id": "15857", "description": "Release 3.2.2", "name": "Release 3.2.2", "archived": false, "released": true, "releaseDate": "2014-03-09" }, { "id": "15422", "description": "Release 3.3.0", "name": "Release 3.3.0", "archived": false, "released": true, "releaseDate": "2014-07-16" } ], "resolution": { "id": "1", "description": "A fix for this issue is checked into the tree and tested.", "name": "Fixed" }, "resolutiondate": "2014-02-21T22:42:00.000+0000", "created": "2013-11-23T21:12:16.000+0000", "priority": { "name": "High", "id": "2" }, "labels": [ "module_listview", "qe-3.2.2", "qe-manualtest", "triage" ], "versions": [ { "id": "15593", "description": "Release 3.1.3", "name": "Release 3.1.3", "archived": true, "released": true, "releaseDate": "2013-09-18" } ], "issuelinks": [ { "id": "35218", "type": { "id": "10002", "name": "Duplicate", "inward": "is duplicated by", "outward": "duplicates" }, "inwardIssue": { "id": "122850", "key": "TIMOB-15885", "fields": { "summary": "iOS: Memory leak on iOS ListView when template contains event listener", "status": { "description": "The issue is considered finished, the resolution is correct. Issues which are closed can be reopened.", "name": "Closed", "id": "6", "statusCategory": { "id": 3, "key": "done", "colorName": "green", "name": "Done" } }, "priority": { "name": "Low", "id": "4" }, "issuetype": { "id": "1", "description": "A problem which impairs or prevents the functions of the product.", "name": "Bug", "subtask": false } } } } ], "assignee": { "name": "vduggal", "key": "vduggal", "displayName": "Vishal Duggal", "active": false, "timeZone": "America/Los_Angeles" }, "updated": "2014-08-08T17:30:45.000+0000", "status": { "description": "The issue is considered finished, the resolution is correct. Issues which are closed can be reopened.", "name": "Closed", "id": "6", "statusCategory": { "id": 3, "key": "done", "colorName": "green", "name": "Done" } }, "components": [ { "id": "10206", "name": "iOS", "description": "iOS Platform" } ], "description": "h2. Problem\r\n\r\niOS: Memory leak on iOS when items in section are changed on ListView after ListView has been created.\r\n(Not tested on Android.)\r\n\r\nh2. Test case\r\n\r\n* Run the app below in Instruments and profile memory usage\r\n* With every opening of the window containing the list view, 12 TiUILabelProxy and TiUIListItemProxy objects are allocated and never freed again.\r\n* Only setting all the sections again on the list view via \"listView.setSections()\" (Variant 4 in code below) prevents a memory leak\r\n\r\n\r\n{code:lang=javascript}\r\nvar baseWin = Ti.UI.createWindow({backgroundColor: 'red'});\r\n\r\nvar b = Ti.UI.createButton({\r\n\ttitle: 'Click',\r\n\tcolor: 'red',\r\n\tselectedColor: 'blue',\r\n\theight: '100dp',\r\n\twidth: '150dp',\r\n\tbackgroundImage: 'appicon.png',\r\n\tbackgroundColor: 'green',\r\n\tstyle: Titanium.UI.iPhone ? Titanium.UI.iPhone.SystemButtonStyle.PLAIN : 0 \r\n});\r\n\r\n\r\nb.addEventListener('click', function(evt) {\r\n\r\n\tvar win = Ti.UI.createWindow({backgroundColor: 'white'});\r\n\r\n\t\r\n\t// Create a custom template that displays an image on the left, \r\n\t// then a title next to it with a subtitle below it.\r\n\tvar myTemplate = {\r\n\t childTemplates: [\r\n\t { // Title \r\n\t type: 'Ti.UI.Label', // Use a label for the title \r\n\t bindId: 'info', // Maps to a custom info property of the item data\r\n\t properties: { // Sets the label properties\r\n\t color: 'black',\r\n\t font: { fontFamily:'Arial', fontSize: '20dp', fontWeight:'bold' },\r\n\t left: '60dp', top: 0,\r\n\t }\r\n\t },\r\n\t { // Subtitle\r\n\t type: 'Ti.UI.Label', // Use a label for the subtitle\r\n\t bindId: 'es_info', // Maps to a custom es_info property of the item data\r\n\t properties: { // Sets the label properties\r\n\t color: 'gray',\r\n\t font: { fontFamily:'Arial', fontSize: '14dp' },\r\n\t left: '60dp', top: '25dp',\r\n\t }\r\n\t }\r\n\t ]\r\n\t};\r\n\t\r\n\tvar listView = Ti.UI.createListView({\r\n\t // Maps myTemplate dictionary to 'template' string\r\n\t templates: { 'template': myTemplate },\r\n\t // Use 'template', that is, the myTemplate dict created earlier\r\n\t // for all items as long as the template property is not defined for an item.\r\n\t defaultItemTemplate: 'template'\r\n\t});\r\n\tvar sections = [];\r\n\t\r\n\tvar fruitSection = Ti.UI.createListSection({ headerTitle: 'Fruits / Frutas'});\r\n\tvar fruitDataSet = [\r\n\t // the text property of info maps to the text property of the title label\r\n\t // the text property of es_info maps to text property of the subtitle label\r\n\t // the image property of pic maps to the image property of the image view\r\n\t { info: {text: 'Apple'}, es_info: {text: 'Manzana'}, pic: {image: 'apple.png'}},\r\n\t { info: {text: 'Banana'}, es_info: {text: 'Banana'}, pic: {image: 'banana.png'}}\r\n\t];\r\n\tfruitSection.setItems(fruitDataSet);\r\n\tsections.push(fruitSection);\r\n\t\r\n\tvar vegSection = Ti.UI.createListSection({ headerTitle: 'Vegetables / Verduras'});\r\n\tvar vegDataSet = [\r\n\t { info: {text: 'Carrot'}, es_info: {text: 'Zanahoria'}, pic: {image: 'carrot.png'}},\r\n\t { info: {text: 'Potato'}, es_info: {text: 'Patata'}, pic: {image: 'potato.png'}}\r\n\t];\r\n\tvegSection.setItems(vegDataSet);\r\n\tsections.push(vegSection);\r\n\t\r\n\tvar grainSection = Ti.UI.createListSection({ headerTitle: 'Grains / Granos'});\r\n\tsections.push(grainSection);\r\n\tlistView.setSections(sections);\r\n\t\r\n\tvar grainDataSet = [\r\n\t { info: {text: 'Corn'}, es_info: {text: 'Maiz'}, pic: {image: 'corn.png'}},\r\n\t { info: {text: 'Rice'}, es_info: {text: 'Arroz'}, pic: {image: 'rice.png'}}\r\n\t];\r\n\t\r\n\t// VARIANT 1: CAUSES MEMORY LEAK\r\n\tgrainSection.setItems(grainDataSet);\r\n\t\r\n\t// VARIANT 2: CAUSES MEMORY LEAK\r\n\t//grainSection.replaceItemsAt(0, 99, grainDataSet);\r\n\t\r\n\t// VARIANT 3: CAUSES MEMORY LEAK\r\n\t/*\r\n\tvar grainSection2 = Ti.UI.createListSection({ headerTitle: 'Grains / Granos'});\r\n\tgrainSection2.setItems(grainDataSet);\r\n\tlistView.replaceSectionAt(2, grainSection2);\r\n\t*/\r\n\t\r\n\t// VARIANT 4: NO MEMORY LEAK\r\n\t/*\r\n\tvar grainSection2 = Ti.UI.createListSection({ headerTitle: 'Grains / Granos'});\r\n\tgrainSection2.setItems(grainDataSet);\t\r\n\tsections = [fruitSection, vegSection, grainSection2];\r\n\tlistView.setSections(sections);\r\n\t*/\r\n\t\r\n\t\r\n\tvar button = Ti.UI.createButton({\r\n\t\ttitle: 'Close Window',\r\n\t\tcolor: 'black',\r\n\t\theight: '100dp',\r\n\t\twidth: '150dp',\r\n\t\tbackgroundColor: 'green',\r\n\t});\r\n\t\r\n\tbutton.addEventListener('click', function(evt) {\r\n\t\twin.close();\r\n\t});\t\r\n\t\r\n\twin.add(listView);\r\n\twin.add(button);\r\n\twin.open();\r\n\t\r\n});\r\n\r\nbaseWin.add(b);\r\nbaseWin.open();\r\n{code}\r\n\r\n", "attachment": [ { "id": "45703", "filename": "app.js.zip", "author": { "name": "skypanther", "key": "skypanther", "displayName": "Tim Poulsen", "active": true, "timeZone": "America/New_York" }, "created": "2014-02-07T18:49:32.000+0000", "size": 764, "mimeType": "application/zip" }, { "id": "45702", "filename": "app.zip", "author": { "name": "skypanther", "key": "skypanther", "displayName": "Tim Poulsen", "active": true, "timeZone": "America/New_York" }, "created": "2014-02-07T18:49:32.000+0000", "size": 3262631, "mimeType": "application/zip" } ], "flagged": false, "summary": "iOS: Memory leak on iOS when items in section are changed on ListView after ListView has been created", "creator": { "name": "philet", "key": "philet", "displayName": "Philippe Wueger", "active": true, "timeZone": "Europe/Berlin" }, "subtasks": [], "reporter": { "name": "philet", "key": "philet", "displayName": "Philippe Wueger", "active": true, "timeZone": "Europe/Berlin" }, "environment": "Titanium 3.1.3\r\niOS SDK 7.0\r\n", "comment": { "comments": [ { "id": "281358", "author": { "name": "mpmiranda", "key": "mpmiranda", "displayName": "Mauro Parra-Miranda", "active": true, "timeZone": "America/Mexico_City" }, "body": "Hello!\n\nCan you please remove the elements of the window, then null them after closing it? Check this sample:\n\nhttps://gist.github.com/mauropm/5923980\n\nRight now, your code says \"I will close this window, but I didn't null it, so I can reopen it if I want\".\n\nBest,\n\nMauro", "updateAuthor": { "name": "mpmiranda", "key": "mpmiranda", "displayName": "Mauro Parra-Miranda", "active": true, "timeZone": "America/Mexico_City" }, "created": "2013-11-26T06:46:29.000+0000", "updated": "2013-11-26T06:46:29.000+0000" }, { "id": "281383", "author": { "name": "philet", "key": "philet", "displayName": "Philippe Wueger", "active": true, "timeZone": "Europe/Berlin" }, "body": "Hi Mauro\n\nThanks for your input. I have adjusted my test app in the following way:\n\n{code}\n\tbutton.addEventListener('click', function(evt) {\n\t\twin.close();\n\t\twin.remove(listView);\n\t\tlistView = null;\n\t\twin.remove(button);\n\t\tbutton = null;\n\t\twin = null;\n\t});\n{code}\n\nThe memory leak however still exists.\n\nAny other suggestions?\n\nMany thanks & regards,\nPhilippe\n", "updateAuthor": { "name": "philet", "key": "philet", "displayName": "Philippe Wueger", "active": true, "timeZone": "Europe/Berlin" }, "created": "2013-11-26T10:18:53.000+0000", "updated": "2013-11-26T10:18:53.000+0000" }, { "id": "281414", "author": { "name": "mpmiranda", "key": "mpmiranda", "displayName": "Mauro Parra-Miranda", "active": true, "timeZone": "America/Mexico_City" }, "body": "Hello,\n\nthis looks like a DUP of TC-3326 :) \n\nIn the second example of click, please remove the event from the label before nulling it. Remember: in order to remove an event, you have not to use anonymous functions, but named ones, so it will be easier. Right now your object is holding in memory because it does have an event listener attached.\n\nBTW, the GC won't usually kick in all time. You need to repeat your testing a lot of times, so the memory reach a memory warning or similar, so that will force the GC to clean the memory.\n\nBest,\n\nMauro", "updateAuthor": { "name": "mpmiranda", "key": "mpmiranda", "displayName": "Mauro Parra-Miranda", "active": true, "timeZone": "America/Mexico_City" }, "created": "2013-11-26T15:11:19.000+0000", "updated": "2013-11-26T15:11:19.000+0000" }, { "id": "281418", "author": { "name": "philet", "key": "philet", "displayName": "Philippe Wueger", "active": true, "timeZone": "Europe/Berlin" }, "body": "Hello Mauro\n\nNo, this is not a duplicate. This is a slightly different case. Here, I update items in the ListView after the ListView itself has been created (see sections VARIANT 1-4 in the code). I do not have a 'click' event listener on the template here.\n\nCleaning up the view did unfortunately not fix the memory leak.\n\n{quote}\nBTW, the GC won't usually kick in all time. You need to repeat your testing a lot of times, so the memory reach a memory warning or similar, so that will force the GC to clean the memory.\n{quote}\nI am aware of this. I have tested it quite extensively in my opinion.\n\nDo you have any further inputs?\n\nMany thanks & best regards,\nPhilippe\n", "updateAuthor": { "name": "philet", "key": "philet", "displayName": "Philippe Wueger", "active": true, "timeZone": "Europe/Berlin" }, "created": "2013-11-26T15:47:22.000+0000", "updated": "2013-11-26T15:47:22.000+0000" }, { "id": "291940", "author": { "name": "skypanther", "key": "skypanther", "displayName": "Tim Poulsen", "active": true, "timeZone": "America/New_York" }, "body": "I can confirm a memory leak exists with the ListView when you update sections. I've attached two files. \r\n\r\n1) Create a default Alloy project. Replace its app folder with the contents of app.zip\r\n\r\n2) Create a \"classic\" project. Replace its app.js file with the contents of the app.js.zip file.\r\n\r\nRun either and monitor with instruments. You will see the allocation of TiUILabelProxy, TiUIListItemProxy, and TiUILabel grow continuously as you repeatedly click Load List and Clear List.\r\n\r\nIssue was raised today in the Q&A at \r\nhttp://developer.appcelerator.com/question/162348/alloy-listview-memory-leak", "updateAuthor": { "name": "skypanther", "key": "skypanther", "displayName": "Tim Poulsen", "active": true, "timeZone": "America/New_York" }, "created": "2014-02-07T18:49:32.000+0000", "updated": "2014-02-07T18:49:32.000+0000" }, { "id": "292142", "author": { "name": "ffadaie", "key": "ffadaie", "displayName": "farid fadaie", "active": true, "timeZone": "America/Los_Angeles" }, "body": "I can also confirm this. The bad thing is, even though this belongs to a separate Window and a separate Alloy Controller, the memory is not released after the Window is closed and the controller is nulled. The memory that was allocated by ListView seems not to be released at all regardless of what I do (nulling the listview, the window, etc).\r\n\r\nConsequently, Alloy binding is also problematic. If you bind any Alloy collection to a ListView, it will have memory leak.", "updateAuthor": { "name": "ffadaie", "key": "ffadaie", "displayName": "farid fadaie", "active": true, "timeZone": "America/Los_Angeles" }, "created": "2014-02-10T16:04:27.000+0000", "updated": "2014-02-11T17:00:07.000+0000" }, { "id": "292885", "author": { "name": "ffadaie", "key": "ffadaie", "displayName": "farid fadaie", "active": true, "timeZone": "America/Los_Angeles" }, "body": "I think this is a critical bug. You cannot really use Listviews because of the leak unless it's static. The app crashes pretty fast if your ListItems are bulky (TiUIListItem, TiUIListItemProxy, TiUIListSectionProxy, etc are never released even when you remove all the references). It is also buggy in 3.2.0", "updateAuthor": { "name": "ffadaie", "key": "ffadaie", "displayName": "farid fadaie", "active": true, "timeZone": "America/Los_Angeles" }, "created": "2014-02-13T08:55:30.000+0000", "updated": "2014-02-13T20:37:51.000+0000" }, { "id": "293661", "author": { "name": "vduggal", "key": "vduggal", "displayName": "Vishal Duggal", "active": false, "timeZone": "America/Los_Angeles" }, "body": "Pull pending \r\nmaster - https://github.com/appcelerator/titanium_mobile/pull/5358\r\n3_2_X - https://github.com/appcelerator/titanium_mobile/pull/5359", "updateAuthor": { "name": "vduggal", "key": "vduggal", "displayName": "Vishal Duggal", "active": false, "timeZone": "America/Los_Angeles" }, "created": "2014-02-18T22:44:24.000+0000", "updated": "2014-02-18T22:49:17.000+0000" }, { "id": "294413", "author": { "name": "viezel", "key": "viezel", "displayName": "Mads Moller", "active": true, "timeZone": "Europe/Berlin" }, "body": "@Vishal,\r\n\r\nI dont think this is fixed. I have major memory leaks in my app, using listview bindings. I tried to use 3.2.2.v20140221161255, which should include this fix right?\r\n\r\nHere is my leaking use case: https://gist.github.com/viezel/07d4ac387609bba3630d\r\n\r\nThats still a bug...", "updateAuthor": { "name": "viezel", "key": "viezel", "displayName": "Mads Moller", "active": true, "timeZone": "Europe/Berlin" }, "created": "2014-02-24T18:49:59.000+0000", "updated": "2014-02-24T18:56:16.000+0000" }, { "id": "294496", "author": { "name": "srahim", "key": "srahim", "displayName": "Sabil Rahim", "active": true, "timeZone": "America/Los_Angeles" }, "body": "[`viezel]the memory leak we fixed was against test case reported in this bug. \n\nWe are not sure if you are encountering the same issue as reported in this bug. We cannot determine what your exact memory leak case is from The excerpt of test case you have pasted above. If you could give us a more complete test case, we would be able to track down the bug more precisely.\n\nCheers \nSabil ", "updateAuthor": { "name": "srahim", "key": "srahim", "displayName": "Sabil Rahim", "active": true, "timeZone": "America/Los_Angeles" }, "created": "2014-02-24T22:38:20.000+0000", "updated": "2014-02-24T22:38:20.000+0000" }, { "id": "294542", "author": { "name": "wluu", "key": "wluu", "displayName": "Wilson Luu", "active": false, "timeZone": "America/Los_Angeles" }, "body": "Closing ticket as fixed. Using the app.js.zip attachment, I was able to verify TiUILabelProxy, TiUIListItemProxy, and TiUILabel will not grow continuously via Instruments.\n\nTested on:\n\nAppcelerator Studio, build: 3.2.1.201402061120\nSDK build: 3.2.2.v20140221161255, 3.3.0.v20140224114904\nCLI: 3.2.1\nAlloy: 1.3.1\nXcode: 5.0.2, 5.1 beta 5\nDevices: iphone 4s (6.0.1), iphone 4 (7.0.6), iphone 5 (7.1) ", "updateAuthor": { "name": "wluu", "key": "wluu", "displayName": "Wilson Luu", "active": false, "timeZone": "America/Los_Angeles" }, "created": "2014-02-25T01:45:47.000+0000", "updated": "2014-02-25T01:45:47.000+0000" }, { "id": "295245", "author": { "name": "magnatronus", "key": "magnatronus", "displayName": "Steve Rogers", "active": true, "timeZone": "Europe/London" }, "body": "I have just tried the Q&A test case that Tim referenced (http://developer.appcelerator.com/question/162348/alloy-listview-memory-leak) - this was an entry that I raised.\r\n\r\nAnd in my Alloy project I still get the same results with Instruments.\r\n\r\nI am testing with\r\n\r\nApp Studio: 3.2.1.201402041146 (vanilla install on a clean machine)\r\nSDK:3.2.2.v20140214120903.802eaf8\r\nXCODE:5.0.2 (5A3005)\r\nDevice: Iphone 3.5 inch Retina (emulator - 7.0.3)\r\n", "updateAuthor": { "name": "magnatronus", "key": "magnatronus", "displayName": "Steve Rogers", "active": true, "timeZone": "Europe/London" }, "created": "2014-03-03T12:05:06.000+0000", "updated": "2014-03-03T12:05:06.000+0000" }, { "id": "295246", "author": { "name": "magnatronus", "key": "magnatronus", "displayName": "Steve Rogers", "active": true, "timeZone": "Europe/London" }, "body": "My project can be found in Git - https://github.com/magnatronus/listviewleak", "updateAuthor": { "name": "magnatronus", "key": "magnatronus", "displayName": "Steve Rogers", "active": true, "timeZone": "Europe/London" }, "created": "2014-03-03T12:18:37.000+0000", "updated": "2014-03-03T12:18:37.000+0000" }, { "id": "295248", "author": { "name": "viezel", "key": "viezel", "displayName": "Mads Moller", "active": true, "timeZone": "Europe/Berlin" }, "body": "@Sabil - Stene Rogers repo also covers my leak. ", "updateAuthor": { "name": "viezel", "key": "viezel", "displayName": "Mads Moller", "active": true, "timeZone": "Europe/Berlin" }, "created": "2014-03-03T12:36:22.000+0000", "updated": "2014-03-03T12:36:22.000+0000" }, { "id": "295315", "author": { "name": "wluu", "key": "wluu", "displayName": "Wilson Luu", "active": false, "timeZone": "America/Los_Angeles" }, "body": "[~viezel], [~magnatronus]: Were you guys able to try with the latest 3.2.2 SDK build (3.2.2.v20140221161255)?", "updateAuthor": { "name": "wluu", "key": "wluu", "displayName": "Wilson Luu", "active": false, "timeZone": "America/Los_Angeles" }, "created": "2014-03-03T19:42:58.000+0000", "updated": "2014-03-03T19:42:58.000+0000" }, { "id": "295475", "author": { "name": "magnatronus", "key": "magnatronus", "displayName": "Steve Rogers", "active": true, "timeZone": "Europe/London" }, "body": "Hi, just tried my test project with SDK 3.2.2.v20140221161255 and Instruments now shows that no increase in the 'Live' proxy objects :-)", "updateAuthor": { "name": "magnatronus", "key": "magnatronus", "displayName": "Steve Rogers", "active": true, "timeZone": "Europe/London" }, "created": "2014-03-04T07:43:47.000+0000", "updated": "2014-03-04T07:43:47.000+0000" }, { "id": "295485", "author": { "name": "viezel", "key": "viezel", "displayName": "Mads Moller", "active": true, "timeZone": "Europe/Berlin" }, "body": "Wilson Luu - Nope its not working with 3.2.2.v20140221161255.\r\nIve created a test app with a simple test case: https://dl.dropboxusercontent.com/u/2132088/LeakApp.zip\r\n\r\nThis is the result: http://postimg.org/image/qcw3ktm3t/ \r\nIts just keeps leaking..\r\n", "updateAuthor": { "name": "viezel", "key": "viezel", "displayName": "Mads Moller", "active": true, "timeZone": "Europe/Berlin" }, "created": "2014-03-04T08:52:45.000+0000", "updated": "2014-03-04T08:52:45.000+0000" }, { "id": "295548", "author": { "name": "wluu", "key": "wluu", "displayName": "Wilson Luu", "active": false, "timeZone": "America/Los_Angeles" }, "body": "[~viezel] I am able to reproduce your issue now! It looks like the issue at hand applies only to iOS simulators and not on iOS devices. \n\nI will file another ticket for your issue later today and link it to this ticket.", "updateAuthor": { "name": "wluu", "key": "wluu", "displayName": "Wilson Luu", "active": false, "timeZone": "America/Los_Angeles" }, "created": "2014-03-04T18:29:12.000+0000", "updated": "2014-03-04T18:29:12.000+0000" }, { "id": "295706", "author": { "name": "wluu", "key": "wluu", "displayName": "Wilson Luu", "active": false, "timeZone": "America/Los_Angeles" }, "body": "[~viezel] While I was in the process of writing your ticket, I was not able to reproduce your issue any more with your app on iOS simulator. The only difference from this morning and this evening is the text are not appearing in the ListView (see https://db.tt/GifZiOlp).\n\nOn the side of precaution, I tried reproducing the issue using Steve's app (https://github.com/magnatronus/listviewleak) on an iOS simulator, and still was not able to reproduce the memory leak issue that I saw this morning or the original bug behavior.", "updateAuthor": { "name": "wluu", "key": "wluu", "displayName": "Wilson Luu", "active": false, "timeZone": "America/Los_Angeles" }, "created": "2014-03-05T03:39:27.000+0000", "updated": "2014-03-05T03:39:27.000+0000" } ], "maxResults": 21, "total": 21, "startAt": 0 } } }