From ead0f2e020d16154a11e6d07355ee2ecc0fce6e2 Mon Sep 17 00:00:00 2001 From: Nick Lockwood Date: Sat, 18 Apr 2015 10:43:20 -0700 Subject: [PATCH] Implemented thread control for exported methods --- .../testTabBarExampleSnapshot_1@2x.png | Bin 28887 -> 27640 bytes .../UIExplorerTests/UIExplorerTests.m | 12 +- .../ActionSheetIOS/RCTActionSheetManager.m | 120 +++++++------- .../RCTAnimationExperimentalManager.m | 5 + Libraries/Geolocation/RCTLocationObserver.m | 130 +++++++-------- Libraries/LinkingIOS/RCTLinkingManager.m | 6 +- Libraries/RCTTest/RCTTestModule.h | 22 ++- Libraries/RCTTest/RCTTestModule.m | 41 ++--- Libraries/RCTTest/RCTTestRunner.m | 41 ++--- React/Base/RCTBridge.h | 7 - React/Base/RCTBridge.m | 52 ++++-- React/Base/RCTBridgeModule.h | 17 ++ React/Base/RCTConvert.h | 4 +- React/Base/RCTConvert.m | 6 +- React/Modules/RCTAlertManager.m | 49 +++--- React/Modules/RCTAsyncLocalStorage.m | 155 ++++++++---------- React/Modules/RCTExceptionsManager.m | 6 +- React/Modules/RCTSourceCode.m | 1 - React/Modules/RCTStatusBarManager.m | 34 ++-- React/Modules/RCTTiming.m | 16 +- React/Modules/RCTUIManager.m | 34 ++-- React/Views/RCTViewManager.h | 23 +-- React/Views/RCTViewManager.m | 6 + 23 files changed, 384 insertions(+), 403 deletions(-) diff --git a/Examples/UIExplorer/UIExplorerTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp/testTabBarExampleSnapshot_1@2x.png b/Examples/UIExplorer/UIExplorerTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp/testTabBarExampleSnapshot_1@2x.png index d3e66652b5c01bcdc1c08114cfea10fba8d5fe05..6fa9c15557705009bd5720dee3e2e02fdcdf88fb 100644 GIT binary patch literal 27640 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 literal 28887 zcmeHw2UJtp`tJc0bO3S0f>ae13j%^PDZz0R3xW;;(osNEnv~E}lu?vAic+LRML_`x zMd?XUNRS#8A@pEkg47TK36S;<&b@EuKX2y#uiSaFX6_o-I=aru{?6WC|9#)y>v;2| zrMdK1@?QY}Abs@6x8DJPC=vi_Gt+{D(2_=p4k?JLc^|Hs~V;|nQig9~+ z-FfZlZ%_N|h`k|w>eSj(r%ta`*tP4-a0TiL!$4if(GUHUI80?meB?yeu)2-9Ic<}z z0ty1b!&%(kdK7|!e>nWZ)}z|MwxhQ_fMd6}HUW3G9=$EH+n1~3XRG*0 z9{+|Ke}zsuicvcq$Q}qKzZmcH$2OTdEQGL|lY>exZpgf=gnJutkUigC;~ya~+!IA9 zNgOmaC5IrewkA!P33galvb8&)kC$iU~@zn2P_TQe zv9(lAsH^jxgzmYDdN`0>UQd64DB`Tgr63B4rVGVv-}dfsZ_tcIc8ug10%Sx`fVn>e zK^McXsAWEKkKqG8of(?OuJL4nz*yQ0bwuA%ai7`c2!j_G?FumuHmW ziES-0gM3+yHp1ld&L5=f=5)x~A*Thy&NDNzJ-3&$%2HKOStfxU&>p}z~eF_@{xy~(@I zmm6FP!y{HTo4R6ar}Ko$ADsU7)<%-kU?AWUJmk`oih?=J zV_o5Fd%X{z>Z3G|fj%Tn76~KYObP zp<(*LCkAEZly|$BQLh+SzMhaT=VWD>aa(0`M#SL8`Nyu<`O$)LE(1q2wPz3Bm#&km zDZtJ}&EDLi0{=m!sg|F)w+E`4*gRPk>?D9U%^l=CQ$(Wl1~vVXEAO-i=}fi=jguQQ z@0bKM`r>!Qx8NI2S&@@IOW+ca*n26*XJIa2euZRQc0^QGMYy}541WrpTrtKWa;P8iuW*@tFfQx!`Y4(1ebcYtuPb|gq1)8^VW&$q7V z+ozJdS>R(MrWG|yOYQkSXVo)fhlLM){xttG3N@)zR&O4|IJ#&fFBOo?)!tGM>4Om? zcwRLkVA*c_sZaGiH!}ohpC#!8yk~lwwl`DeBW&=OC7CDv7GCrbH#$a*+secczJB>q z5$TVs-6Z7qR9F=9^W604{*GOy z!_Nk^YQkq$&iCPOmEFql|3T;w)jGwjIbP8WtRio#cBNtW)knjChL614-mhw)v|BMB z85x#}7iUj;@3LIAu~kG)t-97=>;WZ6J>AF=E=HjZa zlkDsEUU-*Y5CE&48B@WOEqGiv_^k2uZVV}iaD?ao*z-vR!r&>&?iXXrBW`z$oVBlcUmQ!q6G-ei8%HVKr`dkbKJvO>jfkB2iawY!u|LvIP_9z@qJi`1 zbASV_qu4iKXKFoL6Aq(G+YRH^dr4L7k6kD8w7m)zNWE*iou&qDK%49zAy8JFkwRB< z66`u|s3t}41<;TWI6Nw-A6`R($0wy?I|2b)XVqdS#X~1ZV&fk_oLfx5(hGobC^{?l zY|y}s9E6;gk-u~1+P$;=9lOb#N@-F{3Z^71D+k706ELzYSd9(b6bIOIF%38{| zxrg?bPV=c-NJka%1>jNx!!C}OM+i`M%Wf@dDn(3-aB_`|L@I*uwn;W_Sc1Unedp;i z{h>Roa_;8SiuDWy^g1VKpt)(T{6S5P(o8txxssl%H&2=XC?Y<=yd9sE1w#vx-Mn{P;WR_KRyP>%~x!q}=9xqLUl-{!wo)q8esVGDxxL*Xnbxp73_iRomA zcTJmUI#(GeEmPbDsAA`C*Dc!Yy7^rgW@x%T_h4s}qhFeHSY39HXN*c(R_QF^u3BKjO=V_8(=$^` zV~N0i0fMNhY7M{P_^SZyr@X#Y;I>pcP;37K8UEsS2@8`l)4#eT*!fGm@}I-U&$#a= zG`Un5xKz5|pP`LNzZGAK=6|`0&+=FreEc~8)&>Sv{13}nf~kBq+NIL{LMy+x{Z+32 z7bp48ZU1*-$OEYU`nOB9M=Gb({Hoz0lP*S{UK8ccA>-n>;EW^Mz~hhv5KWC2#SvTR zgF#;GuH%~LK~QC!p*wPYyqB67%#}($*B!+f2o2w$^1PzY<3ye>&Yn#vj43S@EMS!? zgygtBUZzQ?qAg;wWfi1&uK(d2x3`Ups^GMHV}v@KfeG%@xNNWtfY)I37#AyIq|EpR zHS})P(se3`JDv7g+*{*!p6Q1;N=J*NIYqsJ^l$f$0P}QgA1wooZN|>cBqvG?ZIfUm z?{R*v<^SGQSliBU4XabeHzwx9)%yY|&lZ79|Mf8_Pf+HEg+JqO_qh0?~7`IJ-9^wHa(=CxO*;cE`B}cYIcT zOA;|8awD?7$XY&UPxvC9|5`z)C!BQ`&J-jLB3_+}S(@jtT;mzwRdd>;uCl0 zsycnwOq8%k85I#HvP10TE)Id`#X>aL7M;1}T+EmRQF~6ZpZeq`u!xB7YXuZ-ok28? z_to~p4b;GesAhoY(Gxi@Bpm#Zr8FzF60lv-4(TvKQ=r%z;Eu=>l(lBYci|^~XqaFHDIs-=sItk3OM-I4}Xk$E2ubM>**+1S9h#HTaHLVp(!d(g55Qssm7|QM}1_JYk~n za7|2GM~7*V0c9vr+^xbD^>QxKj+BP;O&5md1bYNGT?HYh4S(RKN^l3c=a3cT77k`S zLA#YvRABNUKO7*SIEgHxx?qr)u5FkR-20lhbsI-t8_+F`Mcn_!c-i~O_ELl9QZ8Ew zHeMj$qIHS8dB#E^qjoBDv8vKEZ3n9-m$BDfeUh&ECM!M9QxkJjAzu2Z2G%bh{hRP!d9U7RR2TRuv9p8oW~%WZH;L zfknB$RP=^R`7pu86D;bqoOfYV@2qnVtdqPhJq z0>LV+XqKHSDbF#PtGQ*VdRXi1&BhHlZo}Nk|&9neMvsHH_ zx_Rj`A9K_Q{)+ao9BCAM3LZlD>$4q9Wa0wM*O+KCtH#pz+0%jg<5Q2CBJCI`bt z%@)VuqDM&0C@-g8HNL(ncPg59BRt9nn~AQOEm)@L-_ z%VV(?C!1beOQ4+tn;Y-=iBe|L$-|AcQ@l~5=Y)gddy?MpU@;VxmvY+%;)^PS;t#A5 zz9!UfZcBZ7MD0Pt5yS8}$TL;|C2y9pqV-R!h9?GVPTR%f_Z>dBud(|AJYz158GbEkFx)#U=AH!05V@J~azA8~QmOyM zsV)~Rx>^az^u9RW*yL!#kgeA`8lCS4uoq@Ge!_+FnTmB`H}sXHm40jy%++Ly!&$fL z_W|{GuJCk(rj63wB-Karo)PlI$EIhK$u*2yB2OMLvUFuO5TF7F(-^CDNYc?HN{*=8!ni+)8thoK(}uSNTEpxnC8d>nk`ktY!bm?C z#XZL;RhG1X)JZdHxcLkBr_BDc%IIecjG4ei^LxaU4{d;=`r^vY(;-J1dLC!Nohgw! zt0r2ly~!HcUMUs|p=_|{WDD5=o9epF^QGKyL%>{TJpsb4yr^Q`#3&nQ*{Qcp1t_8d zMC*|&SPo*~F!2S3GeW8^Y(}+#l{sd5*qFZs5Fe%e*^Q>{b~$ z3icz=lYB}Q;;8ZP4t_-c&X9;cf^2JnDbWRtLs-iZL-!HOVqErnWg%u}%B5zEjj5P> zLHR&>0Jb-{ov_TXZUc3=p9}6sX5P{agLX>nrxL<6Myk@J`5fzsa3w`&r>Aw z(RR<8A%y;WB62ua=zEM4?=p$U-@FkiU;56foN4?d#Oj?D<%n%X&hBihj!_l7XPm2M zHp<#Y@qq=U%8!Xo*dQdN2}NH+e2}11+YzMbC|wtp==Wn2#uh@H%8?oPtgH=c3c=IX7o9d$kG5hg zCEWUN+G5(8iFO_XvBOfd&`N86-}#V$HHY$0$K&ySc$Qj*gq!gdOdr^sxwuu$zv^sD z-@e3J8|k`#sOj~0#uj$7K7x)&OsTiMAgcEl&ZcubL%=*@5y}4e8^}bPYw;J=44-GEv zFbwFJ9S|2Pq<{c4f!ruu4{Bvhs11Nuis)^EU0 zeEie_wuJc~EK4aCRUu+iAo}$S-+0eUrP9kxm^opZYLHygH>POIH(byLTC7$ljio~O z+ct|8r}mX3qpb@L>n3X9-*y>HCl{?Gjc{|17eC)WzioCp)7;Lr^hp5aU6SW#iG%qM zyWMU7yUZd!sR`s@Oy>=P5ut<(c*zc4nG<%QYEo(-L`*Ajbvf6Va&>b4=IMmwYoGo4 zhg6N1Kj7LKH)qi-1;~e7?~Aj;#W#*;e^j&Dm+jg$nnww{v0WJ+Z@2nW%Wli(Tqnxr z^kDNhli$WyhB!oN(A2s)+JL*+V_`_m@Xoe9m`jJk0d}BKd5te+-5Sa#%eBO$OQHJ= zg$7EsYwhRb-y6hskB-yk!bU#4u2kihNP)J2j@XC{pu<6fVPMOjgf&&tldSdsDQ*`D zxA|KMw&;(y{ZLhV+C}}ahuv7@2f-#_YfmEu%uQhd;!f2oF2%M`H3qb%O z>vY8J+4n(KXxQt$8uA&g5`|!lGE)?3B~miW7u4Is44+l2iqK*To6X0->1jr{D{~UO zHEc16_@rsscP&3^pl|ztNc+9ugA*vtMA6)_2R6>|NBLpUn3-aaf(`GR(mynXln2u?d zuy)H0kdq8bxGwE@)E(4)MIz_86*4hZC%-y+eD(o8_|9hKZ{l-gX)U!4_9e7r6N;{% z#3g{hf>Fj_^L%;3{Z#Q_92RTSuBTDynH*+5!z?ds}7Gi zocUD26*m`|CP;{5^quS1wT#~>^JWf5B@$PF8i+SIjWy-i1`|p5nu@YX!*Y9J#vk+tUm&)x55O=XxOt7dggSdWGG%&nI zM)R&~qkiIfyps96?lI~mK^|j<1Xn_yax$xmy6)@bs4nOmk@pDro-x>>nXiO|VU&!j zd?|^QJcnYY$r_oRgfU9)ht%wET|<|Z3C1b2EF*I1+~9F^_DN+G86q0*9KV@+o$=~RCYfu-v{^t|S>Uqyv`Ud0KXOMGMghT0^eetULdgi^m{T+IBr(IO~a{3O^Vg~)Ft+tH3UJSv;*0OD2dQ_B;^&gr=x+iuzpZf>!L zMXJ#tXgcf6in^3dk%zIs1tBQa#b2VVe%+wzds1NwN6AU}eWm z;d$ed+DXgECw`3vm(~!a;~2N(l=!f|hQ`oxv~T8RI_0g&I7p}+S7e(M0vf4;%-?}V^_ zqt*YrIP-tQ`Iw)FqyK#0=y!}x-~36U)8Eeg|I?ZMmi@8Qzlme!Z)?LWs(V08NaPjd7C+^PTDnSa$Ozsx9p-I@PB zdhqK9xqnSsUshHv)tMg^iMxpdb;MYa6@VKc`o)(Vc&2_Sm-r?C_`(YbE^#T>pPa2l zG5k?%EsEj~($>=1s$K~IQf6O%iLC~JC0upDGT=)uEkFcV%JnB_Yf%h;6kCg;_=B|d z7hr2q6klqDOJ!?O!+&x^)B*;UeQ|zRYQ$L#zJKx@v6TF)D_0RFDie=253F~`b%dQu`Qwzb$S{vB#xs_0nm3P z9abW(k3BdziABL$80)|`-3l`RSls#qH)qVi{ku(I(ZBj1kC%b2Aqw*R8$bWXEH7Hg_-?vTKa<$ z-s=wp+hFxyeI2-6Hpzj=k6lp!iJ}5b+i8W%zb|?hA!DitO&# za~1eQC1cn_B`wN_n7spW6kU|R<$NeSr6n-SNxwn_w1lA7qtb_%A08iKE|@5&Tt;OK z-syv1xX8kSQ`V*Jr=>g$8-};(Ms`T!*gH9QxlZu1;SC_2JC%Ln5OdOBTA#Q7nwv7U z{6w}tXtk~RR_IHUjr#dh-l*v_;T9n1(yGsfmFyTSyFh=1hBdo;DKEZRDlE$JR4xB_ z*#-pdPWbCuu;4^~-AHz1r}O7_=jkI3EE5%7?dDP11U=&|(DQ-Kid{b9E$X{U9Hd<# z6@%Oyo)v!-B_0n|~iJu4k(9dj`iirsSIPyWljsDi)3TWzb zSEcHV$D{R?BhSx?r0ryV1pm1#oDt=j`>jS>(``pd87%)*!kP$gk9=gU8MFk*ux>@R>z==pw)ONeshkGwviS zN4l=nXmQ@U-h>>JYkfMJ@p^y`Ep#zl>B7$8FHj4Op?y14RB>Y9s3#XI@Hq)(6PgL8 z0L!S(pn5dFAok!Y!$)Uo!ninx?fhjxLTeE^bj?9Obp?^UInXUVLxURhkt}PsevXb5zUa89`b+y}Jt2gE-N^ zGWyN4C~gHTV*c)12{*WeoLbNGdr$UL<8?wR)xmMcQ`9#$^FQv_(S@uqbIX?N1m4hQ zj_q9lS{VTKkyAmrXG@BbMhmg|MF5oH#iF+ztU_gFsVv*H3GhB~4fq!L$_$i?i}=7Y z;|UqF45Yo8kLY1Q)ZO<@v>9P9ptS5&V4E^vp3*M)Dsp?U)I@cxK0sNk9ctiwO^)=w+alPZUCAbG)Zv% zhmPux5BmY|3r}$>i{r{8>OV<*uM9HrqrRjW{r;27hm6>)P%%*X&$9ws`W+47@g}%YHX5Z6rkC4F_08a67S+8LWv?p`nH#YYST5>1pQA2Mo@=QU z0f%pnWaA%(aZ?GJ0&S~w{l(FuN&dTIE5>z9nH;+2zTuUi4aFOXHl977qsD%=q}uzmx+h`S!I5xI{;ZMlcB z_$l&S3E+&E5hthY`8*cUiYw6bAFQnpTq|O*-VeuTFIuyhRg4NeEC9dGZYDmljA$A; zy&z*U^QLyLUc#1Kan{3_4(azgt98Yha!zXjSy0QEdtOKK=TWGk?I zlcX8|dGT-uoSo4&Qr^gSPKStu@tBQPW7yJbxgE{2%Z9p_551I7adJZNXO;5CifW3^ zl$}2jFc?0(E=Do%^_>kfrJgOZb}r$b2bK*j%#PKVny0S9{ESAV>HeA#^3#XR35C zL=!Xf?ql=qQT_@u5raH2tx^I5Tta5;#Gwt&iyXVlA$1M`+GNqGN6D7=&S=odJ=Mu~ zRiFbLSVau!tBV&r!a{9_LPbm z&S2znkONndFmUZwgY5nwqLKlCuA<+&3EQfTnLEdjMV_y`H(jki80JnKJKxFmtr)O% zielnr{fDAg?SEoGxuF_sZDsPJl9uAQVA^FNF;p)DF)s1l~FNGbFk;8;zN?jS$wc$iNA`+IPjuD0fBmqx_BOkx9*uC3-hQc#c#6~+>7w($;6OlAx>$H$)%xZPu+@f_-@NsG~ zcUn(sLB+IM(CVpWQEl40Gj5eRHGP#j9w(fm@{@|2S18%rxX$w?e+-eQ{Hpu`>UQI)K<9W zmuy2XWMds)*3#g^_dAU! ziKEw?^Z_0CIc~-t^Rr&?GKeSkqAeoTBJ0&Nd1M~=4YoU3sjLq+f=OD%+rZ|Eeb@TAZZ1dvn^9^@93?rSqDMtngF9&AkcxZo^_2pZ3j+epB=5`t_aypEZ}U@1 zNDpR~h1*}mV(|EpFN#oof_K7oc!;`pyH#WzzkPES$*f#rC@Vosm;9WQxVfs3sfeL^ zKNCWR69)WFhY6=ZZbB z#;C_Xhp+Hmd+ja#6HuQ#%X(7lw*XziY4$|+X7S$PZ3TS=r$`rdB6WcR^MV)Xd}7Q= zD5}k3{=WNJiJ=8^htzG=T_FaC`f|NuDKe|Pof(>m6F5R4$rLFAv03Y1*g!__)9V;4 zx!9=1cW_UhbK@44m3~xgY_CC}Vfe2&_&lGz*koe=f z&~L9PBj~ZWJlto+kTr7a66VDIAMbg?T zXveM`*Tg)pob7T{qn9`xj&CWo2HFDwdJ+mbvX6Fy%=TBCKR@-}+6szcq)XL}h8@RX zAJDEjJG1o0SL7=oJN2#6{ONoy>ZAV~O+YpUa~hQw$O5Rc$d%cyb8NScBm{IpWNu=& zLn`djAx?NyL}ZZc<)YM~(dV;I{QyD7&Szt=q5E_=a-gspHtK*k(u2y>kucTb(;F0s zP1P}>wS(8y97#)A+;9W3=}VprXb53wxW? zP{t*iF8bhSb;)=3m`pr4BVDJxzRYQ~oH10X4mz;9#L#`~E=$kOfC|{x=XU|J4``Am zoE}ET(N!r=l(QinB5A|B_om8Rg_E}j4Ft|c+P>v{RoCx|mPwq>@X^z?OR{v2620t| zcR=hiy4j(GY*5UJQm!qp(sa(X%y1uD<|bOye>$_REvw6*ykOERE&6!%a<`3dFsp)} zw!3F@ECYd~8)H=rUXNJ=*oEci>aSznMd_@HcytsYwdJ9UNE)(0!)`2d(@?xlds;0# zrEEa7IKIwrI7=nZGY?RV)+A_9M;&rBW5&zx9?4*9FRC&T1Oocw9Sao-`VjCD!%4DCkqC3re(jXTW=Zi@MR$ zC)K*Sb^!fJ-+l=LTN{0y&|YYPJd$?)lO^FI=ty3Mz}UE0+6I^{Vp!p%u6Ul#s-a@h z;%bPqvQB-=Oh$87@EDwW?`ON8@2F^?OId{2R1#E95W@K!YcU|kUAq$3B zn&+8S#ZL~qb+z&@x$j65`WB8DQOhSKTrEH(fp8hyFNB6*xNU++^wZY9GR$$-D+MVD;;T3pQlDEaak1=-?iVhHq^BO0&>lU}%H%w^c5N>6a3 z?8J&mfoC&QvP3VhTIN>KcfL6+r0i>?;qYXzzPrr+%Hd)$Ew1Os6DYoE;65mNetZKm zi@kj0QN-2h+I0I$5jo9$Z24|}ZP$4sduN6Z0#gz`w>yE~hKleT7M(&Tp$2JdmGLtrBx;WckoXuLG*t*odP+(q}5BLNMEmreynMJuRr_ zMNPews1bL|8z>_LEXGqefL=W9N4{}=BhucwYY$Ms`eB2|;GJlTdc}cSFeo!i3FulE zwpvq+IlR%5d-uI`9b2~;YC5D{w^Ov(IdQ>6SVTXt#ZbpwS2Sqfgr43Ln_6Fi3kHnF*iZ8!xEa*E4M@YsPT^TenrO~k zkGkU^y)BXv7QPYbFSiM>Dp2IzwkF^)84^j>>s^2ld08#)tJwvMiFL@Eucu3;B&^3I z9o)UG(9Y4L;zI^|7D0zpowcQ*=QP`pIJ8OROtwp2JH2Hzv2;QtjjN&A@_`BchofYt-@G*3f`C~j}PW&Ydli^imC5d+H>OA&)c(c*s7|>g7^migA`v;S%!W~cX>P(-Fse}()7S#$tE8fa1yLM-I z+oYbclxXp3C0Q+|odzoZvvH2It)2a56cV?2QS&FMsXCDs`at7ikQzP$&<`c^H{}m+ zx41F$Rjnnc_D7~hT_F)vP^u=+v3+NbZruO4*4W$}D)~U_y}h${6uH8upYJ$Gyc#s{e_AH~p zK@DJ3QWJL)UAJiEagN^iJaNI6k?KGFt?O)`3mi4NS=b4vM;jv=B{eGDrbCHj10#7m zhMI7)g0Yze0c7nV3wOI{5dQ4LZqCDpmBl4h96BLzsF%=HNbNddqF}Cf%jMmNW!gP= zoCdz-H4*w|Iub58^`0d?Oj6dJt-B2|Ee+4TCAE-g)=%Zpt8NxZWIOfJ9_e;~|IgFw zudRov6I{I*`~iUN*ka>+ zAKAv`WY5UVD@o1FZq9vDl^6&nJf6AgIfq0%pdGST-t5E)Hmt(c1{g^fW;Rv{7RJok z6OShikUimg+=$bpW*gE+TGei{hrQ030g2LxVsl*}2-CmaXt*5xxGi|*t+`d^%g5yT ztM+F^LbII|kcwLbzEq`reW{caC)p-VGq_1dbaPe+*Ud+7dptA;egr zsRsL2Q7@O=V-T_(D6=}Z>hMk#POdpddAS<~h##eQTXHGVYmkq}UDLSF;B>pxn?3hB z7-qNr$=j8d?M=0FWz|OByb#-vqBaFh&C!1Uu83Kqo>_~QjGY;^Iu9fC#6pdBsJ=~+ z$t}E%mc3kM$zI-C_i(XM0`=JFgD5fF6#Pp4>^<0gAU(lX6+ZvbH=Q{b=tn6$n)wuL z&+Pc9kEv$seE5p`&{+2qi2qDVL_N1d>W5!}F8^z^ndqH7UmEf XMUQ+qbQTMMe~uoq{I=*Dx2yjLZA`-) diff --git a/Examples/UIExplorer/UIExplorerTests/UIExplorerTests.m b/Examples/UIExplorer/UIExplorerTests/UIExplorerTests.m index fd321546a..2c2359b44 100644 --- a/Examples/UIExplorer/UIExplorerTests/UIExplorerTests.m +++ b/Examples/UIExplorer/UIExplorerTests/UIExplorerTests.m @@ -59,11 +59,10 @@ return NO; } -// Make sure this test runs first (underscores sort early) otherwise the -// other tests will tear out the rootView -- (void)test__RootViewLoadsAndRenders +// Make sure this test runs first because the other tests will tear out the rootView +- (void)testAAA_RootViewLoadsAndRenders { - UIViewController *vc = [[[[UIApplication sharedApplication] delegate] window] rootViewController]; + UIViewController *vc = [UIApplication sharedApplication].delegate.window.rootViewController; RCTAssert([vc.view isKindOfClass:[RCTRootView class]], @"This test must run first."); NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS]; BOOL foundElement = NO; @@ -72,10 +71,8 @@ while ([date timeIntervalSinceNow] > 0 && !foundElement && !redboxError) { [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:date]; [[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:date]; - redboxError = [[RCTRedBox sharedInstance] currentErrorMessage]; - - foundElement = [self findSubviewInView:vc.view matching:^BOOL(UIView *view) { + foundElement = [self findSubviewInView:vc.view matching:^(UIView *view) { if ([view respondsToSelector:@selector(attributedText)]) { NSString *text = [(id)view attributedText].string; if ([text isEqualToString:@""]) { @@ -120,6 +117,7 @@ [_runner runTest:_cmd module:@"TabBarExample"]; } +// Make sure this test runs last - (void)testZZZ_NotInRecordMode { RCTAssert(_runner.recordMode == NO, @"Don't forget to turn record mode back to NO before commit."); diff --git a/Libraries/ActionSheetIOS/RCTActionSheetManager.m b/Libraries/ActionSheetIOS/RCTActionSheetManager.m index c6d6e404c..ac672249f 100644 --- a/Libraries/ActionSheetIOS/RCTActionSheetManager.m +++ b/Libraries/ActionSheetIOS/RCTActionSheetManager.m @@ -34,92 +34,88 @@ RCT_EXPORT_METHOD(showActionSheetWithOptions:(NSDictionary *)options failureCallback:(RCTResponseSenderBlock)failureCallback successCallback:(RCTResponseSenderBlock)successCallback) { - dispatch_async(dispatch_get_main_queue(), ^{ - UIActionSheet *actionSheet = [[UIActionSheet alloc] init]; + UIActionSheet *actionSheet = [[UIActionSheet alloc] init]; - actionSheet.title = options[@"title"]; + actionSheet.title = options[@"title"]; - for (NSString *option in options[@"options"]) { - [actionSheet addButtonWithTitle:option]; - } + for (NSString *option in options[@"options"]) { + [actionSheet addButtonWithTitle:option]; + } - if (options[@"destructiveButtonIndex"]) { - actionSheet.destructiveButtonIndex = [options[@"destructiveButtonIndex"] integerValue]; - } - if (options[@"cancelButtonIndex"]) { - actionSheet.cancelButtonIndex = [options[@"cancelButtonIndex"] integerValue]; - } + if (options[@"destructiveButtonIndex"]) { + actionSheet.destructiveButtonIndex = [options[@"destructiveButtonIndex"] integerValue]; + } + if (options[@"cancelButtonIndex"]) { + actionSheet.cancelButtonIndex = [options[@"cancelButtonIndex"] integerValue]; + } - actionSheet.delegate = self; + actionSheet.delegate = self; - _callbacks[keyForInstance(actionSheet)] = successCallback; + _callbacks[RCTKeyForInstance(actionSheet)] = successCallback; - UIWindow *appWindow = [[[UIApplication sharedApplication] delegate] window]; - if (appWindow == nil) { - RCTLogError(@"Tried to display action sheet but there is no application window. options: %@", options); - return; - } - [actionSheet showInView:appWindow]; - }); + UIWindow *appWindow = [[[UIApplication sharedApplication] delegate] window]; + if (appWindow == nil) { + RCTLogError(@"Tried to display action sheet but there is no application window. options: %@", options); + return; + } + [actionSheet showInView:appWindow]; } RCT_EXPORT_METHOD(showShareActionSheetWithOptions:(NSDictionary *)options failureCallback:(RCTResponseSenderBlock)failureCallback successCallback:(RCTResponseSenderBlock)successCallback) { - dispatch_async(dispatch_get_main_queue(), ^{ - NSMutableArray *items = [NSMutableArray array]; - id message = options[@"message"]; - id url = options[@"url"]; - if ([message isKindOfClass:[NSString class]]) { - [items addObject:message]; - } - if ([url isKindOfClass:[NSString class]]) { - [items addObject:[NSURL URLWithString:url]]; - } - if ([items count] == 0) { - failureCallback(@[@"No `url` or `message` to share"]); - return; - } - UIActivityViewController *share = [[UIActivityViewController alloc] initWithActivityItems:items applicationActivities:nil]; - UIViewController *ctrl = [[[[UIApplication sharedApplication] delegate] window] rootViewController]; - if ([share respondsToSelector:@selector(setCompletionWithItemsHandler:)]) { - share.completionWithItemsHandler = ^(NSString *activityType, BOOL completed, NSArray *returnedItems, NSError *activityError) { - if (activityError) { - failureCallback(@[[activityError localizedDescription]]); - } else { - successCallback(@[@(completed), (activityType ?: [NSNull null])]); - } - }; - } else { + NSMutableArray *items = [NSMutableArray array]; + id message = options[@"message"]; + id url = options[@"url"]; + if ([message isKindOfClass:[NSString class]]) { + [items addObject:message]; + } + if ([url isKindOfClass:[NSString class]]) { + [items addObject:[NSURL URLWithString:url]]; + } + if ([items count] == 0) { + failureCallback(@[@"No `url` or `message` to share"]); + return; + } + UIActivityViewController *share = [[UIActivityViewController alloc] initWithActivityItems:items applicationActivities:nil]; + UIViewController *ctrl = [[[[UIApplication sharedApplication] delegate] window] rootViewController]; + if ([share respondsToSelector:@selector(setCompletionWithItemsHandler:)]) { + share.completionWithItemsHandler = ^(NSString *activityType, BOOL completed, NSArray *returnedItems, NSError *activityError) { + if (activityError) { + failureCallback(@[[activityError localizedDescription]]); + } else { + successCallback(@[@(completed), (activityType ?: [NSNull null])]); + } + }; + } else { #if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_8_0 - if (![UIActivityViewController instancesRespondToSelector:@selector(completionWithItemsHandler)]) { - // Legacy iOS 7 implementation - share.completionHandler = ^(NSString *activityType, BOOL completed) { - successCallback(@[@(completed), (activityType ?: [NSNull null])]); - }; - } else + if (![UIActivityViewController instancesRespondToSelector:@selector(completionWithItemsHandler)]) { + // Legacy iOS 7 implementation + share.completionHandler = ^(NSString *activityType, BOOL completed) { + successCallback(@[@(completed), (activityType ?: [NSNull null])]); + }; + } else #endif - { - // iOS 8 version - share.completionWithItemsHandler = ^(NSString *activityType, BOOL completed, NSArray *returnedItems, NSError *activityError) { - successCallback(@[@(completed), (activityType ?: [NSNull null])]); - }; - } + { + // iOS 8 version + share.completionWithItemsHandler = ^(NSString *activityType, BOOL completed, NSArray *returnedItems, NSError *activityError) { + successCallback(@[@(completed), (activityType ?: [NSNull null])]); + }; } - [ctrl presentViewController:share animated:YES completion:nil]; - }); + } + [ctrl presentViewController:share animated:YES completion:nil]; } #pragma mark UIActionSheetDelegate Methods - (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex { - NSString *key = keyForInstance(actionSheet); + NSString *key = RCTKeyForInstance(actionSheet); RCTResponseSenderBlock callback = _callbacks[key]; if (callback) { callback(@[@(buttonIndex)]); @@ -133,7 +129,7 @@ RCT_EXPORT_METHOD(showShareActionSheetWithOptions:(NSDictionary *)options #pragma mark Private -NS_INLINE NSString *keyForInstance(id instance) +static NSString *RCTKeyForInstance(id instance) { return [NSString stringWithFormat:@"%p", instance]; } diff --git a/Libraries/Animation/RCTAnimationExperimentalManager.m b/Libraries/Animation/RCTAnimationExperimentalManager.m index eb2ddd1cd..798c1456b 100644 --- a/Libraries/Animation/RCTAnimationExperimentalManager.m +++ b/Libraries/Animation/RCTAnimationExperimentalManager.m @@ -68,6 +68,11 @@ RCT_EXPORT_MODULE() return self; } +- (dispatch_queue_t)methodQueue +{ + return _bridge.uiManager.methodQueue; +} + - (id (^)(CGFloat))interpolateFrom:(CGFloat[])fromArray to:(CGFloat[])toArray count:(NSUInteger)count typeName:(const char *)typeName { if (count == 1) { diff --git a/Libraries/Geolocation/RCTLocationObserver.m b/Libraries/Geolocation/RCTLocationObserver.m index 6bb95acb0..c5303df15 100644 --- a/Libraries/Geolocation/RCTLocationObserver.m +++ b/Libraries/Geolocation/RCTLocationObserver.m @@ -33,7 +33,9 @@ typedef struct { CLLocationAccuracy accuracy; } RCTLocationOptions; -static RCTLocationOptions RCTLocationOptionsWithJSON(id json) +@implementation RCTConvert (RCTLocationOptions) + ++ (RCTLocationOptions)RCTLocationOptions:(id)json { NSDictionary *options = [RCTConvert NSDictionary:json]; return (RCTLocationOptions){ @@ -43,6 +45,8 @@ static RCTLocationOptions RCTLocationOptionsWithJSON(id json) }; } +@end + static NSDictionary *RCTPositionError(RCTPositionErrorCode code, NSString *msg /* nil for default */) { if (!msg) { @@ -121,6 +125,7 @@ RCT_EXPORT_MODULE() - (void)dealloc { [_locationManager stopUpdatingLocation]; + _locationManager.delegate = nil; } #pragma mark - Private API @@ -153,41 +158,33 @@ RCT_EXPORT_MODULE() #pragma mark - Public API -RCT_EXPORT_METHOD(startObserving:(NSDictionary *)optionsJSON) +RCT_EXPORT_METHOD(startObserving:(RCTLocationOptions)options) { [self checkLocationConfig]; - dispatch_async(dispatch_get_main_queue(), ^{ + // Select best options + _observerOptions = options; + for (RCTLocationRequest *request in _pendingRequests) { + _observerOptions.accuracy = MIN(_observerOptions.accuracy, request.options.accuracy); + } - // Select best options - _observerOptions = RCTLocationOptionsWithJSON(optionsJSON); - for (RCTLocationRequest *request in _pendingRequests) { - _observerOptions.accuracy = MIN(_observerOptions.accuracy, request.options.accuracy); - } - - _locationManager.desiredAccuracy = _observerOptions.accuracy; - [self beginLocationUpdates]; - _observingLocation = YES; - - }); + _locationManager.desiredAccuracy = _observerOptions.accuracy; + [self beginLocationUpdates]; + _observingLocation = YES; } RCT_EXPORT_METHOD(stopObserving) { - dispatch_async(dispatch_get_main_queue(), ^{ + // Stop observing + _observingLocation = NO; - // Stop observing - _observingLocation = NO; - - // Stop updating if no pending requests - if (_pendingRequests.count == 0) { - [_locationManager stopUpdatingLocation]; - } - - }); + // Stop updating if no pending requests + if (_pendingRequests.count == 0) { + [_locationManager stopUpdatingLocation]; + } } -RCT_EXPORT_METHOD(getCurrentPosition:(NSDictionary *)optionsJSON +RCT_EXPORT_METHOD(getCurrentPosition:(RCTLocationOptions)options withSuccessCallback:(RCTResponseSenderBlock)successBlock errorCallback:(RCTResponseSenderBlock)errorBlock) { @@ -198,56 +195,49 @@ RCT_EXPORT_METHOD(getCurrentPosition:(NSDictionary *)optionsJSON return; } - dispatch_async(dispatch_get_main_queue(), ^{ - - if (![CLLocationManager locationServicesEnabled]) { - if (errorBlock) { - errorBlock(@[ - RCTPositionError(RCTPositionErrorUnavailable, @"Location services disabled.") - ]); - return; - } - } - - if ([CLLocationManager authorizationStatus] == kCLAuthorizationStatusDenied) { - if (errorBlock) { - errorBlock(@[ - RCTPositionError(RCTPositionErrorDenied, nil) - ]); - return; - } - } - - // Get options - RCTLocationOptions options = RCTLocationOptionsWithJSON(optionsJSON); - - // Check if previous recorded location exists and is good enough - if (_lastLocationEvent && - CFAbsoluteTimeGetCurrent() - [RCTConvert NSTimeInterval:_lastLocationEvent[@"timestamp"]] < options.maximumAge && - [_lastLocationEvent[@"coords"][@"accuracy"] doubleValue] >= options.accuracy) { - - // Call success block with most recent known location - successBlock(@[_lastLocationEvent]); + if (![CLLocationManager locationServicesEnabled]) { + if (errorBlock) { + errorBlock(@[ + RCTPositionError(RCTPositionErrorUnavailable, @"Location services disabled.") + ]); return; } + } - // Create request - RCTLocationRequest *request = [[RCTLocationRequest alloc] init]; - request.successBlock = successBlock; - request.errorBlock = errorBlock ?: ^(NSArray *args){}; - request.options = options; - request.timeoutTimer = [NSTimer scheduledTimerWithTimeInterval:options.timeout - target:self - selector:@selector(timeout:) - userInfo:request - repeats:NO]; - [_pendingRequests addObject:request]; + if ([CLLocationManager authorizationStatus] == kCLAuthorizationStatusDenied) { + if (errorBlock) { + errorBlock(@[ + RCTPositionError(RCTPositionErrorDenied, nil) + ]); + return; + } + } - // Configure location manager and begin updating location - _locationManager.desiredAccuracy = MIN(_locationManager.desiredAccuracy, options.accuracy); - [self beginLocationUpdates]; + // Check if previous recorded location exists and is good enough + if (_lastLocationEvent && + CFAbsoluteTimeGetCurrent() - [RCTConvert NSTimeInterval:_lastLocationEvent[@"timestamp"]] < options.maximumAge && + [_lastLocationEvent[@"coords"][@"accuracy"] doubleValue] >= options.accuracy) { - }); + // Call success block with most recent known location + successBlock(@[_lastLocationEvent]); + return; + } + + // Create request + RCTLocationRequest *request = [[RCTLocationRequest alloc] init]; + request.successBlock = successBlock; + request.errorBlock = errorBlock ?: ^(NSArray *args){}; + request.options = options; + request.timeoutTimer = [NSTimer scheduledTimerWithTimeInterval:options.timeout + target:self + selector:@selector(timeout:) + userInfo:request + repeats:NO]; + [_pendingRequests addObject:request]; + + // Configure location manager and begin updating location + _locationManager.desiredAccuracy = MIN(_locationManager.desiredAccuracy, options.accuracy); + [self beginLocationUpdates]; } #pragma mark - CLLocationManagerDelegate diff --git a/Libraries/LinkingIOS/RCTLinkingManager.m b/Libraries/LinkingIOS/RCTLinkingManager.m index 990c5900f..4be8bfb8e 100644 --- a/Libraries/LinkingIOS/RCTLinkingManager.m +++ b/Libraries/LinkingIOS/RCTLinkingManager.m @@ -62,8 +62,10 @@ RCT_EXPORT_METHOD(openURL:(NSURL *)URL) RCT_EXPORT_METHOD(canOpenURL:(NSURL *)URL callback:(RCTResponseSenderBlock)callback) { - BOOL canOpen = [[UIApplication sharedApplication] canOpenURL:URL]; - callback(@[@(canOpen)]); + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + BOOL canOpen = [[UIApplication sharedApplication] canOpenURL:URL]; + callback(@[@(canOpen)]); + }); } - (NSDictionary *)constantsToExport diff --git a/Libraries/RCTTest/RCTTestModule.h b/Libraries/RCTTest/RCTTestModule.h index 21ed60c6b..a8a2da16e 100644 --- a/Libraries/RCTTest/RCTTestModule.h +++ b/Libraries/RCTTest/RCTTestModule.h @@ -15,14 +15,24 @@ @interface RCTTestModule : NSObject -// This is typically polled while running the runloop until true -@property (nonatomic, readonly, getter=isDone) BOOL done; - -// This is used to give meaningful names to snapshot image files. -@property (nonatomic, assign) SEL testSelector; +/** + * The snapshot test controller for this module. + */ +@property (nonatomic, weak) FBSnapshotTestController *controller; +/** + * This is the view to be snapshotted. + */ @property (nonatomic, weak) UIView *view; -- (instancetype)initWithSnapshotController:(FBSnapshotTestController *)controller view:(UIView *)view; +/** + * This is used to give meaningful names to snapshot image files. + */ +@property (nonatomic, assign) SEL testSelector; + +/** + * This is typically polled while running the runloop until true. + */ +@property (nonatomic, readonly, getter=isDone) BOOL done; @end diff --git a/Libraries/RCTTest/RCTTestModule.m b/Libraries/RCTTest/RCTTestModule.m index 58b6572f8..33f562515 100644 --- a/Libraries/RCTTest/RCTTestModule.m +++ b/Libraries/RCTTest/RCTTestModule.m @@ -12,21 +12,25 @@ #import "FBSnapshotTestController.h" #import "RCTAssert.h" #import "RCTLog.h" +#import "RCTUIManager.h" @implementation RCTTestModule { - __weak FBSnapshotTestController *_snapshotController; - __weak UIView *_view; NSMutableDictionary *_snapshotCounter; } +@synthesize bridge = _bridge; + RCT_EXPORT_MODULE() -- (instancetype)initWithSnapshotController:(FBSnapshotTestController *)controller view:(UIView *)view +- (dispatch_queue_t)methodQueue +{ + return _bridge.uiManager.methodQueue; +} + +- (instancetype)init { if ((self = [super init])) { - _snapshotController = controller; - _view = view; _snapshotCounter = [NSMutableDictionary new]; } return self; @@ -34,30 +38,29 @@ RCT_EXPORT_MODULE() RCT_EXPORT_METHOD(verifySnapshot:(RCTResponseSenderBlock)callback) { - if (!_snapshotController) { - RCTLogWarn(@"No snapshot controller configured."); - callback(@[]); - return; - } + RCTAssert(_controller != nil, @"No snapshot controller configured."); + + [_bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) { - dispatch_async(dispatch_get_main_queue(), ^{ NSString *testName = NSStringFromSelector(_testSelector); - _snapshotCounter[testName] = @([_snapshotCounter[testName] integerValue] + 1); + _snapshotCounter[testName] = [@([_snapshotCounter[testName] integerValue] + 1) stringValue]; + NSError *error = nil; - BOOL success = [_snapshotController compareSnapshotOfView:_view - selector:_testSelector - identifier:[_snapshotCounter[testName] stringValue] - error:&error]; + BOOL success = [_controller compareSnapshotOfView:_view + selector:_testSelector + identifier:_snapshotCounter[testName] + error:&error]; + RCTAssert(success, @"Snapshot comparison failed: %@", error); callback(@[]); - }); + }]; } RCT_EXPORT_METHOD(markTestCompleted) { - dispatch_async(dispatch_get_main_queue(), ^{ + [_bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) { _done = YES; - }); + }]; } @end diff --git a/Libraries/RCTTest/RCTTestRunner.m b/Libraries/RCTTest/RCTTestRunner.m index 9b3a7d3c8..75a811831 100644 --- a/Libraries/RCTTest/RCTTestRunner.m +++ b/Libraries/RCTTest/RCTTestRunner.m @@ -19,7 +19,7 @@ @implementation RCTTestRunner { - FBSnapshotTestController *_snapshotController; + FBSnapshotTestController *_testController; } - (instancetype)initWithApp:(NSString *)app referenceDir:(NSString *)referenceDir @@ -27,8 +27,8 @@ if ((self = [super init])) { NSString *sanitizedAppName = [app stringByReplacingOccurrencesOfString:@"/" withString:@"-"]; sanitizedAppName = [sanitizedAppName stringByReplacingOccurrencesOfString:@"\\" withString:@"-"]; - _snapshotController = [[FBSnapshotTestController alloc] initWithTestName:sanitizedAppName]; - _snapshotController.referenceImagesDirectory = referenceDir; + _testController = [[FBSnapshotTestController alloc] initWithTestName:sanitizedAppName]; + _testController.referenceImagesDirectory = referenceDir; _scriptURL = [NSURL URLWithString:[NSString stringWithFormat:@"http://localhost:8081/%@.includeRequire.runModule.bundle?dev=true", app]]; } return self; @@ -36,12 +36,12 @@ - (void)setRecordMode:(BOOL)recordMode { - _snapshotController.recordMode = recordMode; + _testController.recordMode = recordMode; } - (BOOL)recordMode { - return _snapshotController.recordMode; + return _testController.recordMode; } - (void)runTest:(SEL)test module:(NSString *)moduleName @@ -59,27 +59,22 @@ - (void)runTest:(SEL)test module:(NSString *)moduleName initialProps:(NSDictionary *)initialProps expectErrorBlock:(BOOL(^)(NSString *error))expectErrorBlock { - UIViewController *vc = [[[[UIApplication sharedApplication] delegate] window] rootViewController]; - if ([vc.view isKindOfClass:[RCTRootView class]]) { - [(RCTRootView *)vc.view invalidate]; // Make sure the normal app view doesn't interfere - } - vc.view = [[UIView alloc] init]; - - RCTTestModule *testModule = [[RCTTestModule alloc] initWithSnapshotController:_snapshotController view:nil]; - testModule.testSelector = test; - RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:_scriptURL - moduleProvider:^(){ - return @[testModule]; - } - launchOptions:nil]; - - RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge - moduleName:moduleName]; - testModule.view = rootView; - [vc.view addSubview:rootView]; // Add as subview so it doesn't get resized + RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:_scriptURL + moduleName:moduleName + launchOptions:nil]; rootView.initialProperties = initialProps; rootView.frame = CGRectMake(0, 0, 320, 2000); // Constant size for testing on multiple devices + NSString *testModuleName = RCTBridgeModuleNameForClass([RCTTestModule class]); + RCTTestModule *testModule = rootView.bridge.modules[testModuleName]; + testModule.controller = _testController; + testModule.testSelector = test; + testModule.view = rootView; + + UIViewController *vc = [UIApplication sharedApplication].delegate.window.rootViewController; + vc.view = [[UIView alloc] init]; + [vc.view addSubview:rootView]; // Add as subview so it doesn't get resized + NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS]; NSString *error = [[RCTRedBox sharedInstance] currentErrorMessage]; while ([date timeIntervalSinceNow] > 0 && ![testModule isDone] && error == nil) { diff --git a/React/Base/RCTBridge.h b/React/Base/RCTBridge.h index ab853851c..544e5e1a2 100644 --- a/React/Base/RCTBridge.h +++ b/React/Base/RCTBridge.h @@ -101,13 +101,6 @@ static const char *__rct_import_##module##_##method##__ = #module"."#method; */ @property (nonatomic, copy, readonly) NSDictionary *modules; -/** - * The shadow queue is used to execute callbacks from the JavaScript code. All - * native hooks (e.g. exported module methods) will be executed on the shadow - * queue. - */ -@property (nonatomic, readonly) dispatch_queue_t shadowQueue; - /** * The launch options that were used to initialize the bridge. */ diff --git a/React/Base/RCTBridge.m b/React/Base/RCTBridge.m index 5c6bcf7cb..7b1f95b69 100644 --- a/React/Base/RCTBridge.m +++ b/React/Base/RCTBridge.m @@ -792,6 +792,7 @@ static NSDictionary *RCTLocalModulesConfig() @implementation RCTBridge { RCTSparseArray *_modulesByID; + RCTSparseArray *_queuesByID; NSDictionary *_modulesByName; id _javaScriptExecutor; Class _executorClass; @@ -824,13 +825,13 @@ static id _latestJSExecutor; return self; } + - (void)setUp { Class executorClass = _executorClass ?: _globalExecutorClass ?: [RCTContextExecutor class]; _javaScriptExecutor = [[executorClass alloc] init]; _latestJSExecutor = _javaScriptExecutor; _eventDispatcher = [[RCTEventDispatcher alloc] initWithBridge:self]; - _shadowQueue = dispatch_queue_create("com.facebook.React.ShadowQueue", DISPATCH_QUEUE_SERIAL); _displayLink = [[RCTDisplayLink alloc] initWithBridge:self]; _frameUpdateObservers = [[NSMutableSet alloc] init]; _scheduledCalls = [[NSMutableArray alloc] init]; @@ -848,21 +849,29 @@ static id _latestJSExecutor; [RCTBridgeModuleClassesByModuleID() enumerateObjectsUsingBlock:^(Class moduleClass, NSUInteger moduleID, BOOL *stop) { NSString *moduleName = RCTModuleNamesByID[moduleID]; // Check if module instance has already been registered for this name - if ((_modulesByID[moduleID] = modulesByName[moduleName])) { + id module = modulesByName[moduleName]; + if (module) { // Preregistered instances takes precedence, no questions asked if (!preregisteredModules[moduleName]) { // It's OK to have a name collision as long as the second instance is nil RCTAssert([[moduleClass alloc] init] == nil, - @"Attempted to register RCTBridgeModule class %@ for the name '%@', \ - but name was already registered by class %@", moduleClass, + @"Attempted to register RCTBridgeModule class %@ for the name " + "'%@', but name was already registered by class %@", moduleClass, moduleName, [modulesByName[moduleName] class]); } + if ([module class] != moduleClass) { + RCTLogInfo(@"RCTBridgeModule of class %@ with name '%@' was encountered " + "in the project, but name was already registered by class %@." + "That's fine if it's intentional - just letting you know.", + moduleClass, moduleName, [modulesByName[moduleName] class]); + } } else { // Module name hasn't been used before, so go ahead and instantiate - id module = [[moduleClass alloc] init]; - if (module) { - _modulesByID[moduleID] = modulesByName[moduleName] = module; - } + module = [[moduleClass alloc] init]; + } + if (module) { + // Store module instance + _modulesByID[moduleID] = modulesByName[moduleName] = module; } }]; @@ -876,6 +885,14 @@ static id _latestJSExecutor; } } + // Get method queue + _queuesByID = [[RCTSparseArray alloc] init]; + [_modulesByID enumerateObjectsUsingBlock:^(id module, NSNumber *moduleID, BOOL *stop) { + if ([module respondsToSelector:@selector(methodQueue)]) { + _queuesByID[moduleID] = [module methodQueue] ?: dispatch_get_main_queue(); + } + }]; + // Inject module data into JS context NSString *configJSON = RCTJSONStringify(@{ @"remoteModuleConfig": RCTRemoteModulesConfig(_modulesByName), @@ -1027,6 +1044,7 @@ static id _latestJSExecutor; // Release modules (breaks retain cycle if module has strong bridge reference) _modulesByID = nil; + _queuesByID = nil; _modulesByName = nil; } @@ -1203,6 +1221,8 @@ static id _latestJSExecutor; return; } + // TODO: if we sort the requests by module, we could dispatch once per + // module instead of per request, which would reduce the call overhead. for (NSUInteger i = 0; i < numRequests; i++) { @autoreleasepool { [self _handleRequestNumber:i @@ -1212,14 +1232,15 @@ static id _latestJSExecutor; } } - // TODO: only used by RCTUIManager - can we eliminate this special case? - dispatch_async(self.shadowQueue, ^{ - for (id module in _modulesByID.allObjects) { - if ([module respondsToSelector:@selector(batchDidComplete)]) { + // TODO: batchDidComplete is only used by RCTUIManager - can we eliminate this special case? + [_modulesByID enumerateObjectsUsingBlock:^(id module, NSNumber *moduleID, BOOL *stop) { + if ([module respondsToSelector:@selector(batchDidComplete)]) { + dispatch_queue_t queue = _queuesByID[moduleID]; + dispatch_async(queue ?: dispatch_get_main_queue(), ^{ [module batchDidComplete]; - } + }); } - }); + }]; } - (BOOL)_handleRequestNumber:(NSUInteger)i @@ -1241,7 +1262,8 @@ static id _latestJSExecutor; RCTModuleMethod *method = methods[methodID]; __weak RCTBridge *weakSelf = self; - dispatch_async(self.shadowQueue, ^{ + dispatch_queue_t queue = _queuesByID[moduleID]; + dispatch_async(queue ?: dispatch_get_main_queue(), ^{ __strong RCTBridge *strongSelf = weakSelf; if (!strongSelf.isValid) { diff --git a/React/Base/RCTBridgeModule.h b/React/Base/RCTBridgeModule.h index 70d5c76d0..c8fa41c44 100644 --- a/React/Base/RCTBridgeModule.h +++ b/React/Base/RCTBridgeModule.h @@ -89,6 +89,23 @@ typedef void (^RCTResponseSenderBlock)(NSArray *response); __attribute__((__aligned__(1))) \ static const char *__rct_export_entry__[] = { __func__, #js_name, NULL } +/** + * The queue that will be used to call all exported methods. If omitted, this + * will default the main queue, which is recommended for any methods that + * interact with UIKit. If your methods perform heavy work such as filesystem + * or network access, you should return a custom serial queue. Example: + * + * - (dispatch_queue_t)methodQueue + * { + * return dispatch_queue_create("com.mydomain.FileQueue", DISPATCH_QUEUE_SERIAL); + * } + * + * Alternatively, if only some methods on the module should be executed on a + * background queue you can leave this method unimplemented, and simply + * dispatch_async() within the method itself. + */ +- (dispatch_queue_t)methodQueue; + /** * Injects constants into JS. These constants are made accessible via * NativeModules.ModuleName.X. This method is called when the module is diff --git a/React/Base/RCTConvert.h b/React/Base/RCTConvert.h index e3f5e8598..22cc7ec81 100644 --- a/React/Base/RCTConvert.h +++ b/React/Base/RCTConvert.h @@ -138,7 +138,7 @@ BOOL RCTCopyProperty(id target, id source, NSString *keyPath); /** * Underlying implementation of RCT_ENUM_CONVERTER macro. Ignore this. */ -NSNumber *RCTConverterEnumValue(const char *, NSDictionary *, NSNumber *, id); +NSNumber *RCTConvertEnumValue(const char *, NSDictionary *, NSNumber *, id); #ifdef __cplusplus } @@ -190,7 +190,7 @@ RCT_CUSTOM_CONVERTER(type, type, [[self NSNumber:json] getter]) dispatch_once(&onceToken, ^{ \ mapping = values; \ }); \ - NSNumber *converted = RCTConverterEnumValue(#type, mapping, @(default), json); \ + NSNumber *converted = RCTConvertEnumValue(#type, mapping, @(default), json); \ return ((type(*)(id, SEL))objc_msgSend)(converted, @selector(getter)); \ } diff --git a/React/Base/RCTConvert.m b/React/Base/RCTConvert.m index 25de29656..2aefe8940 100644 --- a/React/Base/RCTConvert.m +++ b/React/Base/RCTConvert.m @@ -58,6 +58,10 @@ RCT_CONVERTER(NSString *, NSString, description) + (NSURL *)NSURL:(id)json { + if (!json || json == (id)kCFNull) { + return nil; + } + if (![json isKindOfClass:[NSString class]]) { RCTLogError(@"Expected NSString for NSURL, received %@: %@", [json classForCoder], json); return nil; @@ -115,7 +119,7 @@ RCT_CUSTOM_CONVERTER(NSTimeInterval, NSTimeInterval, [self double:json] / 1000.0 // JS standard for time zones is minutes. RCT_CUSTOM_CONVERTER(NSTimeZone *, NSTimeZone, [NSTimeZone timeZoneForSecondsFromGMT:[self double:json] * 60.0]) -NSNumber *RCTConverterEnumValue(const char *typeName, NSDictionary *mapping, NSNumber *defaultValue, id json) +NSNumber *RCTConvertEnumValue(const char *typeName, NSDictionary *mapping, NSNumber *defaultValue, id json) { if (!json || json == (id)kCFNull) { return defaultValue; diff --git a/React/Modules/RCTAlertManager.m b/React/Modules/RCTAlertManager.m index ae11ce52b..b97364e38 100644 --- a/React/Modules/RCTAlertManager.m +++ b/React/Modules/RCTAlertManager.m @@ -64,37 +64,34 @@ RCT_EXPORT_METHOD(alertWithArgs:(NSDictionary *)args return; } - dispatch_async(dispatch_get_main_queue(), ^{ + UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:title + message:message + delegate:self + cancelButtonTitle:nil + otherButtonTitles:nil]; - UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:title - message:message - delegate:self - cancelButtonTitle:nil - otherButtonTitles:nil]; + NSMutableArray *buttonKeys = [[NSMutableArray alloc] initWithCapacity:buttons.count]; - NSMutableArray *buttonKeys = [[NSMutableArray alloc] initWithCapacity:buttons.count]; - - NSInteger index = 0; - for (NSDictionary *button in buttons) { - if (button.count != 1) { - RCTLogError(@"Button definitions should have exactly one key."); - } - NSString *buttonKey = [button.allKeys firstObject]; - NSString *buttonTitle = [button[buttonKey] description]; - [alertView addButtonWithTitle:buttonTitle]; - if ([buttonKey isEqualToString: @"cancel"]) { - alertView.cancelButtonIndex = index; - } - [buttonKeys addObject:buttonKey]; - index ++; + NSInteger index = 0; + for (NSDictionary *button in buttons) { + if (button.count != 1) { + RCTLogError(@"Button definitions should have exactly one key."); } + NSString *buttonKey = [button.allKeys firstObject]; + NSString *buttonTitle = [button[buttonKey] description]; + [alertView addButtonWithTitle:buttonTitle]; + if ([buttonKey isEqualToString: @"cancel"]) { + alertView.cancelButtonIndex = index; + } + [buttonKeys addObject:buttonKey]; + index ++; + } - [_alerts addObject:alertView]; - [_alertCallbacks addObject:callback ?: ^(id unused) {}]; - [_alertButtonKeys addObject:buttonKeys]; + [_alerts addObject:alertView]; + [_alertCallbacks addObject:callback ?: ^(id unused) {}]; + [_alertButtonKeys addObject:buttonKeys]; - [alertView show]; - }); + [alertView show]; } #pragma mark - UIAlertViewDelegate diff --git a/React/Modules/RCTAsyncLocalStorage.m b/React/Modules/RCTAsyncLocalStorage.m index 8e6d414cf..2c01161d4 100644 --- a/React/Modules/RCTAsyncLocalStorage.m +++ b/React/Modules/RCTAsyncLocalStorage.m @@ -61,20 +61,6 @@ static id RCTReadFile(NSString *filePath, NSString *key, NSDictionary **errorOut return nil; } -static dispatch_queue_t RCTFileQueue(void) -{ - static dispatch_queue_t fileQueue = NULL; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - // All JS is single threaded, so a serial queue is our only option. - fileQueue = dispatch_queue_create("com.facebook.rkFile", DISPATCH_QUEUE_SERIAL); - dispatch_set_target_queue(fileQueue, - dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)); - }); - - return fileQueue; -} - #pragma mark - RCTAsyncLocalStorage @implementation RCTAsyncLocalStorage @@ -90,6 +76,11 @@ static dispatch_queue_t RCTFileQueue(void) RCT_EXPORT_MODULE() +- (dispatch_queue_t)methodQueue +{ + return dispatch_queue_create("com.facebook.React.AsyncLocalStorageQueue", DISPATCH_QUEUE_SERIAL); +} + - (NSString *)_filePathForKey:(NSString *)key { NSString *safeFileName = RCTMD5Hash(key); @@ -196,99 +187,89 @@ RCT_EXPORT_METHOD(multiGet:(NSArray *)keys return; } - dispatch_async(RCTFileQueue(), ^{ - id errorOut = [self _ensureSetup]; - if (errorOut) { - callback(@[@[errorOut], [NSNull null]]); - return; - } - NSMutableArray *errors; - NSMutableArray *result = [[NSMutableArray alloc] initWithCapacity:keys.count]; - for (NSString *key in keys) { - id keyError = [self _appendItemForKey:key toArray:result]; - RCTAppendError(keyError, &errors); - } - [self _writeManifest:&errors]; - callback(@[errors ?: [NSNull null], result]); - }); + id errorOut = [self _ensureSetup]; + if (errorOut) { + callback(@[@[errorOut], [NSNull null]]); + return; + } + NSMutableArray *errors; + NSMutableArray *result = [[NSMutableArray alloc] initWithCapacity:keys.count]; + for (NSString *key in keys) { + id keyError = [self _appendItemForKey:key toArray:result]; + RCTAppendError(keyError, &errors); + } + [self _writeManifest:&errors]; + callback(@[errors ?: [NSNull null], result]); } RCT_EXPORT_METHOD(multiSet:(NSArray *)kvPairs callback:(RCTResponseSenderBlock)callback) { - dispatch_async(RCTFileQueue(), ^{ - id errorOut = [self _ensureSetup]; - if (errorOut) { - callback(@[@[errorOut]]); - return; - } - NSMutableArray *errors; - for (NSArray *entry in kvPairs) { - id keyError = [self _writeEntry:entry]; - RCTAppendError(keyError, &errors); - } - [self _writeManifest:&errors]; - if (callback) { - callback(@[errors ?: [NSNull null]]); - } - }); + id errorOut = [self _ensureSetup]; + if (errorOut) { + callback(@[@[errorOut]]); + return; + } + NSMutableArray *errors; + for (NSArray *entry in kvPairs) { + id keyError = [self _writeEntry:entry]; + RCTAppendError(keyError, &errors); + } + [self _writeManifest:&errors]; + if (callback) { + callback(@[errors ?: [NSNull null]]); + } } RCT_EXPORT_METHOD(multiRemove:(NSArray *)keys callback:(RCTResponseSenderBlock)callback) { - dispatch_async(RCTFileQueue(), ^{ - id errorOut = [self _ensureSetup]; - if (errorOut) { - callback(@[@[errorOut]]); - return; + id errorOut = [self _ensureSetup]; + if (errorOut) { + callback(@[@[errorOut]]); + return; + } + NSMutableArray *errors; + for (NSString *key in keys) { + id keyError = RCTErrorForKey(key); + if (!keyError) { + NSString *filePath = [self _filePathForKey:key]; + [[NSFileManager defaultManager] removeItemAtPath:filePath error:nil]; + [_manifest removeObjectForKey:key]; } - NSMutableArray *errors; - for (NSString *key in keys) { - id keyError = RCTErrorForKey(key); - if (!keyError) { - NSString *filePath = [self _filePathForKey:key]; - [[NSFileManager defaultManager] removeItemAtPath:filePath error:nil]; - [_manifest removeObjectForKey:key]; - } - RCTAppendError(keyError, &errors); - } - [self _writeManifest:&errors]; - if (callback) { - callback(@[errors ?: [NSNull null]]); - } - }); + RCTAppendError(keyError, &errors); + } + [self _writeManifest:&errors]; + if (callback) { + callback(@[errors ?: [NSNull null]]); + } } RCT_EXPORT_METHOD(clear:(RCTResponseSenderBlock)callback) { - dispatch_async(RCTFileQueue(), ^{ - id errorOut = [self _ensureSetup]; - if (!errorOut) { - NSError *error; - for (NSString *key in _manifest) { - NSString *filePath = [self _filePathForKey:key]; - [[NSFileManager defaultManager] removeItemAtPath:filePath error:&error]; - } - [_manifest removeAllObjects]; - errorOut = [self _writeManifest:nil]; + id errorOut = [self _ensureSetup]; + if (!errorOut) { + NSError *error; + for (NSString *key in _manifest) { + NSString *filePath = [self _filePathForKey:key]; + [[NSFileManager defaultManager] removeItemAtPath:filePath error:&error]; } - if (callback) { - callback(@[errorOut ?: [NSNull null]]); - } - }); + [_manifest removeAllObjects]; + errorOut = [self _writeManifest:nil]; + } + if (callback) { + callback(@[errorOut ?: [NSNull null]]); + } } RCT_EXPORT_METHOD(getAllKeys:(RCTResponseSenderBlock)callback) { - dispatch_async(RCTFileQueue(), ^{ - id errorOut = [self _ensureSetup]; - if (errorOut) { - callback(@[errorOut, [NSNull null]]); - } else { - callback(@[[NSNull null], [_manifest allKeys]]); - } - }); + id errorOut = [self _ensureSetup]; + if (errorOut) { + callback(@[errorOut, [NSNull null]]); + } else { + callback(@[[NSNull null], [_manifest allKeys]]); + } } @end diff --git a/React/Modules/RCTExceptionsManager.m b/React/Modules/RCTExceptionsManager.m index 5be80133b..9e919f3a3 100644 --- a/React/Modules/RCTExceptionsManager.m +++ b/React/Modules/RCTExceptionsManager.m @@ -48,13 +48,11 @@ RCT_EXPORT_METHOD(reportUnhandledException:(NSString *)message } #ifdef DEBUG - [[RCTRedBox sharedInstance] showErrorMessage:message withStack:stack]; + [[RCTRedBox sharedInstance] showErrorMessage:message withStack:stack]; #else if (RCTReloadRetries < _maxReloadAttempts) { RCTReloadRetries++; - dispatch_async(dispatch_get_main_queue(), ^{ - [[NSNotificationCenter defaultCenter] postNotificationName:RCTReloadNotification object:nil]; - }); + [[NSNotificationCenter defaultCenter] postNotificationName:RCTReloadNotification object:nil]; } else { NSError *error; const NSUInteger MAX_SANITIZED_LENGTH = 75; diff --git a/React/Modules/RCTSourceCode.m b/React/Modules/RCTSourceCode.m index 76e9190bc..e29a05637 100644 --- a/React/Modules/RCTSourceCode.m +++ b/React/Modules/RCTSourceCode.m @@ -24,7 +24,6 @@ RCT_EXPORT_METHOD(getScriptText:(RCTResponseSenderBlock)successCallback } else { failureCallback(@[RCTMakeError(@"Source code is not available", nil, nil)]); } - } @end diff --git a/React/Modules/RCTStatusBarManager.m b/React/Modules/RCTStatusBarManager.m index ad8ee1df6..149ad568e 100644 --- a/React/Modules/RCTStatusBarManager.m +++ b/React/Modules/RCTStatusBarManager.m @@ -29,31 +29,25 @@ RCT_EXPORT_MODULE() RCT_EXPORT_METHOD(setStyle:(UIStatusBarStyle)statusBarStyle animated:(BOOL)animated) { - dispatch_async(dispatch_get_main_queue(), ^{ - - if (RCTViewControllerBasedStatusBarAppearance()) { - RCTLogError(@"RCTStatusBarManager module requires that the \ - UIViewControllerBasedStatusBarAppearance key in the Info.plist is set to NO"); - } else { - [[UIApplication sharedApplication] setStatusBarStyle:statusBarStyle - animated:animated]; - } - }); + if (RCTViewControllerBasedStatusBarAppearance()) { + RCTLogError(@"RCTStatusBarManager module requires that the \ + UIViewControllerBasedStatusBarAppearance key in the Info.plist is set to NO"); + } else { + [[UIApplication sharedApplication] setStatusBarStyle:statusBarStyle + animated:animated]; + } } RCT_EXPORT_METHOD(setHidden:(BOOL)hidden withAnimation:(UIStatusBarAnimation)animation) { - dispatch_async(dispatch_get_main_queue(), ^{ - - if (RCTViewControllerBasedStatusBarAppearance()) { - RCTLogError(@"RCTStatusBarManager module requires that the \ - UIViewControllerBasedStatusBarAppearance key in the Info.plist is set to NO"); - } else { - [[UIApplication sharedApplication] setStatusBarHidden:hidden - withAnimation:animation]; - } - }); + if (RCTViewControllerBasedStatusBarAppearance()) { + RCTLogError(@"RCTStatusBarManager module requires that the \ + UIViewControllerBasedStatusBarAppearance key in the Info.plist is set to NO"); + } else { + [[UIApplication sharedApplication] setStatusBarHidden:hidden + withAnimation:animation]; + } } - (NSDictionary *)constantsToExport diff --git a/React/Modules/RCTTiming.m b/React/Modules/RCTTiming.m index aaab5fae0..1f6e84d6a 100644 --- a/React/Modules/RCTTiming.m +++ b/React/Modules/RCTTiming.m @@ -187,21 +187,17 @@ RCT_EXPORT_METHOD(createTimer:(NSNumber *)callbackID interval:jsDuration targetTime:targetTime repeats:repeats]; - dispatch_async(dispatch_get_main_queue(), ^{ - _timers[callbackID] = timer; - [self startTimers]; - }); + _timers[callbackID] = timer; + [self startTimers]; } RCT_EXPORT_METHOD(deleteTimer:(NSNumber *)timerID) { if (timerID) { - dispatch_async(dispatch_get_main_queue(), ^{ - _timers[timerID] = nil; - if (_timers.count == 0) { - [self stopTimers]; - } - }); + _timers[timerID] = nil; + if (_timers.count == 0) { + [self stopTimers]; + } } else { RCTLogWarn(@"Called deleteTimer: with a nil timerID"); } diff --git a/React/Modules/RCTUIManager.m b/React/Modules/RCTUIManager.m index ae04f9a1d..961b5a0b5 100644 --- a/React/Modules/RCTUIManager.m +++ b/React/Modules/RCTUIManager.m @@ -178,7 +178,7 @@ static UIViewAnimationCurve UIViewAnimationCurveFromRCTAnimationType(RCTAnimatio @implementation RCTUIManager { - __weak dispatch_queue_t _shadowQueue; + dispatch_queue_t _shadowQueue; // Root views are only mutated on the shadow queue NSMutableSet *_rootViewTags; @@ -242,24 +242,12 @@ static NSDictionary *RCTViewConfigForModule(Class managerClass, NSString *viewNa }; } -/** - * This private constructor should only be called when creating - * isolated UIImanager instances for testing. Normal initialization - * is via -init:, which is called automatically by the bridge. - */ -- (instancetype)initWithShadowQueue:(dispatch_queue_t)shadowQueue -{ - if ((self = [self init])) { - _shadowQueue = shadowQueue; - _viewManagers = [[NSMutableDictionary alloc] init]; - } - return self; -} - - (instancetype)init { if ((self = [super init])) { + _shadowQueue = dispatch_queue_create("com.facebook.React.ShadowQueue", DISPATCH_QUEUE_SERIAL); + _pendingUIBlocksLock = [[NSLock alloc] init]; _defaultShadowViews = [[NSMutableDictionary alloc] init]; @@ -310,7 +298,6 @@ static NSDictionary *RCTViewConfigForModule(Class managerClass, NSString *viewNa RCTAssert(_bridge == nil, @"Should not re-use same UIIManager instance"); _bridge = bridge; - _shadowQueue = _bridge.shadowQueue; _shadowViewRegistry = [[RCTSparseArray alloc] init]; // Get view managers from bridge @@ -328,6 +315,11 @@ static NSDictionary *RCTViewConfigForModule(Class managerClass, NSString *viewNa _viewConfigs = [viewConfigs copy]; } +- (dispatch_queue_t)methodQueue +{ + return _shadowQueue; +} + - (void)registerRootView:(UIView *)rootView; { RCTAssertMainThread(); @@ -366,7 +358,7 @@ static NSDictionary *RCTViewConfigForModule(Class managerClass, NSString *viewNa NSNumber *reactTag = rootView.reactTag; RCTAssert(RCTIsReactRootView(reactTag), @"Specified view %@ is not a root view", reactTag); - dispatch_async(_bridge.shadowQueue, ^{ + dispatch_async(_shadowQueue, ^{ RCTShadowView *rootShadowView = _shadowViewRegistry[reactTag]; RCTAssert(rootShadowView != nil, @"Could not locate root view with tag #%@", reactTag); rootShadowView.frame = frame; @@ -396,8 +388,6 @@ static NSDictionary *RCTViewConfigForModule(Class managerClass, NSString *viewNa - (void)addUIBlock:(RCTViewManagerUIBlock)block { - RCTAssert(![NSThread isMainThread], @"This method should only be called on the shadow thread"); - if (!self.isValid) { return; } @@ -417,7 +407,7 @@ static NSDictionary *RCTViewConfigForModule(Class managerClass, NSString *viewNa - (RCTViewManagerUIBlock)uiBlockWithLayoutUpdateForRootView:(RCTShadowView *)rootShadowView { - RCTAssert(![NSThread isMainThread], @"This should never be executed on main thread."); + RCTAssert(![NSThread isMainThread], @"Should be called on shadow thread"); NSMutableSet *viewsWithNewFrames = [NSMutableSet setWithCapacity:1]; @@ -679,6 +669,8 @@ RCT_EXPORT_METHOD(manageChildren:(NSNumber *)containerReactTag [self _purgeChildren:permanentlyRemovedChildren fromRegistry:registry]; + // TODO (#5906496): optimize all these loops - constantly calling array.count is not efficient + // Figure out what to insert - merge temporary inserts and adds NSMutableDictionary *destinationsToChildrenToAdd = [NSMutableDictionary dictionary]; for (NSInteger index = 0, length = temporarilyRemovedChildren.count; index < length; index++) { @@ -886,8 +878,6 @@ RCT_EXPORT_METHOD(blur:(NSNumber *)reactTag) - (void)flushUIBlocks { - RCTAssert(![NSThread isMainThread], @"Should be called on shadow thread"); - // First copy the previous blocks into a temporary variable, then reset the // pending blocks to a new array. This guards against mutation while // processing the pending blocks in another thread. diff --git a/React/Views/RCTViewManager.h b/React/Views/RCTViewManager.h index 74c7bbd8c..ed97cf3b2 100644 --- a/React/Views/RCTViewManager.h +++ b/React/Views/RCTViewManager.h @@ -109,8 +109,7 @@ typedef void (^RCTViewManagerUIBlock)(RCTUIManager *uiManager, RCTSparseArray *v * within the view or shadowView. */ #define RCT_REMAP_VIEW_PROPERTY(name, keyPath, type) \ -RCT_EXPORT_VIEW_PROP_CONFIG(name, type) \ -- (void)set_##name:(id)json forView:(id)view withDefaultView:(id)defaultView { \ +RCT_CUSTOM_VIEW_PROPERTY(name, type, UIView) { \ if ((json && !RCTSetProperty(view, @#keyPath, @selector(type:), json)) || \ (!json && !RCTCopyProperty(view, defaultView, @#keyPath))) { \ RCTLogError(@"%@ does not have setter for `%s` property", [view class], #name); \ @@ -118,8 +117,7 @@ RCT_EXPORT_VIEW_PROP_CONFIG(name, type) \ } #define RCT_REMAP_SHADOW_PROPERTY(name, keyPath, type) \ -RCT_EXPORT_SHADOW_PROP_CONFIG(name, type) \ -- (void)set_##name:(id)json forShadowView:(id)view withDefaultView:(id)defaultView { \ +RCT_CUSTOM_SHADOW_PROPERTY(name, type, RCTShadowView) { \ if ((json && !RCTSetProperty(view, @#keyPath, @selector(type:), json)) || \ (!json && !RCTCopyProperty(view, defaultView, @#keyPath))) { \ RCTLogError(@"%@ does not have setter for `%s` property", [view class], #name); \ @@ -132,11 +130,11 @@ RCT_EXPORT_SHADOW_PROP_CONFIG(name, type) \ * refer to "json", "view" and "defaultView" to implement the required logic. */ #define RCT_CUSTOM_VIEW_PROPERTY(name, type, viewClass) \ -RCT_EXPORT_VIEW_PROP_CONFIG(name, type) \ ++ (NSDictionary *)getPropConfigView_##name { return @{@"name": @#name, @"type": @#type}; } \ - (void)set_##name:(id)json forView:(viewClass *)view withDefaultView:(viewClass *)defaultView #define RCT_CUSTOM_SHADOW_PROPERTY(name, type, viewClass) \ -RCT_EXPORT_SHADOW_PROP_CONFIG(name, type) \ ++ (NSDictionary *)getPropConfigShadow_##name { return @{@"name": @#name, @"type": @#type}; } \ - (void)set_##name:(id)json forShadowView:(viewClass *)view withDefaultView:(viewClass *)defaultView /** @@ -164,17 +162,4 @@ RCT_EXPORT_SHADOW_PROP_CONFIG(name, type) \ [self set_##newName:json forView:view withDefaultView:defaultView]; \ } -/** - * PROP_CONFIG macros should only be paired with property setters. - */ -#define RCT_EXPORT_VIEW_PROP_CONFIG(name, type) \ -+ (NSDictionary *)getPropConfigView_##name { \ - return @{@"name": @#name, @"type": @#type}; \ -} - -#define RCT_EXPORT_SHADOW_PROP_CONFIG(name, type) \ -+ (NSDictionary *)getPropConfigShadow_##name { \ - return @{@"name": @#name, @"type": @#type}; \ -} - @end diff --git a/React/Views/RCTViewManager.m b/React/Views/RCTViewManager.m index 3c8485374..8388b83cf 100644 --- a/React/Views/RCTViewManager.m +++ b/React/Views/RCTViewManager.m @@ -14,6 +14,7 @@ #import "RCTEventDispatcher.h" #import "RCTLog.h" #import "RCTShadowView.h" +#import "RCTUIManager.h" #import "RCTUtils.h" #import "RCTView.h" @@ -23,6 +24,11 @@ RCT_EXPORT_MODULE() +- (dispatch_queue_t)methodQueue +{ + return [_bridge.uiManager methodQueue]; +} + - (UIView *)view { return [[RCTView alloc] init];